diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2bff304fba..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -osu!lazer is currently still under heavy development! - -Please ensure that you are making an issue for one of the following: - -- A bug with currently implemented features (not features that don't exist) -- A feature you are considering adding, so we can collaborate on feedback and design. -- Discussions about technical design decisions - -If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide. - -Screenshots and log files are highly welcomed. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md index 8d85c92fec..c8c41e5a78 100644 --- a/.github/ISSUE_TEMPLATE/bug-issues.md +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -1,14 +1,11 @@ --- name: Bug Report -about: For issues regarding encountered game bugs +about: Issues regarding encountered bugs. --- - - - -**Describe your problem:** +**Describe the bug:** **Screenshots or videos showing encountered issue:** -**osu!lazer version:** +**osu!lazer version:** -**Logs:** \ No newline at end of file +**Logs:** diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md index 849f042c1f..8ad27e9e31 100644 --- a/.github/ISSUE_TEMPLATE/crash-issues.md +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -1,16 +1,13 @@ --- name: Crash Report -about: For issues regarding game crashes or permanent freezes +about: Issues regarding crashes or permanent freezes. --- - - - -**Describe your problem:** +**Describe the crash:** **Screenshots or videos showing encountered issue:** -**osu!lazer version:** +**osu!lazer version:** -**Logs:** +**Logs:** -**Computer Specifications:** \ No newline at end of file +**Computer Specifications:** diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md index 73c4f37a3e..54c4ff94e5 100644 --- a/.github/ISSUE_TEMPLATE/feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -1,10 +1,7 @@ --- name: Feature Request -about: Let us know what you would like to see in the game! +about: Features you would like to see in the game! --- +**Describe the new feature:** - - -**Describe the feature:** - -**Proposal designs of the feature:** +**Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md index ae3cf20a8c..5822da9c65 100644 --- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -1,10 +1,7 @@ --- name: Missing for Live -about: Let us know the features you need which are available in osu-stable but not lazer +about: Features which are available in osu!stable but not yet in osu!lazer. --- +**Describe the missing feature:** - - -**Describe the feature:** - -**Designs:** +**Proposal designs of the feature:** diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 7e5b003f03..e7e0af7eea 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -14,6 +14,7 @@ using osuTK.Input; using Microsoft.Win32; using osu.Desktop.Updater; using osu.Framework; +using osu.Framework.Logging; using osu.Framework.Platform.Windows; using osu.Framework.Screens; using osu.Game.Screens.Menu; @@ -35,12 +36,15 @@ namespace osu.Desktop { try { - return new StableStorage(); + if (Host is DesktopGameHost desktopHost) + return new StableStorage(desktopHost); } - catch + catch (Exception e) { - return null; + Logger.Error(e, "Error while searching for stable install"); } + + return null; } protected override void LoadComplete() @@ -139,8 +143,8 @@ namespace osu.Desktop return null; } - public StableStorage() - : base(string.Empty, null) + public StableStorage(DesktopGameHost host) + : base(string.Empty, host) { } } diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index b8a0e337b6..711ffa7d9e 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -110,7 +110,7 @@ namespace osu.Desktop.Overlays public UpdateCompleteNotification(string version, Action openUrl = null) { Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - Icon = FontAwesome.fa_check_square; + Icon = FontAwesome.Solid.CheckSquare; Activated = delegate { openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}"); diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index ab05888408..0600804339 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -7,10 +7,10 @@ using Newtonsoft.Json; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Network; using osu.Framework.Platform; using osu.Game; -using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -54,7 +54,7 @@ namespace osu.Desktop.Updater { Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", - Icon = FontAwesome.fa_upload, + Icon = FontAwesome.Solid.Upload, Activated = () => { host.OpenUrlExternally(getBestUrl(latest)); diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 6400fd776d..5fed2a63e1 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game; using osu.Game.Graphics; @@ -158,7 +159,7 @@ namespace osu.Desktop.Updater { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_upload, + Icon = FontAwesome.Solid.Upload, Colour = Color4.White, Size = new Vector2(20), } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index fad2b8b2a8..7a8c2f7d9c 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 01c57a6b9a..51fe0b035d 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(4.2038001515546597d, "diffcalc-test")] + [TestCase(4.2058561036909863d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 9319fb3dfb..fbb2db33b0 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseAutoJuiceStream : TestCasePlayer + public class TestCaseAutoJuiceStream : PlayerTestCase { public TestCaseAutoJuiceStream() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs index 9e1c44ba40..d413b53d17 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs @@ -8,11 +8,12 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + public class TestCaseBananaShower : PlayerTestCase { public override IReadOnlyList RequiredTypes => new[] { @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBananaShower), typeof(CatchRuleset), - typeof(CatchRulesetContainer), + typeof(DrawableCatchRuleset), }; public TestCaseBananaShower() diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs index 8f9dd73b80..5b242d05d7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchPlayer : PlayerTestCase { public TestCaseCatchPlayer() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs index 1e3d60d968..5a16a23a4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs @@ -4,11 +4,12 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchStacker : PlayerTestCase { public TestCaseCatchStacker() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs index 0851fbed87..a7e7f0ab14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs @@ -2,26 +2,45 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer + public class TestCaseHyperDash : PlayerTestCase { public TestCaseHyperDash() : base(new CatchRuleset()) { } + [BackgroundDependencyLoader] + private void load() + { + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + } + protected override IBeatmap CreateBeatmap(Ruleset ruleset) { - var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; + var beatmap = new Beatmap + { + BeatmapInfo = + { + Ruleset = ruleset.RulesetInfo, + BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f } + } + }; + + // Should produce a hyper-dash + beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true }); + beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, }); for (int i = 0; i < 512; i++) if (i % 5 < 3) - beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); + beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 }); return beatmap; } 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 feab3ed81c..3f8b3bf086 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,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index c5ced26e42..18cc300ff9 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 5f1e0b97da..78b5a510b2 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps CatchHitObject nextObject = objectWithDroplets[i + 1]; int thisDirection = nextObject.X > currentObject.X ? 1 : -1; - double timeToNext = nextObject.StartTime - currentObject.StartTime; + 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) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index a69070e93e..aa00e182a9 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -9,19 +9,22 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); @@ -99,6 +102,11 @@ namespace osu.Game.Rulesets.Catch new MultiMod(new CatchModAutoplay(), new ModCinema()), new CatchModRelax(), }; + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()) + }; default: return new Mod[] { }; } @@ -108,10 +116,12 @@ namespace osu.Game.Rulesets.Catch public override string ShortName => "fruits"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); + public override int? LegacyID => 2; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 8cfda5d532..b4998347f4 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Skills; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty; @@ -22,16 +23,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override int SectionLength => 750; - private readonly float halfCatchWidth; - public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { - var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); - halfCatchWidth = catcher.CatchWidth * 0.5f; - - // We're only using 80% of the catcher's width to simulate imperfect gameplay. - halfCatchWidth *= 0.8f; } protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) @@ -53,6 +47,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { + float halfCatchWidth; + + using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty)) + { + halfCatchWidth = catcher.CatchWidth * 0.5f; + halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay. + } + CatchHitObject lastObject = null; foreach (var hitObject in beatmap.HitObjects.OfType()) @@ -88,5 +90,13 @@ namespace osu.Game.Rulesets.Catch.Difficulty { new Movement(), }; + + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new CatchModDoubleTime(), + new CatchModHalfTime(), + new CatchModHardRock(), + new CatchModEasy(), + }; } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs new file mode 100644 index 0000000000..5a640f6d1a --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchPerformanceCalculator : PerformanceCalculator + { + protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes; + + private Mod[] mods; + + private int fruitsHit; + private int ticksHit; + private int tinyTicksHit; + private int tinyTicksMissed; + private int misses; + + public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) + : base(ruleset, beatmap, score) + { + } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + mods = Score.Mods; + + var legacyScore = Score as LegacyScoreInfo; + + fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect]; + ticksHit = legacyScore?.Count100 ?? 0; + tinyTicksHit = legacyScore?.Count50 ?? 0; + tinyTicksMissed = legacyScore?.CountKatu ?? 0; + misses = Score.Statistics[HitResult.Miss]; + + // Don't count scores made with supposedly unranked mods + if (mods.Any(m => !m.Ranked)) + return 0; + + // We are heavily relying on aim in catch the beat + double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f; + + // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo + int numTotalHits = totalComboHits(); + + // Longer maps are worth more + float lengthBonus = + 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + + (numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + + // Longer maps are worth more + value *= lengthBonus; + + // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available + value *= Math.Pow(0.97f, misses); + + // Combo scaling + float beatmapMaxCombo = Attributes.MaxCombo; + if (beatmapMaxCombo > 0) + value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + + float approachRate = (float)Attributes.ApproachRate; + float approachRateFactor = 1.0f; + if (approachRate > 9.0f) + approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 + else if (approachRate < 8.0f) + approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 + + value *= approachRateFactor; + + if (mods.Any(m => m is ModHidden)) + // Hiddens gives nothing on max approach rate, and more the lower it is + value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10 + + if (mods.Any(m => m is ModFlashlight)) + // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. + value *= 1.35f * lengthBonus; + + // Scale the aim value with accuracy _slightly_ + value *= Math.Pow(accuracy(), 5.5f); + + // Custom multipliers for NoFail. SpunOut is not applicable. + if (mods.Any(m => m is ModNoFail)) + value *= 0.90f; + + return value; + } + + private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; + private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; + private int totalComboHits() => misses + ticksHit + fruitsHit; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 612df5bde5..692e63fa69 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 82cda7df47..71268d899d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -21,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods private CatchPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (CatchPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (CatchPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class CatchFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 0cfa3e98f7..060e51e31d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -15,77 +15,107 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1.12; public override bool Ranked => true; - private float lastStartX; - private int lastStartTime; + private float? lastPosition; + private double lastStartTime; public void ApplyToHitObject(HitObject hitObject) { + if (hitObject is JuiceStream stream) + { + lastPosition = stream.EndX; + lastStartTime = stream.EndTime; + return; + } + + if (!(hitObject is Fruit)) + return; + var catchObject = (CatchHitObject)hitObject; float position = catchObject.X; - int startTime = (int)hitObject.StartTime; + double startTime = hitObject.StartTime; - if (lastStartX == 0) + if (lastPosition == null) { - lastStartX = position; + lastPosition = position; lastStartTime = startTime; + return; } - float diff = lastStartX - position; - int timeDiff = startTime - lastStartTime; + float positionDiff = position - lastPosition.Value; + double timeDiff = startTime - lastStartTime; if (timeDiff > 1000) { - lastStartX = position; + lastPosition = position; lastStartTime = startTime; return; } - if (diff == 0) + if (positionDiff == 0) { - bool right = RNG.NextBool(); - - float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH; - - if (right) - { - if (position + rand <= 1) - position += rand; - else - position -= rand; - } - else - { - if (position - rand >= 0) - position -= rand; - else - position += rand; - } - + applyRandomOffset(ref position, timeDiff / 4d); catchObject.X = position; - return; } - if (Math.Abs(diff) < timeDiff / 3d) - { - if (diff > 0) - { - if (position - diff > 0) - position -= diff; - } - else - { - if (position - diff < 1) - position -= diff; - } - } + if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) + applyOffset(ref position, positionDiff); catchObject.X = position; - lastStartX = position; + lastPosition = position; lastStartTime = startTime; } + + /// + /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The maximum offset, cannot exceed 20px. + private void applyRandomOffset(ref float position, double maxOffset) + { + bool right = RNG.NextBool(); + float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; + + if (right) + { + // Clamp to the right bound + if (position + rand <= 1) + position += rand; + else + position -= rand; + } + else + { + // Clamp to the left bound + if (position - rand >= 0) + position -= rand; + else + position += rand; + } + } + + /// + /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The amount to offset by. + private void applyOffset(ref float position, float amount) + { + if (amount > 0) + { + // Clamp to the right bound + if (position + amount < 1) + position += amount; + } + else + { + // Clamp to the left bound + if (position + amount > 0) + position += amount; + } + } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index aaf723fae6..42646851d7 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -13,17 +13,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container bananaContainer; - public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) + public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null) : base(s) { RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; X = 0; - InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; + AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); foreach (var b in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(b)); + AddNested(createDrawableRepresentation?.Invoke(b)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 8fed8eb4cd..9cabdc3dd9 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -26,10 +26,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable [BackgroundDependencyLoader] private void load() { - InternalChild = pulp = new Pulp - { - Size = Size - }; + AddInternal(pulp = new Pulp { Size = Size }); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index fac4b8098c..0dc3f73404 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable // todo: this should come from the skin. AccentColour = colourForRepresentation(HitObject.VisualRepresentation); - InternalChildren = new[] + AddRangeInternal(new[] { createPulp(HitObject.VisualRepresentation), border = new Circle @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } } }, - }; + }); if (HitObject.HyperDash) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 7bb12453a8..9e5e9f6a04 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -13,17 +13,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container dropletContainer; - public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) + public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null) : base(s) { RelativeSizeAxes = Axes.Both; Origin = Anchor.BottomLeft; X = 0; - InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; + AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); foreach (var o in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(o)); + AddNested(createDrawableRepresentation?.Invoke(o)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 61bb4335f3..2adc156efd 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Audio; @@ -25,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity; public double TickDistance; + /// + /// The length of one span of this . + /// + public double SpanDuration => Duration / this.SpanCount(); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -41,19 +45,6 @@ namespace osu.Game.Rulesets.Catch.Objects protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); - createTicks(); - } - - private void createTicks() - { - if (TickDistance == 0) - return; - - var length = Path.Distance; - var tickDistance = Math.Min(TickDistance, length); - var spanDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; var tickSamples = Samples.Select(s => new SampleInfo { @@ -62,81 +53,59 @@ namespace osu.Game.Rulesets.Catch.Objects Volume = s.Volume }).ToList(); - AddNested(new Fruit + SliderEventDescriptor? lastEvent = null; + + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - Samples = Samples, - StartTime = StartTime, - X = X - }); - - double lastTickTime = StartTime; - - for (int span = 0; span < this.SpanCount(); span++) - { - var spanStartTime = StartTime + span * spanDuration; - var reversed = span % 2 == 1; - - for (double d = tickDistance;; d += tickDistance) + // generate tiny droplets since the last point + if (lastEvent != null) { - bool isLastTick = false; - if (d + minDistanceFromEnd >= length) + double sinceLastTick = e.Time - lastEvent.Value.Time; + + if (sinceLastTick > 80) { - d = length; - isLastTick = true; - } + double timeBetweenTiny = sinceLastTick; + while (timeBetweenTiny > 100) + timeBetweenTiny /= 2; - var timeProgress = d / length; - var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - - double time = spanStartTime + timeProgress * spanDuration; - - if (LegacyLastTickOffset != null) - { - // If we're the last tick, apply the legacy offset - if (span == this.SpanCount() - 1 && isLastTick) - time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value); - } - - int tinyTickCount = 1; - double tinyTickInterval = time - lastTickTime; - while (tinyTickInterval > 100 && tinyTickCount < 10000) - { - tinyTickInterval /= 2; - tinyTickCount *= 2; - } - - for (int tinyTickIndex = 0; tinyTickIndex < tinyTickCount - 1; tinyTickIndex++) - { - var t = lastTickTime + (tinyTickIndex + 1) * tinyTickInterval; - double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; - - AddNested(new TinyDroplet + for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { - StartTime = t, - X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = tickSamples - }); + AddNested(new TinyDroplet + { + Samples = tickSamples, + StartTime = t + lastEvent.Value.Time, + X = X + Path.PositionAt( + lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, + }); + } } - - lastTickTime = time; - - if (isLastTick) - break; - - AddNested(new Droplet - { - StartTime = time, - X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = tickSamples - }); } - AddNested(new Fruit + // this also includes LegacyLastTick and this is used for TinyDroplet generation above. + // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied. + lastEvent = e; + + switch (e.Type) { - Samples = Samples, - StartTime = spanStartTime + spanDuration, - X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH - }); + case SliderEventType.Tick: + AddNested(new Droplet + { + Samples = tickSamples, + StartTime = e.Time, + X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + }); + break; + case SliderEventType.Head: + case SliderEventType.Tail: + case SliderEventType.Repeat: + AddNested(new Fruit + { + Samples = Samples, + StartTime = e.Time, + X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + }); + break; + } } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 2fd05483ef..daa3f61de3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -6,17 +6,20 @@ using System.Linq; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Replays; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays { - internal class CatchAutoGenerator : AutoGenerator + internal class CatchAutoGenerator : AutoGenerator { public const double RELEASE_DELAY = 20; - public CatchAutoGenerator(Beatmap beatmap) + public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap; + + public CatchAutoGenerator(IBeatmap beatmap) : base(beatmap) { Replay = new Replay(); diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index dd0223314d..103aa6c3f1 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; using osu.Game.Replays; @@ -22,10 +23,14 @@ namespace osu.Game.Rulesets.Catch.Replays { get { - if (!HasFrames) + var frame = CurrentFrame; + + if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + Debug.Assert(CurrentTime != null); + + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index e1fda1a7b3..af614f95d0 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public CatchScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 4dae95b53c..b6d8cf9cbe 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -20,33 +19,24 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; - public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) + public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate - - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + explodingFruitContainer = new Container { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - CatcherArea = new CatcherArea(difficulty) - { - GetVisualRepresentation = getVisualRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - HitObjectContainer - } + RelativeSizeAxes = Axes.Both, + }, + CatcherArea = new CatcherArea(difficulty) + { + CreateDrawableRepresentation = createDrawableRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }, + HitObjectContainer }; } diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs similarity index 78% rename from osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 76daee2bbf..b8d3dc9017 100644 --- a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -3,17 +3,23 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class PlayfieldAdjustmentContainer : Container + public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public CatchPlayfieldAdjustmentContainer() { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index d0f50c6af2..83f791690a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.UI protected internal readonly Catcher MovableCatcher; - public Func> GetVisualRepresentation; + public Func> CreateDrawableRepresentation; public Container ExplodingFruitTarget { @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Catch.UI if (lastPlateableFruit.IsLoaded) action(); else - lastPlateableFruit.OnLoadComplete = _ => action(); + lastPlateableFruit.OnLoadComplete += _ => action(); } if (result.IsHit && fruit.CanBePlated) { - var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); + var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); if (caughtFruit == null) return; diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs similarity index 71% rename from osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs rename to osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index f421969449..ba0f5b90ba 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -17,13 +17,13 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI { - public class CatchRulesetContainer : ScrollingRulesetContainer + public class DrawableCatchRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant; protected override bool UserScrollSpeedAdjustment => false; - public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { Direction.Value = ScrollingDirection.Down; @@ -34,11 +34,13 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); - public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); - public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + + public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) { switch (h) { @@ -47,9 +49,9 @@ namespace osu.Game.Rulesets.Catch.UI case Fruit fruit: return new DrawableFruit(fruit); case JuiceStream stream: - return new DrawableJuiceStream(stream, GetVisualRepresentation); + return new DrawableJuiceStream(stream, CreateDrawableRepresentation); case BananaShower shower: - return new DrawableBananaShower(shower, GetVisualRepresentation); + return new DrawableBananaShower(shower, CreateDrawableRepresentation); case TinyDroplet tiny: return new DrawableTinyDroplet(tiny); case Droplet droplet: diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs index 78e7768788..e721eb6fd9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs @@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) { - var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaSetting.ScrollDirection, direction); + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ScrollDirection, direction); } } } 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 e26d2433f9..fd17285a38 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,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 77a10131a6..dc24a344e9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; @@ -42,13 +42,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { Name = @"Note Count", Content = notes.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Hold Note Count", Content = holdnotes.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, }; } diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs similarity index 57% rename from osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs rename to osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 4e0ad31105..b591f9da22 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -8,9 +8,9 @@ using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Configuration { - public class ManiaConfigManager : RulesetConfigManager + public class ManiaRulesetConfigManager : RulesetConfigManager { - public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) { } @@ -19,17 +19,17 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); - Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down); + Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); + Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) + new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) }; } - public enum ManiaSetting + public enum ManiaRulesetSetting { ScrollTime, ScrollDirection diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs similarity index 83% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index 89e531fd9f..acafaffee6 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -10,11 +10,11 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditRulesetContainer : ManiaRulesetContainer + public class DrawableManiaEditRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3dbbd132a6..56c9471462 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Cached(Type = typeof(IManiaHitObjectComposer))] public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { - protected new ManiaEditRulesetContainer RulesetContainer { get; private set; } + protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; } public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -32,23 +32,23 @@ namespace osu.Game.Rulesets.Mania.Edit /// /// The screen-space position. /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition); + public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition); private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns; + public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) { - RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(RulesetContainer.ScrollingInfo); + dependencies.CacheAs(DrawableRuleset.ScrollingInfo); - return RulesetContainer; + return DrawableRuleset; } protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 525bacee65..0ff79d2836 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -10,8 +10,10 @@ using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; @@ -30,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); @@ -145,6 +147,11 @@ namespace osu.Game.Rulesets.Mania { new MultiMod(new ManiaModAutoplay(), new ModCinema()), }; + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()) + }; default: return new Mod[] { }; } @@ -154,7 +161,7 @@ namespace osu.Game.Rulesets.Mania public override string ShortName => "mania"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); @@ -162,7 +169,7 @@ namespace osu.Game.Rulesets.Mania public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 2ab40b2bc6..2ebfd0cfc1 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Mania [BackgroundDependencyLoader] private void load() { - var config = (ManiaConfigManager)Config; + var config = (ManiaRulesetConfigManager)Config; Children = new Drawable[] { new SettingsEnumDropdown { LabelText = "Scrolling direction", - Bindable = config.GetBindable(ManiaSetting.ScrollDirection) + Bindable = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, new SettingsSlider { LabelText = "Scroll speed", - Bindable = config.GetBindable(ManiaSetting.ScrollTime) + Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime) }, }; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 02eb7ac883..c05e979e9a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 269e318a73..39185e6a57 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Name => "Fade In"; public override string Acronym => "FI"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override IconUsage Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; public override string Description => @"Keys appear out of nowhere!"; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 5d5023abae..ba16140644 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Random"; public override string Acronym => "RD"; public override ModType Type => ModType.Conversion; - public override FontAwesome Icon => FontAwesome.fa_osu_dice; + public override IconUsage Icon => OsuIcon.Dice; public override string Description => @"Shuffle around the keys!"; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 4bfd940aa0..9368af987d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { bodyPiece = new BodyPiece { @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre } - }; + }); foreach (var tick in tickContainer) AddNested(tick); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 43aac7907f..f2be8d614c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; Size = new Vector2(1); - InternalChildren = new[] + AddRangeInternal(new[] { glowContainer = new CircularContainer { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } } - }; + }); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 7ef90cdb9c..82a34224f4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables CornerRadius = 5; Masking = true; - InternalChild = headPiece = new NotePiece(); + AddInternal(headPiece = new NotePiece()); } protected override void OnDirectionChanged(ValueChangedEvent e) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index df6afa040e..65b7d54cd2 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -5,13 +5,12 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays { - internal class ManiaAutoGenerator : AutoGenerator + internal class ManiaAutoGenerator : AutoGenerator { public const double RELEASE_DELAY = 20; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index 197b105437..899718b77e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index cf3d0734fb..5c914d8eac 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { } - public ManiaScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public ManiaScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 5874bac7f6..8797f014df 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -22,22 +22,15 @@ namespace osu.Game.Rulesets.Mania.UI JudgementText.Font = JudgementText.Font.With(size: 25); } - protected override void LoadComplete() + protected override double FadeInDuration => 50; + + protected override void ApplyHitAnimations() { - base.LoadComplete(); + JudgementBody.ScaleTo(0.8f); + JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - this.FadeInFromZero(50, Easing.OutQuint); - - if (Result.IsHit) - { - JudgementBody.ScaleTo(0.8f); - JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - - JudgementBody.Delay(50).ScaleTo(0.75f, 250); - this.Delay(50).FadeOut(200); - } - - Expire(); + JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); + this.Delay(FadeInDuration).FadeOut(200); } } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs similarity index 81% rename from osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs rename to osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 9b8d63dc92..1c1ec604f6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -28,17 +27,19 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaRulesetContainer : ScrollingRulesetContainer + public class DrawableManiaRuleset : DrawableScrollingRuleset { + protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; + public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; public IEnumerable BarLines; - protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; + protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); - public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { // Generate the bar lines @@ -74,10 +75,10 @@ namespace osu.Game.Rulesets.Mania.UI { BarLines.ForEach(Playfield.Add); - Config.BindWith(ManiaSetting.ScrollDirection, configDirection); + Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection); configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); - Config.BindWith(ManiaSetting.ScrollTime, TimeRange); + Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); } /// @@ -87,19 +88,17 @@ namespace osu.Game.Rulesets.Mania.UI /// The column which intersects with . public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); + + protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); + protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 81888d2773..cbabfcc8b4 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); - Size = new Vector2(1, 0.8f); - GridContainer playfieldGrid; AddInternal(playfieldGrid = new GridContainer { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..d893a3fdde --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer + { + public ManiaPlayfieldAdjustmentContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(1, 0.8f); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index 3d553c334f..1e2a936002 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -16,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor { - private GameplayCursor cursor; + private GameplayCursorContainer cursorContainer; public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) }; - public CursorContainer Cursor => cursor; + public CursorContainer Cursor => cursorContainer; public bool ProvidingUserCursor => true; [BackgroundDependencyLoader] private void load() { - Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); + Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs index f5fe36b56a..8d097ff1c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs @@ -4,12 +4,13 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer + public class TestCaseHitCircleLongCombo : PlayerTestCase { public TestCaseHitCircleLongCombo() : base(new OsuRuleset()) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs new file mode 100644 index 0000000000..720c3c66fe --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseOsuPlayer : PlayerTestCase + { + public TestCaseOsuPlayer() + : base(new OsuRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs new file mode 100644 index 0000000000..5956f12146 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseResumeOverlay : ManualInputManagerTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuResumeOverlay), + }; + + public TestCaseResumeOverlay() + { + ManualOsuInputManager osuInputManager; + CursorContainer cursor; + ResumeOverlay resume; + + bool resumeFired = false; + + Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) + { + Children = new Drawable[] + { + cursor = new CursorContainer(), + resume = new OsuResumeOverlay + { + GameplayCursor = cursor + }, + } + }; + + resume.ResumeAction = () => resumeFired = true; + + AddStep("move mouse to center", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("show", () => resume.Show()); + + AddStep("move mouse away", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft)); + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("not dismissed", () => !resumeFired && resume.State == Visibility.Visible); + + AddStep("move mouse back", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("dismissed", () => resumeFired && resume.State == Visibility.Hidden); + } + + private class ManualOsuInputManager : OsuInputManager + { + public ManualOsuInputManager(RulesetInfo ruleset) + : base(ruleset) + { + } + + public void GameClick() + { + KeyBindingContainer.TriggerPressed(OsuAction.LeftButton); + KeyBindingContainer.TriggerReleased(OsuAction.LeftButton); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 57effe01f1..76bd9ef758 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -297,11 +297,6 @@ namespace osu.Game.Rulesets.Osu.Tests private void performTest(List frames) { - // Empty frame to be added as a workaround for first frame behavior. - // If an input exists on the first frame, the input would apply to the entire intro lead-in - // Likely requires some discussion regarding how first frame inputs should be handled. - frames.Insert(0, new OsuReplayFrame()); - AddStep("load player", () => { Beatmap.Value = new TestWorkingBeatmap(new Beatmap @@ -330,12 +325,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, }, Clock); - var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }) - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false - }; + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); p.OnLoadComplete += _ => { @@ -354,9 +344,9 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep(() => Beatmap.Value.Track.CurrentTime == 0, "Beatmap at 0"); - AddUntilStep(() => currentPlayer.IsCurrentScreen(), "Wait until player is loaded"); - AddUntilStep(() => allJudgedFired, "Wait for all judged"); + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for all judged", () => allJudgedFired); } private class ScoreAccessibleReplayPlayer : ReplayPlayer @@ -364,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public ScoreAccessibleReplayPlayer(Score score) - : base(score) + : base(score, false, false) { } } 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 273d29c3de..8c31db9a7d 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,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 1a352aa2a1..491d82b89e 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs new file mode 100644 index 0000000000..f6edd062e9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; + +namespace osu.Game.Rulesets.Osu.Configuration +{ + public class OsuRulesetConfigManager : RulesetConfigManager + { + public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + + Set(OsuRulesetSetting.SnakingInSliders, true); + Set(OsuRulesetSetting.SnakingOutSliders, true); + } + } + + public enum OsuRulesetSetting + { + SnakingInSliders, + SnakingOutSliders + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index a164b7e6bb..e257369ad9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components path = new SmoothPath { Anchor = Anchor.Centre, - PathWidth = 1 + PathRadius = 1 }, marker = new CircularContainer { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 179fa1bc72..957550a051 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChild = body = new ManualSliderBody { AccentColour = Color4.Transparent, - PathWidth = slider.Scale * 64 + PathRadius = slider.Scale * 64 }; } @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components body.BorderColour = colours.Yellow; PositionBindable.BindValueChanged(_ => updatePosition(), true); - ScaleBindable.BindValueChanged(scale => body.PathWidth = scale.NewValue * 64, true); + ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true); } private void updatePosition() => Position = slider.StackedPosition; diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs new file mode 100644 index 0000000000..d9cb203bdf --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class DrawableOsuEditRuleset : DrawableOsuRuleset + { + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; + + private class OsuPlayfieldNoCursor : OsuPlayfield + { + protected override GameplayCursorContainer CreateCursor() => null; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs deleted file mode 100644 index c293a5a4f8..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Cursor; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public class OsuEditRulesetContainer : OsuRulesetContainer - { - public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - protected override CursorContainer CreateCursor() => null; - - protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One }; - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 174321b8b9..039ec5585e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -2,8 +2,6 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -13,7 +11,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -26,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - => new OsuEditRulesetContainer(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + => new DrawableOsuEditRuleset(ruleset, beatmap); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { @@ -38,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; - public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 21b4dbfeda..401bd28d7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Autopilot"; public override string Acronym => "AP"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; + public override IconUsage Icon => OsuIcon.ModAutopilot; public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index d7ba0d4da9..bea2bbcb32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - protected override Score CreateReplayScore(Beatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, Replay = new OsuAutoGenerator(beatmap).Generate() diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index a203e23687..f3c7939a94 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -18,13 +17,13 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBlinds : Mod, IApplicableToRulesetContainer, IApplicableToScoreProcessor + public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor { public override string Name => "Blinds"; public override string Description => "Play with blinds on your screen."; public override string Acronym => "BL"; - public override FontAwesome Icon => FontAwesome.fa_adjust; + public override IconUsage Icon => FontAwesome.Solid.Adjust; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => false; @@ -32,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1.12; private DrawableOsuBlinds blinds; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - rulesetContainer.Overlays.Add(blinds = new DrawableOsuBlinds(rulesetContainer.Playfield.HitObjectContainer, rulesetContainer.Beatmap)); + drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap)); } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 65e9eb7a1d..35a5992e25 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "GR"; - public override FontAwesome Icon => FontAwesome.fa_arrows_v; + public override IconUsage Icon => FontAwesome.Solid.ArrowsAltV; public override ModType Type => ModType.Fun; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index efcab28310..ec23570f54 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -13,7 +13,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer + public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods state.Apply(osuInputManager.CurrentState, osuInputManager); } - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // grab the input manager for future use. - osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager; + osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; osuInputManager.AllowUserPresses = false; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 0c8436d096..1cdcddbd33 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Spun Out"; public override string Acronym => "SO"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; + public override IconUsage Icon => OsuIcon.ModSpunout; public override ModType Type => ModType.DifficultyReduction; public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 36fa5f3098..8360e2692e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Target"; public override string Acronym => "TP"; public override ModType Type => ModType.Conversion; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; + public override IconUsage Icon => OsuIcon.ModTarget; public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 9a769ec39c..9b079895fa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Transform"; public override string Acronym => "TR"; - public override FontAwesome Icon => FontAwesome.fa_arrows; + public override IconUsage Icon => FontAwesome.Solid.ArrowsAlt; public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index af9f0ec8f3..17fcd03dd5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Wiggle"; public override string Acronym => "WG"; - public override FontAwesome Icon => FontAwesome.fa_certificate; + public override IconUsage Icon => FontAwesome.Solid.Certificate; public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 2512e74da7..938a2293ba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -16,12 +15,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.Type != HitResult.Miss) - JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); - - base.LoadComplete(); + JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + base.ApplyHitAnimations(); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 22d2034fe0..edf2d90c08 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; using osuTK; -using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_chevron_right + Icon = FontAwesome.Solid.ChevronRight }, restrictSize: false) }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6595e53a6a..57ea0abdd8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -10,8 +10,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -33,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); + [Resolved(CanBeNull = true)] + private OsuRulesetConfigManager config { get; set; } + public DrawableSlider(Slider s) : base(s) { @@ -47,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Body = new SnakingSliderBody(s) { - PathWidth = s.Scale * 64, + PathRadius = s.Scale * 64, }, ticks = new Container { RelativeSizeAxes = Axes.Both }, repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, @@ -94,15 +97,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { - config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); - config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); + config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); scaleBindable.BindValueChanged(scale => { - Body.PathWidth = scale.NewValue * 64; + Body.PathRadius = scale.NewValue * 64; Ball.Scale = new Vector2(scale.NewValue); }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 789af4f49b..ab4935e350 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Ranking; using osu.Game.Rulesets.Scoring; @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(48), - Icon = FontAwesome.fa_asterisk, + Icon = FontAwesome.Solid.Asterisk, Shadow = false, }, } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 1b2e2c1f47..7d1d77ae96 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -14,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SliderBall : CircularContainer, ISliderProgress + public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition { private const float width = 128; @@ -107,18 +108,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private Vector2? lastScreenSpaceMousePosition; - protected override bool OnMouseDown(MouseDownEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseUp(e); - } - protected override bool OnMouseMove(MouseMoveEvent e) { lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; @@ -132,6 +121,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces base.ClearTransformsAfter(time, false, targetMember); } + public override void ApplyTransformsAt(double time, bool propagateChildren = false) + { + // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. + base.ApplyTransformsAt(time, false); + } + private bool tracking; public bool Tracking diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index b088f1914b..2f5c326bda 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private readonly BufferedContainer container; - public float PathWidth + public float PathRadius { - get => path.PathWidth; - set => path.PathWidth = value; + get => path.PathRadius; + set => path.PathRadius = value; } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 345f599b9d..1afbacc01e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.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 osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; @@ -155,116 +154,76 @@ namespace osu.Game.Rulesets.Osu.Objects { base.CreateNestedHitObjects(); - createSliderEnds(); - createTicks(); - createRepeatPoints(); - - if (LegacyLastTickOffset != null) - TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value); - } - - private void createSliderEnds() - { - HeadCircle = new SliderCircle + foreach (var e in + SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - StartTime = StartTime, - Position = Position, - Samples = getNodeSamples(0), - SampleControlPoint = SampleControlPoint, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; + var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL) + ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + var sampleList = new List(); - TailCircle = new SliderTailCircle(this) - { - StartTime = EndTime, - Position = EndPosition, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; - - AddNested(HeadCircle); - AddNested(TailCircle); - } - - private void createTicks() - { - // A very lenient maximum length of a slider for ticks to be generated. - // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. - const double max_length = 100000; - - var length = Math.Min(max_length, Path.Distance); - var tickDistance = MathHelper.Clamp(TickDistance, 0, length); - - if (tickDistance == 0) return; - - var minDistanceFromEnd = Velocity * 10; - - var spanCount = this.SpanCount(); - - for (var span = 0; span < spanCount; span++) - { - var spanStartTime = StartTime + span * SpanDuration; - var reversed = span % 2 == 1; - - for (var d = tickDistance; d <= length; d += tickDistance) - { - if (d > length - minDistanceFromEnd) - break; - - var distanceProgress = d / length; - var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - - var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL) - ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - var sampleList = new List(); - - if (firstSample != null) - sampleList.Add(new SampleInfo - { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", - }); - - AddNested(new SliderTick + if (firstSample != null) + sampleList.Add(new SampleInfo { - SpanIndex = span, - SpanStartTime = spanStartTime, - StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Path.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - Samples = sampleList + Bank = firstSample.Bank, + Volume = firstSample.Volume, + Name = @"slidertick", }); + + switch (e.Type) + { + case SliderEventType.Tick: + AddNested(new SliderTick + { + SpanIndex = e.SpanIndex, + SpanStartTime = e.SpanStartTime, + StartTime = e.Time, + Position = Position + Path.PositionAt(e.PathProgress), + StackHeight = StackHeight, + Scale = Scale, + Samples = sampleList + }); + break; + case SliderEventType.Head: + AddNested(HeadCircle = new SliderCircle + { + StartTime = e.Time, + Position = Position, + Samples = getNodeSamples(0), + SampleControlPoint = SampleControlPoint, + IndexInCurrentCombo = IndexInCurrentCombo, + 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. + // if this is to change, we should revisit this. + AddNested(TailCircle = new SliderTailCircle(this) + { + StartTime = e.Time, + Position = EndPosition, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, + }); + break; + case SliderEventType.Repeat: + AddNested(new RepeatPoint + { + RepeatIndex = e.SpanIndex, + SpanDuration = SpanDuration, + StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, + Position = Position + Path.PositionAt(e.PathProgress), + StackHeight = StackHeight, + Scale = Scale, + Samples = getNodeSamples(e.SpanIndex + 1) + }); + break; } } } - private void createRepeatPoints() - { - for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) - { - AddNested(new RepeatPoint - { - RepeatIndex = repeatIndex, - SpanDuration = SpanDuration, - StartTime = StartTime + repeat * SpanDuration, - Position = Position + Path.PositionAt(repeat % 2), - StackHeight = StackHeight, - Scale = Scale, - Samples = getNodeSamples(1 + repeatIndex) - }); - } - } - - private List getNodeSamples(int nodeIndex) - { - if (nodeIndex < NodeSamples.Count) - return NodeSamples[nodeIndex]; - - return Samples; - } + private List getNodeSamples(int nodeIndex) => + nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; public override Judgement CreateJudgement() => new OsuJudgement(); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 43a2ae0fbb..4f2af64161 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -8,6 +8,10 @@ using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects { + /// + /// Note that this should not be used for timing correctness. + /// See usage in for more information. + /// public class SliderTailCircle : SliderCircle { private readonly IBindable pathBindable = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5ae7344996..44bce5bed8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -9,15 +9,20 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Scoring; @@ -25,7 +30,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); @@ -125,14 +130,15 @@ namespace osu.Game.Rulesets.Osu { new OsuModTransform(), new OsuModWiggle(), - new OsuModGrow() + new OsuModGrow(), + new MultiMod(new ModWindUp(), new ModWindDown()), }; default: return new Mod[] { }; } } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); @@ -144,12 +150,14 @@ namespace osu.Game.Rulesets.Osu public override string ShortName => "osu"; - public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this); + public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); public override int? LegacyID => 0; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + public OsuRuleset(RulesetInfo rulesetInfo = null) : base(rulesetInfo) { diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index b0fb85d7ed..41bb740e46 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -10,12 +10,15 @@ using System.Linq; using osu.Framework.Graphics; using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Replays { public class OsuAutoGenerator : OsuAutoGeneratorBase { + public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap; + #region Parameters /// @@ -42,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Replays #region Construction / Initialisation - public OsuAutoGenerator(Beatmap beatmap) + public OsuAutoGenerator(IBeatmap beatmap) : base(beatmap) { // Already superhuman, but still somewhat realistic @@ -289,7 +292,6 @@ namespace osu.Game.Rulesets.Osu.Replays { // We add intermediate frames for spinning / following a slider here. case Spinner spinner: - { Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; @@ -312,9 +314,7 @@ namespace osu.Game.Rulesets.Osu.Replays endFrame.Position = endPosition; break; - } case Slider slider: - { for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) { Vector2 pos = slider.StackedPositionAt(j / slider.Duration); @@ -323,7 +323,6 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action)); break; - } } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 9a60f0cafc..9ab358ee12 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -3,7 +3,6 @@ using osuTK; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; using System; using System.Collections.Generic; using osu.Game.Replays; @@ -12,7 +11,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Osu.Replays { - public abstract class OsuAutoGeneratorBase : AutoGenerator + public abstract class OsuAutoGeneratorBase : AutoGenerator { #region Constants @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected Replay Replay; protected List Frames => Replay.Frames; - protected OsuAutoGeneratorBase(Beatmap beatmap) + protected OsuAutoGeneratorBase(IBeatmap beatmap) : base(beatmap) { Replay = new Replay(); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index d1ac77857d..c6ac1dd346 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; @@ -24,10 +25,14 @@ namespace osu.Game.Rulesets.Osu.Replays { get { - if (!HasFrames) + var frame = CurrentFrame; + + if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + Debug.Assert(CurrentTime != null); + + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } @@ -41,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Replays }, new ReplayState { - PressedActions = CurrentFrame.Actions + PressedActions = CurrentFrame?.Actions ?? new List() } }; } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 4f97cc0da5..2c8bf11016 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public OsuScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 0f8a0ce1ae..03dbf7ac63 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { private int currentIndex; - private Shader shader; + private IShader shader; private Texture texture; private Vector2 size => texture.Size * Scale; @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public override bool IsPresent => true; - private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); private const int max_sprites = 2048; private readonly TrailPart[] parts = new TrailPart[max_sprites]; @@ -55,7 +54,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor tNode.Texture = texture; tNode.Size = size; tNode.Time = time; - tNode.Shared = trailDrawNodeSharedData; for (int i = 0; i < parts.Length; ++i) if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) @@ -81,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) { - shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); + shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); texture = textures.Get(@"Cursor/cursortrail"); Scale = new Vector2(1 / texture.ScaleAdjust); } @@ -167,22 +165,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public bool WasUpdated; } - private class TrailDrawNodeSharedData - { - public VertexBuffer VertexBuffer; - } - private class TrailDrawNode : DrawNode { - public Shader Shader; + public IShader Shader; public Texture Texture; public float Time; - public TrailDrawNodeSharedData Shared; public readonly TrailPart[] Parts = new TrailPart[max_sprites]; public Vector2 Size; + private readonly VertexBuffer vertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); + public TrailDrawNode() { for (int i = 0; i < max_sprites; i++) @@ -194,9 +188,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public override void Draw(Action vertexAction) { - if (Shared.VertexBuffer == null) - Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); - Shader.GetUniform("g_FadeClock").UpdateValue(ref Time); int updateStart = -1, updateEnd = 0; @@ -218,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), DrawColourInfo.Colour, null, - v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex + v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex { Position = v.Position, TexturePosition = v.TexturePosition, @@ -230,24 +221,31 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } else if (updateStart != -1) { - Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); + vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); updateStart = -1; } } // Update all remaining vertices that have been changed. if (updateStart != -1) - Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); + vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); base.Draw(vertexAction); Shader.Bind(); Texture.TextureGL.Bind(); - Shared.VertexBuffer.Draw(); + vertexBuffer.Draw(); Shader.Unbind(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBuffer.Dispose(); + } } [StructLayout(LayoutKind.Sequential)] diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs deleted file mode 100644 index ef126cdf7d..0000000000 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI.Cursor -{ - public class GameplayCursor : CursorContainer, IKeyBindingHandler - { - protected override Drawable CreateCursor() => new OsuCursor(); - - protected override Container Content => fadeContainer; - - private readonly Container fadeContainer; - - public GameplayCursor() - { - InternalChild = fadeContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new CursorTrail { Depth = 1 } - } - }; - } - - private int downCount; - - private void updateExpandedState() - { - if (downCount > 0) - (ActiveCursor as OsuCursor)?.Expand(); - else - (ActiveCursor as OsuCursor)?.Contract(); - } - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - downCount++; - updateExpandedState(); - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (--downCount == 0) - updateExpandedState(); - break; - } - - return false; - } - - public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. - - protected override void PopIn() - { - fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); - } - - public class OsuCursor : SkinReloadableDrawable - { - private bool cursorExpand; - - private Bindable cursorScale; - private Bindable autoCursorScale; - private readonly IBindable beatmap = new Bindable(); - - private Container expandTarget; - private Drawable scaleTarget; - - public OsuCursor() - { - Origin = Anchor.Centre; - Size = new Vector2(42); - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IBindable beatmap) - { - InternalChild = expandTarget = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 6, - BorderColour = Color4.White, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Pink.Opacity(0.5f), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 3, - BorderColour = Color4.White.Opacity(0.5f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - }, restrictSize: false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - } - }; - - this.beatmap.BindTo(beatmap); - this.beatmap.ValueChanged += _ => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += _ => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = (float)cursorScale.Value; - - if (autoCursorScale.Value && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); - } - - scaleTarget.Scale = new Vector2(scale); - } - - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; - - public void Expand() - { - if (!cursorExpand) return; - - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); - } - - public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs new file mode 100644 index 0000000000..ecdafb0fa2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -0,0 +1,148 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursor : SkinReloadableDrawable + { + private bool cursorExpand; + + private Bindable cursorScale; + private Bindable autoCursorScale; + private readonly IBindable beatmap = new Bindable(); + + private Container expandTarget; + private Drawable scaleTarget; + + public OsuCursor() + { + Origin = Anchor.Centre; + Size = new Vector2(28); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, IBindable beatmap) + { + InternalChild = expandTarget = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 6, + BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Pink.Opacity(0.5f), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 3, + BorderColour = Color4.White.Opacity(0.5f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.1f), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + }, + }, + } + }, restrictSize: false) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + }; + + this.beatmap.BindTo(beatmap); + this.beatmap.ValueChanged += _ => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += _ => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = (float)cursorScale.Value; + + if (autoCursorScale.Value && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + } + + scaleTarget.Scale = new Vector2(scale); + } + + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + + public void Expand() + { + if (!cursorExpand) return; + + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + } + + public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs new file mode 100644 index 0000000000..f028a5f15c --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursorContainer : GameplayCursorContainer, IKeyBindingHandler + { + protected override Drawable CreateCursor() => new OsuCursor(); + + protected override Container Content => fadeContainer; + + private readonly Container fadeContainer; + + public OsuCursorContainer() + { + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CursorTrail { Depth = 1 } + } + }; + } + + private int downCount; + + private void updateExpandedState() + { + if (downCount > 0) + (ActiveCursor as OsuCursor)?.Expand(); + else + (ActiveCursor as OsuCursor)?.Contract(); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + updateExpandedState(); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (--downCount == 0) + updateExpandedState(); + break; + } + + return false; + } + + public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + + protected override void PopIn() + { + fadeContainer.FadeTo(1, 300, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs similarity index 67% rename from osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs rename to osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 935f9c5c0d..f6a3be40b0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -2,25 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.UI { - public class OsuRulesetContainer : RulesetContainer + public class DrawableOsuRuleset : DrawableRuleset { - public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } @@ -29,9 +31,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override Playfield CreatePlayfield() => new OsuPlayfield(); - public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + + protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); + + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) { switch (h) { @@ -56,7 +62,5 @@ namespace osu.Game.Rulesets.Osu.UI return first.StartTime - first.TimePreempt; } } - - protected override CursorContainer CreateCursor() => new GameplayCursor(); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2db2b45540..0cbe0cca85 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.UI.Cursor; namespace osu.Game.Rulesets.Osu.UI { @@ -22,35 +23,28 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); + public OsuPlayfield() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Size = new Vector2(0.75f); - - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + connectionLayer = new FollowPointRenderer { - connectionLayer = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, - HitObjectContainer, - approachCircles = new ApproachCircleProxyContainer - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, - } + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, + judgementLayer = new JudgementContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + }, + HitObjectContainer, + approachCircles = new ApproachCircleProxyContainer + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + }, }; } diff --git a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs similarity index 82% rename from osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index c383c47491..e28ff5f460 100644 --- a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -3,17 +3,23 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.UI { - public class PlayfieldAdjustmentContainer : Container + public class OsuPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public OsuPlayfieldAdjustmentContainer() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(0.75f); + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs new file mode 100644 index 0000000000..0d4e7edb7b --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuResumeOverlay : ResumeOverlay + { + private OsuClickToResumeCursor clickToResumeCursor; + + private GameplayCursorContainer localCursorContainer; + + public override CursorContainer LocalCursor => State == Visibility.Visible ? localCursorContainer : null; + + protected override string Message => "Click the orange cursor to resume"; + + [BackgroundDependencyLoader] + private void load() + { + Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); + } + + public override void Show() + { + base.Show(); + clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); + + if (localCursorContainer == null) + Add(localCursorContainer = new OsuCursorContainer()); + } + + public override void Hide() + { + localCursorContainer?.Expire(); + localCursorContainer = null; + + base.Hide(); + } + + protected override bool OnHover(HoverEvent e) => true; + + public class OsuClickToResumeCursor : OsuCursor, IKeyBindingHandler + { + public override bool HandlePositionalInput => true; + + public Action ResumeRequested; + + public OsuClickToResumeCursor() + { + RelativePositionAxes = Axes.Both; + } + + protected override bool OnHover(HoverEvent e) + { + updateColour(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateColour(); + base.OnHoverLost(e); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (!IsHovered) return false; + + this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); + + ResumeRequested?.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(OsuAction action) => false; + + public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => + { + updateColour(); + this.MoveTo(activeCursorPosition); + this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint); + }); + + private void updateColour() + { + this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs similarity index 64% rename from osu.Game.Rulesets.Osu/UI/OsuSettings.cs rename to osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 3620145ced..ce3432c73d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -3,34 +3,36 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Osu.Configuration; namespace osu.Game.Rulesets.Osu.UI { - public class OsuSettings : RulesetSettingsSubsection + public class OsuSettingsSubsection : RulesetSettingsSubsection { protected override string Header => "osu!"; - public OsuSettings(Ruleset ruleset) + public OsuSettingsSubsection(Ruleset ruleset) : base(ruleset) { } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { + var config = (OsuRulesetConfigManager)Config; + Children = new Drawable[] { new SettingsCheckbox { LabelText = "Snaking in sliders", - Bindable = config.GetBindable(OsuSetting.SnakingInSliders) + Bindable = config.GetBindable(OsuRulesetSetting.SnakingInSliders) }, new SettingsCheckbox { LabelText = "Snaking out sliders", - Bindable = config.GetBindable(OsuSetting.SnakingOutSliders) + Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) }, }; } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 00e1b649d9..369cdd49d2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private TaikoRulesetContainer rulesetContainer; + private DrawableTaikoRuleset drawableRuleset; private Container playfieldContainer; [BackgroundDependencyLoader] @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) } }); } @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -154,33 +154,33 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; + BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; - rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { var swell = new Swell { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, Duration = duration, }; swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableSwell(swell)); + drawableRuleset.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration) @@ -190,40 +190,40 @@ namespace osu.Game.Rulesets.Taiko.Tests var d = new DrumRoll { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, }; d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); + drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); + drawableRuleset.Playfield.Add(new DrawableCentreHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableRimHit(h)); + drawableRuleset.Playfield.Add(new DrawableRimHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit 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 fade054382..72ce6c947b 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,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index 2bc39a1c58..b595f43fbb 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { Name = @"Hit Count", Content = hits.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Drumroll Count", Content = drumrolls.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Swell Count", Content = swells.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 4e5491da9c..5b890b3d03 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, Replay = new TaikoAutoGenerator(beatmap).Generate(), diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index b99ec57166..b7db3307ad 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Mods private TaikoPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (TaikoPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (TaikoPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class TaikoFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index 5e4c6edb43..f8909fb98c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -44,17 +44,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Y; Width = tracker_width; - InternalChildren = new[] + AddInternal(Tracker = new Box { - Tracker = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(0.5f, 0), - Alpha = 0.75f - } - }; + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + Alpha = 0.75f + }); } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f755c7cc3..8dfe89eea7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected DrawableTaikoHitObject(TaikoHitObject hitObject) : base(hitObject) { - InternalChildren = new[] + AddRangeInternal(new[] { nonProxiedContent = new Container { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Child = Content = new Container { RelativeSizeAxes = Axes.Both } }, proxiedContent = new ProxiedContentContainer { RelativeSizeAxes = Axes.Both } - }; + }); } /// diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index f8dcaa0b45..0ed9923924 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -4,7 +4,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_asterisk, + Icon = FontAwesome.Solid.Asterisk, Shadow = false } }; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 14a6f98480..01ba53e07b 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -9,14 +9,17 @@ using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Taiko.Beatmaps; namespace osu.Game.Rulesets.Taiko.Replays { - public class TaikoAutoGenerator : AutoGenerator + public class TaikoAutoGenerator : AutoGenerator { + public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap; + private const double swell_hit_speed = 50; - public TaikoAutoGenerator(Beatmap beatmap) + public TaikoAutoGenerator(IBeatmap beatmap) : base(beatmap) { Replay = new Replay(); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index d97d7626ef..97337acc45 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 73cd9ba821..442cca49f8 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; - public TaikoScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public TaikoScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7851a2f919..448b1b42bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -9,8 +9,10 @@ using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; @@ -22,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] @@ -99,6 +101,11 @@ namespace osu.Game.Rulesets.Taiko new MultiMod(new TaikoModAutoplay(), new ModCinema()), new TaikoModRelax(), }; + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()) + }; default: return new Mod[] { }; } @@ -108,7 +115,7 @@ namespace osu.Game.Rulesets.Taiko public override string ShortName => "taiko"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 90841f11f5..943adaed4b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -39,12 +39,10 @@ namespace osu.Game.Rulesets.Taiko.UI } } - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.IsHit) - this.MoveToY(-100, 500); - - base.LoadComplete(); + this.MoveToY(-100, 500); + base.ApplyHitAnimations(); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs similarity index 87% rename from osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs rename to osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 7a73f4bd2a..f4b9c46dfc 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -20,13 +20,13 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoRulesetContainer : ScrollingRulesetContainer + public class DrawableTaikoRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override bool UserScrollSpeedAdjustment => false; - public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { Direction.Value = ScrollingDirection.Left; @@ -81,11 +81,13 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + + protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); - public override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(TaikoHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index cb527adb98..dbff5270d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class TaikoPlayfield : ScrollingPlayfield { /// - /// Default height of a when inside a . + /// Default height of a when inside a . /// public const float DEFAULT_HEIGHT = 178; @@ -55,143 +55,137 @@ namespace osu.Game.Rulesets.Taiko.UI public TaikoPlayfield(ControlPointInfo controlPoints) { - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + backgroundContainer = new Container { - backgroundContainer = new Container + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + background = new Box { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container - { - Name = "Right area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container - { - Name = "Masked elements before hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Children = new Drawable[] - { - hitExplosionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Blending = BlendingMode.Additive, - }, - HitTarget = new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - } - } - }, - barlineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } - }, - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Child = HitObjectContainer - }, - kiaiExplosionContainer = new Container - { - Name = "Kiai hit explosions", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - judgementContainer = new JudgementContainer - { - Name = "Judgements", - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - } - }, - overlayBackgroundContainer = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Y, - Size = new Vector2(left_area_size, 1), - Children = new Drawable[] - { - overlayBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum(controlPoints) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Scale = new Vector2(0.9f), - Margin = new MarginPadding { Right = 20 } - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }, - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - BorderThickness = 2, - AlwaysPresent = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - topLevelHitContainer = new Container - { - Name = "Top level hit objects", - RelativeSizeAxes = Axes.Both, } + }, + new Container + { + Name = "Right area", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + Name = "Masked elements before hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Blending = BlendingMode.Additive, + }, + HitTarget = new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + } + } + }, + barlineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } + }, + new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Child = HitObjectContainer + }, + kiaiExplosionContainer = new Container + { + Name = "Kiai hit explosions", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + judgementContainer = new JudgementContainer + { + Name = "Judgements", + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + } + }, + overlayBackgroundContainer = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Y, + Size = new Vector2(left_area_size, 1), + Children = new Drawable[] + { + overlayBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum(controlPoints) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Scale = new Vector2(0.9f), + Margin = new MarginPadding { Right = 20 } + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }, + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + BorderThickness = 2, + AlwaysPresent = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + topLevelHitContainer = new Container + { + Name = "Top level hit objects", + RelativeSizeAxes = Axes.Both, } }; } diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs similarity index 68% rename from osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 0f0ad59fd3..84464b199e 100644 --- a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -1,16 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI { - public class PlayfieldAdjustmentContainer : Container + public class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; + public TaikoPlayfieldAdjustmentContainer() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 171ba91ada..02dff6993d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -333,6 +333,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); + Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); } SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 136d1de930..2288d04493 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -29,28 +29,28 @@ namespace osu.Game.Tests.Beatmaps.Formats StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); - Assert.AreEqual(16, background.Elements.Count()); + Assert.AreEqual(16, background.Elements.Count); Assert.IsTrue(background.EnabledWhenFailing); Assert.IsTrue(background.EnabledWhenPassing); Assert.AreEqual("Background", background.Name); StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); Assert.IsNotNull(fail); - Assert.AreEqual(0, fail.Elements.Count()); + Assert.AreEqual(0, fail.Elements.Count); Assert.IsTrue(fail.EnabledWhenFailing); Assert.IsFalse(fail.EnabledWhenPassing); Assert.AreEqual("Fail", fail.Name); StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); Assert.IsNotNull(pass); - Assert.AreEqual(0, pass.Elements.Count()); + Assert.AreEqual(0, pass.Elements.Count); Assert.IsFalse(pass.EnabledWhenFailing); Assert.IsTrue(pass.EnabledWhenPassing); Assert.AreEqual("Pass", pass.Name); StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); Assert.IsNotNull(foreground); - Assert.AreEqual(151, foreground.Elements.Count()); + Assert.AreEqual(151, foreground.Elements.Count); Assert.IsTrue(foreground.EnabledWhenFailing); Assert.IsTrue(foreground.EnabledWhenPassing); Assert.AreEqual("Foreground", foreground.Name); @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(15, spriteCount); Assert.AreEqual(1, animationCount); Assert.AreEqual(0, sampleCount); - Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount); + Assert.AreEqual(background.Elements.Count, spriteCount + animationCount + sampleCount); var sprite = background.Elements.ElementAt(0) as StoryboardSprite; Assert.NotNull(sprite); @@ -70,9 +70,9 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); Assert.IsTrue(sprite.IsDrawable); Assert.AreEqual(Anchor.Centre, sprite.Origin); - Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); + Assert.AreEqual("SB/black.jpg", sprite.Path); - var animation = background.Elements.ElementAt(12) as StoryboardAnimation; + var animation = background.Elements.OfType().First(); Assert.NotNull(animation); Assert.AreEqual(141175, animation.EndTime); Assert.AreEqual(10, animation.FrameCount); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index d5ab0e43d1..a867ddebae 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -113,6 +113,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestCase(normal)] [TestCase(marathon)] + [Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")] // Currently fails: // [TestCase(with_sb)] public void TestParity(string beatmap) diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs new file mode 100644 index 0000000000..b3863bcf44 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using NUnit.Framework; +using osu.Game.Beatmaps.Formats; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class ParsingTest + { + [Test] + public void TestNaNHandling() => allThrow("NaN"); + + [Test] + public void TestBadStringHandling() => allThrow("Random string 123"); + + [TestCase(Parsing.MAX_PARSE_VALUE)] + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(-Parsing.MAX_PARSE_VALUE)] + [TestCase(10, 10)] + [TestCase(-10, 10)] + public void TestValidRanges(double input, double limit = Parsing.MAX_PARSE_VALUE) + { + Assert.AreEqual(Parsing.ParseInt((input).ToString(CultureInfo.InvariantCulture), (int)limit), (int)input); + Assert.AreEqual(Parsing.ParseFloat((input).ToString(CultureInfo.InvariantCulture), (float)limit), (float)input); + Assert.AreEqual(Parsing.ParseDouble((input).ToString(CultureInfo.InvariantCulture), limit), input); + } + + [TestCase(double.PositiveInfinity)] + [TestCase(double.NegativeInfinity)] + [TestCase(999999999999)] + [TestCase(Parsing.MAX_PARSE_VALUE * 1.1)] + [TestCase(-Parsing.MAX_PARSE_VALUE * 1.1)] + [TestCase(11, 10)] + [TestCase(-11, 10)] + public void TestOutOfRangeHandling(double input, double limit = Parsing.MAX_PARSE_VALUE) + => allThrow(input.ToString(CultureInfo.InvariantCulture), limit); + + private void allThrow(string input, double limit = Parsing.MAX_PARSE_VALUE) + where T : Exception + { + Assert.Throws(getIntParseException(input) ?? typeof(T), () => Parsing.ParseInt(input, (int)limit)); + Assert.Throws(() => Parsing.ParseFloat(input, (float)limit)); + Assert.Throws(() => Parsing.ParseDouble(input, limit)); + } + + /// + /// may not be able to parse some inputs. + /// In this case we expect to receive the raw parsing exception. + /// + /// The input attempting to be parsed. + /// The type of exception thrown by . Null if no exception is thrown. + private Type getIntParseException(string input) + { + try + { + var _ = int.Parse(input); + } + catch (Exception e) + { + return e.GetType(); + } + + return null; + } + } +} diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index b6a8b3b06c..f020c2a805 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.IO int fireCount = 0; // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded += (_, __, ___) => fireCount++; + manager.ItemAdded += (_, __) => fireCount++; manager.ItemRemoved += _ => fireCount++; var imported = LoadOszIntoOsu(osu); @@ -207,6 +207,96 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [TestCase(true)] + [TestCase(false)] + public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}")) + { + try + { + var osu = loadOsu(host); + + var imported = LoadOszIntoOsu(osu); + + if (set) + imported.OnlineBeatmapSetID = 1234; + else + imported.Beatmaps.First().OnlineBeatmapID = 1234; + + osu.Dependencies.Get().Update(imported); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = LoadOszIntoOsu(osu); + + // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportWithDuplicateBeatmapIDs() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID")) + { + try + { + var osu = loadOsu(host); + + var metadata = new BeatmapMetadata + { + Artist = "SomeArtist", + AuthorString = "SomeAuthor" + }; + + var difficulty = new BeatmapDifficulty(); + + var toImport = new BeatmapSetInfo + { + OnlineBeatmapSetID = 1, + Metadata = metadata, + Beatmaps = new List + { + new BeatmapInfo + { + OnlineBeatmapID = 2, + Metadata = metadata, + BaseDifficulty = difficulty + }, + new BeatmapInfo + { + OnlineBeatmapID = 2, + Metadata = metadata, + Status = BeatmapSetOnlineStatus.Loved, + BaseDifficulty = difficulty + } + } + }; + + var manager = osu.Dependencies.Get(); + + var imported = manager.Import(toImport); + + Assert.NotNull(imported); + Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID); + Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID); + } + finally + { + host.Exit(); + } + } + } + [Test] [NonParallelizable] [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs new file mode 100644 index 0000000000..73387fa5ab --- /dev/null +++ b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs @@ -0,0 +1,284 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class FramedReplayinputHandlerTest + { + private Replay replay; + private TestInputHandler handler; + + [SetUp] + public void SetUp() + { + handler = new TestInputHandler(replay = new Replay + { + Frames = new List + { + new TestReplayFrame(0), + new TestReplayFrame(1000), + new TestReplayFrame(2000), + new TestReplayFrame(3000, true), + new TestReplayFrame(4000, true), + new TestReplayFrame(5000, true), + new TestReplayFrame(7000, true), + new TestReplayFrame(8000), + } + }); + } + + [Test] + public void TestNormalPlayback() + { + Assert.IsNull(handler.CurrentFrame); + + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + + //if we hit the first frame perfectly, time should progress to it. + setTime(1000, 1000); + confirmCurrentFrame(1); + confirmNextFrame(2); + + //in between non-important frames should progress based on input. + setTime(1200, 1200); + confirmCurrentFrame(1); + + setTime(1400, 1400); + confirmCurrentFrame(1); + + // progressing beyond the next frame should force time to that frame once. + setTime(2200, 2000); + confirmCurrentFrame(2); + + // second attempt should progress to input time + setTime(2200, 2200); + confirmCurrentFrame(2); + + // entering important section + setTime(3000, 3000); + confirmCurrentFrame(3); + + // cannot progress within + setTime(3500, null); + confirmCurrentFrame(3); + + setTime(4000, 4000); + confirmCurrentFrame(4); + + // still cannot progress + setTime(4500, null); + confirmCurrentFrame(4); + + setTime(5200, 5000); + confirmCurrentFrame(5); + + // important section AllowedImportantTimeSpan allowance + setTime(5200, 5200); + confirmCurrentFrame(5); + + setTime(7200, 7000); + confirmCurrentFrame(6); + + setTime(7200, null); + confirmCurrentFrame(6); + + // exited important section + setTime(8200, 8000); + confirmCurrentFrame(7); + confirmNextFrame(null); + + setTime(8200, 8200); + confirmCurrentFrame(7); + confirmNextFrame(null); + } + + [Test] + public void TestIntroTime() + { + setTime(-1000, -1000); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(-500, -500); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + } + + [Test] + public void TestBasicRewind() + { + setTime(2800, 0); + setTime(2800, 1000); + setTime(2800, 2000); + setTime(2800, 2800); + confirmCurrentFrame(2); + confirmNextFrame(3); + + // pivot without crossing a frame boundary + setTime(2700, 2700); + confirmCurrentFrame(2); + confirmNextFrame(1); + + // cross current frame boundary; should not yet update frame + setTime(1980, 1980); + confirmCurrentFrame(2); + confirmNextFrame(1); + + setTime(1200, 1200); + confirmCurrentFrame(2); + confirmNextFrame(1); + + //ensure each frame plays out until start + setTime(-500, 1000); + confirmCurrentFrame(1); + confirmNextFrame(0); + + setTime(-500, 0); + confirmCurrentFrame(0); + confirmNextFrame(null); + + setTime(-500, -500); + confirmCurrentFrame(0); + confirmNextFrame(null); + } + + [Test] + public void TestRewindInsideImportantSection() + { + // fast forward to important section + while (handler.SetFrameFromTime(3000) != null) + { + } + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + + setTime(3500, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4500, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4000, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + [Test] + public void TestRewindOutOfImportantSection() + { + // fast forward to important section + while (handler.SetFrameFromTime(3500) != null) + { + } + + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3200, null); + // next frame doesn't change even though direction reversed, because of important section. + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3000, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(2800, 2800); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + private void setTime(double set, double? expect) + { + Assert.AreEqual(expect, handler.SetFrameFromTime(set)); + } + + private void confirmCurrentFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.CurrentFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time); + } + else + { + Assert.IsNull(handler.CurrentFrame); + } + } + + private void confirmNextFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.NextFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time); + } + else + { + Assert.IsNull(handler.NextFrame); + } + } + + private class TestReplayFrame : ReplayFrame + { + public readonly bool IsImportant; + + public TestReplayFrame(double time, bool isImportant = false) + : base(time) + { + IsImportant = isImportant; + } + } + + private class TestInputHandler : FramedReplayInputHandler + { + public TestInputHandler(Replay replay) + : base(replay) + { + } + + protected override double AllowedImportantTimeSpan => 1000; + + protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant; + } + } +} diff --git a/osu.Game.Tests/Resources/hitobject-file-samples.osu b/osu.Game.Tests/Resources/hitobject-file-samples.osu index 588672e2d9..7c69f259b8 100644 --- a/osu.Game.Tests/Resources/hitobject-file-samples.osu +++ b/osu.Game.Tests/Resources/hitobject-file-samples.osu @@ -13,4 +13,4 @@ SampleSet: Normal 255,193,2170,1,0,0:0:0:0:hit_1.wav 256,191,2638,5,0,0:0:0:0:hit_2.wav 255,193,3107,1,0,0:0:0:0: -256,191,3576,1,0,0:0:0:0:hit_1.wav +256,191,3576,1,0,0:0:0:70:hit_1.wav diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs similarity index 68% rename from osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs rename to osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index d850c58f09..891b89e72d 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -9,7 +9,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Input.States; @@ -33,7 +32,7 @@ using osu.Game.Users; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Background { [TestFixture] public class TestCaseBackgroundScreenBeatmap : ManualInputManagerTestCase @@ -48,14 +47,12 @@ namespace osu.Game.Tests.Visual }; private DummySongSelect songSelect; - private DimAccessiblePlayerLoader playerLoader; - private DimAccessiblePlayer player; + private TestPlayerLoader playerLoader; + private TestPlayer player; private DatabaseContextFactory factory; private BeatmapManager manager; private RulesetStore rulesets; - private ScreenStackCacheContainer screenStackContainer; - [BackgroundDependencyLoader] private void load(GameHost host) { @@ -74,31 +71,29 @@ namespace osu.Game.Tests.Visual Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); + manager.Import(TestResources.GetTestBeatmapForImport()); + Beatmap.SetDefault(); } [SetUp] - public virtual void SetUp() + public virtual void SetUp() => Schedule(() => { - Schedule(() => + Child = new OsuScreenStack(songSelect = new DummySongSelect()) { - manager.Delete(manager.GetAllUsableBeatmapSets()); - var temp = TestResources.GetTestBeatmapForImport(); - manager.Import(temp); - Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both }; - screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect()); - }); - } + RelativeSizeAxes = Axes.Both + }; + }); /// - /// Check if properly triggers background dim previews when a user hovers over the visual settings panel. + /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel. /// [Test] public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer()))); - AddUntilStep(() => playerLoader?.IsLoaded ?? false, "Wait for Player Loader to load"); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true }))); + AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => { @@ -106,16 +101,16 @@ namespace osu.Game.Tests.Visual InputManager.MoveMouseTo(playerLoader.VisualSettingsPos); }); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); waitForDim(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); } /// /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings: - /// The OnHover of PlayerLoader will trigger, which could potentially trigger an undim unless checked for in PlayerLoader. - /// We need to check that in this scenario, the dim is still properly applied after entering player. + /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader. + /// We need to check that in this scenario, the dim and blur is still properly applied after entering player. /// [Test] public void PlayerLoaderTransitionTest() @@ -124,7 +119,7 @@ namespace osu.Game.Tests.Visual AddStep("Trigger hover event", () => playerLoader.TriggerOnHover()); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); waitForDim(); - AddAssert("Screen is dimmed and unblurred", () => songSelect.IsBackgroundDimmed() && songSelect.IsBackgroundUnblurred()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// @@ -165,51 +160,54 @@ namespace osu.Game.Tests.Visual } /// - /// Check if the is properly accepting user dim changes at all. + /// Check if the is properly accepting user-defined visual changes at all. /// [Test] public void DisableUserDimTest() { performFullSetup(); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false); waitForDim(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// - /// Check if the fade container retains dim when pausing + /// Check if the visual settings container retains dim and blur when pausing /// [Test] public void PauseTest() { performFullSetup(true); - AddStep("Pause", () => player.CurrentPauseContainer.Pause()); + AddStep("Pause", () => player.Pause()); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); - AddStep("Unpause", () => player.CurrentPauseContainer.Resume()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Unpause", () => player.Resume()); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// - /// Check if the fade container removes user dim when suspending for + /// Check if the visual settings container removes user dim when suspending for /// [Test] public void TransitionTest() { performFullSetup(); - AddStep("Transition to Results", () => player.Push(new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); + var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); + AddStep("Transition to Results", () => player.Push(results)); + AddUntilStep("Wait for results is current", results.IsCurrentScreen); waitForDim(); - AddAssert("Screen is undimmed and is original background", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent()); + AddAssert("Screen is undimmed, original background retained", () => + songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); } /// - /// Check if background gets undimmed when leaving for + /// Check if background gets undimmed and unblurred when leaving for /// [Test] public void TransitionOutTest() @@ -217,10 +215,26 @@ namespace osu.Game.Tests.Visual performFullSetup(); AddStep("Exit to song select", () => player.Exit()); waitForDim(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); } - private void waitForDim() => AddWaitStep(5, "Wait for dim"); + /// + /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim. + /// + [Test] + public void ResumeFromPlayerTest() + { + performFullSetup(); + AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); + AddStep("Resume PlayerLoader", () => player.Restart()); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); + waitForDim(); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); + } + + private void waitForDim() => AddWaitStep("Wait for dim", 5); private void createFakeStoryboard() => AddStep("Create storyboard", () => { @@ -241,27 +255,21 @@ namespace osu.Game.Tests.Visual { setupUserSettings(); - AddStep("Start player loader", () => - { - songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer - { - AllowPause = allowPause, - Ready = true, - })); - }); - AddUntilStep(() => playerLoader.IsLoaded, "Wait for Player Loader to load"); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause)))); + + AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); - AddUntilStep(() => player.IsLoaded, "Wait for player to load"); + AddUntilStep("Wait for player to load", () => player.IsLoaded); } private void setupUserSettings() { - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "Song select has selection"); + 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() }); songSelect.DimLevel.Value = 0.7f; - songSelect.BlurLevel.Value = 0.0f; + songSelect.BlurLevel.Value = 0.4f; }); } @@ -289,14 +297,18 @@ namespace osu.Game.Tests.Visual public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value); - public bool IsBackgroundUnblurred() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0); - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; + public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25); + + public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0); + public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0; public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1; + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + /// /// Make sure every time a screen gets pushed, the background doesn't get replaced /// @@ -312,9 +324,11 @@ namespace osu.Game.Tests.Visual } protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); + + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } - private class DimAccessiblePlayer : Player + private class TestPlayer : Player { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); @@ -328,58 +342,50 @@ namespace osu.Game.Tests.Visual }; } - public PauseContainer CurrentPauseContainer => PauseContainer; - public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. - public bool Ready; + public bool BlockLoad; public Bindable StoryboardEnabled; public readonly Bindable ReplacesBackground = new Bindable(); public readonly Bindable IsPaused = new Bindable(); + public TestPlayer(bool allowPause = true) + : base(allowPause) + { + } + public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1; public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, CancellationToken token) { - while (!Ready) + while (BlockLoad && !token.IsCancellationRequested) Thread.Sleep(1); + StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); ReplacesBackground.BindTo(Background.StoryboardReplacesBackground); - RulesetContainer.IsPaused.BindTo(IsPaused); + DrawableRuleset.IsPaused.BindTo(IsPaused); } } - private class ScreenStackCacheContainer : Container - { - [Cached] - private BackgroundScreenStack backgroundScreenStack; - - public readonly ScreenStack ScreenStack; - - public ScreenStackCacheContainer() - { - Add(backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }); - Add(ScreenStack = new ScreenStack { RelativeSizeAxes = Axes.Both }); - } - } - - private class DimAccessiblePlayerLoader : PlayerLoader + private class TestPlayerLoader : PlayerLoader { public VisualSettings VisualSettingsPos => VisualSettings; public BackgroundScreen ScreenPos => Background; - public DimAccessiblePlayerLoader(Player player) + public TestPlayerLoader(Player player) : base(() => player) { } public void TriggerOnHover() => OnHover(new HoverEvent(new InputState())); + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); } @@ -388,6 +394,7 @@ namespace osu.Game.Tests.Visual protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both }; public Color4 CurrentColour => fadeContainer.CurrentColour; + public float CurrentAlpha => fadeContainer.CurrentAlpha; public Vector2 CurrentBlur => Background.BlurSigma; diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs similarity index 88% rename from osu.Game.Tests/Visual/TestCaseIdleTracker.cs rename to osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs index 8e8c4e38ae..bf59c116bb 100644 --- a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs @@ -9,7 +9,7 @@ using osu.Game.Input; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { [TestFixture] public class TestCaseIdleTracker : ManualInputManagerTestCase @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual { AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre)); - AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual AddAssert("check idle", () => !box3.IsIdle); AddAssert("check idle", () => !box4.IsIdle); - AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); } [Test] @@ -96,13 +96,13 @@ namespace osu.Game.Tests.Visual AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); - AddUntilStep(() => box1.IsIdle, "Wait for idle"); + AddUntilStep("Wait for idle", () => box1.IsIdle); AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); - AddUntilStep(() => box2.IsIdle, "Wait for idle"); + AddUntilStep("Wait for idle", () => box2.IsIdle); AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle); - AddUntilStep(() => box3.IsIdle, "Wait for idle"); + AddUntilStep("Wait for idle", () => box3.IsIdle); - AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); } private class IdleTrackingBox : CompositeDrawable diff --git a/osu.Game.Tests/Visual/TestCasePollingComponent.cs b/osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCasePollingComponent.cs rename to osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs index 63f4f88948..6582145f6e 100644 --- a/osu.Game.Tests/Visual/TestCasePollingComponent.cs +++ b/osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs @@ -13,7 +13,7 @@ using osu.Game.Online; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { public class TestCasePollingComponent : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs rename to osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs index 87a1fb0faf..4b6ae696fe 100644 --- a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { public class TestCasePreviewTrackManager : OsuTestCase, IPreviewTrackOwner { diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs rename to osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs index daf71a6447..e822e01110 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { public class TestCaseBeatDivisorControl : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseEditorCompose.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs index a52454d684..aa7c7f5cb3 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Compose; using osu.Game.Tests.Beatmaps; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorCompose : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs index 5a4ac77372..499db1b69f 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Components.RadioButtons; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorComposeRadioButtons : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs index 9ae9b55546..d7712293c3 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,9 +13,10 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorComposeTimeline : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs index 2abbf7cb80..b012d4b52d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components.Menus; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorMenuBar : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs index 0ec87e6f52..9daba54b58 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorSeekSnapping : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs index 305924958b..99d6385804 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osuTK; -using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorSummaryTimeline : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs rename to osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs index 988c0459d4..be335fb71f 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Timing; -using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -20,8 +19,9 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] [Cached(Type = typeof(IPlacementHandler))] diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCasePlaybackControl.cs rename to osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs index abcff24c67..7d9b43251e 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Tests.Beatmaps; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCasePlaybackControl : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseWaveContainer.cs rename to osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs index 07a282a1a7..e87304ded6 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseWaveContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseWaveform.cs rename to osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs index 9330070392..c35e8741c1 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osuTK; -using osuTK.Graphics; 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 +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseWaveform : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs rename to osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs index c3e8e4e05f..e2cf1ef28a 100644 --- a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs similarity index 55% rename from osu.Game.Tests/Visual/TestCaseAutoplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index 61339a6af8..a2d92b7861 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -1,40 +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; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : TestCasePlayer + public class TestCaseAutoplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return new ScoreAccessiblePlayer - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; + return new ScoreAccessiblePlayer(); } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } private class ScoreAccessiblePlayer : Player { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public ScoreAccessiblePlayer() + : base(false, false) + { + } } } } diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseBreakOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs index f45d3c98ca..dda8005f70 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs @@ -1,12 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps.Timing; using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseBreakOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs similarity index 88% rename from osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs index a21573236a..8e43bf6d3a 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs @@ -5,38 +5,48 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using osuTK.Input; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Game.Input.Bindings; using osu.Game.Screens.Play; using osuTK; +using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("player pause/fail screens")] public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; private FailOverlay failOverlay; - private PauseContainer.PauseOverlay pauseOverlay; + private PauseOverlay pauseOverlay; + + private GlobalActionContainer globalActionContainer; [BackgroundDependencyLoader] - private void load() + private void load(OsuGameBase game) { - Add(pauseOverlay = new PauseContainer.PauseOverlay + Child = globalActionContainer = new GlobalActionContainer(game) { - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); + Children = new Drawable[] + { + pauseOverlay = new PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }, + failOverlay = new FailOverlay - Add(failOverlay = new FailOverlay - { - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + } + } + }; var retryCount = 0; @@ -79,12 +89,6 @@ namespace osu.Game.Tests.Visual AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); } - private void press(Key key) - { - InputManager.PressKey(key); - InputManager.ReleaseKey(key); - } - /// /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. /// @@ -92,7 +96,7 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => pauseOverlay.Show()); - AddStep("Press enter", () => press(Key.Enter)); + AddStep("Press select", () => press(GlobalAction.Select)); AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); AddStep("Hide overlay", () => pauseOverlay.Hide()); @@ -270,5 +274,17 @@ namespace osu.Game.Tests.Visual }); AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); } + + private void press(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } + + private void press(GlobalAction action) + { + globalActionContainer.TriggerPressed(action); + globalActionContainer.TriggerReleased(action); + } } } diff --git a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs similarity index 84% rename from osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs index 5ee1340044..14e9c7cdb6 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("'Hold to Quit' UI element")] public class TestCaseHoldForMenuButton : ManualInputManagerTestCase @@ -32,9 +32,9 @@ namespace osu.Game.Tests.Visual var text = holdForMenuButton.Children.OfType().First(); AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); - AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); + AddUntilStep("Text visible", () => text.IsPresent && !exitAction); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); - AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); + AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction); AddStep("Trigger exit action", () => { @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual AddAssert("action not triggered", () => !exitAction); AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep(() => exitAction, $"{nameof(holdForMenuButton.Action)} was triggered"); + AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); } } } diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseKeyCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs index 52caffc29f..4b55879224 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs @@ -11,7 +11,7 @@ using osu.Framework.Timing; using osu.Game.Screens.Play; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseKeyCounter : ManualInputManagerTestCase @@ -20,13 +20,13 @@ namespace osu.Game.Tests.Visual { typeof(KeyCounterKeyboard), typeof(KeyCounterMouse), - typeof(KeyCounterCollection) + typeof(KeyCounterDisplay) }; public TestCaseKeyCounter() { KeyCounterKeyboard rewindTestKeyCounterKeyboard; - KeyCounterCollection kc = new KeyCounterCollection + KeyCounterDisplay kc = new KeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMedalOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs index c7c85fc412..dd686c36e6 100644 --- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.MedalSplash; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseMedalOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs new file mode 100644 index 0000000000..a52e84ed62 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -0,0 +1,205 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCasePause : PlayerTestCase + { + protected new PausePlayer Player => (PausePlayer)base.Player; + + private readonly Container content; + + protected override Container Content => content; + + public TestCasePause() + : base(new OsuRuleset()) + { + base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); + } + + [Test] + public void TestPauseResume() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + pauseAndConfirm(); + resumeAndConfirm(); + } + + [Test] + public void TestResumeWithResumeOverlay() + { + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + resume(); + + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + AddStep("click to resume", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + confirmClockRunning(true); + } + + [Test] + public void TestResumeWithResumeOverlaySkipped() + { + AddStep("move cursor to button", () => + InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType().First().ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + resumeAndConfirm(); + } + + [Test] + public void TestPauseTooSoon() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + resumeAndConfirm(); + + pause(); + + confirmClockRunning(true); + confirmPauseOverlayShown(false); + } + + [Test] + public void TestExitTooSoon() + { + pauseAndConfirm(); + + resume(); + + AddStep("exit too soon", () => Player.Exit()); + + confirmClockRunning(true); + confirmPauseOverlayShown(false); + + AddAssert("not exited", () => Player.IsCurrentScreen()); + } + + [Test] + public void TestPauseAfterFail() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddAssert("fail overlay shown", () => Player.FailOverlayVisible); + + confirmClockRunning(false); + + pause(); + + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); + + exitAndConfirm(); + } + + [Test] + public void TestExitFromGameplay() + { + AddStep("exit", () => Player.Exit()); + + confirmPaused(); + + exitAndConfirm(); + } + + [Test] + public void TestExitFromPause() + { + pauseAndConfirm(); + exitAndConfirm(); + } + + private void pauseAndConfirm() + { + pause(); + confirmPaused(); + } + + private void resumeAndConfirm() + { + resume(); + confirmResumed(); + } + + private void exitAndConfirm() + { + AddUntilStep("player not exited", () => Player.IsCurrentScreen()); + AddStep("exit", () => Player.Exit()); + confirmExited(); + } + + private void confirmPaused() + { + confirmClockRunning(false); + AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); + } + + private void confirmResumed() + { + confirmClockRunning(true); + confirmPauseOverlayShown(false); + } + + private void confirmExited() + { + AddUntilStep("player exited", () => !Player.IsCurrentScreen()); + } + + private void pause() => AddStep("pause", () => Player.Pause()); + private void resume() => AddStep("resume", () => Player.Resume()); + + private void confirmPauseOverlayShown(bool isShown) => + AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown); + + private void confirmClockRunning(bool isRunning) => + AddAssert("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning); + + protected override bool AllowFail => true; + + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + protected class PausePlayer : Player + { + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible; + + public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; + + public PausePlayer() + { + PauseOnFocusLost = false; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs similarity index 58% rename from osu.Game.Tests/Visual/TestCasePlayerLoader.cs rename to osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 244f553e97..41d484e21f 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -9,20 +9,16 @@ using osu.Game.Beatmaps; using osu.Game.Screens; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerLoader : ManualInputManagerTestCase { private PlayerLoader loader; - private readonly ScreenStack stack; - - [Cached] - private BackgroundScreenStack backgroundStack; + private readonly OsuScreenStack stack; public TestCasePlayerLoader() { - InputManager.Add(backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }); - InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both }); + InputManager.Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] @@ -30,44 +26,39 @@ namespace osu.Game.Tests.Visual { Beatmap.Value = new DummyWorkingBeatmap(game); - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }))); + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); - AddUntilStep(() => loader.IsCurrentScreen(), "wait for current"); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep(() => !loader.IsCurrentScreen(), "wait for no longer current"); + AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); AddStep("exit loader", () => loader.Exit()); - AddUntilStep(() => !loader.IsAlive, "wait for no longer alive"); + AddUntilStep("wait for no longer alive", () => !loader.IsAlive); AddStep("load slow dummy beatmap", () => { SlowLoadPlayer slow = null; - stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - })); + stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false))); Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); - AddUntilStep(() => !loader.IsCurrentScreen(), "wait for no longer current"); + 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() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs new file mode 100644 index 0000000000..5937d489f2 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.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; +using osu.Framework.Lists; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCasePlayerReferenceLeaking : AllPlayersTestCase + { + private readonly WeakList workingWeakReferences = new WeakList(); + + private readonly WeakList playerWeakReferences = new WeakList(); + + protected override void AddCheckSteps() + { + AddUntilStep("no leaked beatmaps", () => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + workingWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }); + + AddUntilStep("no leaked players", () => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + playerWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) + { + var working = base.CreateWorkingBeatmap(beatmap, clock); + workingWeakReferences.Add(working); + return working; + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + var player = base.CreatePlayer(ruleset); + playerWeakReferences.Add(player); + return player; + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs new file mode 100644 index 0000000000..b98ce96fbb --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; +using System.Linq; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [Description("Player instantiated with a replay.")] + public class TestCaseReplay : AllPlayersTestCase + { + protected override Player CreatePlayer(Ruleset ruleset) + { + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo); + + return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); + } + + protected override void AddCheckSteps() + { + AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs index af71efb9e7..2fdfda0d80 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseReplaySettingsOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseResults.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs index c2880c1ea2..d9da45f39a 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Pages; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseResults : ScreenTestCase diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseScoreCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs index 3519ea67a6..3dd5c99e45 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseScoreCounter : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs index 5ebb9b270f..c99a4bb89b 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,8 +14,9 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseScrollingHitObjects : OsuTestCase @@ -170,12 +170,12 @@ namespace osu.Game.Tests.Visual { Origin = Anchor.Centre; - InternalChild = new Box + AddInternal(new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both - }; + }); switch (direction) { @@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - InternalChild = new Box { Size = new Vector2(75) }; + AddInternal(new Box { Size = new Vector2(75) }); } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseSkinReloadable.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs index 94f01e9d32..a9fbf35d37 100644 --- a/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics; using osu.Game.Skinning; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCaseSkinReloadable : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCaseSkipOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs index b51ba9c563..b46d79ac04 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseSkipOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs similarity index 70% rename from osu.Game.Tests/Visual/TestCaseSongProgress.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs index 9845df7461..e17dcef19c 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseSongProgress : OsuTestCase @@ -19,14 +20,20 @@ namespace osu.Game.Tests.Visual private readonly StopwatchClock clock; + [Cached] + private readonly GameplayClock gameplayClock; + + private readonly FramedClock framedClock; + public TestCaseSongProgress() { clock = new StopwatchClock(true); + gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); + Add(progress = new SongProgress { RelativeSizeAxes = Axes.X, - AudioClock = new StopwatchClock(true), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }); @@ -39,23 +46,23 @@ namespace osu.Game.Tests.Visual Origin = Anchor.TopLeft, }); - AddWaitStep(5); + AddWaitStep("wait some", 5); AddAssert("ensure not created", () => graph.CreationCount == 0); AddStep("display values", displayNewValues); - AddWaitStep(5); - AddUntilStep(() => graph.CreationCount == 1, "wait for creation count"); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(5); - AddUntilStep(() => graph.CreationCount == 1, "wait for creation count"); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(5); - AddUntilStep(() => graph.CreationCount == 1, "wait for creation count"); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); AddRepeatStep("New Values", displayNewValues, 5); - AddWaitStep(5); + AddWaitStep("wait some", 5); AddAssert("ensure debounced", () => graph.CreationCount == 2); } @@ -68,8 +75,13 @@ namespace osu.Game.Tests.Visual progress.Objects = objects; graph.Objects = objects; - progress.AudioClock = clock; - progress.OnSeek = pos => clock.Seek(pos); + progress.RequestSeek = pos => clock.Seek(pos); + } + + protected override void Update() + { + base.Update(); + framedClock.ProcessFrame(); } private class TestSongProgressGraph : SongProgressGraph diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseStoryboard.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs index c4b41e40f4..651683a671 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseStoryboard : OsuTestCase diff --git a/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs new file mode 100644 index 0000000000..68a1ceec16 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Screens.Menu; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Menus +{ + public class TestCaseDisclaimer : ScreenTestCase + { + [Cached(typeof(IAPIProvider))] + private readonly DummyAPIAccess api = new DummyAPIAccess(); + + [BackgroundDependencyLoader] + private void load() + { + Add(api); + + AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); + + AddStep("toggle support", () => + { + api.LocalUser.Value = new User + { + Username = api.LocalUser.Value.Username, + Id = api.LocalUser.Value.Id, + IsSupporter = !api.LocalUser.Value.IsSupporter, + }; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseIntroSequence.cs rename to osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs index 0e57a76167..0b924e56f5 100644 --- a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs @@ -4,14 +4,14 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Screens.Menu; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] public class TestCaseIntroSequence : OsuTestCase diff --git a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs new file mode 100644 index 0000000000..df12e14891 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs @@ -0,0 +1,137 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public class TestCaseLoaderAnimation : ScreenTestCase + { + private TestLoader loader; + + [Cached] + private OsuLogo logo; + + public TestCaseLoaderAnimation() + { + Child = logo = new OsuLogo { Depth = float.MinValue }; + } + + [Test] + public void TestInstantLoad() + { + bool logoVisible = false; + + AddStep("begin loading", () => + { + loader = new TestLoader(); + loader.AllowLoad.Set(); + + LoadScreen(loader); + }); + + AddAssert("loaded", () => + { + logoVisible = loader.Logo?.Alpha > 0; + return loader.Logo != null && loader.ScreenLoaded; + }); + + AddAssert("logo was not visible", () => !logoVisible); + } + + [Test] + public void TestShortLoad() + { + bool logoVisible = false; + + AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); + AddWaitStep("wait", 2); + 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); + } + + [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); + } + + private class TestLoader : Loader + { + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); + + public OsuLogo Logo; + private TestScreen screen; + + public bool ScreenLoaded => screen.IsCurrentScreen(); + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + Logo = logo; + base.LogoArriving(logo, resuming); + } + + protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen(); + protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(AllowLoad); + + private class TestShaderPrecompiler : ShaderPrecompiler + { + private readonly ManualResetEventSlim allowLoad; + + public TestShaderPrecompiler(ManualResetEventSlim allowLoad) + { + this.allowLoad = allowLoad; + } + + protected override bool AllLoaded => allowLoad.IsSet; + } + + private class TestScreen : OsuScreen + { + public TestScreen() + { + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.DarkSlateGray, + Alpha = 0, + }; + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + InternalChild.FadeInFromZero(200); + } + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs similarity index 81% rename from osu.Game.Tests/Visual/TestCaseToolbar.cs rename to osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs index be1b75823a..4da17f9944 100644 --- a/osu.Game.Tests/Visual/TestCaseToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs @@ -8,7 +8,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Toolbar; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] public class TestCaseToolbar : OsuTestCase @@ -24,10 +24,13 @@ namespace osu.Game.Tests.Visual public TestCaseToolbar() { var toolbar = new Toolbar { State = Visibility.Visible }; + ToolbarNotificationButton notificationButton = null; - Add(toolbar); - - var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + AddStep("create toolbar", () => + { + Add(toolbar); + notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + }); void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); diff --git a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs index 13bc5e24d9..497da33a05 100644 --- a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Users; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseLoungeRoomsContainer : MultiplayerTestCase { @@ -27,7 +27,8 @@ namespace osu.Game.Tests.Visual [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); - public TestCaseLoungeRoomsContainer() + [BackgroundDependencyLoader] + private void load() { RoomsContainer container; diff --git a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMatchHeader.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs index 296e5f24ac..81cb90c7cd 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchHeader : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs index 45092c5b93..d2dc417100 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchHostInfo : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseMatchInfo.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs index 901c4f1644..6b04b71da4 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs @@ -11,7 +11,7 @@ using osu.Game.Online.Multiplayer.RoomStatuses; using osu.Game.Rulesets; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMatchInfo : MultiplayerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs index 42a886a5a3..8ec323dbc3 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchLeaderboard : MultiplayerTestCase { @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMatchParticipants.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs index 716523c23c..5382726516 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMatchParticipants : MultiplayerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseMatchResults.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs index 582c035e82..69606c9ba7 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs @@ -16,7 +16,7 @@ using osu.Game.Screens.Multi.Ranking.Types; using osu.Game.Screens.Ranking; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchResults : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs index a320fc88fa..51854800e3 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs @@ -15,7 +15,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchSettingsOverlay : MultiplayerTestCase { @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual settings.ApplyButton.Action.Invoke(); }); - AddUntilStep(() => !settings.ErrorText.IsPresent, "error not displayed"); + AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent); } private class TestRoomSettings : MatchSettingsOverlay diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs similarity index 86% rename from osu.Game.Tests/Visual/TestCaseMultiHeader.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs index f7802e2d08..b49bb7fd84 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs @@ -7,7 +7,7 @@ using osu.Framework.Screens; using osu.Game.Screens; using osu.Game.Screens.Multi; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMultiHeader : OsuTestCase @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual { int index = 0; - ScreenStack screenStack = new ScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; + OsuScreenStack screenStack = new OsuScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs similarity index 79% rename from osu.Game.Tests/Visual/TestCaseMultiScreen.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs index 804e3c5b1f..ef381efd67 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs @@ -4,25 +4,24 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMultiScreen : ScreenTestCase { public override IReadOnlyList RequiredTypes => new[] { - typeof(Multiplayer), + typeof(Screens.Multi.Multiplayer), typeof(LoungeSubScreen), typeof(FilterControl) }; public TestCaseMultiScreen() { - Multiplayer multi = new Multiplayer(); + Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); AddStep(@"show", () => LoadScreen(multi)); } diff --git a/osu.Game.Tests/Visual/TestCaseRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseRoomStatus.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs index 7d175c3c49..a7c7d41ed4 100644 --- a/osu.Game.Tests/Visual/TestCaseRoomStatus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs @@ -9,7 +9,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.RoomStatuses; using osu.Game.Screens.Multi.Lounge.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseRoomStatus : OsuTestCase { diff --git a/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs new file mode 100644 index 0000000000..5cdb90b61f --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.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; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Overlays; +using osu.Game.Overlays.AccountCreation; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestCaseAccountCreationOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ErrorTextFlowContainer), + typeof(AccountCreationBackground), + typeof(ScreenEntry), + typeof(ScreenWarning), + typeof(ScreenWelcome), + typeof(AccountCreationScreen), + }; + + [Cached(typeof(IAPIProvider))] + private DummyAPIAccess api = new DummyAPIAccess(); + + public TestCaseAccountCreationOverlay() + { + Container userPanelArea; + AccountCreationOverlay accountCreation; + + Children = new Drawable[] + { + api, + accountCreation = new AccountCreationOverlay(), + userPanelArea = new Container + { + Padding = new MarginPadding(10), + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }; + + api.Logout(); + api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true); + + AddStep("show", () => accountCreation.State = Visibility.Visible); + AddStep("logout", () => api.Logout()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseBadgeContainer.cs rename to osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs index 892c87653d..631cb190d2 100644 --- a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseBadgeContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs rename to osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs index b98014b866..e2985623fc 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseBeatmapSetOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseChannelTabControl.cs rename to osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs index 749303b1bb..fdc3d5394f 100644 --- a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs @@ -15,7 +15,7 @@ using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseChannelTabControl : OsuTestCase { @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual AddStep("set second channel", () => channelTabControl.Current.Value = channelTabControl.Items.Skip(1).First()); AddAssert("selector tab is inactive", () => !channelTabControl.ChannelSelectorActive.Value); - AddUntilStep(() => + AddUntilStep("remove all channels", () => { var first = channelTabControl.Items.First(); if (first.Name == "+") @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual channelTabControl.RemoveChannel(first); return false; - }, "remove all channels"); + }); AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value); } diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseChatDisplay.cs rename to osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs index 57e4850f84..6e20165c1b 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.Tabs; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [Description("Testing chat api and overlay")] public class TestCaseChatDisplay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/Online/TestCaseChatLink.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseChatLink.cs rename to osu.Game.Tests/Visual/Online/TestCaseChatLink.cs index b2ec2c9b47..8843f136a1 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChatLink.cs @@ -1,24 +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 osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat; -using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.Chat; +using osu.Game.Users; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseChatLink : OsuTestCase @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); }); - AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}"); + AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage)); } } diff --git a/osu.Game.Tests/Visual/TestCaseDirect.cs b/osu.Game.Tests/Visual/Online/TestCaseDirect.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseDirect.cs rename to osu.Game.Tests/Visual/Online/TestCaseDirect.cs index 24ac38c128..ff57104d8a 100644 --- a/osu.Game.Tests/Visual/TestCaseDirect.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseDirect.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseDirect : OsuTestCase diff --git a/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs new file mode 100644 index 0000000000..fbda531792 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.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 System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestCaseDirectPanel : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DirectGridPanel), + typeof(DirectListPanel), + typeof(IconPill) + }; + + [BackgroundDependencyLoader] + private void load() + { + var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null); + beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true; + beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true; + + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new DirectGridPanel(beatmap.BeatmapSetInfo), + new DirectListPanel(beatmap.BeatmapSetInfo) + } + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs rename to osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs index 6f807e96f1..a73cbd86d0 100644 --- a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseExternalLinkButton : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/Online/TestCaseGraph.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseGraph.cs rename to osu.Game.Tests/Visual/Online/TestCaseGraph.cs index 6a5865b752..77e850fc92 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseGraph.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseGraph : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseHistoricalSection.cs rename to osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs index 60e6148c49..92aa9320c8 100644 --- a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseHistoricalSection : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseRankGraph.cs rename to osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs index f41033c0be..dff018bf91 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs @@ -1,19 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osuTK; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using System.Collections.Generic; -using System; -using NUnit.Framework; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseRankGraph : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/Online/TestCaseSocial.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseSocial.cs rename to osu.Game.Tests/Visual/Online/TestCaseSocial.cs index d621bc600d..48325713df 100644 --- a/osu.Game.Tests/Visual/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseSocial.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Social; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseSocial : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs rename to osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs index 65ae70168e..4c4b3b2612 100644 --- a/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseStandAloneChatDisplay : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseUserPanel.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs index 8caa608d5e..b2877f7bd7 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserPanel : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseUserProfile.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs index 726134294e..5b86de28f9 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs @@ -13,13 +13,13 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserProfile : OsuTestCase { private readonly TestUserProfileOverlay profile; - private APIAccess api; + private IAPIProvider api; public override IReadOnlyList RequiredTypes => new[] { @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } @@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual private void checkSupporterTag(bool isSupporter) { - AddUntilStep(() => profile.Header.User != null, "wait for load"); + AddUntilStep("wait for load", () => profile.Header.User != null); if (isSupporter) AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); else diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs index da50653831..6b29ed1e85 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs @@ -1,20 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Recent; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Online.API.Requests.Responses; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserProfileRecentSection : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseUserRanks.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs index 96638ef703..64257f8877 100644 --- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,11 +11,8 @@ using osu.Game.Graphics; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Users; -using System; -using System.Collections.Generic; -using NUnit.Framework; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserRanks : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs b/osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs rename to osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs index cd299be1e9..ce179c21ba 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs +++ b/osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Settings { [TestFixture] public class TestCaseKeyConfiguration : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/Settings/TestCaseSettings.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseSettings.cs rename to osu.Game.Tests/Visual/Settings/TestCaseSettings.cs index 67f32a8335..e846d5c020 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestCaseSettings.cs @@ -6,7 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Settings { [TestFixture] public class TestCaseSettings : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs index 618d8376c0..1500605896 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs @@ -17,7 +17,7 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public class TestCaseBeatmapCarousel : OsuTestCase @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; }); - AddUntilStep(() => changed, "Wait for load"); + AddUntilStep("Wait for load", () => changed); } private void ensureRandomFetchSuccess() => @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual checkSelected(3, 2); AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); - AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); checkVisibleItemCount(diff: false, count: set_count); checkVisibleItemCount(diff: true, count: 3); @@ -327,13 +327,13 @@ namespace osu.Game.Tests.Visual AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); checkSelected(1); - AddUntilStep(() => + AddUntilStep("Remove all", () => { if (!carousel.BeatmapSets.Any()) return true; carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); return false; - }, "Remove all"); + }); checkNoSelection(); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs new file mode 100644 index 0000000000..722a63f2b0 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs @@ -0,0 +1,160 @@ +// 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.Beatmaps; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [TestFixture] + [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] + public class TestCaseBeatmapDetailArea : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) }; + + public TestCaseBeatmapDetailArea() + { + BeatmapDetailArea detailsArea; + Add(detailsArea = new BeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(550f, 450f), + }); + + AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + } + ); + + AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + }); + + AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "Only Ratings", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has ratings metrics but not retries or fails", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + }, + StarDifficulty = 4.8f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + }, + } + }); + + AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "Only Retries and Fails", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has retries and fails but no ratings", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + }, + StarDifficulty = 2.91f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + }); + + AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "No Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has no metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + }, + StarDifficulty = 1.97f, + } + }); + + AddStep("null beatmap", () => detailsArea.Beatmap = null); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs index 84af6453f5..37987b8884 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Screens.Select; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect beatmap details")] public class TestCaseBeatmapDetails : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs index 31bb8b64a3..f3e44bd808 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,8 +19,9 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Select; using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public class TestCaseBeatmapInfoWedge : OsuTestCase @@ -56,11 +56,11 @@ namespace osu.Game.Tests.Visual // select part is redundant, but wait for load isn't selectBeatmap(Beatmap.Value.Beatmap); - AddWaitStep(3); + AddWaitStep("wait for select", 3); AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); - AddWaitStep(3); + AddWaitStep("wait for hide", 3); AddStep("show", () => { infoWedge.State = Visibility.Visible; }); @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : new TestWorkingBeatmap(b); }); - AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); + AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore); } private IBeatmap createTestBeatmap(RulesetInfo ruleset) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs similarity index 67% rename from osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs index fdab57193b..7d09debbd6 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Select.Options; using osuTK.Graphics; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("bottom beatmap details")] public class TestCaseBeatmapOptionsOverlay : OsuTestCase @@ -16,10 +16,10 @@ namespace osu.Game.Tests.Visual { var overlay = new BeatmapOptionsOverlay(); - overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, Color4.Purple, null, Key.Number1); - overlay.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, Color4.Purple, null, Key.Number2); - overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, Color4.Yellow, null, Key.Number3); - overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, Color4.Pink, null, Key.Number4, float.MaxValue); + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null, Key.Number1); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null, Key.Number2); + overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number3); + overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number4, float.MaxValue); Add(overlay); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index bb55c0b1e8..8de6cc2a88 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -1,24 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; -using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; +using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [System.ComponentModel.Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase @@ -44,9 +43,9 @@ namespace osu.Game.Tests.Visual } }; - IEnumerable scores = new[] + IEnumerable scores = new[] { - new APIScoreInfo + new ScoreInfo { User = new User { @@ -69,7 +68,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -91,7 +90,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -112,7 +111,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -132,7 +131,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -157,9 +156,9 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - IEnumerable anotherScores = new[] + IEnumerable anotherScores = new[] { - new APIScoreInfo + new ScoreInfo { User = new User { @@ -181,7 +180,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -220,7 +219,7 @@ namespace osu.Game.Tests.Visual TotalScore = 123456, Accuracy = 0.6543, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -241,7 +240,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -269,7 +268,7 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - var topScoreInfo = new APIScoreInfo + var topScoreInfo = new ScoreInfo { User = new User { diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseLeaderboard.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs index eb1a2c0249..13ae6f228a 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs @@ -4,18 +4,18 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using osu.Framework.Graphics; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; -using osu.Framework.Allocation; -using osuTK; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect leaderboard")] public class TestCaseLeaderboard : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCasePlaySongSelect.cs rename to osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index 5fa818472c..d5bc452d75 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs @@ -23,7 +23,7 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public class TestCasePlaySongSelect : ScreenTestCase @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { - typeof(SongSelect), + typeof(Screens.Select.SongSelect), typeof(BeatmapCarousel), typeof(CarouselItem), @@ -112,10 +112,10 @@ namespace osu.Game.Tests.Visual createSongSelect(); AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); - AddUntilStep(() => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap, "dummy shown on wedge"); + AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); addManyTestMaps(); - AddWaitStep(3); + AddWaitStep("wait for select", 3); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); } @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual { createSongSelect(); addManyTestMaps(); - AddWaitStep(3); + AddWaitStep("wait for add", 3); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual createSongSelect(); changeRuleset(2); importForRuleset(0); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } [Test] @@ -152,13 +152,13 @@ namespace osu.Game.Tests.Visual changeRuleset(2); importForRuleset(2); importForRuleset(1); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 2, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); changeRuleset(1); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 1, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 1); changeRuleset(0); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } [Test] @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Visual { createSongSelect(); addManyTestMaps(); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); bool startRequested = false; @@ -225,7 +225,7 @@ namespace osu.Game.Tests.Visual private void createSongSelect() { AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); - AddUntilStep(() => songSelect.IsCurrentScreen(), "wait for present"); + AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); } private void addManyTestMaps() diff --git a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs deleted file mode 100644 index 543a43b439..0000000000 --- a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; -using osu.Game.Overlays.AccountCreation; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseAccountCreationOverlay : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(ErrorTextFlowContainer), - typeof(AccountCreationBackground), - typeof(ScreenEntry), - typeof(ScreenWarning), - typeof(ScreenWelcome), - typeof(AccountCreationScreen), - }; - - public TestCaseAccountCreationOverlay() - { - var accountCreation = new AccountCreationOverlay(); - Child = accountCreation; - - accountCreation.State = Visibility.Visible; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs deleted file mode 100644 index a5decaa9fb..0000000000 --- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseAllPlayers : TestCasePlayer - { - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs deleted file mode 100644 index c23075a127..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ /dev/null @@ -1,25 +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.Graphics; -using osu.Game.Screens.Select; -using osuTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] - public class TestCaseBeatmapDetailArea : OsuTestCase - { - public TestCaseBeatmapDetailArea() - { - Add(new BeatmapDetailArea - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(550f, 450f), - }); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs deleted file mode 100644 index 3ceb3eb4bd..0000000000 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ /dev/null @@ -1,17 +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.Game.Screens.Menu; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseDisclaimer : ScreenTestCase - { - [BackgroundDependencyLoader] - private void load() - { - LoadScreen(new Disclaimer()); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs deleted file mode 100644 index 2088f97580..0000000000 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ /dev/null @@ -1,116 +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.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Screens; -using osu.Game.Screens; -using osu.Game.Screens.Menu; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseLoaderAnimation : OsuTestCase - { - private TestLoader loader; - - protected override void LoadComplete() - { - base.LoadComplete(); - - // required to preload the logo in a headless run (so it doesn't delay the loading itself). - Add(new OsuLogo()); - - bool logoVisible = false; - AddStep("almost instant display", () => Child = loader = new TestLoader(250)); - AddUntilStep(() => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }, "loaded"); - AddAssert("logo not visible", () => !logoVisible); - - AddStep("short load", () => Child = loader = new TestLoader(800)); - AddUntilStep(() => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }, "loaded"); - AddAssert("logo visible", () => logoVisible); - AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone"); - - AddStep("longer load", () => Child = loader = new TestLoader(1400)); - AddUntilStep(() => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }, "loaded"); - AddAssert("logo visible", () => logoVisible); - AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone"); - } - - private class TestLoader : Loader - { - private readonly double delay; - - public OsuLogo Logo; - private TestScreen screen; - - public bool ScreenLoaded => screen.IsCurrentScreen(); - - public TestLoader(double delay) - { - this.delay = delay; - } - - protected override void LogoArriving(OsuLogo logo, bool resuming) - { - Logo = logo; - base.LogoArriving(logo, resuming); - } - - protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen(); - protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(delay); - - private class TestShaderPrecompiler : ShaderPrecompiler - { - private readonly double delay; - private double startTime; - - public TestShaderPrecompiler(double delay) - { - this.delay = delay; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - startTime = Time.Current; - } - - protected override bool AllLoaded => Time.Current > startTime + delay; - } - - private class TestScreen : OsuScreen - { - public TestScreen() - { - InternalChild = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.DarkSlateGray, - Alpha = 0, - }; - } - - protected override void LogoArriving(OsuLogo logo, bool resuming) - { - base.LogoArriving(logo, resuming); - InternalChild.FadeInFromZero(200); - } - } - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs new file mode 100644 index 0000000000..0831228681 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.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 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.Screens; +using osu.Game.Screens.Play; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseOsuScreenStack : OsuTestCase + { + private TestOsuScreenStack stack; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); + } + + [Test] + public void ParallaxAssignmentTest() + { + NoParallaxTestScreen noParallaxScreen = null; + TestScreen parallaxScreen = null; + + AddStep("Push no parallax", () => stack.Push(noParallaxScreen = new NoParallaxTestScreen("NO PARALLAX"))); + AddUntilStep("Wait for current", () => noParallaxScreen.IsLoaded); + AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); + + AddStep("Push parallax", () => noParallaxScreen.Push(parallaxScreen = new TestScreen("PARALLAX"))); + AddUntilStep("Wait for current", () => parallaxScreen.IsLoaded); + AddAssert("Parallax is on", () => stack.ParallaxAmount > 0); + + AddStep("Exit from new screen", () => { noParallaxScreen.MakeCurrent(); }); + AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); + } + + private class TestScreen : ScreenWithBeatmapBackground + { + private readonly string screenText; + + public TestScreen(string screenText) + { + this.screenText = screenText; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new SpriteText + { + Text = screenText, + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } + + private class NoParallaxTestScreen : TestScreen + { + public NoParallaxTestScreen(string screenText) + : base(screenText) + { + } + + public override float BackgroundParallaxAmount => 0.0f; + } + + private class TestOsuScreenStack : OsuScreenStack + { + public new float ParallaxAmount => base.ParallaxAmount; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs deleted file mode 100644 index c12015a019..0000000000 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.ComponentModel; -using System.Linq; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("Player instantiated with a replay.")] - public class TestCaseReplay : TestCasePlayer - { - protected override Player CreatePlayer(Ruleset ruleset) - { - // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here - // to simulate setting a replay rather than having the replay already set for us - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value); - - // Reset the mods - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - - return new ReplayPlayer(dummyRulesetContainer.ReplayScore); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs deleted file mode 100644 index 6ab9a46e8d..0000000000 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseTextAwesome : OsuTestCase - { - public TestCaseTextAwesome() - { - FillFlowContainer flow; - - Add(new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = flow = new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - }, - }); - - foreach (FontAwesome fa in Enum.GetValues(typeof(FontAwesome))) - flow.Add(new Icon(fa)); - } - - private class Icon : Container, IHasTooltip - { - public string TooltipText { get; } - - public Icon(FontAwesome fa) - { - TooltipText = fa.ToString(); - - AutoSizeAxes = Axes.Both; - Child = new SpriteIcon - { - Icon = fa, - Size = new Vector2(60), - }; - } - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs index 2fd8d467f6..dcd194e050 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs @@ -8,16 +8,16 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK.Graphics; -using osu.Framework.Lists; -using osu.Game.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseBeatSyncedContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs index 98ab884ead..5e09e0a5b9 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseBreadcrumbs : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseButtonSystem.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs index 8ea2ab9dde..261e87ff07 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseButtonSystem : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseContextMenu.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs index 5cbe97e21d..71cde787f9 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs @@ -7,12 +7,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; -using osu.Game.Graphics.Cursor; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseContextMenu : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseCursors.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs index 7d38a76c7d..5f45d9ba4d 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseCursors : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs similarity index 92% rename from osu.Game.Tests/Visual/TestCaseDialogOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs index e832793fc2..8964d20564 100644 --- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseDialogOverlay : OsuTestCase @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual AddStep("dialog #1", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.fa_trash_o, + Icon = FontAwesome.Regular.TrashAlt, HeaderText = @"Confirm deletion of", BodyText = @"Ayase Rie - Yuima-ru*World TVver.", Buttons = new PopupDialogButton[] @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual AddStep("dialog #2", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.fa_gear, + Icon = FontAwesome.Solid.Cog, HeaderText = @"What do you want to do with", BodyText = "Camellia as \"Bang Riot\" - Blastix Riotz", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseDrawableDate.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs index 8d2182dd78..e8662ce965 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseDrawableDate : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs index f66bf34875..38dc4a11dc 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Menu; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseHoldToConfirmOverlay : OsuTestCase { @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual AddStep("start confirming", () => overlay.Begin()); - AddUntilStep(() => fired, "wait until confirmed"); + AddUntilStep("wait until confirmed", () => fired); } private class TestHoldToConfirmOverlay : ExitConfirmOverlay diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseIconButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs index 63e10b6ecc..6bb1347608 100644 --- a/osu.Game.Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osuTK; -using osuTK.Graphics; 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 osuTK; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseIconButton : OsuTestCase @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual button.Anchor = Anchor.Centre; button.Origin = Anchor.Centre; - button.Icon = FontAwesome.fa_osu_osu_o; + button.Icon = OsuIcon.RulesetOsu; } } } diff --git a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs index 4b424f9875..781dfbdcc1 100644 --- a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs @@ -1,15 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using System; -using System.Collections.Generic; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseLabelledTextBox : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs index f5dc1d449a..43f6f0e4db 100644 --- a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs @@ -7,7 +7,7 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseLoadingAnimation : GridTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMods.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs index 99bc10d8cc..aab44f7d92 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs @@ -2,26 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets; -using osu.Game.Screens.Play.HUD; -using osuTK; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using System.Linq; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Bindables; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Mods.Sections; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("mod select and icon display")] public class TestCaseMods : OsuTestCase @@ -208,22 +208,22 @@ namespace osu.Game.Tests.Visual { checkLabelColor(Color4.White); selectNext(mod); - AddWaitStep(1, "wait for changing colour"); + AddWaitStep("wait for changing colour", 1); checkLabelColor(colour); selectPrevious(mod); - AddWaitStep(1, "wait for changing colour"); + AddWaitStep("wait for changing colour", 1); checkLabelColor(Color4.White); } private void testRankedText(Mod mod) { - AddWaitStep(1, "wait for fade"); + AddWaitStep("wait for fade", 1); AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); selectNext(mod); - AddWaitStep(1, "wait for fade"); + AddWaitStep("wait for fade", 1); AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0); selectPrevious(mod); - AddWaitStep(1, "wait for fade"); + AddWaitStep("wait for fade", 1); AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); } diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMusicController.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs index b4a1c11b1a..644c7eb4fc 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseMusicController : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs index d6a3361cf2..4819597d22 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs @@ -12,7 +12,7 @@ using osu.Framework.MathUtils; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseNotificationOverlay : OsuTestCase @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual setState(Visibility.Hidden); AddRepeatStep(@"add many simple", sendManyNotifications, 3); - AddWaitStep(5); + AddWaitStep("wait some", 5); checkProgressingCount(0); @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33); - AddWaitStep(10); + AddWaitStep("wait some", 10); checkProgressingCount(0); diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs index 8b5ae0b208..7ad42cb926 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs @@ -8,7 +8,7 @@ using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseOnScreenDisplay : OsuTestCase diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs new file mode 100644 index 0000000000..a57e11cb0c --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +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.Testing; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestCaseOsuIcon : TestCase + { + public TestCaseOsuIcon() + { + FillFlowContainer flow; + + AddRange(new Drawable[] + { + new Box + { + Colour = Color4.Teal, + RelativeSizeAxes = Axes.Both, + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = flow = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + }, + } + }); + + foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static)) + flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null))); + + AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow)); + AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1)))); + } + + private class Icon : Container, IHasTooltip + { + public string TooltipText { get; } + + public SpriteIcon SpriteIcon { get; } + + public Icon(string name, IconUsage icon) + { + TooltipText = name; + + AutoSizeAxes = Axes.Both; + Child = SpriteIcon = new SpriteIcon + { + Icon = icon, + Size = new Vector2(60), + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseParallaxContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs index 41b029d69e..5de4c3f41f 100644 --- a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs @@ -6,7 +6,7 @@ using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseParallaxContainer : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs similarity index 87% rename from osu.Game.Tests/Visual/TestCasePopupDialog.cs rename to osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs index 51b5c41e0d..2f01f593c7 100644 --- a/osu.Game.Tests/Visual/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs @@ -3,10 +3,10 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCasePopupDialog : OsuTestCase @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual { RelativeSizeAxes = Axes.Both, State = Framework.Graphics.Containers.Visibility.Visible, - Icon = FontAwesome.fa_assistive_listening_systems, + Icon = FontAwesome.Solid.AssistiveListeningSystems, HeaderText = @"This is a test popup", BodyText = "I can say lots of stuff and even wrap my words!", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs index 204f4a493d..c92072eb71 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs @@ -13,20 +13,20 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseScreenBreadcrumbControl : OsuTestCase { private readonly ScreenBreadcrumbControl breadcrumbs; - private readonly ScreenStack screenStack; + private readonly OsuScreenStack screenStack; public TestCaseScreenBreadcrumbControl() { OsuSpriteText titleText; IScreen startScreen = new TestScreenOne(); - screenStack = new ScreenStack(startScreen) { RelativeSizeAxes = Axes.Both }; + screenStack = new OsuScreenStack(startScreen) { RelativeSizeAxes = Axes.Both }; Children = new Drawable[] { @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual } private void pushNext() => AddStep(@"push next screen", () => ((TestScreen)screenStack.CurrentScreen).PushNext()); - private void waitForCurrent() => AddUntilStep(() => screenStack.CurrentScreen.IsCurrentScreen(), "current screen"); + private void waitForCurrent() => AddUntilStep("current screen", () => screenStack.CurrentScreen.IsCurrentScreen()); private abstract class TestScreen : OsuScreen { diff --git a/osu.Game.Tests/Visual/TestCaseTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseTabControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs index ebf8f3bb30..480dc73dde 100644 --- a/osu.Game.Tests/Visual/TestCaseTabControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("SongSelect filter control")] public class TestCaseTabControl : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs similarity index 90% rename from osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs index 9141aaa580..8d3cc7a0f2 100644 --- a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("mostly back button")] public class TestCaseTwoLayerButton : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs similarity index 52% rename from osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index 3ee617e092..74114b2e53 100644 --- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -12,46 +12,52 @@ using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase { - private UpdateableBeatmapBackgroundSprite backgroundSprite; + private TestUpdateableBeatmapBackgroundSprite backgroundSprite; [Resolved] private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuGameBase osu, APIAccess api, RulesetStore rulesets) + private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { Bindable beatmapBindable = new Bindable(); var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); - Child = backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; backgroundSprite.Beatmap.BindTo(beatmapBindable); var req = new GetBeatmapSetRequest(1); api.Queue(req); - AddStep("null", () => beatmapBindable.Value = null); - - AddStep("imported", () => beatmapBindable.Value = imported.Beatmaps.First()); + 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(() => req.Result != null, "wait for api response"); - - AddStep("online", () => beatmapBindable.Value = new BeatmapInfo + 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/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseVolumePieces.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs index 6dee047ae6..3ad1c922e4 100644 --- a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseVolumePieces : OsuTestCase { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index b22c1aed99..938e1ae0f8 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,9 +3,9 @@ - + - + diff --git a/osu.Game.Tournament.Tests/TestCaseBeatmapPanel.cs b/osu.Game.Tournament.Tests/TestCaseBeatmapPanel.cs index 8bcc34bd7a..0a23b1e82f 100644 --- a/osu.Game.Tournament.Tests/TestCaseBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/TestCaseBeatmapPanel.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tournament.Tests public class TestCaseBeatmapPanel : OsuTestCase { [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private RulesetStore rulesets { get; set; } diff --git a/osu.Game.Tournament.Tests/TestCaseDrawings.cs b/osu.Game.Tournament.Tests/TestCaseDrawings.cs index f5f5615bf6..d632c6efb6 100644 --- a/osu.Game.Tournament.Tests/TestCaseDrawings.cs +++ b/osu.Game.Tournament.Tests/TestCaseDrawings.cs @@ -6,12 +6,14 @@ using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; using osu.Game.Tournament.Screens.Drawings; using osu.Game.Tournament.Screens.Drawings.Components; +using osu.Framework.Allocation; namespace osu.Game.Tournament.Tests { public class TestCaseDrawings : ScreenTestCase { - public TestCaseDrawings() + [BackgroundDependencyLoader] + private void load() { Add(new DrawingsScreen { diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 8cdd06003f..1086991eb4 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tournament.IPC public class FileBasedIPC : MatchIPCInfo { [Resolved] - protected APIAccess API { get; private set; } + protected IAPIProvider API { get; private set; } [Resolved] protected RulesetStore Rulesets { get; private set; } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs index 4ed46223c8..ddf41b0117 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components Source = source; Destination = destination; - PathWidth = 3; + PathRadius = 3; BypassAutoSizeAxes = Axes.Both; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2b559d5912..9caa64ec96 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; @@ -50,11 +49,6 @@ namespace osu.Game.Beatmaps /// public event Action BeatmapDownloadFailed; - /// - /// Fired when a beatmap load is requested (into the interactive game UI). - /// - public Action PresentBeatmap; - /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. /// @@ -70,7 +64,7 @@ namespace osu.Game.Beatmaps private readonly BeatmapStore beatmaps; - private readonly APIAccess api; + private readonly IAPIProvider api; private readonly AudioManager audioManager; @@ -78,7 +72,7 @@ namespace osu.Game.Beatmaps private readonly List currentDownloads = new List(); - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, GameHost host = null, + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, new BeatmapStore(contextFactory), host) { @@ -108,10 +102,16 @@ namespace osu.Game.Beatmaps b.BeatmapSet = beatmapSet; } - validateOnlineIds(beatmapSet.Beatmaps); + validateOnlineIds(beatmapSet); foreach (BeatmapInfo b in beatmapSet.Beatmaps) - fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps); + fetchAndPopulateOnlineValues(b); + } + + protected override void PreImport(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) + throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) @@ -126,14 +126,30 @@ namespace osu.Game.Beatmaps } } - private void validateOnlineIds(List beatmaps) + private void validateOnlineIds(BeatmapSetInfo beatmapSet) { - var beatmapIds = beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - // ensure all IDs are unique in this set and none match existing IDs in the local beatmap store. - if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1) || QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).Any()) - // remove all online IDs if any problems were found. - beatmaps.ForEach(b => b.OnlineBeatmapID = null); + // ensure all IDs are unique + if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) + { + resetIds(); + return; + } + + // find any existing beatmaps in the database that have matching online ids + var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList(); + + if (existingBeatmaps.Count > 0) + { + // reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set. + // we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted. + var existing = CheckForExisting(beatmapSet); + if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b))) + resetIds(); + } + + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); } /// @@ -151,8 +167,7 @@ namespace osu.Game.Beatmaps var downloadNotification = new DownloadNotification { - CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", - Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", + Text = $"Downloading {beatmapSetInfo}", }; var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); @@ -165,20 +180,10 @@ namespace osu.Game.Beatmaps request.Success += filename => { - downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}"; - Task.Factory.StartNew(() => { // This gets scheduled back to the update thread, but we want the import to run in the background. - var importedBeatmap = Import(filename); - - downloadNotification.CompletionClickAction = () => - { - PresentCompletedImport(importedBeatmap.Yield()); - return true; - }; - downloadNotification.State = ProgressNotificationState.Completed; - + Import(downloadNotification, filename); currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; @@ -221,12 +226,6 @@ namespace osu.Game.Beatmaps return true; } - protected override void PresentCompletedImport(IEnumerable imported) - { - base.PresentCompletedImport(imported); - PresentBeatmap?.Invoke(imported.LastOrDefault()); - } - /// /// Get an existing download request if it exists. /// @@ -277,6 +276,18 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanUndelete(existing, import)) + return false; + + var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + + // force re-import if we are not in a sane state. + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + } + /// /// Returns a list of all usable s. /// @@ -374,7 +385,7 @@ namespace osu.Game.Beatmaps /// 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, IEnumerable otherBeatmaps, bool force = false) + private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false) { if (api?.State != APIState.Online) return false; @@ -397,13 +408,6 @@ namespace osu.Game.Beatmaps beatmap.Status = res.Status; beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - - if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID)) - { - Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database); - return false; - } - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; beatmap.OnlineBeatmapID = res.OnlineBeatmapID; diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 8c0c7c09ae..0745ec5222 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Beatmaps { public class BeatmapStatistic { - public FontAwesome Icon; + public IconUsage Icon; public string Content; public string Name; } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index ec52517197..f1607ad749 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -48,11 +48,11 @@ namespace osu.Game.Beatmaps.Drawables var rating = beatmap.StarDifficulty; - if (rating < 1.5) return DifficultyRating.Easy; - if (rating < 2.25) return DifficultyRating.Normal; - if (rating < 3.75) return DifficultyRating.Hard; - if (rating < 5.25) return DifficultyRating.Insane; - if (rating < 6.75) return DifficultyRating.Expert; + if (rating < 2.0) return DifficultyRating.Easy; + if (rating < 2.7) return DifficultyRating.Normal; + if (rating < 4.0) return DifficultyRating.Hard; + if (rating < 5.3) return DifficultyRating.Insane; + if (rating < 6.5) return DifficultyRating.Expert; return DifficultyRating.ExpertPlus; } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index dd8cdb862e..1be7411bec 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -7,7 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osuTK; @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } + Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } } }; } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 6fa7d47683..ce7811fc0d 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Beatmaps.Drawables { /// - /// Display a baetmap background from a local source, but fallback to online source if not available. + /// Display a beatmap background from a local source, but fallback to online source if not available. /// public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable { @@ -18,34 +18,53 @@ namespace osu.Game.Beatmaps.Drawables [Resolved] private BeatmapManager beatmaps { get; set; } - public UpdateableBeatmapBackgroundSprite() + private readonly BeatmapSetCoverType beatmapSetCoverType; + + public UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) { Beatmap.BindValueChanged(b => Model = b.NewValue); + this.beatmapSetCoverType = beatmapSetCoverType; + } + + private BeatmapInfo lastModel; + + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) + { + return new DelayedLoadUnloadWrapper(() => + { + // 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) + 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; + return content; + }, timeBeforeLoad, 10000); } protected override Drawable CreateDrawable(BeatmapInfo model) { - return new DelayedLoadUnloadWrapper(() => - { - Drawable drawable; + Drawable drawable = getDrawableForModel(model); - var localBeatmap = beatmaps.GetWorkingBeatmap(model); + drawable.RelativeSizeAxes = Axes.Both; + drawable.Anchor = Anchor.Centre; + drawable.Origin = Anchor.Centre; + drawable.FillMode = FillMode.Fill; + drawable.OnLoadComplete += d => d.FadeInFromZero(400); - if (localBeatmap.BeatmapInfo.ID == 0 && model?.BeatmapSet?.OnlineInfo != null) - drawable = new BeatmapSetCover(model.BeatmapSet); - else - drawable = new BeatmapBackgroundSprite(localBeatmap); - - drawable.RelativeSizeAxes = Axes.Both; - drawable.Anchor = Anchor.Centre; - drawable.Origin = Anchor.Centre; - drawable.FillMode = FillMode.Fill; - drawable.OnLoadComplete = d => d.FadeInFromZero(400); - - return drawable; - }, 500, 10000); + return drawable; } - protected override double FadeDuration => 0; + private Drawable getDrawableForModel(BeatmapInfo model) + { + // prefer online cover where available. + if (model?.BeatmapSet?.OnlineInfo != null) + return new BeatmapSetCover(model.BeatmapSet, beatmapSetCoverType); + + return model?.ID > 0 + ? new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(model)) + : new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); + } } } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 367b63d6d1..c7c4c1fb1e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -67,16 +67,19 @@ namespace osu.Game.Beatmaps.Drawables if (beatmapSet != null) { + BeatmapSetCover cover; + Add(displayedCover = new DelayedLoadWrapper( - new BeatmapSetCover(beatmapSet, coverType) + cover = new BeatmapSetCover(beatmapSet, coverType) { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), }) ); + + cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); } } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 0aa1697bf8..73aa12a3db 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -26,7 +26,12 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = 0, + CircleSize = 0, + OverallDifficulty = 0, + }, Ruleset = new DummyRulesetInfo() }) { @@ -47,7 +52,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) { throw new NotImplementedException(); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4f19fbd323..a27126ad9c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Globalization; using System.IO; using System.Linq; using osu.Framework.IO.File; +using osu.Framework.Logging; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.Formats public static void Register() { - AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); + AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(Parsing.ParseInt(m.Split('v').Last()))); } /// @@ -104,25 +104,25 @@ namespace osu.Game.Beatmaps.Formats metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); + beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); break; case @"PreviewTime": - metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value)); + metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value)); break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.Countdown = Parsing.ParseInt(pair.Value) == 1; break; case @"SampleSet": defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; case @"SampleVolume": - defaultSampleVolume = int.Parse(pair.Value); + defaultSampleVolume = Parsing.ParseInt(pair.Value); break; case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); + beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); switch (beatmap.BeatmapInfo.RulesetID) { @@ -142,13 +142,13 @@ namespace osu.Game.Beatmaps.Formats break; case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; } } @@ -163,16 +163,16 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); + beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); break; case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); + beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); break; case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; } } @@ -209,10 +209,10 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); + beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) }; + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; break; } } @@ -225,22 +225,22 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; case @"CircleSize": - difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.CircleSize = Parsing.ParseFloat(pair.Value); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); break; case @"ApproachRate": - difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); break; case @"SliderMultiplier": - difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; case @"SliderTickRate": - difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; } } @@ -260,10 +260,12 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; case EventType.Break: + double start = getOffsetTime(Parsing.ParseDouble(split[1])); + var breakEvent = new BreakPeriod { - StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)), - EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo)) + StartTime = start, + EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))) }; if (!breakEvent.HasEffect) @@ -280,25 +282,25 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo)); - double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); + double beatLength = Parsing.ParseDouble(split[1].Trim()); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) - sampleSet = (LegacySampleBank)int.Parse(split[3]); + sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); int customSampleBank = 0; if (split.Length >= 5) - customSampleBank = int.Parse(split[4]); + customSampleBank = Parsing.ParseInt(split[4]); int sampleVolume = defaultSampleVolume; if (split.Length >= 6) - sampleVolume = int.Parse(split[5]); + sampleVolume = Parsing.ParseInt(split[5]); bool timingChange = true; if (split.Length >= 7) @@ -308,7 +310,7 @@ namespace osu.Game.Beatmaps.Formats bool omitFirstBarSignature = false; if (split.Length >= 8) { - EffectFlags effectFlags = (EffectFlags)int.Parse(split[7]); + EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); } @@ -348,8 +350,13 @@ namespace osu.Game.Beatmaps.Formats CustomSampleBank = customSampleBank }); } - catch (FormatException e) + catch (FormatException) { + Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + } + catch (OverflowException) + { + Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 9584b10ef5..0f83edf034 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -38,6 +39,10 @@ namespace osu.Game.Beatmaps.Formats { this.storyboard = storyboard; base.ParseStreamInto(stream, storyboard); + + // OrderBy is used to guarantee that the parsing order of elements with equal start times is maintained (stably-sorted) + foreach (StoryboardLayer layer in storyboard.Layers) + layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList(); } protected override void ParseLine(Storyboard storyboard, Section section, string line) diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs new file mode 100644 index 0000000000..c3efb8c760 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; + +namespace osu.Game.Beatmaps.Formats +{ + /// + /// Helper methods to parse from string to number and perform very basic validation. + /// + public static class Parsing + { + public const int MAX_COORDINATE_VALUE = 65536; + + public const double MAX_PARSE_VALUE = int.MaxValue; + + public static float ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) + { + var output = float.Parse(input, CultureInfo.InvariantCulture); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + if (float.IsNaN(output)) throw new FormatException("Not a number"); + + return output; + } + + public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE) + { + var output = double.Parse(input, CultureInfo.InvariantCulture); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + if (double.IsNaN(output)) throw new FormatException("Not a number"); + + return output; + } + + public static int ParseInt(string input, int parseLimit = (int)MAX_PARSE_VALUE) + { + var output = int.Parse(input, CultureInfo.InvariantCulture); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + return output; + } + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1260a524b4..795f0b43f7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -55,7 +55,7 @@ namespace osu.Game.Configuration // Input Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.GameplayCursorSize, 1.0, 0.1f, 2, 0.01); Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.MouseDisableButtons, false); @@ -72,9 +72,6 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuParallax, true); - Set(OsuSetting.SnakingInSliders, true); - Set(OsuSetting.SnakingOutSliders, true); - // Gameplay Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -150,8 +147,6 @@ namespace osu.Game.Configuration DisplayStarsMinimum, DisplayStarsMaximum, RandomSelectAlgorithm, - SnakingInSliders, - SnakingOutSliders, ShowFpsDisplay, ChatDisplayHeight, Version, diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5b4a191682..3805921ac2 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -33,7 +33,7 @@ namespace osu.Game.Database where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { - public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent); + public delegate void ItemAddedDelegate(TModel model, bool existing); /// /// Set an endpoint for notifications to be posted to. @@ -113,7 +113,7 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent)); + ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item, false)); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); Files = new FileStore(contextFactory, storage); @@ -131,14 +131,18 @@ namespace osu.Game.Database /// One or more archive locations on disk. public void Import(params string[] paths) { - var notification = new ProgressNotification - { - Text = "Import is initialising...", - Progress = 0, - State = ProgressNotificationState.Active, - }; + var notification = new ProgressNotification { State = ProgressNotificationState.Active }; PostNotification?.Invoke(notification); + Import(notification, paths); + } + + protected void Import(ProgressNotification notification, params string[] paths) + { + notification.Progress = 0; + notification.Text = "Import is initialising..."; + + var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; List imported = new List(); @@ -151,7 +155,18 @@ namespace osu.Game.Database try { - notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; + var text = "Importing "; + + if (path.Length > 1) + text += $"{++current} of {paths.Length} {term}s.."; + else + text += $"{term}.."; + + // only show the filename if it isn't a temporary one (as those look ugly). + if (!path.Contains(Path.GetTempPath())) + text += $"\n{Path.GetFileName(path)}"; + + notification.Text = text; imported.Add(Import(path)); @@ -171,13 +186,20 @@ namespace osu.Game.Database } else { - notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!"; - notification.CompletionClickAction += () => + notification.CompletionText = imported.Count == 1 + ? $"Imported {imported.First()}!" + : $"Imported {current} {term}s!"; + + if (imported.Count > 0 && PresentImport != null) { - if (imported.Count > 0) - PresentCompletedImport(imported); - return true; - }; + notification.CompletionText += " Click to view."; + notification.CompletionClickAction = () => + { + PresentImport?.Invoke(imported); + return true; + }; + } + notification.State = ProgressNotificationState.Completed; } } @@ -210,9 +232,10 @@ namespace osu.Game.Database return import; } - protected virtual void PresentCompletedImport(IEnumerable imported) - { - } + /// + /// Fired when the user requests to view the resulting import. + /// + public Action> PresentImport; /// /// Import an item from an . @@ -228,7 +251,7 @@ namespace osu.Game.Database model.Hash = computeHash(archive); - return Import(model, false, archive); + return Import(model, archive); } catch (Exception e) { @@ -262,9 +285,8 @@ namespace osu.Game.Database /// Import an item from a . /// /// The model to be imported. - /// Whether the user should be notified fo the import. /// An optional archive to use for model population. - public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null) + public TModel Import(TModel item, ArchiveReader archive = null) { delayEvents(); @@ -278,23 +300,33 @@ namespace osu.Game.Database { if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - var existing = CheckForExisting(item); - - if (existing != null) - { - Undelete(existing); - Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); - handleEvent(() => ItemAdded?.Invoke(existing, true, silent)); - return existing; - } - if (archive != null) item.Files = createFileInfos(archive, Files); Populate(item, archive); + var existing = CheckForExisting(item); + + if (existing != null) + { + if (CanUndelete(existing, item)) + { + Undelete(existing); + Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); + handleEvent(() => ItemAdded?.Invoke(existing, true)); + return existing; + } + else + { + Delete(existing); + ModelStore.PurgeDeletable(s => s.ID == existing.ID); + } + } + + PreImport(item); + // import to store - ModelStore.Add(item, silent); + ModelStore.Add(item); } catch (Exception e) { @@ -520,12 +552,29 @@ namespace osu.Game.Database { } + /// + /// Perform any final actions before the import to database executes. + /// + /// The model prepared for import. + protected virtual void PreImport(TModel model) + { + } + /// /// Check whether an existing model already exists for a new import item. /// - /// The new model proposed for import. Note that has not yet been run on this model. + /// The new model proposed for import. /// An existing model which matches the criteria to skip importing, else null. - protected virtual TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + + /// + /// After an existing is found during an import process, the default behaviour is to restore the existing + /// item and skip the import. This method allows changing that behaviour. + /// + /// The existing model. + /// The newly imported model. + /// Whether the existing model should be restored and used. Returning false will delete the existing a force a re-import. + protected virtual bool CanUndelete(TModel existing, TModel import) => true; private DbSet queryModel() => ContextFactory.Get().Set(); diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 8ed38fedb8..39a48b5be6 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -16,9 +16,7 @@ namespace osu.Game.Database public abstract class MutableDatabaseBackedStore : DatabaseBackedStore where T : class, IHasPrimaryKey, ISoftDelete { - public delegate void ItemAddedDelegate(T model, bool silent); - - public event ItemAddedDelegate ItemAdded; + public event Action ItemAdded; public event Action ItemRemoved; protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) @@ -35,8 +33,7 @@ namespace osu.Game.Database /// Add a to the database. /// /// The item to add. - /// Whether the user should be notified of the addition. - public void Add(T item, bool silent) + public void Add(T item) { using (var usage = ContextFactory.GetForWrite()) { @@ -44,7 +41,7 @@ namespace osu.Game.Database context.Attach(item); } - ItemAdded?.Invoke(item, silent); + ItemAdded?.Invoke(item); } /// @@ -57,7 +54,7 @@ namespace osu.Game.Database usage.Context.Update(item); ItemRemoved?.Invoke(item); - ItemAdded?.Invoke(item, true); + ItemAdded?.Invoke(item); } /// @@ -94,7 +91,7 @@ namespace osu.Game.Database item.DeletePending = false; } - ItemAdded?.Invoke(item, true); + ItemAdded?.Invoke(item); return true; } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index ebd9db786f..c67d779c37 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly SortedList parts = new SortedList(Comparer.Default); - private Shader shader; + private IShader shader; private readonly Texture texture; public Triangles() @@ -75,7 +75,7 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load(ShaderManager shaders) { - shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } protected override void LoadComplete() @@ -180,8 +180,6 @@ namespace osu.Game.Graphics.Backgrounds protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); - private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData(); - protected override void ApplyDrawNode(DrawNode node) { base.ApplyDrawNode(node); @@ -191,27 +189,21 @@ namespace osu.Game.Graphics.Backgrounds trianglesNode.Shader = shader; trianglesNode.Texture = texture; trianglesNode.Size = DrawSize; - trianglesNode.Shared = sharedData; trianglesNode.Parts.Clear(); trianglesNode.Parts.AddRange(parts); } - private class TrianglesDrawNodeSharedData - { - public readonly LinearBatch VertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); - } - private class TrianglesDrawNode : DrawNode { - public Shader Shader; + public IShader Shader; public Texture Texture; - public TrianglesDrawNodeSharedData Shared; - public readonly List Parts = new List(); public Vector2 Size; + private readonly LinearBatch vertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + public override void Draw(Action vertexAction) { base.Draw(vertexAction); @@ -239,12 +231,19 @@ namespace osu.Game.Graphics.Backgrounds triangle, colourInfo, null, - Shared.VertexBatch.AddAction, + vertexBatch.AddAction, Vector2.Divide(localInflationAmount, size)); } Shader.Unbind(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); + } } protected struct TriangleParticle : IComparable diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 37afefb7f8..dace873b92 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers showNotImplementedError = () => notifications?.Post(new SimpleNotification { Text = @"This link type is not yet supported!", - Icon = FontAwesome.fa_life_saver, + Icon = FontAwesome.Solid.LifeRing, }); } diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index 56e5f411b8..6a87a4b8b9 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -21,6 +21,6 @@ namespace osu.Game.Graphics.Containers public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable); - public IEnumerable AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); + public IEnumerable AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); } } diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 51f068d920..8f07c3a656 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -5,8 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Configuration; -using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens; +using osu.Game.Screens.Backgrounds; using osuTK; namespace osu.Game.Graphics.Containers @@ -32,7 +34,7 @@ namespace osu.Game.Graphics.Containers private readonly Container sizableContainer; - private Drawable backgroundLayer; + private BackgroundScreenStack backgroundStack; /// /// Create a new instance. @@ -112,27 +114,29 @@ namespace osu.Game.Graphics.Containers private void updateSize() { + const float fade_time = 500; + if (targetMode == ScalingMode.Everything) { // the top level scaling container manages the background to be displayed while scaling. if (requiresBackgroundVisible) { - if (backgroundLayer == null) - LoadComponentAsync(backgroundLayer = new Background("Menu/menu-background-1") + if (backgroundStack == null) + { + AddInternal(backgroundStack = new BackgroundScreenStack { Colour = OsuColour.Gray(0.1f), Alpha = 0, Depth = float.MaxValue - }, d => - { - AddInternal(d); - d.FadeTo(requiresBackgroundVisible ? 1 : 0, 4000, Easing.OutQuint); }); - else - backgroundLayer.FadeIn(500); + + backgroundStack.Push(new ScalingBackgroundScreen()); + } + + backgroundStack.FadeIn(fade_time); } else - backgroundLayer?.FadeOut(500); + backgroundStack?.FadeOut(fade_time); } bool scaling = targetMode == null || scalingMode.Value == targetMode; @@ -148,6 +152,14 @@ namespace osu.Game.Graphics.Containers sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } + private class ScalingBackgroundScreen : BackgroundScreenDefault + { + public override void OnEntering(IScreen last) + { + this.FadeInFromZero(4000, Easing.OutQuint); + } + } + private class AlwaysInputContainer : Container { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 4c8928e122..fe9eb7baf4 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -1,27 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens.Play; +using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.Containers { /// - /// A container that applies user-configured dim levels to its contents. + /// A container that applies user-configured visual settings to its contents. /// This container specifies behavior that applies to both Storyboards and Backgrounds. /// public class UserDimContainer : Container { private const float background_fade_duration = 800; - private Bindable dimLevel { get; set; } - - private Bindable showStoryboard { get; set; } - /// /// Whether or not user-configured dim levels should be applied to the container. /// @@ -32,19 +32,40 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); + /// + /// The amount of blur to be applied to the background in addition to user-specified blur. + /// + /// + /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in + /// + public readonly Bindable BlurAmount = new Bindable(); + + private Bindable userDimLevel { get; set; } + + private Bindable userBlurLevel { get; set; } + + private Bindable showStoryboard { get; set; } + protected Container DimContainer { get; } protected override Container Content => DimContainer; private readonly bool isStoryboard; + /// + /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. + /// + private Vector2 blurTarget => EnableUserDim.Value + ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) + : new Vector2(BlurAmount.Value); + /// /// Creates a new . /// - /// Whether or not this instance of UserDimContainer contains a storyboard. + /// Whether or not this instance contains a storyboard. /// /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via - /// and can cause backgrounds to become hidden via . + /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. /// /// public UserDimContainer(bool isStoryboard = false) @@ -53,36 +74,62 @@ namespace osu.Game.Graphics.Containers AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } + private Background background; + + public Background Background + { + get => background; + set + { + base.Add(background = value); + background.BlurTo(blurTarget, 0, Easing.OutQuint); + } + } + + public override void Add(Drawable drawable) + { + if (drawable is Background) + throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); + + base.Add(drawable); + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - dimLevel = config.GetBindable(OsuSetting.DimLevel); + userDimLevel = config.GetBindable(OsuSetting.DimLevel); + userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableUserDim.ValueChanged += _ => updateBackgroundDim(); - dimLevel.ValueChanged += _ => updateBackgroundDim(); - showStoryboard.ValueChanged += _ => updateBackgroundDim(); - StoryboardReplacesBackground.ValueChanged += _ => updateBackgroundDim(); + + EnableUserDim.ValueChanged += _ => updateVisuals(); + userDimLevel.ValueChanged += _ => updateVisuals(); + userBlurLevel.ValueChanged += _ => updateVisuals(); + showStoryboard.ValueChanged += _ => updateVisuals(); + StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); + BlurAmount.ValueChanged += _ => updateVisuals(); } protected override void LoadComplete() { base.LoadComplete(); - updateBackgroundDim(); + updateVisuals(); } - private void updateBackgroundDim() + private void updateVisuals() { if (isStoryboard) { - DimContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); + DimContainer.FadeTo(!showStoryboard.Value || userDimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); } else { // The background needs to be hidden in the case of it being replaced by the storyboard DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); + + Background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); } - DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)userDimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 22937333d7..dd390906fe 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics { - public struct OsuFont + public static class OsuFont { /// /// The default font size. @@ -42,8 +42,6 @@ namespace osu.Game.Graphics { case Typeface.Exo: return "Exo2.0"; - case Typeface.FontAwesome: - return "FontAwesome"; case Typeface.Venera: return "Venera"; case Typeface.Aquatico: @@ -103,7 +101,6 @@ namespace osu.Game.Graphics public enum Typeface { Exo, - FontAwesome, Venera, Aquatico // tournament use only } diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs new file mode 100644 index 0000000000..982e9dacab --- /dev/null +++ b/osu.Game/Graphics/OsuIcon.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.Graphics.Sprites; + +namespace osu.Game.Graphics +{ + public static class OsuIcon + { + public static IconUsage Get(int icon) => new IconUsage((char)icon, "osuFont"); + + // ruleset icons in circles + public static IconUsage RulesetOsu => Get(0xe000); + public static IconUsage RulesetMania => Get(0xe001); + public static IconUsage RulesetCatch => Get(0xe002); + public static IconUsage RulesetTaiko => Get(0xe003); + + // ruleset icons without circles + public static IconUsage FilledCircle => Get(0xe004); + public static IconUsage CrossCircle => Get(0xe005); + public static IconUsage Logo => Get(0xe006); + public static IconUsage ChevronDownCircle => Get(0xe007); + public static IconUsage EditCircle => Get(0xe033); + public static IconUsage LeftCircle => Get(0xe034); + public static IconUsage RightCircle => Get(0xe035); + public static IconUsage Charts => Get(0xe036); + public static IconUsage Solo => Get(0xe037); + public static IconUsage Multi => Get(0xe038); + public static IconUsage Gear => Get(0xe039); + + // misc icons + public static IconUsage Bat => Get(0xe008); + public static IconUsage Bubble => Get(0xe009); + public static IconUsage BubblePop => Get(0xe02e); + public static IconUsage Dice => Get(0xe011); + public static IconUsage Heart => Get(0xe02f); + public static IconUsage HeartBreak => Get(0xe030); + public static IconUsage Hot => Get(0xe031); + public static IconUsage ListSearch => Get(0xe032); + + //osu! playstyles + public static IconUsage PlaystyleTablet => Get(0xe02a); + public static IconUsage PlaystyleMouse => Get(0xe029); + public static IconUsage PlaystyleKeyboard => Get(0xe02b); + public static IconUsage PlaystyleTouch => Get(0xe02c); + + // osu! difficulties + public static IconUsage EasyOsu => Get(0xe015); + public static IconUsage NormalOsu => Get(0xe016); + public static IconUsage HardOsu => Get(0xe017); + public static IconUsage InsaneOsu => Get(0xe018); + public static IconUsage ExpertOsu => Get(0xe019); + + // taiko difficulties + public static IconUsage EasyTaiko => Get(0xe01a); + public static IconUsage NormalTaiko => Get(0xe01b); + public static IconUsage HardTaiko => Get(0xe01c); + public static IconUsage InsaneTaiko => Get(0xe01d); + public static IconUsage ExpertTaiko => Get(0xe01e); + + // fruits difficulties + public static IconUsage EasyFruits => Get(0xe01f); + public static IconUsage NormalFruits => Get(0xe020); + public static IconUsage HardFruits => Get(0xe021); + public static IconUsage InsaneFruits => Get(0xe022); + public static IconUsage ExpertFruits => Get(0xe023); + + // mania difficulties + public static IconUsage EasyMania => Get(0xe024); + public static IconUsage NormalMania => Get(0xe025); + public static IconUsage HardMania => Get(0xe026); + public static IconUsage InsaneMania => Get(0xe027); + public static IconUsage ExpertMania => Get(0xe028); + + // mod icons + public static IconUsage ModPerfect => Get(0xe049); + public static IconUsage ModAutopilot => Get(0xe03a); + public static IconUsage ModAuto => Get(0xe03b); + public static IconUsage ModCinema => Get(0xe03c); + public static IconUsage ModDoubletime => Get(0xe03d); + public static IconUsage ModEasy => Get(0xe03e); + public static IconUsage ModFlashlight => Get(0xe03f); + public static IconUsage ModHalftime => Get(0xe040); + public static IconUsage ModHardrock => Get(0xe041); + public static IconUsage ModHidden => Get(0xe042); + public static IconUsage ModNightcore => Get(0xe043); + public static IconUsage ModNofail => Get(0xe044); + public static IconUsage ModRelax => Get(0xe045); + public static IconUsage ModSpunout => Get(0xe046); + public static IconUsage ModSuddendeath => Get(0xe047); + public static IconUsage ModTarget => Get(0xe048); + public static IconUsage ModBg => Get(0xe04a); + } +} diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs deleted file mode 100644 index f7d7d21435..0000000000 --- a/osu.Game/Graphics/SpriteIcon.cs +++ /dev/null @@ -1,1007 +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.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics; -using osu.Framework.IO.Stores; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Caching; - -namespace osu.Game.Graphics -{ - public class SpriteIcon : CompositeDrawable - { - private Sprite spriteShadow; - private Sprite spriteMain; - - private Cached layout = new Cached(); - private Container shadowVisibility; - - private FontStore store; - - [BackgroundDependencyLoader] - private void load(FontStore store) - { - this.store = store; - - InternalChildren = new Drawable[] - { - shadowVisibility = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = spriteShadow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Y = 2, - Colour = new Color4(0f, 0f, 0f, 0.2f), - }, - Alpha = shadow ? 1 : 0, - }, - spriteMain = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }, - }; - - updateTexture(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateTexture(); - } - - private FontAwesome loadedIcon; - - private void updateTexture() - { - var loadableIcon = icon; - - if (loadableIcon == loadedIcon) return; - - var texture = store.Get(((char)loadableIcon).ToString()); - - spriteMain.Texture = texture; - spriteShadow.Texture = texture; - - if (Size == Vector2.Zero) - Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); - - loadedIcon = loadableIcon; - } - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.Colour) > 0 && Shadow) - layout.Invalidate(); - return base.Invalidate(invalidation, source, shallPropagate); - } - - protected override void Update() - { - if (!layout.IsValid) - { - //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. - //squared result for quadratic fall-off seems to give the best result. - var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; - - spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); - - layout.Validate(); - } - } - - private bool shadow; - - public bool Shadow - { - get => shadow; - set - { - shadow = value; - if (shadowVisibility != null) - shadowVisibility.Alpha = value ? 1 : 0; - } - } - - private FontAwesome icon; - - public FontAwesome Icon - { - get => icon; - set - { - if (icon == value) return; - - icon = value; - if (LoadState == LoadState.Loaded) - updateTexture(); - } - } - } - - public enum FontAwesome - { - fa_500px = 0xf26e, - fa_address_book = 0xf2b9, - fa_address_book_o = 0xf2ba, - fa_address_card = 0xf2bb, - fa_address_card_o = 0xf2bc, - fa_adjust = 0xf042, - fa_adn = 0xf170, - fa_align_center = 0xf037, - fa_align_justify = 0xf039, - fa_align_left = 0xf036, - fa_align_right = 0xf038, - fa_amazon = 0xf270, - fa_ambulance = 0xf0f9, - fa_american_sign_language_interpreting = 0xf2a3, - fa_anchor = 0xf13d, - fa_android = 0xf17b, - fa_angellist = 0xf209, - fa_angle_double_down = 0xf103, - fa_angle_double_left = 0xf100, - fa_angle_double_right = 0xf101, - fa_angle_double_up = 0xf102, - fa_angle_down = 0xf107, - fa_angle_left = 0xf104, - fa_angle_right = 0xf105, - fa_angle_up = 0xf106, - fa_apple = 0xf179, - fa_archive = 0xf187, - fa_area_chart = 0xf1fe, - fa_arrow_circle_down = 0xf0ab, - fa_arrow_circle_left = 0xf0a8, - fa_arrow_circle_o_down = 0xf01a, - fa_arrow_circle_o_left = 0xf190, - fa_arrow_circle_o_right = 0xf18e, - fa_arrow_circle_o_up = 0xf01b, - fa_arrow_circle_right = 0xf0a9, - fa_arrow_circle_up = 0xf0aa, - fa_arrow_down = 0xf063, - fa_arrow_left = 0xf060, - fa_arrow_right = 0xf061, - fa_arrow_up = 0xf062, - fa_arrows = 0xf047, - fa_arrows_alt = 0xf0b2, - fa_arrows_h = 0xf07e, - fa_arrows_v = 0xf07d, - fa_asl_interpreting = 0xf2a3, - fa_assistive_listening_systems = 0xf2a2, - fa_asterisk = 0xf069, - fa_at = 0xf1fa, - fa_audio_description = 0xf29e, - fa_automobile = 0xf1b9, - fa_backward = 0xf04a, - fa_balance_scale = 0xf24e, - fa_ban = 0xf05e, - fa_bandcamp = 0xf2d5, - fa_bank = 0xf19c, - fa_bar_chart = 0xf080, - fa_bar_chart_o = 0xf080, - fa_barcode = 0xf02a, - fa_bars = 0xf0c9, - fa_bath = 0xf2cd, - fa_bathtub = 0xf2cd, - fa_battery = 0xf240, - fa_battery_0 = 0xf244, - fa_battery_1 = 0xf243, - fa_battery_2 = 0xf242, - fa_battery_3 = 0xf241, - fa_battery_4 = 0xf240, - fa_battery_empty = 0xf244, - fa_battery_full = 0xf240, - fa_battery_half = 0xf242, - fa_battery_quarter = 0xf243, - fa_battery_three_quarters = 0xf241, - fa_bed = 0xf236, - fa_beer = 0xf0fc, - fa_behance = 0xf1b4, - fa_behance_square = 0xf1b5, - fa_bell = 0xf0f3, - fa_bell_o = 0xf0a2, - fa_bell_slash = 0xf1f6, - fa_bell_slash_o = 0xf1f7, - fa_bicycle = 0xf206, - fa_binoculars = 0xf1e5, - fa_birthday_cake = 0xf1fd, - fa_bitbucket = 0xf171, - fa_bitbucket_square = 0xf172, - fa_bitcoin = 0xf15a, - fa_black_tie = 0xf27e, - fa_blind = 0xf29d, - fa_bluetooth = 0xf293, - fa_bluetooth_b = 0xf294, - fa_bold = 0xf032, - fa_bolt = 0xf0e7, - fa_bomb = 0xf1e2, - fa_book = 0xf02d, - fa_bookmark = 0xf02e, - fa_bookmark_o = 0xf097, - fa_braille = 0xf2a1, - fa_briefcase = 0xf0b1, - fa_btc = 0xf15a, - fa_bug = 0xf188, - fa_building = 0xf1ad, - fa_building_o = 0xf0f7, - fa_bullhorn = 0xf0a1, - fa_bullseye = 0xf140, - fa_bus = 0xf207, - fa_buysellads = 0xf20d, - fa_cab = 0xf1ba, - fa_calculator = 0xf1ec, - fa_calendar = 0xf073, - fa_calendar_check_o = 0xf274, - fa_calendar_minus_o = 0xf272, - fa_calendar_o = 0xf133, - fa_calendar_plus_o = 0xf271, - fa_calendar_times_o = 0xf273, - fa_camera = 0xf030, - fa_camera_retro = 0xf083, - fa_car = 0xf1b9, - fa_caret_down = 0xf0d7, - fa_caret_left = 0xf0d9, - fa_caret_right = 0xf0da, - fa_caret_square_o_down = 0xf150, - fa_caret_square_o_left = 0xf191, - fa_caret_square_o_right = 0xf152, - fa_caret_square_o_up = 0xf151, - fa_caret_up = 0xf0d8, - fa_cart_arrow_down = 0xf218, - fa_cart_plus = 0xf217, - fa_cc = 0xf20a, - fa_cc_amex = 0xf1f3, - fa_cc_diners_club = 0xf24c, - fa_cc_discover = 0xf1f2, - fa_cc_jcb = 0xf24b, - fa_cc_mastercard = 0xf1f1, - fa_cc_paypal = 0xf1f4, - fa_cc_stripe = 0xf1f5, - fa_cc_visa = 0xf1f0, - fa_certificate = 0xf0a3, - fa_chain = 0xf0c1, - fa_chain_broken = 0xf127, - fa_check = 0xf00c, - fa_check_circle = 0xf058, - fa_check_circle_o = 0xf05d, - fa_check_square = 0xf14a, - fa_check_square_o = 0xf046, - fa_chevron_circle_down = 0xf13a, - fa_chevron_circle_left = 0xf137, - fa_chevron_circle_right = 0xf138, - fa_chevron_circle_up = 0xf139, - fa_chevron_down = 0xf078, - fa_chevron_left = 0xf053, - fa_chevron_right = 0xf054, - fa_chevron_up = 0xf077, - fa_child = 0xf1ae, - fa_chrome = 0xf268, - fa_circle = 0xf111, - fa_circle_o = 0xf10c, - fa_circle_o_notch = 0xf1ce, - fa_circle_thin = 0xf1db, - fa_clipboard = 0xf0ea, - fa_clock_o = 0xf017, - fa_clone = 0xf24d, - fa_close = 0xf00d, - fa_cloud = 0xf0c2, - fa_cloud_download = 0xf0ed, - fa_cloud_upload = 0xf0ee, - fa_cny = 0xf157, - fa_code = 0xf121, - fa_code_fork = 0xf126, - fa_codepen = 0xf1cb, - fa_codiepie = 0xf284, - fa_coffee = 0xf0f4, - fa_cog = 0xf013, - fa_cogs = 0xf085, - fa_columns = 0xf0db, - fa_comment = 0xf075, - fa_comment_o = 0xf0e5, - fa_commenting = 0xf27a, - fa_commenting_o = 0xf27b, - fa_comments = 0xf086, - fa_comments_o = 0xf0e6, - fa_compass = 0xf14e, - fa_compress = 0xf066, - fa_connectdevelop = 0xf20e, - fa_contao = 0xf26d, - fa_copy = 0xf0c5, - fa_copyright = 0xf1f9, - fa_creative_commons = 0xf25e, - fa_credit_card = 0xf09d, - fa_credit_card_alt = 0xf283, - fa_crop = 0xf125, - fa_crosshairs = 0xf05b, - fa_css3 = 0xf13c, - fa_cube = 0xf1b2, - fa_cubes = 0xf1b3, - fa_cut = 0xf0c4, - fa_cutlery = 0xf0f5, - fa_dashboard = 0xf0e4, - fa_dashcube = 0xf210, - fa_database = 0xf1c0, - fa_deaf = 0xf2a4, - fa_deafness = 0xf2a4, - fa_dedent = 0xf03b, - fa_delicious = 0xf1a5, - fa_desktop = 0xf108, - fa_deviantart = 0xf1bd, - fa_diamond = 0xf219, - fa_digg = 0xf1a6, - fa_dollar = 0xf155, - fa_dot_circle_o = 0xf192, - fa_download = 0xf019, - fa_dribbble = 0xf17d, - fa_drivers_license = 0xf2c2, - fa_drivers_license_o = 0xf2c3, - fa_dropbox = 0xf16b, - fa_drupal = 0xf1a9, - fa_edge = 0xf282, - fa_edit = 0xf044, - fa_eercast = 0xf2da, - fa_eject = 0xf052, - fa_ellipsis_h = 0xf141, - fa_ellipsis_v = 0xf142, - fa_empire = 0xf1d1, - fa_envelope = 0xf0e0, - fa_envelope_o = 0xf003, - fa_envelope_open = 0xf2b6, - fa_envelope_open_o = 0xf2b7, - fa_envelope_square = 0xf199, - fa_envira = 0xf299, - fa_eraser = 0xf12d, - fa_etsy = 0xf2d7, - fa_eur = 0xf153, - fa_euro = 0xf153, - fa_exchange = 0xf0ec, - fa_exclamation = 0xf12a, - fa_exclamation_circle = 0xf06a, - fa_exclamation_triangle = 0xf071, - fa_expand = 0xf065, - fa_expeditedssl = 0xf23e, - fa_external_link = 0xf08e, - fa_external_link_square = 0xf14c, - fa_eye = 0xf06e, - fa_eye_slash = 0xf070, - fa_eyedropper = 0xf1fb, - fa_fa = 0xf2b4, - fa_facebook = 0xf09a, - fa_facebook_f = 0xf09a, - fa_facebook_official = 0xf230, - fa_facebook_square = 0xf082, - fa_fast_backward = 0xf049, - fa_fast_forward = 0xf050, - fa_fax = 0xf1ac, - fa_feed = 0xf09e, - fa_female = 0xf182, - fa_fighter_jet = 0xf0fb, - fa_file = 0xf15b, - fa_file_archive_o = 0xf1c6, - fa_file_audio_o = 0xf1c7, - fa_file_code_o = 0xf1c9, - fa_file_excel_o = 0xf1c3, - fa_file_image_o = 0xf1c5, - fa_file_movie_o = 0xf1c8, - fa_file_o = 0xf016, - fa_file_pdf_o = 0xf1c1, - fa_file_photo_o = 0xf1c5, - fa_file_picture_o = 0xf1c5, - fa_file_powerpoint_o = 0xf1c4, - fa_file_sound_o = 0xf1c7, - fa_file_text = 0xf15c, - fa_file_text_o = 0xf0f6, - fa_file_video_o = 0xf1c8, - fa_file_word_o = 0xf1c2, - fa_file_zip_o = 0xf1c6, - fa_files_o = 0xf0c5, - fa_film = 0xf008, - fa_filter = 0xf0b0, - fa_fire = 0xf06d, - fa_fire_extinguisher = 0xf134, - fa_firefox = 0xf269, - fa_first_order = 0xf2b0, - fa_flag = 0xf024, - fa_flag_checkered = 0xf11e, - fa_flag_o = 0xf11d, - fa_flash = 0xf0e7, - fa_flask = 0xf0c3, - fa_flickr = 0xf16e, - fa_floppy_o = 0xf0c7, - fa_folder = 0xf07b, - fa_folder_o = 0xf114, - fa_folder_open = 0xf07c, - fa_folder_open_o = 0xf115, - fa_font = 0xf031, - fa_font_awesome = 0xf2b4, - fa_fonticons = 0xf280, - fa_fort_awesome = 0xf286, - fa_forumbee = 0xf211, - fa_forward = 0xf04e, - fa_foursquare = 0xf180, - fa_free_code_camp = 0xf2c5, - fa_frown_o = 0xf119, - fa_futbol_o = 0xf1e3, - fa_gamepad = 0xf11b, - fa_gavel = 0xf0e3, - fa_gbp = 0xf154, - fa_ge = 0xf1d1, - fa_gear = 0xf013, - fa_gears = 0xf085, - fa_genderless = 0xf22d, - fa_get_pocket = 0xf265, - fa_gg = 0xf260, - fa_gg_circle = 0xf261, - fa_gift = 0xf06b, - fa_git = 0xf1d3, - fa_git_square = 0xf1d2, - fa_github = 0xf09b, - fa_github_alt = 0xf113, - fa_github_square = 0xf092, - fa_gitlab = 0xf296, - fa_gittip = 0xf184, - fa_glass = 0xf000, - fa_glide = 0xf2a5, - fa_glide_g = 0xf2a6, - fa_globe = 0xf0ac, - fa_google = 0xf1a0, - fa_google_plus = 0xf0d5, - fa_google_plus_circle = 0xf2b3, - fa_google_plus_official = 0xf2b3, - fa_google_plus_square = 0xf0d4, - fa_google_wallet = 0xf1ee, - fa_graduation_cap = 0xf19d, - fa_gratipay = 0xf184, - fa_grav = 0xf2d6, - fa_group = 0xf0c0, - fa_h_square = 0xf0fd, - fa_hacker_news = 0xf1d4, - fa_hand_grab_o = 0xf255, - fa_hand_lizard_o = 0xf258, - fa_hand_o_down = 0xf0a7, - fa_hand_o_left = 0xf0a5, - fa_hand_o_right = 0xf0a4, - fa_hand_o_up = 0xf0a6, - fa_hand_paper_o = 0xf256, - fa_hand_peace_o = 0xf25b, - fa_hand_pointer_o = 0xf25a, - fa_hand_rock_o = 0xf255, - fa_hand_scissors_o = 0xf257, - fa_hand_spock_o = 0xf259, - fa_hand_stop_o = 0xf256, - fa_handshake_o = 0xf2b5, - fa_hard_of_hearing = 0xf2a4, - fa_hashtag = 0xf292, - fa_hdd_o = 0xf0a0, - fa_header = 0xf1dc, - fa_headphones = 0xf025, - fa_heart = 0xf004, - fa_heart_o = 0xf08a, - fa_heartbeat = 0xf21e, - fa_history = 0xf1da, - fa_home = 0xf015, - fa_hospital_o = 0xf0f8, - fa_hotel = 0xf236, - fa_hourglass = 0xf254, - fa_hourglass_1 = 0xf251, - fa_hourglass_2 = 0xf252, - fa_hourglass_3 = 0xf253, - fa_hourglass_end = 0xf253, - fa_hourglass_half = 0xf252, - fa_hourglass_o = 0xf250, - fa_hourglass_start = 0xf251, - fa_houzz = 0xf27c, - fa_html5 = 0xf13b, - fa_i_cursor = 0xf246, - fa_id_badge = 0xf2c1, - fa_id_card = 0xf2c2, - fa_id_card_o = 0xf2c3, - fa_ils = 0xf20b, - fa_image = 0xf03e, - fa_imdb = 0xf2d8, - fa_inbox = 0xf01c, - fa_indent = 0xf03c, - fa_industry = 0xf275, - fa_info = 0xf129, - fa_info_circle = 0xf05a, - fa_inr = 0xf156, - fa_instagram = 0xf16d, - fa_institution = 0xf19c, - fa_internet_explorer = 0xf26b, - fa_intersex = 0xf224, - fa_ioxhost = 0xf208, - fa_italic = 0xf033, - fa_joomla = 0xf1aa, - fa_jpy = 0xf157, - fa_jsfiddle = 0xf1cc, - fa_key = 0xf084, - fa_keyboard_o = 0xf11c, - fa_krw = 0xf159, - fa_language = 0xf1ab, - fa_laptop = 0xf109, - fa_lastfm = 0xf202, - fa_lastfm_square = 0xf203, - fa_leaf = 0xf06c, - fa_leanpub = 0xf212, - fa_legal = 0xf0e3, - fa_lemon_o = 0xf094, - fa_level_down = 0xf149, - fa_level_up = 0xf148, - fa_life_bouy = 0xf1cd, - fa_life_buoy = 0xf1cd, - fa_life_ring = 0xf1cd, - fa_life_saver = 0xf1cd, - fa_lightbulb_o = 0xf0eb, - fa_line_chart = 0xf201, - fa_link = 0xf0c1, - fa_linkedin = 0xf0e1, - fa_linkedin_square = 0xf08c, - fa_linode = 0xf2b8, - fa_linux = 0xf17c, - fa_list = 0xf03a, - fa_list_alt = 0xf022, - fa_list_ol = 0xf0cb, - fa_list_ul = 0xf0ca, - fa_location_arrow = 0xf124, - fa_lock = 0xf023, - fa_long_arrow_down = 0xf175, - fa_long_arrow_left = 0xf177, - fa_long_arrow_right = 0xf178, - fa_long_arrow_up = 0xf176, - fa_low_vision = 0xf2a8, - fa_magic = 0xf0d0, - fa_magnet = 0xf076, - fa_mail_forward = 0xf064, - fa_mail_reply = 0xf112, - fa_mail_reply_all = 0xf122, - fa_male = 0xf183, - fa_map = 0xf279, - fa_map_marker = 0xf041, - fa_map_o = 0xf278, - fa_map_pin = 0xf276, - fa_map_signs = 0xf277, - fa_mars = 0xf222, - fa_mars_double = 0xf227, - fa_mars_stroke = 0xf229, - fa_mars_stroke_h = 0xf22b, - fa_mars_stroke_v = 0xf22a, - fa_maxcdn = 0xf136, - fa_meanpath = 0xf20c, - fa_medium = 0xf23a, - fa_medkit = 0xf0fa, - fa_meetup = 0xf2e0, - fa_meh_o = 0xf11a, - fa_mercury = 0xf223, - fa_microchip = 0xf2db, - fa_microphone = 0xf130, - fa_microphone_slash = 0xf131, - fa_minus = 0xf068, - fa_minus_circle = 0xf056, - fa_minus_square = 0xf146, - fa_minus_square_o = 0xf147, - fa_mixcloud = 0xf289, - fa_mobile = 0xf10b, - fa_mobile_phone = 0xf10b, - fa_modx = 0xf285, - fa_money = 0xf0d6, - fa_moon_o = 0xf186, - fa_mortar_board = 0xf19d, - fa_motorcycle = 0xf21c, - fa_mouse_pointer = 0xf245, - fa_music = 0xf001, - fa_navicon = 0xf0c9, - fa_neuter = 0xf22c, - fa_newspaper_o = 0xf1ea, - fa_object_group = 0xf247, - fa_object_ungroup = 0xf248, - fa_odnoklassniki = 0xf263, - fa_odnoklassniki_square = 0xf264, - fa_opencart = 0xf23d, - fa_openid = 0xf19b, - fa_opera = 0xf26a, - fa_optin_monster = 0xf23c, - fa_outdent = 0xf03b, - fa_pagelines = 0xf18c, - fa_paint_brush = 0xf1fc, - fa_paper_plane = 0xf1d8, - fa_paper_plane_o = 0xf1d9, - fa_paperclip = 0xf0c6, - fa_paragraph = 0xf1dd, - fa_paste = 0xf0ea, - fa_pause = 0xf04c, - fa_pause_circle = 0xf28b, - fa_pause_circle_o = 0xf28c, - fa_paw = 0xf1b0, - fa_paypal = 0xf1ed, - fa_pencil = 0xf040, - fa_pencil_square = 0xf14b, - fa_pencil_square_o = 0xf044, - fa_percent = 0xf295, - fa_phone = 0xf095, - fa_phone_square = 0xf098, - fa_photo = 0xf03e, - fa_picture_o = 0xf03e, - fa_pie_chart = 0xf200, - fa_pied_piper = 0xf2ae, - fa_pied_piper_alt = 0xf1a8, - fa_pied_piper_pp = 0xf1a7, - fa_pinterest = 0xf0d2, - fa_pinterest_p = 0xf231, - fa_pinterest_square = 0xf0d3, - fa_plane = 0xf072, - fa_play = 0xf04b, - fa_play_circle = 0xf144, - fa_play_circle_o = 0xf01d, - fa_plug = 0xf1e6, - fa_plus = 0xf067, - fa_plus_circle = 0xf055, - fa_plus_square = 0xf0fe, - fa_plus_square_o = 0xf196, - fa_podcast = 0xf2ce, - fa_power_off = 0xf011, - fa_print = 0xf02f, - fa_product_hunt = 0xf288, - fa_puzzle_piece = 0xf12e, - fa_qq = 0xf1d6, - fa_qrcode = 0xf029, - fa_question = 0xf128, - fa_question_circle = 0xf059, - fa_question_circle_o = 0xf29c, - fa_quora = 0xf2c4, - fa_quote_left = 0xf10d, - fa_quote_right = 0xf10e, - fa_ra = 0xf1d0, - fa_random = 0xf074, - fa_ravelry = 0xf2d9, - fa_rebel = 0xf1d0, - fa_recycle = 0xf1b8, - fa_reddit = 0xf1a1, - fa_reddit_alien = 0xf281, - fa_reddit_square = 0xf1a2, - fa_refresh = 0xf021, - fa_registered = 0xf25d, - fa_remove = 0xf00d, - fa_renren = 0xf18b, - fa_reorder = 0xf0c9, - fa_repeat = 0xf01e, - fa_reply = 0xf112, - fa_reply_all = 0xf122, - fa_resistance = 0xf1d0, - fa_retweet = 0xf079, - fa_rmb = 0xf157, - fa_road = 0xf018, - fa_rocket = 0xf135, - fa_rotate_left = 0xf0e2, - fa_rotate_right = 0xf01e, - fa_rouble = 0xf158, - fa_rss = 0xf09e, - fa_rss_square = 0xf143, - fa_rub = 0xf158, - fa_ruble = 0xf158, - fa_rupee = 0xf156, - fa_s15 = 0xf2cd, - fa_safari = 0xf267, - fa_save = 0xf0c7, - fa_scissors = 0xf0c4, - fa_scribd = 0xf28a, - fa_search = 0xf002, - fa_search_minus = 0xf010, - fa_search_plus = 0xf00e, - fa_sellsy = 0xf213, - fa_send = 0xf1d8, - fa_send_o = 0xf1d9, - fa_server = 0xf233, - fa_share = 0xf064, - fa_share_alt = 0xf1e0, - fa_share_alt_square = 0xf1e1, - fa_share_square = 0xf14d, - fa_share_square_o = 0xf045, - fa_shekel = 0xf20b, - fa_sheqel = 0xf20b, - fa_shield = 0xf132, - fa_ship = 0xf21a, - fa_shirtsinbulk = 0xf214, - fa_shopping_bag = 0xf290, - fa_shopping_basket = 0xf291, - fa_shopping_cart = 0xf07a, - fa_shower = 0xf2cc, - fa_sign_in = 0xf090, - fa_sign_language = 0xf2a7, - fa_sign_out = 0xf08b, - fa_signal = 0xf012, - fa_signing = 0xf2a7, - fa_simplybuilt = 0xf215, - fa_sitemap = 0xf0e8, - fa_skyatlas = 0xf216, - fa_skype = 0xf17e, - fa_slack = 0xf198, - fa_sliders = 0xf1de, - fa_slideshare = 0xf1e7, - fa_smile_o = 0xf118, - fa_snapchat = 0xf2ab, - fa_snapchat_ghost = 0xf2ac, - fa_snapchat_square = 0xf2ad, - fa_snowflake_o = 0xf2dc, - fa_soccer_ball_o = 0xf1e3, - fa_sort = 0xf0dc, - fa_sort_alpha_asc = 0xf15d, - fa_sort_alpha_desc = 0xf15e, - fa_sort_amount_asc = 0xf160, - fa_sort_amount_desc = 0xf161, - fa_sort_asc = 0xf0de, - fa_sort_desc = 0xf0dd, - fa_sort_down = 0xf0dd, - fa_sort_numeric_asc = 0xf162, - fa_sort_numeric_desc = 0xf163, - fa_sort_up = 0xf0de, - fa_soundcloud = 0xf1be, - fa_space_shuttle = 0xf197, - fa_spinner = 0xf110, - fa_spoon = 0xf1b1, - fa_spotify = 0xf1bc, - fa_square = 0xf0c8, - fa_square_o = 0xf096, - fa_stack_exchange = 0xf18d, - fa_stack_overflow = 0xf16c, - fa_star = 0xf005, - fa_star_half = 0xf089, - fa_star_half_empty = 0xf123, - fa_star_half_full = 0xf123, - fa_star_half_o = 0xf123, - fa_star_o = 0xf006, - fa_steam = 0xf1b6, - fa_steam_square = 0xf1b7, - fa_step_backward = 0xf048, - fa_step_forward = 0xf051, - fa_stethoscope = 0xf0f1, - fa_sticky_note = 0xf249, - fa_sticky_note_o = 0xf24a, - fa_stop = 0xf04d, - fa_stop_circle = 0xf28d, - fa_stop_circle_o = 0xf28e, - fa_street_view = 0xf21d, - fa_strikethrough = 0xf0cc, - fa_stumbleupon = 0xf1a4, - fa_stumbleupon_circle = 0xf1a3, - fa_subscript = 0xf12c, - fa_subway = 0xf239, - fa_suitcase = 0xf0f2, - fa_sun_o = 0xf185, - fa_superpowers = 0xf2dd, - fa_superscript = 0xf12b, - fa_support = 0xf1cd, - fa_table = 0xf0ce, - fa_tablet = 0xf10a, - fa_tachometer = 0xf0e4, - fa_tag = 0xf02b, - fa_tags = 0xf02c, - fa_tasks = 0xf0ae, - fa_taxi = 0xf1ba, - fa_telegram = 0xf2c6, - fa_television = 0xf26c, - fa_tencent_weibo = 0xf1d5, - fa_terminal = 0xf120, - fa_text_height = 0xf034, - fa_text_width = 0xf035, - fa_th = 0xf00a, - fa_th_large = 0xf009, - fa_th_list = 0xf00b, - fa_themeisle = 0xf2b2, - fa_thermometer = 0xf2c7, - fa_thermometer_0 = 0xf2cb, - fa_thermometer_1 = 0xf2ca, - fa_thermometer_2 = 0xf2c9, - fa_thermometer_3 = 0xf2c8, - fa_thermometer_4 = 0xf2c7, - fa_thermometer_empty = 0xf2cb, - fa_thermometer_full = 0xf2c7, - fa_thermometer_half = 0xf2c9, - fa_thermometer_quarter = 0xf2ca, - fa_thermometer_three_quarters = 0xf2c8, - fa_thumb_tack = 0xf08d, - fa_thumbs_down = 0xf165, - fa_thumbs_o_down = 0xf088, - fa_thumbs_o_up = 0xf087, - fa_thumbs_up = 0xf164, - fa_ticket = 0xf145, - fa_times = 0xf00d, - fa_times_circle = 0xf057, - fa_times_circle_o = 0xf05c, - fa_times_rectangle = 0xf2d3, - fa_times_rectangle_o = 0xf2d4, - fa_tint = 0xf043, - fa_toggle_down = 0xf150, - fa_toggle_left = 0xf191, - fa_toggle_off = 0xf204, - fa_toggle_on = 0xf205, - fa_toggle_right = 0xf152, - fa_toggle_up = 0xf151, - fa_trademark = 0xf25c, - fa_train = 0xf238, - fa_transgender = 0xf224, - fa_transgender_alt = 0xf225, - fa_trash = 0xf1f8, - fa_trash_o = 0xf014, - fa_tree = 0xf1bb, - fa_trello = 0xf181, - fa_tripadvisor = 0xf262, - fa_trophy = 0xf091, - fa_truck = 0xf0d1, - fa_try = 0xf195, - fa_tty = 0xf1e4, - fa_tumblr = 0xf173, - fa_tumblr_square = 0xf174, - fa_turkish_lira = 0xf195, - fa_tv = 0xf26c, - fa_twitch = 0xf1e8, - fa_twitter = 0xf099, - fa_twitter_square = 0xf081, - fa_umbrella = 0xf0e9, - fa_underline = 0xf0cd, - fa_undo = 0xf0e2, - fa_universal_access = 0xf29a, - fa_university = 0xf19c, - fa_unlink = 0xf127, - fa_unlock = 0xf09c, - fa_unlock_alt = 0xf13e, - fa_unsorted = 0xf0dc, - fa_upload = 0xf093, - fa_usb = 0xf287, - fa_usd = 0xf155, - fa_user = 0xf007, - fa_user_circle = 0xf2bd, - fa_user_circle_o = 0xf2be, - fa_user_md = 0xf0f0, - fa_user_o = 0xf2c0, - fa_user_plus = 0xf234, - fa_user_secret = 0xf21b, - fa_user_times = 0xf235, - fa_users = 0xf0c0, - fa_vcard = 0xf2bb, - fa_vcard_o = 0xf2bc, - fa_venus = 0xf221, - fa_venus_double = 0xf226, - fa_venus_mars = 0xf228, - fa_viacoin = 0xf237, - fa_viadeo = 0xf2a9, - fa_viadeo_square = 0xf2aa, - fa_video_camera = 0xf03d, - fa_vimeo = 0xf27d, - fa_vimeo_square = 0xf194, - fa_vine = 0xf1ca, - fa_vk = 0xf189, - fa_volume_control_phone = 0xf2a0, - fa_volume_down = 0xf027, - fa_volume_off = 0xf026, - fa_volume_up = 0xf028, - fa_warning = 0xf071, - fa_wechat = 0xf1d7, - fa_weibo = 0xf18a, - fa_weixin = 0xf1d7, - fa_whatsapp = 0xf232, - fa_wheelchair = 0xf193, - fa_wheelchair_alt = 0xf29b, - fa_wifi = 0xf1eb, - fa_wikipedia_w = 0xf266, - fa_window_close = 0xf2d3, - fa_window_close_o = 0xf2d4, - fa_window_maximize = 0xf2d0, - fa_window_minimize = 0xf2d1, - fa_window_restore = 0xf2d2, - fa_windows = 0xf17a, - fa_won = 0xf159, - fa_wordpress = 0xf19a, - fa_wpbeginner = 0xf297, - fa_wpexplorer = 0xf2de, - fa_wpforms = 0xf298, - fa_wrench = 0xf0ad, - fa_xing = 0xf168, - fa_xing_square = 0xf169, - fa_y_combinator = 0xf23b, - fa_y_combinator_square = 0xf1d4, - fa_yahoo = 0xf19e, - fa_yc = 0xf23b, - fa_yc_square = 0xf1d4, - fa_yelp = 0xf1e9, - fa_yen = 0xf157, - fa_yoast = 0xf2b1, - fa_youtube = 0xf167, - fa_youtube_play = 0xf16a, - fa_youtube_square = 0xf166, - - // ruleset icons in circles - fa_osu_osu_o = 0xe000, - fa_osu_mania_o = 0xe001, - fa_osu_fruits_o = 0xe002, - fa_osu_taiko_o = 0xe003, - - // ruleset icons without circles - fa_osu_filled_circle = 0xe004, - fa_osu_cross_o = 0xe005, - fa_osu_logo = 0xe006, - fa_osu_chevron_down_o = 0xe007, - fa_osu_edit_o = 0xe033, - fa_osu_left_o = 0xe034, - fa_osu_right_o = 0xe035, - fa_osu_charts = 0xe036, - fa_osu_solo = 0xe037, - fa_osu_multi = 0xe038, - fa_osu_gear = 0xe039, - - // misc icons - fa_osu_bat = 0xe008, - fa_osu_bubble = 0xe009, - fa_osu_bubble_pop = 0xe02e, - fa_osu_dice = 0xe011, - fa_osu_heart1 = 0xe02f, - fa_osu_heart1_break = 0xe030, - fa_osu_hot = 0xe031, - fa_osu_list_search = 0xe032, - - //osu! playstyles - fa_osu_playstyle_tablet = 0xe02a, - fa_osu_playstyle_mouse = 0xe029, - fa_osu_playstyle_keyboard = 0xe02b, - fa_osu_playstyle_touch = 0xe02c, - - // osu! difficulties - fa_osu_easy_osu = 0xe015, - fa_osu_normal_osu = 0xe016, - fa_osu_hard_osu = 0xe017, - fa_osu_insane_osu = 0xe018, - fa_osu_expert_osu = 0xe019, - - // taiko difficulties - fa_osu_easy_taiko = 0xe01a, - fa_osu_normal_taiko = 0xe01b, - fa_osu_hard_taiko = 0xe01c, - fa_osu_insane_taiko = 0xe01d, - fa_osu_expert_taiko = 0xe01e, - - // fruits difficulties - fa_osu_easy_fruits = 0xe01f, - fa_osu_normal_fruits = 0xe020, - fa_osu_hard_fruits = 0xe021, - fa_osu_insane_fruits = 0xe022, - fa_osu_expert_fruits = 0xe023, - - // mania difficulties - fa_osu_easy_mania = 0xe024, - fa_osu_normal_mania = 0xe025, - fa_osu_hard_mania = 0xe026, - fa_osu_insane_mania = 0xe027, - fa_osu_expert_mania = 0xe028, - - // mod icons - fa_osu_mod_perfect = 0xe049, - fa_osu_mod_autopilot = 0xe03a, - fa_osu_mod_auto = 0xe03b, - fa_osu_mod_cinema = 0xe03c, - fa_osu_mod_doubletime = 0xe03d, - fa_osu_mod_easy = 0xe03e, - fa_osu_mod_flashlight = 0xe03f, - fa_osu_mod_halftime = 0xe040, - fa_osu_mod_hardrock = 0xe041, - fa_osu_mod_hidden = 0xe042, - fa_osu_mod_nightcore = 0xe043, - fa_osu_mod_nofail = 0xe044, - fa_osu_mod_relax = 0xe045, - fa_osu_mod_spunout = 0xe046, - fa_osu_mod_suddendeath = 0xe047, - fa_osu_mod_target = 0xe048, - fa_osu_mod_bg = 0xe04a, - } -} diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 16a9f367e6..10e8227f16 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -11,7 +11,7 @@ namespace osu.Game.Graphics.UserInterface public BackButton() { Text = @"back"; - Icon = FontAwesome.fa_osu_left_o; + Icon = OsuIcon.LeftCircle; Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 40bc98a654..f5e57e5f27 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -92,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreRight, Origin = Anchor.CentreLeft, Size = new Vector2(item_chevron_size), - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, Margin = new MarginPadding { Left = padding }, Alpha = 0f, }); diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 2ed37799f6..8c00cae08a 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -5,6 +5,7 @@ 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.Input.Events; using osu.Framework.Platform; using osuTK; @@ -25,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface Size = new Vector2(12); InternalChild = new SpriteIcon { - Icon = FontAwesome.fa_external_link, + Icon = FontAwesome.Solid.ExternalLinkAlt, RelativeSizeAxes = Axes.Both }; } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 73c9c0dd0e..f873db0dcb 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -16,9 +16,6 @@ namespace osu.Game.Graphics.UserInterface /// public class FocusedTextBox : OsuTextBox { - protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255); - protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255); - public Action Exit; private bool focus; @@ -47,6 +44,9 @@ namespace osu.Game.Graphics.UserInterface private void load(GameHost host) { this.host = host; + + BackgroundUnfocused = new Color4(10, 10, 10, 255); + BackgroundFocused = new Color4(10, 10, 10, 255); } // We may not be focused yet, but we need to handle keyboard input to be able to request focus diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index 025aa30986..6414e488e8 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -4,6 +4,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface @@ -41,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface /// /// The icon. /// - public FontAwesome Icon + public IconUsage Icon { get => icon.Icon; set => icon.Icon = value; diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 7a20dd3d16..74025b71ff 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface { Masking = true, RelativeSizeAxes = Axes.Both, - Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1 } + Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathRadius = 1 } }); } @@ -102,9 +102,10 @@ namespace osu.Game.Graphics.UserInterface for (int i = 0; i < values.Length; i++) { - float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; - float y = GetYPosition(values[i]) * DrawHeight - 1; - // the -1 is for inner offset in path (actually -PathWidth) + // Make sure that we are accounting for path width when calculating vertex positions + // We need to apply 2x the path radius to account for it because the full diameter of the line accounts into height + float x = (i + count - values.Length) / (float)(count - 1) * (DrawWidth - 2 * path.PathRadius); + float y = GetYPosition(values[i]) * (DrawHeight - 2 * path.PathRadius); path.AddVertex(new Vector2(x, y)); } } diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index c7c6d0462c..5a8a0da135 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -36,14 +37,14 @@ namespace osu.Game.Graphics.UserInterface Position = new Vector2(1, 1), Colour = Color4.Black, Alpha = 0.4f, - Icon = FontAwesome.fa_circle_o_notch + Icon = FontAwesome.Solid.CircleNotch }, spinner = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_circle_o_notch + Icon = FontAwesome.Solid.CircleNotch } }; } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index db38067a50..8245de9f70 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -179,7 +179,7 @@ namespace osu.Game.Graphics.UserInterface Chevron = new SpriteIcon { AlwaysPresent = true, - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, Colour = Color4.Black, Alpha = 0.5f, Size = new Vector2(8), @@ -244,7 +244,7 @@ namespace osu.Game.Graphics.UserInterface }, Icon = new SpriteIcon { - Icon = FontAwesome.fa_chevron_down, + Icon = FontAwesome.Solid.ChevronDown, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding { Right = 4 }, diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index aeb974681d..418ad038f7 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -9,6 +9,7 @@ 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.Input; using osu.Framework.Input.Events; using osu.Framework.Platform; @@ -107,7 +108,7 @@ namespace osu.Game.Graphics.UserInterface public CapsWarning() { - Icon = FontAwesome.fa_warning; + Icon = FontAwesome.Solid.ExclamationTriangle; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index e2a4955011..fadc905541 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -254,7 +254,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.fa_ellipsis_h, + Icon = FontAwesome.Solid.EllipsisH, Size = new Vector2(14), Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 918473ac53..869005d05c 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.UserInterface icon = new SpriteIcon { Size = new Vector2(14), - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.Regular.Circle, Shadow = true, }, }, @@ -120,12 +120,12 @@ namespace osu.Game.Graphics.UserInterface if (selected.NewValue) { fadeIn(); - icon.Icon = FontAwesome.fa_check_circle_o; + icon.Icon = FontAwesome.Regular.CheckCircle; } else { fadeOut(); - icon.Icon = FontAwesome.fa_circle_o; + icon.Icon = FontAwesome.Regular.Circle; } }; } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 21cdfbf5af..ebe38db60a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -16,10 +16,6 @@ namespace osu.Game.Graphics.UserInterface { public class OsuTextBox : TextBox, IKeyBindingHandler { - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f); - protected override Color4 BackgroundCommit => BorderColour; - protected override float LeftRightPadding => 10; protected override SpriteText CreatePlaceholder() => new OsuSpriteText @@ -41,7 +37,9 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colour) { - BorderColour = colour.Yellow; + BackgroundUnfocused = Color4.Black.Opacity(0.5f); + BackgroundFocused = OsuColour.Gray(0.3f).Opacity(0.8f); + BackgroundCommit = BorderColour = colour.Yellow; } protected override void OnFocus(FocusEvent e) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs new file mode 100644 index 0000000000..1574023068 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour + { + private readonly SpriteIcon iconSprite; + private readonly OsuSpriteText titleText, pageText; + + protected IconUsage Icon + { + get => iconSprite.Icon; + set => iconSprite.Icon = value; + } + + protected string Title + { + get => titleText.Text; + set => titleText.Text = value; + } + + protected string Section + { + get => pageText.Text; + set => pageText.Text = value; + } + + public Color4 AccentColour + { + get => pageText.Colour; + set => pageText.Colour = value; + } + + protected ScreenTitle() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + iconSprite = new SpriteIcon + { + Size = new Vector2(25), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6, 0), + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25), + }, + pageText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25), + } + } + } + } + }, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 54bb968d21..7023711aaa 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osuTK; using osuTK.Input; @@ -21,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.fa_search, + Icon = FontAwesome.Solid.Search, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, Margin = new MarginPadding { Right = 10 }, diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 7dc05d174f..8ccf3001e3 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using System; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -142,7 +143,7 @@ namespace osu.Game.Graphics.UserInterface Child = Icon = new SpriteIcon { Size = new Vector2(star_size), - Icon = FontAwesome.fa_star, + Icon = FontAwesome.Solid.Star, Anchor = Anchor.Centre, Origin = Anchor.Centre, }; diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs index 685d230a4b..5baf794227 100644 --- a/osu.Game/Graphics/UserInterface/TriangleButton.cs +++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs @@ -33,5 +33,7 @@ namespace osu.Game.Graphics.UserInterface { set => this.FadeTo(value ? 1 : 0); } + + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 1d8298904b..9911a7c368 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -149,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface }; } - public FontAwesome Icon + public IconUsage Icon { set => bouncingIcon.Icon = value; } @@ -207,7 +207,7 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteIcon icon; - public FontAwesome Icon + public IconUsage Icon { set => icon.Icon = value; } diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index c38cd19b42..026442f328 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -13,7 +13,6 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { - } } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 4d039e0b8a..c5f6ef41c2 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.API private readonly OsuConfigManager config; private readonly OAuth authentication; - public string Endpoint = @"https://osu.ppy.sh"; + public string Endpoint => @"https://osu.ppy.sh"; private const string client_id = @"5"; private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; @@ -70,13 +70,15 @@ namespace osu.Game.Online.API internal new void Schedule(Action action) => base.Schedule(action); + /// + /// Register a component to receive API events. + /// Fires once immediately to ensure a correct state. + /// + /// public void Register(IOnlineComponent component) { - Scheduler.Add(delegate - { - components.Add(component); - component.APIStateChanged(this, state); - }); + Scheduler.Add(delegate { components.Add(component); }); + component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) @@ -264,20 +266,18 @@ namespace osu.Game.Online.API get => state; private set { - APIState oldState = state; - APIState newState = value; + if (state == value) + return; + APIState oldState = state; state = value; - if (oldState != newState) + log.Add($@"We just went {state}!"); + Scheduler.Add(delegate { - log.Add($@"We just went {newState}!"); - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - OnStateChange?.Invoke(oldState, newState); - }); - } + components.ForEach(c => c.APIStateChanged(this, state)); + OnStateChange?.Invoke(oldState, state); + }); } } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 2781a5709b..96f3b85272 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -61,9 +61,12 @@ namespace osu.Game.Online.API private Action pendingFailure; - public void Perform(APIAccess api) + public void Perform(IAPIProvider api) { - API = api; + if (!(api is APIAccess apiAccess)) + throw new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."); + + API = apiAccess; if (checkAndScheduleFailure()) return; @@ -71,7 +74,7 @@ namespace osu.Game.Online.API WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; - WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); + WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); if (checkAndScheduleFailure()) return; @@ -85,7 +88,7 @@ namespace osu.Game.Online.API if (checkAndScheduleFailure()) return; - api.Schedule(delegate { Success?.Invoke(); }); + API.Schedule(delegate { Success?.Invoke(); }); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 096ab5d8c8..99fde10309 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -1,23 +1,44 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Threading; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Users; namespace osu.Game.Online.API { - public class DummyAPIAccess : IAPIProvider + public class DummyAPIAccess : Component, IAPIProvider { public Bindable LocalUser { get; } = new Bindable(new User { Username = @"Dummy", - Id = 1, + Id = 1001, }); public bool IsLoggedIn => true; - public void Update() + public string ProvidedUsername => LocalUser.Value.Username; + + public string Endpoint => "http://localhost"; + + private APIState state = APIState.Online; + + private readonly List components = new List(); + + public APIState State { + get => state; + private set + { + if (state == value) + return; + + state = value; + + Scheduler.Add(() => components.ForEach(c => c.APIStateChanged(this, value))); + } } public virtual void Queue(APIRequest request) @@ -26,6 +47,36 @@ namespace osu.Game.Online.API public void Register(IOnlineComponent component) { + Scheduler.Add(delegate { components.Add(component); }); + component.APIStateChanged(this, state); + } + + public void Unregister(IOnlineComponent component) + { + Scheduler.Add(delegate { components.Remove(component); }); + } + + public void Login(string username, string password) + { + LocalUser.Value = new User + { + Username = username, + Id = 1001, + }; + + State = APIState.Online; + } + + public void Logout() + { + LocalUser.Value = new GuestUser(); + State = APIState.Offline; + } + + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) + { + Thread.Sleep(200); + return null; } } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index e4533ecb3d..7c1f850943 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -18,6 +18,19 @@ namespace osu.Game.Online.API /// bool IsLoggedIn { get; } + /// + /// The last username provided by the end-user. + /// May not be authenticated. + /// + string ProvidedUsername { get; } + + /// + /// The URL endpoint for this API. Does not include a trailing slash. + /// + string Endpoint { get; } + + APIState State { get; } + /// /// Queue a new request. /// @@ -29,5 +42,32 @@ namespace osu.Game.Online.API /// /// The component to register. void Register(IOnlineComponent component); + + /// + /// Unregisters a component to receive state changes. + /// + /// The component to unregister. + void Unregister(IOnlineComponent component); + + /// + /// Attempt to login using the provided credentials. This is a non-blocking operation. + /// + /// The user's username. + /// The user's password. + void Login(string username, string password); + + /// + /// Log out the current user. + /// + void Logout(); + + /// + /// Create a new user account. This is a blocking operation. + /// + /// The email to create the account with. + /// The username to create the account with. + /// The password to create the account with. + /// Any errors encoutnered during account creation. + RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password); } } diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs index fb40bfd1e6..da6b784759 100644 --- a/osu.Game/Online/API/IOnlineComponent.cs +++ b/osu.Game/Online/API/IOnlineComponent.cs @@ -5,6 +5,6 @@ namespace osu.Game.Online.API { public interface IOnlineComponent { - void APIStateChanged(APIAccess api, APIState state); + void APIStateChanged(IAPIProvider api, APIState state); } } diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index f6be849b56..0b6f65a0e0 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -10,7 +10,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; private readonly BeatmapLeaderboardScope scope; @@ -31,9 +31,9 @@ namespace osu.Game.Online.API.Requests Success += onSuccess; } - private void onSuccess(APIScores r) + private void onSuccess(APILegacyScores r) { - foreach (APIScoreInfo score in r.Scores) + foreach (APILegacyScoreInfo score in r.Scores) score.Beatmap = beatmap; } diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index c5b436f99c..48a43bbbad 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : APIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs similarity index 82% rename from osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index ded4ca71ee..8ee71ce9ac 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -8,12 +8,12 @@ using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScoreInfo : ScoreInfo + public class APILegacyScoreInfo : LegacyScoreInfo { [JsonProperty(@"score")] private int totalScore @@ -74,29 +74,37 @@ namespace osu.Game.Online.API.Requests.Responses HitResult newKey; switch (kvp.Key) { + case @"count_geki": + CountGeki = kvp.Value; + break; case @"count_300": - newKey = HitResult.Great; + Count300 = kvp.Value; + break; + case @"count_katu": + CountKatu = kvp.Value; break; case @"count_100": - newKey = HitResult.Good; + Count100 = kvp.Value; break; case @"count_50": - newKey = HitResult.Meh; + Count50 = kvp.Value; break; case @"count_miss": - newKey = HitResult.Miss; + CountMiss = kvp.Value; break; default: continue; } - - Statistics.Add(newKey, kvp.Value); } } } [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } + public int OnlineRulesetID + { + get => RulesetID; + set => RulesetID = value; + } [JsonProperty(@"mods")] private string[] modStrings { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs similarity index 77% rename from osu.Game/Online/API/Requests/Responses/APIScores.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index a867d86d9b..15ec5d3b13 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -6,9 +6,9 @@ using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { - public class APIScores + public class APILegacyScores { [JsonProperty(@"scores")] - public IEnumerable Scores; + public IEnumerable Scores; } } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 01c91ca780..d9d05ff285 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -36,11 +36,12 @@ namespace osu.Game.Online.API.Requests [Description("Ranked & Approved")] RankedApproved = 0, - Approved = 1, + Qualified = 3, Loved = 8, Favourites = 2, - Qualified = 3, - Pending = 4, + + [Description("Pending & WIP")] + PendingWIP = 4, Graveyard = 5, [Description("My Maps")] diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 38df0efd6f..ac1666f8ed 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -174,12 +174,12 @@ namespace osu.Game.Online.Leaderboards }; } - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingUpdateScores; [BackgroundDependencyLoader(true)] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; api?.Register(this); @@ -195,7 +195,7 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { if (state == APIState.Online) UpdateScores(); @@ -213,12 +213,6 @@ namespace osu.Game.Online.Leaderboards pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { - if (api?.IsLoggedIn != true) - { - PlaceholderState = PlaceholderState.NotLoggedIn; - return; - } - PlaceholderState = PlaceholderState.Retrieving; loading.Show(); @@ -231,6 +225,12 @@ namespace osu.Game.Online.Leaderboards if (getScoresRequest == null) return; + if (api?.IsLoggedIn != true) + { + PlaceholderState = PlaceholderState.NotLoggedIn; + return; + } + getScoresRequest.Failure += e => Schedule(() => { if (e is OperationCanceledException) @@ -243,6 +243,11 @@ namespace osu.Game.Online.Leaderboards }); } + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. protected abstract APIRequest FetchScores(Action> scoresCallback); private Placeholder currentPlaceholder; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 34981bf849..70edcc3fc8 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -64,6 +64,8 @@ namespace osu.Game.Online.Leaderboards statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + Avatar innerAvatar; + Children = new Drawable[] { new Container @@ -109,12 +111,11 @@ namespace osu.Game.Online.Leaderboards Children = new[] { avatar = new DelayedLoadWrapper( - new Avatar(user) + innerAvatar = new Avatar(user) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, Masking = true, - OnLoadComplete = d => d.FadeInFromZero(200), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -214,6 +215,8 @@ namespace osu.Game.Online.Leaderboards }, }, }; + + innerAvatar.OnLoadComplete += d => d.FadeInFromZero(200); } public override void Show() @@ -255,8 +258,8 @@ namespace osu.Game.Online.Leaderboards protected virtual IEnumerable GetStatistics(ScoreInfo model) => new[] { - new LeaderboardScoreStatistic(FontAwesome.fa_link, "Max Combo", model.MaxCombo.ToString()), - new LeaderboardScoreStatistic(FontAwesome.fa_crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) + new LeaderboardScoreStatistic(FontAwesome.Solid.Link, "Max Combo", model.MaxCombo.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) }; protected override bool OnHover(HoverEvent e) @@ -350,7 +353,7 @@ namespace osu.Game.Online.Leaderboards Size = new Vector2(icon_size), Rotation = 45, Colour = OsuColour.FromHex(@"3087ac"), - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Solid.Square, Shadow = true, }, new SpriteIcon @@ -375,11 +378,11 @@ namespace osu.Game.Online.Leaderboards public class LeaderboardScoreStatistic { - public FontAwesome Icon; + public IconUsage Icon; public string Value; public string Name; - public LeaderboardScoreStatistic(FontAwesome icon, string name, string value) + public LeaderboardScoreStatistic(IconUsage icon, string name, string value) { Icon = icon; Name = name; diff --git a/osu.Game/Online/Leaderboards/MessagePlaceholder.cs b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs index d4256e4a9d..ef425dacd8 100644 --- a/osu.Game/Online/Leaderboards/MessagePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Online.Leaderboards { @@ -12,7 +12,7 @@ namespace osu.Game.Online.Leaderboards public MessagePlaceholder(string message) { - AddIcon(FontAwesome.fa_exclamation_circle, cp => + AddIcon(FontAwesome.Solid.ExclamationCircle, cp => { cp.Font = cp.Font.With(size: TEXT_SIZE); cp.Padding = new MarginPadding { Right = 10 }; diff --git a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs index 9edd578967..801f3f8ff0 100644 --- a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs @@ -3,8 +3,8 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -41,7 +41,7 @@ namespace osu.Game.Online.Leaderboards Action = () => Action?.Invoke(), Child = icon = new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(TEXT_SIZE), Shadow = true, }, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs index 4d6a792377..5ba5f1a415 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -17,7 +18,7 @@ namespace osu.Game.Online.Multiplayer.GameTypes { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(size), Colour = colours.Blue, Shadow = false, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs index 350e609b83..ef0a00a9f0 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -25,14 +26,14 @@ namespace osu.Game.Online.Multiplayer.GameTypes { new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(size * 0.75f), Colour = colours.Blue, Shadow = false, }, new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(size * 0.75f), Colour = colours.Pink, Shadow = false, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs index 8971368db1..1a3d2837ce 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -15,7 +16,7 @@ namespace osu.Game.Online.Multiplayer.GameTypes { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_clock_o, + Icon = FontAwesome.Regular.Clock, Size = new Vector2(size), Colour = colours.Blue, Shadow = false diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 77236ce3c8..53089897f7 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -124,6 +124,7 @@ namespace osu.Game.Online.Multiplayer if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) Host.Value = other.Host.Value; + ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bf9b4ced3b..f8ca1bc65f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -15,13 +15,13 @@ using osu.Framework.Allocation; using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; -using osuTK; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; @@ -62,6 +62,8 @@ namespace osu.Game private NotificationOverlay notifications; + private LoginOverlay loginOverlay; + private DialogOverlay dialogOverlay; private AccountCreationOverlay accountCreation; @@ -87,11 +89,7 @@ namespace osu.Game public readonly Bindable OverlayActivationMode = new Bindable(); - private BackgroundScreenStack backgroundStack; - - private ParallaxContainer backgroundParallax; - - private ScreenStack screenStack; + private OsuScreenStack screenStack; private VolumeOverlay volume; private OnScreenDisplay onscreenDisplay; private OsuLogo osuLogo; @@ -167,8 +165,6 @@ namespace osu.Game { this.frameworkConfig = frameworkConfig; - ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent)); - if (!Host.IsPrimaryInstance) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); @@ -217,63 +213,12 @@ namespace osu.Game externalLinkOpener.OpenUrlExternally(url); } - private ScheduledDelegate scoreLoad; - /// /// Show a beatmap set as an overlay. /// /// The set to display. public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); - /// - /// Present a beatmap at song select. - /// - /// The beatmap to select. - public void PresentBeatmap(BeatmapSetInfo beatmap) - { - if (menuScreen == null) - { - Schedule(() => PresentBeatmap(beatmap)); - return; - } - - CloseAllOverlays(false); - - void setBeatmap() - { - if (Beatmap.Disabled) - { - Schedule(setBeatmap); - return; - } - - var databasedSet = beatmap.OnlineBeatmapSetID != null ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); - - if (databasedSet != null) - { - // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); - - ruleset.Value = first.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); - } - } - - switch (screenStack.CurrentScreen) - { - case SongSelect _: - break; - default: - // navigate to song select if we are not already there. - - menuScreen.MakeCurrent(); - menuScreen.LoadToSolo(); - break; - } - - setBeatmap(); - } - /// /// Show a user's profile as an overlay. /// @@ -286,19 +231,44 @@ namespace osu.Game /// The beatmap to show. public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); - protected void LoadScore(ScoreInfo score, bool silent) + /// + /// Present a beatmap at song select immediately. + /// The user should have already requested this interactively. + /// + /// The beatmap to select. + public void PresentBeatmap(BeatmapSetInfo beatmap) { - if (silent) - return; + var databasedSet = beatmap.OnlineBeatmapSetID != null + ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) + : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); - scoreLoad?.Cancel(); - - if (menuScreen == null) + if (databasedSet == null) { - scoreLoad = Schedule(() => LoadScore(score, false)); + Logger.Log("The requested beatmap could not be loaded.", LoggingTarget.Information); return; } + performFromMainMenu(() => + { + // we might already be at song select, so a check is required before performing the load to solo. + if (menuScreen.IsCurrentScreen()) + menuScreen.LoadToSolo(); + + // Use first beatmap available for current ruleset, else switch ruleset. + var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); + + ruleset.Value = first.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); + }, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect)); + } + + /// + /// 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) @@ -314,14 +284,40 @@ namespace osu.Game return; } - if ((screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) + performFromMainMenu(() => + { + ruleset.Value = databasedScoreInfo.Ruleset; + + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); + Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; + + menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); + }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); + } + + private ScheduledDelegate performFromMainMenuTask; + + /// + /// Perform an action only after returning to the main menu. + /// Eagerly tries to exit the current screen until it succeeds. + /// + /// The action to perform once we are in the correct state. + /// The task name to display in a notification (if we can't immediately reach the main menu state). + /// An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu. + /// Whether checking should be bypassed. + private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false) + { + performFromMainMenuTask?.Cancel(); + + // if the current screen does not allow screen changing, give the user an option to try again later. + if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) { notifications.Post(new SimpleNotification { - Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}", + Text = $"Click here to {taskName}", Activated = () => { - loadScore(); + performFromMainMenu(action, taskName, targetScreen, true); return true; } }); @@ -329,24 +325,26 @@ namespace osu.Game return; } - loadScore(); + CloseAllOverlays(false); - void loadScore() + // we may already be at the target screen type. + if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen) { - if (!menuScreen.IsCurrentScreen() || Beatmap.Disabled) - { - menuScreen.MakeCurrent(); - this.Delay(500).Schedule(loadScore, out scoreLoad); - return; - } - - ruleset.Value = databasedScoreInfo.Ruleset; - - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; - - menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); + action(); + return; } + + // all conditions have been met to continue with the action. + if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled) + { + action(); + return; + } + + // menuScreen may not be initialised yet (null check required). + menuScreen?.MakeCurrent(); + + performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); } protected override void Dispose(bool isDisposing) @@ -370,8 +368,10 @@ namespace osu.Game BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; + BeatmapManager.PresentImport = items => PresentBeatmap(items.First()); - BeatmapManager.PresentBeatmap = PresentBeatmap; + ScoreManager.PostNotification = n => notifications?.Post(n); + ScoreManager.PresentImport = items => PresentScore(items.First()); Container logoContainer; @@ -388,92 +388,79 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - backgroundParallax = new ParallaxContainer - { - RelativeSizeAxes = Axes.Both, - Child = backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, - }, - screenStack = new ScreenStack { RelativeSizeAxes = Axes.Both }, + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, - overlayContent = new Container - { - RelativeSizeAxes = Axes.Both, - }, - floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + overlayContent = new Container { RelativeSizeAxes = Axes.Both }, + floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker = new GameIdleTracker(6000) }); - dependencies.Cache(backgroundStack); - screenStack.ScreenPushed += screenPushed; screenStack.ScreenExited += screenExited; - loadComponentSingleFile(osuLogo, logoContainer.Add); - - loadComponentSingleFile(new Loader + loadComponentSingleFile(osuLogo, logo => { - RelativeSizeAxes = Axes.Both - }, screenStack.Push); + logoContainer.Add(logo); + + // Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering. + screenStack.Push(new Loader + { + RelativeSizeAxes = Axes.Both + }); + }); loadComponentSingleFile(Toolbar = new Toolbar { - Depth = -5, OnHome = delegate { CloseAllOverlays(false); menuScreen?.MakeCurrent(); }, - }, floatingOverlayContent.Add); + }, topMostOverlayContent.Add); loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); - loadComponentSingleFile(screenshotManager, Add); - - //overlay elements - loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, overlayContent.Add); - loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, overlayContent.Add); - loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); - loadComponentSingleFile(chatOverlay = new ChatOverlay { Depth = -1 }, overlayContent.Add); - loadComponentSingleFile(settings = new MainSettings + loadComponentSingleFile(loginOverlay = new LoginOverlay { GetToolbarHeight = () => ToolbarOffset, - Depth = -1 - }, floatingOverlayContent.Add); - loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, overlayContent.Add); - loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, overlayContent.Add); - loadComponentSingleFile(musicController = new MusicController - { - Depth = -5, - Position = new Vector2(0, Toolbar.HEIGHT), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, floatingOverlayContent.Add); + 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 }, floatingOverlayContent.Add); + loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); + loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, - Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, floatingOverlayContent.Add); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay + loadComponentSingleFile(musicController = new MusicController { - Depth = -6, + GetToolbarHeight = () => ToolbarOffset, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, floatingOverlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay - { - Depth = -7, - }, floatingOverlayContent.Add); + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener - { - Depth = -8, - }, floatingOverlayContent.Add); + loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); + + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); dependencies.CacheAs(idleTracker); dependencies.Cache(settings); @@ -486,6 +473,7 @@ namespace osu.Game dependencies.Cache(musicController); dependencies.Cache(beatmapSetOverlay); dependencies.Cache(notifications); + dependencies.Cache(loginOverlay); dependencies.Cache(dialogOverlay); dependencies.Cache(accountCreation); @@ -592,7 +580,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb, + Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb, Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } @@ -600,7 +588,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = FontAwesome.fa_ellipsis_h, + Icon = FontAwesome.Solid.EllipsisH, Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { @@ -719,6 +707,8 @@ namespace osu.Game private Container floatingOverlayContent; + private Container topMostOverlayContent; + private FrameworkConfigManager frameworkConfig; private ScalingContainer screenContainer; @@ -775,8 +765,6 @@ namespace osu.Game if (newScreen is IOsuScreen newOsuScreen) { - backgroundParallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * newOsuScreen.BackgroundParallaxAmount; - OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; if (newOsuScreen.HideOverlaysOnEnter) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 84025abb8c..41bd0e6e9b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -119,9 +119,6 @@ namespace osu.Game dependencies.CacheAs(this); dependencies.Cache(LocalConfig); - //this completely overrides the framework default. will need to change once we make a proper FontStore. - dependencies.Cache(Fonts = new FontStore(new GlyphStore(Resources, @"Fonts/FontAwesome"))); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); @@ -152,7 +149,6 @@ namespace osu.Game API = new APIAccess(LocalConfig); - dependencies.Cache(API); dependencies.CacheAs(API); var defaultBeatmap = new DummyWorkingBeatmap(this); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 2fc00edbc1..13d8df098f 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.AccountCreation private OsuTextBox emailTextBox; private OsuPasswordTextBox passwordTextBox; - private APIAccess api; + private IAPIProvider api; private ShakeContainer registerShake; private IEnumerable characterCheckText; @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.AccountCreation private GameHost host; [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, GameHost host) + private void load(OsuColour colours, IAPIProvider api, GameHost host) { this.api = api; this.host = host; diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 4e2cc1ea00..be417f4aac 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.AccountCreation { private OsuTextFlowContainer multiAccountExplanationText; private LinkFlowContainer furtherAssistance; - private APIAccess api; + private IAPIProvider api; private const string help_centre_url = "/help/wiki/Help_Centre#login"; @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.AccountCreation } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, APIAccess api, OsuGame game, TextureStore textures) + private void load(OsuColour colours, IAPIProvider api, OsuGame game, TextureStore textures) { this.api = api; diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index bc780538d5..e8e44c206e 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { api.Register(this); @@ -96,7 +96,7 @@ namespace osu.Game.Overlays this.FadeOut(100); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 9ed9875be9..8ed52dade5 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -6,6 +6,7 @@ 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.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -74,10 +75,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Horizontal, Children = new[] { - length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = 0.25f }, - bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = 0.25f }, - circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = 0.25f }, - sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = 0.25f }, + length = new Statistic(FontAwesome.Regular.Clock, "Length") { Width = 0.25f }, + bpm = new Statistic(FontAwesome.Regular.Circle, "BPM") { Width = 0.25f }, + circleCount = new Statistic(FontAwesome.Regular.Circle, "Circle Count") { Width = 0.25f }, + sliderCount = new Statistic(FontAwesome.Regular.Circle, "Slider Count") { Width = 0.25f }, }, }; } @@ -101,7 +102,7 @@ namespace osu.Game.Overlays.BeatmapSet set => this.value.Text = value; } - public Statistic(FontAwesome icon, string name) + public Statistic(IconUsage icon, string name) { this.name = name; RelativeSizeAxes = Axes.X; @@ -120,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Solid.Square, Size = new Vector2(13), Rotation = 45, Colour = OsuColour.FromHex(@"441288"), diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 55dee904b4..baf702eebc 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -130,8 +131,8 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 5 }, Children = new[] { - plays = new Statistic(FontAwesome.fa_play_circle), - favourites = new Statistic(FontAwesome.fa_heart), + plays = new Statistic(FontAwesome.Solid.PlayCircle), + favourites = new Statistic(FontAwesome.Solid.Heart), }, }, }, @@ -292,7 +293,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - public Statistic(FontAwesome icon) + public Statistic(IconUsage icon) { AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index edd886f0f2..4a60b69a5a 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -6,6 +6,7 @@ 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.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -39,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons } [BackgroundDependencyLoader] - private void load(APIAccess api, BeatmapManager beatmaps) + private void load(IAPIProvider api, BeatmapManager beatmaps) { FillFlowContainer textSprites; @@ -77,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Depth = -1, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Icon = FontAwesome.fa_download, + Icon = FontAwesome.Solid.Download, Size = new Vector2(16), Margin = new MarginPadding { Right = 5 }, }, diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7824a78a14..7207739646 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -6,6 +6,7 @@ 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.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osuTK; @@ -47,7 +48,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_heart_o, + Icon = FontAwesome.Regular.Heart, Size = new Vector2(18), Shadow = false, }, @@ -58,12 +59,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons if (favourited.NewValue) { pink.FadeIn(200); - icon.Icon = FontAwesome.fa_heart; + icon.Icon = FontAwesome.Solid.Heart; } else { pink.FadeOut(200); - icon.Icon = FontAwesome.fa_heart_o; + icon.Icon = FontAwesome.Regular.Heart; } }; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index c6c8315aeb..e3fb1bc961 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -9,12 +9,12 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; - public DrawableScore(int index, APIScoreInfo score) + public DrawableScore(int index, ScoreInfo score) { ScoreModsContainer modsContainer; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 78e560cdbe..ac4485a410 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; @@ -43,9 +42,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly InfoColumn statistics; private readonly ScoreModsContainer modsContainer; - private APIScoreInfo score; + private ScoreInfo score; - public APIScoreInfo Score + public ScoreInfo Score { get => score; set diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 6c65d491af..ef3129441b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -11,7 +11,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -29,10 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; + private IEnumerable scores; private BeatmapInfo beatmap; - public IEnumerable Scores + public IEnumerable Scores { get => scores; set @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } private GetScoresRequest getScoresRequest; - private APIAccess api; + private IAPIProvider api; public BeatmapInfo Beatmap { @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; updateDisplay(); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 55c21b7fc9..c49268bc16 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays private readonly Header header; - private APIAccess api; + private IAPIProvider api; private RulesetStore rulesets; private readonly ScrollContainer scroll; @@ -101,7 +101,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { this.api = api; this.rulesets = rulesets; diff --git a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs index 6c3fb4e6f6..dbae091fb0 100644 --- a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs +++ b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Chat @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat HeaderText = "Just checking..."; BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; - Icon = FontAwesome.fa_warning; + Icon = FontAwesome.Solid.ExclamationTriangle; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index a36abc4f99..4d77e5f93d 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -42,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Selection set => this.FadeTo(value ? 1f : 0f, 100); } + public bool FilteringActive { get; set; } + public Action OnRequestJoin; public Action OnRequestLeave; @@ -71,7 +74,7 @@ namespace osu.Game.Overlays.Chat.Selection { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Icon = FontAwesome.fa_check_circle, + Icon = FontAwesome.Solid.CheckCircle, Size = new Vector2(text_size), Shadow = false, Margin = new MarginPadding { Right = 10f }, @@ -118,7 +121,7 @@ namespace osu.Game.Overlays.Chat.Selection { new SpriteIcon { - Icon = FontAwesome.fa_user, + Icon = FontAwesome.Solid.User, Size = new Vector2(text_size - 2), Shadow = false, Margin = new MarginPadding { Top = 1 }, diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index 3f979b6309..eac48ca5cb 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Chat.Selection set => this.FadeTo(value ? 1f : 0f, 100); } + public bool FilteringActive { get; set; } + public string Header { get => header.Text; diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index feb47b9e8e..71e9e4bdf3 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -190,8 +190,12 @@ namespace osu.Game.Overlays.Chat.Selection private class HeaderSearchTextBox : SearchTextBox { - protected override Color4 BackgroundFocused => Color4.Black.Opacity(0.2f); - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.2f); + [BackgroundDependencyLoader] + private void load() + { + BackgroundFocused = Color4.Black.Opacity(0.2f); + BackgroundUnfocused = Color4.Black.Opacity(0.2f); + } } } } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 7a43ca4b8c..2e7f2d5908 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -3,13 +3,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osuTK; using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Chat.Tabs { @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Chat.Tabs AddInternal(new SpriteIcon { - Icon = FontAwesome.fa_comments, + Icon = FontAwesome.Solid.Comments, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(20), diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 95c5fbf8fa..a4aefa4c4f 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Chat.Tabs }; } - protected virtual FontAwesome DisplayIcon => FontAwesome.fa_hashtag; + protected virtual IconUsage DisplayIcon => FontAwesome.Solid.Hashtag; protected virtual bool ShowCloseOnHover => true; diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 8cedde82ff..8aa6d6fecd 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -7,6 +7,7 @@ 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.Sprites; using osu.Game.Online.Chat; @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Chat.Tabs private readonly OsuSpriteText username; private readonly Avatar avatarContainer; - protected override FontAwesome DisplayIcon => FontAwesome.fa_at; + protected override IconUsage DisplayIcon => FontAwesome.Solid.At; public PrivateChannelTabItem(Channel value) : base(value) @@ -28,6 +29,8 @@ namespace osu.Game.Overlays.Chat.Tabs if (value.Type != ChannelType.PM) throw new ArgumentException("Argument value needs to have the targettype user!"); + Avatar avatar; + AddRange(new Drawable[] { new Container @@ -49,11 +52,10 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Masking = true, - Child = new DelayedLoadWrapper(new Avatar(value.Users.First()) + Child = new DelayedLoadWrapper(avatar = new Avatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, OpenOnClick = { Value = false }, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }) { RelativeSizeAxes = Axes.Both, @@ -63,6 +65,8 @@ namespace osu.Game.Overlays.Chat.Tabs }, }); + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; } diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs index e8217fa9f6..bde930d4fb 100644 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(0.75f), - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, RelativeSizeAxes = Axes.Both, }; } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 5428279325..77f88ab4e7 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -320,6 +320,8 @@ namespace osu.Game.Overlays this.MoveToY(Height, transition_length, Easing.InSine); this.FadeOut(transition_length, Easing.InSine); + channelSelectionOverlay.State = Visibility.Hidden; + textbox.HoldFocus = false; base.PopOut(); } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 72e3cc4f6a..91f42a491a 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -38,7 +39,7 @@ namespace osu.Game.Overlays.Dialog private bool actionInvoked; - public FontAwesome Icon + public IconUsage Icon { get => icon.Icon; set => icon.Icon = value; @@ -165,7 +166,7 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(50), }, }, diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 1413f0f885..eb73a50f99 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -185,11 +186,8 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, statusContainer = new FillFlowContainer @@ -208,12 +206,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_film)); + statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_image)); + statusContainer.Add(new IconPill(FontAwesome.Solid.Image)); } statusContainer.Add(new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 7bf372dff7..d645fd3bda 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -11,8 +11,10 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; namespace osu.Game.Overlays.Direct { @@ -23,6 +25,7 @@ namespace osu.Game.Overlays.Direct private const float vertical_padding = 5; private const float height = 70; + private FillFlowContainer statusContainer; private PlayButton playButton; private Box progressBar; @@ -108,10 +111,24 @@ namespace osu.Game.Overlays.Direct }, new FillFlowContainer { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + statusContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 }, + Spacing = new Vector2(5), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = 20, + Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Children = GetDifficultyIcons(), + }, + }, }, }, }, @@ -144,11 +161,8 @@ namespace osu.Game.Overlays.Direct Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new FillFlowContainer { Anchor = Anchor.TopRight, @@ -194,6 +208,23 @@ namespace osu.Game.Overlays.Direct Colour = colours.Yellow, }, }); + + if (SetInfo.OnlineInfo?.HasVideo ?? false) + { + statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + } + + if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + { + statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + } + + statusContainer.Add(new BeatmapSetOnlineStatusPill + { + TextSize = 12, + TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 }, + Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + }); } } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 3867886f6d..2b509f370e 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Direct } } - public Statistic(FontAwesome icon, int value = 0) + public Statistic(IconUsage icon, int value = 0) { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index f15413d522..6107dc3af3 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -48,7 +49,7 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(13), - Icon = FontAwesome.fa_download, + Icon = FontAwesome.Solid.Download, }, checkmark = new SpriteIcon { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, X = 8, Size = Vector2.Zero, - Icon = FontAwesome.fa_check, + Icon = FontAwesome.Solid.Check, } } } diff --git a/osu.Game/Overlays/Direct/DownloadTrackingComposite.cs b/osu.Game/Overlays/Direct/DownloadTrackingComposite.cs index 7be9eadefb..37f13aefc8 100644 --- a/osu.Game/Overlays/Direct/DownloadTrackingComposite.cs +++ b/osu.Game/Overlays/Direct/DownloadTrackingComposite.cs @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Direct private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable); + private void setAdded(BeatmapSetInfo s, bool existing) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable); private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded); diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index d7e0760fc6..268e011350 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -116,5 +116,6 @@ namespace osu.Game.Overlays.Direct Ranked, Rating, Plays, + Favourites, } } diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index e85cb3b4ac..80870dcb68 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.SearchableList; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Direct protected override DirectTab DefaultTab => DirectTab.Search; protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", Font = OsuFont.GetFont(size: 25) }; - protected override FontAwesome Icon => FontAwesome.fa_osu_chevron_down_o; + protected override IconUsage Icon => OsuIcon.ChevronDownCircle; public Header() { diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/Direct/IconPill.cs index 83ee0cb6c5..d63bb2a292 100644 --- a/osu.Game/Overlays/Direct/IconPill.cs +++ b/osu.Game/Overlays/Direct/IconPill.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -12,7 +12,15 @@ namespace osu.Game.Overlays.Direct { public class IconPill : CircularContainer { - public IconPill(FontAwesome icon) + public Vector2 IconSize + { + get => iconContainer.Size; + set => iconContainer.Size = value; + } + + private readonly Container iconContainer; + + public IconPill(IconUsage icon) { AutoSizeAxes = Axes.Both; Masking = true; @@ -25,16 +33,16 @@ namespace osu.Game.Overlays.Direct Colour = Color4.Black, Alpha = 0.5f, }, - new Container + iconContainer = new Container { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(5), + Size = new Vector2(22), + Padding = new MarginPadding(5), Child = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, Icon = icon, - Size = new Vector2(12), }, }, }; diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 3c5508ba00..6daebb3c15 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -73,7 +74,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, FillMode = FillMode.Fit, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_play, + Icon = FontAwesome.Solid.Play, }, loadingAnimation = new LoadingAnimation { @@ -115,7 +116,7 @@ namespace osu.Game.Overlays.Direct private void playingStateChanged(ValueChangedEvent e) { - icon.Icon = e.NewValue ? FontAwesome.fa_stop : FontAwesome.fa_play; + icon.Icon = e.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; icon.FadeColour(e.NewValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); if (e.NewValue) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 0dc74b6a88..34edbbcc8b 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays { private const float panel_padding = 10f; - private APIAccess api; + private IAPIProvider api; private RulesetStore rulesets; private readonly FillFlowContainer resultCountsContainer; @@ -134,9 +134,9 @@ namespace osu.Game.Overlays Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; } }; - ((FilterControl)Filter).Ruleset.ValueChanged += _ => Scheduler.AddOnce(updateSearch); + ((FilterControl)Filter).Ruleset.ValueChanged += _ => queueUpdateSearch(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdateSearch(); Header.Tabs.Current.ValueChanged += tab => { @@ -144,24 +144,11 @@ namespace osu.Game.Overlays { currentQuery.Value = string.Empty; Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value; - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); } }; - currentQuery.ValueChanged += text => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(text.NewValue)) - Scheduler.AddOnce(updateSearch); - else - { - BeatmapSets = null; - ResultAmounts = null; - - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - } - }; + currentQuery.ValueChanged += text => queueUpdateSearch(!string.IsNullOrEmpty(text.NewValue)); currentQuery.BindTo(Filter.Search.Current); @@ -170,14 +157,14 @@ namespace osu.Game.Overlays if (Header.Tabs.Current.Value != DirectTab.Search && tab.NewValue != (DirectSortCriteria)Header.Tabs.Current.Value) Header.Tabs.Current.Value = DirectTab.Search; - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); }; updateResultCounts(); } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, PreviewTrackManager previewTrackManager) + private void load(OsuColour colours, IAPIProvider api, RulesetStore rulesets, PreviewTrackManager previewTrackManager) { this.api = api; this.rulesets = rulesets; @@ -242,37 +229,42 @@ namespace osu.Game.Overlays // Queries are allowed to be run only on the first pop-in if (getSetsRequest == null) - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); } private SearchBeatmapSetsRequest getSetsRequest; - private readonly Bindable currentQuery = new Bindable(); + private readonly Bindable currentQuery = new Bindable(string.Empty); private ScheduledDelegate queryChangedDebounce; private PreviewTrackManager previewTrackManager; + private void queueUpdateSearch(bool queryTextChanged = false) + { + BeatmapSets = null; + ResultAmounts = null; + + getSetsRequest?.Cancel(); + + queryChangedDebounce?.Cancel(); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); + } + private void updateSearch() { - queryChangedDebounce?.Cancel(); - if (!IsLoaded) return; if (State == Visibility.Hidden) return; - BeatmapSets = null; - ResultAmounts = null; - - getSetsRequest?.Cancel(); - if (api == null) return; previewTrackManager.StopAnyPlaying(this); - getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, + getSetsRequest = new SearchBeatmapSetsRequest( + currentQuery.Value, ((FilterControl)Filter).Ruleset.Value, Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.Tabs.Current.Value); //todo: sort direction (?) diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 82e24f550b..7e33d7ba27 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.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.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.KeyBinding { public class GlobalKeyBindingsSection : SettingsSection { - public override FontAwesome Icon => FontAwesome.fa_globe; + public override IconUsage Icon => FontAwesome.Solid.Globe; public override string Header => "Global"; public GlobalKeyBindingsSection(GlobalActionContainer manager) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index ef16c81dfc..8313dac50a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -43,6 +43,8 @@ namespace osu.Game.Overlays.KeyBinding } } + public bool FilteringActive { get; set; } + private OsuSpriteText text; private OsuTextFlowContainer pressAKey; diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs index 7b3bef90c0..1f4042c57c 100644 --- a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.KeyBinding { public class RulesetBindingsSection : SettingsSection { - public override FontAwesome Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? FontAwesome.fa_osu_hot; + public override IconUsage Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? OsuIcon.Hot; public override string Header => ruleset.Name; private readonly RulesetInfo ruleset; diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index 300563dc59..b223d4701d 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays Y = -15, Size = new Vector2(15), Shadow = true, - Icon = FontAwesome.fa_chevron_left + Icon = FontAwesome.Solid.ChevronLeft }, new OsuSpriteText { diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index e7caaa3aca..d0411ba9e7 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -10,6 +10,7 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using System; namespace osu.Game.Overlays { @@ -19,6 +20,11 @@ namespace osu.Game.Overlays private const float transition_time = 400; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + public LoginOverlay() { AutoSizeAxes = Axes.Both; @@ -88,5 +94,12 @@ namespace osu.Game.Overlays settingsSection.Bounding = false; this.FadeOut(transition_time); } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } } } diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index eeb42ec991..431ae98c2c 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -109,7 +109,7 @@ namespace osu.Game.Overlays.MedalSplash s.Font = s.Font.With(size: 16); }); - medalContainer.OnLoadComplete = d => + medalContainer.OnLoadComplete += d => { unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 6bceade271..99017579a2 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; namespace osu.Game.Overlays.Music @@ -53,15 +53,16 @@ namespace osu.Game.Overlays.Music public class FilterTextBox : SearchTextBox { - protected override Color4 BackgroundUnfocused => OsuColour.Gray(0.06f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.12f); - protected override bool AllowCommit => true; - public FilterTextBox() + [BackgroundDependencyLoader] + private void load() { Masking = true; CornerRadius = 5; + + BackgroundUnfocused = OsuColour.Gray(0.06f); + BackgroundFocused = OsuColour.Gray(0.12f); } } } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 886a202c2a..df37a1b2c7 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -155,6 +155,8 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive { get; set; } + private class PlaylistItemHandle : SpriteIcon { public PlaylistItemHandle() @@ -162,7 +164,7 @@ namespace osu.Game.Overlays.Music Anchor = Anchor.TopLeft; Origin = Anchor.TopLeft; Size = new Vector2(12); - Icon = FontAwesome.fa_bars; + Icon = FontAwesome.Solid.Bars; Alpha = 0f; Margin = new MarginPadding { Left = 5, Top = 2 }; } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 7df09f1abe..310c6c919f 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps, IBindable beatmap) { - beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false)); + beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false)); beatmaps.ItemAdded += addBeatmapSet; beatmaps.ItemRemoved += removeBeatmapSet; @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Music beatmapBacking.ValueChanged += _ => updateSelectedSet(); } - private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() => + private void addBeatmapSet(BeatmapSetInfo obj, bool existing) => Schedule(() => { if (existing) return; @@ -231,6 +231,11 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive + { + set { } + } + public IEnumerable FilterableChildren => Children; public ItemSearchContainer() diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 6ac597451d..b24c6c3508 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -56,6 +56,11 @@ namespace osu.Game.Overlays private readonly Bindable beatmap = new Bindable(); + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + public MusicController() { Width = 400; @@ -143,7 +148,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = prev, - Icon = FontAwesome.fa_step_backward, + Icon = FontAwesome.Solid.StepBackward, }, playButton = new MusicIconButton { @@ -152,14 +157,14 @@ namespace osu.Game.Overlays Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), Action = play, - Icon = FontAwesome.fa_play_circle_o, + Icon = FontAwesome.Regular.PlayCircle, }, nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = () => next(), - Icon = FontAwesome.fa_step_forward, + Icon = FontAwesome.Solid.StepForward, }, } }, @@ -168,7 +173,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-bottom_black_area_height / 2, 0), - Icon = FontAwesome.fa_bars, + Icon = FontAwesome.Solid.Bars, Action = () => playlist.ToggleVisibility(), }, } @@ -212,7 +217,7 @@ namespace osu.Game.Overlays beatmapSets.Insert(index, beatmapSetInfo); } - private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent) + private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing) { if (existing) return; @@ -244,6 +249,8 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); Height = dragContainer.Height; + + dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected override void Update() @@ -257,13 +264,13 @@ namespace osu.Game.Overlays progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; - playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } else { progressBar.CurrentTime = 0; progressBar.EndTime = 1; - playButton.Icon = FontAwesome.fa_play_circle_o; + playButton.Icon = FontAwesome.Regular.PlayCircle; } } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index ea6e250556..522e039cdb 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -174,7 +175,7 @@ namespace osu.Game.Overlays.Notifications { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_times_circle, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(20), } }; diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index f4807b00a8..feffb4fa66 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Notifications { @@ -11,7 +12,7 @@ namespace osu.Game.Overlays.Notifications { public ProgressCompletionNotification() { - Icon = FontAwesome.fa_check; + Icon = FontAwesome.Solid.Check; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index aee056b63d..3a3136b1ea 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -26,9 +27,9 @@ namespace osu.Game.Overlays.Notifications } } - private FontAwesome icon = FontAwesome.fa_info_circle; + private IconUsage icon = FontAwesome.Solid.InfoCircle; - public FontAwesome Icon + public IconUsage Icon { get => icon; set diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 722c9c9af2..5c9126dbe0 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -6,6 +6,7 @@ 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; @@ -49,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, + Icon = FontAwesome.Solid.Heart, Scale = new Vector2(0.45f), } }; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2641a0551d..138e522cd7 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -335,9 +335,12 @@ namespace osu.Game.Overlays.Profile Anchor = Anchor.Centre, Origin = Anchor.Centre, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), Depth = float.MaxValue, - }, coverContainer.Add); + }, background => + { + coverContainer.Add(background); + background.FadeInFromZero(200); + }); if (user.IsSupporter) SupporterTag.Show(); @@ -412,16 +415,16 @@ namespace osu.Game.Overlays.Profile websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); } - tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); - tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); + 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.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.fa_gamepad, user.Discord); - tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfoRightLine(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); + 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) { @@ -460,7 +463,7 @@ namespace osu.Game.Overlays.Profile badgeContainer.ShowBadges(user.Badges); } - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) + private void tryAddInfoRightLine(IconUsage icon, string str, string url = null) { if (string.IsNullOrEmpty(str)) return; diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 497d6c3fc4..46c65b9db7 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections protected readonly Bindable User = new Bindable(); - protected APIAccess Api; + protected IAPIProvider Api; protected APIRequest RetrievalRequest; protected RulesetStore Rulesets; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { Api = api; Rulesets = rulesets; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 7e721ac807..8fab29e42c 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public class DrawableRecentActivity : DrawableProfileRow { - private APIAccess api; + private IAPIProvider api; private readonly APIRecentActivity activity; @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index f55e5f8c59..0808cc8fcc 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -5,7 +5,7 @@ using osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList @@ -37,8 +37,8 @@ namespace osu.Game.Overlays.SearchableList Direction = FillDirection.Horizontal, Children = new[] { - new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle), - new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ListUl, PanelDisplayStyle.List, DisplayStyle), }, }, Dropdown = new SlimEnumDropdown @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.SearchableList private readonly PanelDisplayStyle style; private readonly Bindable bindable; - public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable bindable) + public DisplayStyleToggleButton(IconUsage icon, PanelDisplayStyle style, Bindable bindable) { this.bindable = bindable; this.style = style; diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 478e3d4c95..b0a8a0e77d 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -127,10 +127,14 @@ namespace osu.Game.Overlays.SearchableList private class FilterSearchTextBox : SearchTextBox { - protected override Color4 BackgroundUnfocused => OsuColour.Gray(0.06f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.12f); - protected override bool AllowCommit => true; + + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = OsuColour.Gray(0.06f); + BackgroundFocused = OsuColour.Gray(0.12f); + } } } } diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index afdbe33adb..73dca956d1 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.SearchableList { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.SearchableList protected abstract Color4 BackgroundColour { get; } protected abstract T DefaultTab { get; } protected abstract Drawable CreateHeaderText(); - protected abstract FontAwesome Icon { get; } + protected abstract IconUsage Icon { get; } protected SearchableListHeader() { diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index dfb24a08ae..772f5c2039 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Audio; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class AudioSection : SettingsSection { public override string Header => "Audio"; - public override FontAwesome Icon => FontAwesome.fa_volume_up; + public override IconUsage Icon => FontAwesome.Solid.VolumeUp; public AudioSection() { diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 441ee12f0d..0149cab802 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Debug; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class DebugSection : SettingsSection { public override string Header => "Debug"; - public override FontAwesome Icon => FontAwesome.fa_bug; + public override IconUsage Icon => FontAwesome.Solid.Bug; public DebugSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index bf4034d641..97d9d3c697 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -3,17 +3,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections { public class GameplaySection : SettingsSection { public override string Header => "Gameplay"; - public override FontAwesome Icon => FontAwesome.fa_circle_o; + public override IconUsage Icon => FontAwesome.Regular.Circle; public GameplaySection() { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 026424e1ff..e4ddc53e17 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.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; @@ -58,14 +59,14 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { this.colours = colours; api?.Register(this); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { form = null; @@ -194,7 +195,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { private TextBox username; private TextBox password; - private APIAccess api; + private IAPIProvider api; public Action RequestHide; @@ -205,7 +206,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config, AccountCreationOverlay accountCreation) + private void load(IAPIProvider api, OsuConfigManager config, AccountCreationOverlay accountCreation) { this.api = api; Direction = FillDirection.Vertical; @@ -362,7 +363,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.Regular.Circle, Size = new Vector2(14), }); diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index ad1e714096..d9947f16cc 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.General; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GeneralSection : SettingsSection { public override string Header => "General"; - public override FontAwesome Icon => FontAwesome.fa_gear; + public override IconUsage Icon => FontAwesome.Solid.Cog; public GeneralSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index d37acf8700..3d6086d3ea 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Graphics; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GraphicsSection : SettingsSection { public override string Header => "Graphics"; - public override FontAwesome Icon => FontAwesome.fa_laptop; + public override IconUsage Icon => FontAwesome.Solid.Laptop; public GraphicsSection() { diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index d37a2a6d65..6a3f8783b0 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class InputSection : SettingsSection { public override string Header => "Input"; - public override FontAwesome Icon => FontAwesome.fa_keyboard_o; + public override IconUsage Icon => FontAwesome.Regular.Keyboard; public InputSection(KeyBindingOverlay keyConfig) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs index 9b09a41c92..a124501454 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { BodyText = "Everything?"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 98ed8ebdaa..0f3acd5b7f 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Maintenance; using osuTK; @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections public class MaintenanceSection : SettingsSection { public override string Header => "Maintenance"; - public override FontAwesome Icon => FontAwesome.fa_wrench; + public override IconUsage Icon => FontAwesome.Solid.Wrench; public MaintenanceSection() { diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 7c959525f7..80295690c0 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Online; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class OnlineSection : SettingsSection { public override string Header => "Online"; - public override FontAwesome Icon => FontAwesome.fa_globe; + public override IconUsage Icon => FontAwesome.Solid.GlobeAsia; public OnlineSection() { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index d1f47b6016..100022bd13 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -5,8 +5,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Skin"; - public override FontAwesome Icon => FontAwesome.fa_paint_brush; + public override IconUsage Icon => FontAwesome.Solid.PaintBrush; private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default }; private readonly Bindable configBindable = new Bindable(); @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Settings.Sections private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray()); - private void itemAdded(SkinInfo s, bool existing, bool silent) + private void itemAdded(SkinInfo s, bool existing) { if (existing) return; diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 56c750b552..f63d4a7378 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -81,6 +81,8 @@ namespace osu.Game.Overlays.Settings set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsItem() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 38a8b58a68..e92f28d5d2 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings protected FillFlowContainer FlowContent; protected override Container Content => FlowContent; - public abstract FontAwesome Icon { get; } + public abstract IconUsage Icon { get; } public abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); @@ -34,6 +35,8 @@ namespace osu.Game.Overlays.Settings set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsSection() { Margin = new MarginPadding { Top = 20 }; diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 2215e95fec..a1b4d8b131 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.Settings set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsSubsection() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index cf8053ac6e..22bca9b421 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Allocation; using System.ComponentModel; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Social { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Social protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); protected override SocialTab DefaultTab => SocialTab.AllPlayers; - protected override FontAwesome Icon => FontAwesome.fa_users; + protected override IconUsage Icon => FontAwesome.Solid.Users; protected override Drawable CreateHeaderText() { diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 3464058abb..daf3d1c576 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays { public class SocialOverlay : SearchableListOverlay, IOnlineComponent { - private APIAccess api; + private IAPIProvider api; private readonly LoadingAnimation loading; private FillFlowContainer panels; @@ -89,7 +89,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; api.Register(this); @@ -193,7 +193,7 @@ namespace osu.Game.Overlays } } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 61b2014af8..a7f2a0e8d0 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Toolbar public Action OnHome; - private readonly ToolbarUserArea userArea; + private ToolbarUserButton userButton; protected override bool BlockPositionalInput => false; @@ -34,6 +34,13 @@ namespace osu.Game.Overlays.Toolbar private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); public Toolbar() + { + RelativeSizeAxes = Axes.X; + Size = new Vector2(1, HEIGHT); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame osuGame) { Children = new Drawable[] { @@ -68,21 +75,14 @@ namespace osu.Game.Overlays.Toolbar new ToolbarMusicButton(), //new ToolbarButton //{ - // Icon = FontAwesome.fa_search + // Icon = FontAwesome.Solid.search //}, - userArea = new ToolbarUserArea(), + userButton = new ToolbarUserButton(), new ToolbarNotificationButton(), } } }; - RelativeSizeAxes = Axes.X; - Size = new Vector2(1, HEIGHT); - } - - [BackgroundDependencyLoader(true)] - private void load(OsuGame osuGame) - { StateChanged += visibility => { if (overlayActivationMode.Value == OverlayActivation.Disabled) @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Toolbar protected override void PopOut() { - userArea?.LoginOverlay.Hide(); + userButton?.StateContainer.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.FadeOut(transition_time); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 855c7ad823..71374d5180 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -27,13 +27,13 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } - public void SetIcon(FontAwesome icon) => SetIcon(new SpriteIcon + public void SetIcon(IconUsage icon) => SetIcon(new SpriteIcon { Size = new Vector2(20), Icon = icon }); - public FontAwesome Icon + public IconUsage Icon { set => SetIcon(value); } diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index 226960564d..ad0e5be551 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarChatButton() { - SetIcon(FontAwesome.fa_comments); + SetIcon(FontAwesome.Solid.Comments); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs index 38ce4c7ccf..1d07a3ae70 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarDirectButton() { - SetIcon(FontAwesome.fa_osu_chevron_down_o); + SetIcon(OsuIcon.ChevronDownCircle); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 3675c4578e..6f5e703a66 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.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.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Icon = FontAwesome.fa_home; + Icon = FontAwesome.Solid.Home; TooltipMain = "Home"; TooltipSub = "Return to the main menu"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 40ffc71d87..f03df2ed93 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarMusicButton() { - Icon = FontAwesome.fa_music; + Icon = FontAwesome.Solid.Music; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index 751045f61c..dbd6c557d3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -6,6 +6,7 @@ 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.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNotificationButton() { - Icon = FontAwesome.fa_bars; + Icon = FontAwesome.Solid.Bars; TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 14f652f6fe..08f8f867fd 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Icon = FontAwesome.fa_gear; + Icon = FontAwesome.Solid.Cog; TooltipMain = "Settings"; TooltipSub = "Change your settings"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index d0e664ecae..5e353d3319 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSocialButton() { - Icon = FontAwesome.fa_users; + Icon = FontAwesome.Solid.Users; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs deleted file mode 100644 index f9cf5d4350..0000000000 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ /dev/null @@ -1,42 +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 osuTK; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarUserArea : Container - { - public LoginOverlay LoginOverlay; - private ToolbarUserButton button; - - public override RectangleF BoundingBox => button.BoundingBox; - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] - { - button = new ToolbarUserButton - { - Action = () => LoginOverlay.ToggleVisibility(), - }, - LoginOverlay = new LoginOverlay - { - BypassAutoSizeAxes = Axes.Both, - Position = new Vector2(0, 1), - RelativePositionAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - } - }; - } - } -} diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index d47f3a7b16..356ffa5180 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -13,7 +13,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { - public class ToolbarUserButton : ToolbarButton, IOnlineComponent + public class ToolbarUserButton : ToolbarOverlayToggleButton, IOnlineComponent { private readonly UpdateableAvatar avatar; @@ -42,13 +42,15 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader] - private void load(APIAccess api) + [BackgroundDependencyLoader(true)] + private void load(IAPIProvider api, LoginOverlay login) { api.Register(this); + + StateContainer = login; } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 1ff1c2ce91..48ce055975 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - private APIAccess api; + private IAPIProvider api; protected ProfileHeader Header; private SectionsContainer sectionsContainer; private ProfileTabControl tabs; @@ -56,7 +56,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 6061ead2da..2b1f78243b 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -9,6 +9,7 @@ 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; @@ -71,7 +72,7 @@ namespace osu.Game.Overlays.Volume Current.ValueChanged += muted => { - icon.Icon = muted.NewValue ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; + 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 }; Current.TriggerChange(); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index db8bdde6bb..aad55f8a38 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { + mods = mods.Select(m => m.CreateCopy()).ToArray(); + beatmap.Mods.Value = mods; IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs similarity index 70% rename from osu.Game/Rulesets/Edit/EditRulesetContainer.cs rename to osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 8992be2da2..2200caeb20 100644 --- a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -11,14 +11,16 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit { - public abstract class EditRulesetContainer : CompositeDrawable + public abstract class DrawableEditRuleset : CompositeDrawable { /// - /// The contained by this . + /// The contained by this . /// public abstract Playfield Playfield { get; } - internal EditRulesetContainer() + public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer(); + + internal DrawableEditRuleset() { RelativeSizeAxes = Axes.Both; } @@ -38,21 +40,23 @@ namespace osu.Game.Rulesets.Edit internal abstract DrawableHitObject Remove(HitObject hitObject); } - public class EditRulesetContainer : EditRulesetContainer + public class DrawableEditRuleset : DrawableEditRuleset where TObject : HitObject { - public override Playfield Playfield => rulesetContainer.Playfield; + public override Playfield Playfield => drawableRuleset.Playfield; - private Ruleset ruleset => rulesetContainer.Ruleset; - private Beatmap beatmap => rulesetContainer.Beatmap; + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); - private readonly RulesetContainer rulesetContainer; + private Ruleset ruleset => drawableRuleset.Ruleset; + private Beatmap beatmap => drawableRuleset.Beatmap; - public EditRulesetContainer(RulesetContainer rulesetContainer) + private readonly DrawableRuleset drawableRuleset; + + public DrawableEditRuleset(DrawableRuleset drawableRuleset) { - this.rulesetContainer = rulesetContainer; + this.drawableRuleset = drawableRuleset; - InternalChild = rulesetContainer; + InternalChild = drawableRuleset; Playfield.DisplayJudgements.Value = false; } @@ -73,10 +77,10 @@ namespace osu.Game.Rulesets.Edit processor?.PostProcess(); // Add visual representation - var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); + var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject); - rulesetContainer.Playfield.Add(drawableObject); - rulesetContainer.Playfield.PostProcess(); + drawableRuleset.Playfield.Add(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } @@ -97,8 +101,8 @@ namespace osu.Game.Rulesets.Edit // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); - rulesetContainer.Playfield.Remove(drawableObject); - rulesetContainer.Playfield.PostProcess(); + drawableRuleset.Playfield.Remove(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 025564e249..41de0c36fc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - public IEnumerable HitObjects => RulesetContainer.Playfield.AllHitObjects; + public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; protected readonly Ruleset Ruleset; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit private readonly List layerContainers = new List(); - protected EditRulesetContainer RulesetContainer { get; private set; } + protected DrawableEditRuleset DrawableRuleset { get; private set; } private BlueprintContainer blueprintContainer; @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Edit try { - RulesetContainer = CreateRulesetContainer(); - RulesetContainer.Clock = framedClock; + DrawableRuleset = CreateDrawableRuleset(); + DrawableRuleset.Clock = framedClock; } catch (Exception e) { @@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = CreateLayerContainer(); + var layerBelowRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = CreateLayerContainer(); + var layerAboveRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - RulesetContainer, + DrawableRuleset, layerAboveRuleset } } @@ -140,27 +140,27 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = RulesetContainer.Playfield.Anchor; - l.Origin = RulesetContainer.Playfield.Origin; - l.Position = RulesetContainer.Playfield.Position; - l.Size = RulesetContainer.Playfield.Size; + l.Anchor = DrawableRuleset.Playfield.Anchor; + l.Origin = DrawableRuleset.Playfield.Origin; + l.Position = DrawableRuleset.Playfield.Position; + l.Size = DrawableRuleset.Playfield.Size; }); } /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// - public virtual bool CursorInPlacementArea => RulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); /// /// Adds a to the and visualises it. /// /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject)); + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject)); - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject)); + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract EditRulesetContainer CreateRulesetContainer(); + internal abstract DrawableEditRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -174,11 +174,6 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s and handles movement of selections. /// public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); - - /// - /// Creates a which provides a layer above or below the . - /// - protected virtual Container CreateLayerContainer() => new Container { RelativeSizeAxes = Axes.Both }; } public abstract class HitObjectComposer : HitObjectComposer @@ -189,9 +184,9 @@ namespace osu.Game.Rulesets.Edit { } - internal override EditRulesetContainer CreateRulesetContainer() - => new EditRulesetContainer(CreateRulesetContainer(Ruleset, Beatmap.Value)); + internal override DrawableEditRuleset CreateDrawableRuleset() + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); - protected abstract RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 0d6e11c649..89db954c36 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -32,6 +32,12 @@ namespace osu.Game.Rulesets.Judgements protected Container JudgementBody; protected SpriteText JudgementText; + /// + /// Duration of initial fade in. + /// Default fade out will start immediately after this duration. + /// + protected virtual double FadeInDuration => 100; + /// /// Creates a drawable which visualises a . /// @@ -65,11 +71,19 @@ namespace osu.Game.Rulesets.Judgements }; } + protected virtual void ApplyHitAnimations() + { + JudgementBody.ScaleTo(0.9f); + JudgementBody.ScaleTo(1, 500, Easing.OutElastic); + + this.Delay(FadeInDuration).FadeOut(400); + } + protected override void LoadComplete() { base.LoadComplete(); - this.FadeInFromZero(100, Easing.OutQuint); + this.FadeInFromZero(FadeInDuration, Easing.OutQuint); switch (Result.Type) { @@ -85,10 +99,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); break; default: - JudgementBody.ScaleTo(0.9f); - JudgementBody.ScaleTo(1, 500, Easing.OutElastic); - - this.Delay(100).FadeOut(400); + ApplyHitAnimations(); break; } diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs similarity index 50% rename from osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs rename to osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs index addb96a4fe..b012beb0c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs @@ -7,15 +7,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { /// - /// An interface for s that can be applied to s. + /// An interface for s that can be applied to s. /// - public interface IApplicableToRulesetContainer : IApplicableMod + public interface IApplicableToDrawableRuleset : IApplicableMod where TObject : HitObject { /// - /// Applies this to a . + /// Applies this to a . /// - /// The to apply to. - void ApplyToRulesetContainer(RulesetContainer rulesetContainer); + /// The to apply to. + void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 705c5c4ef6..d2d0a5bb26 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; using System; using Newtonsoft.Json; +using osu.Framework.Graphics.Sprites; using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Mods @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods /// The icon of this mod. /// [JsonIgnore] - public virtual FontAwesome Icon => FontAwesome.fa_question; + public virtual IconUsage Icon => FontAwesome.Solid.Question; /// /// The type of this mod. @@ -65,5 +65,10 @@ namespace osu.Game.Rulesets.Mods /// [JsonIgnore] public virtual Type[] IncompatibleMods => new Type[] { }; + + /// + /// Creates a copy of this initialised to a default state. + /// + public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 22e70b446e..e70d58acea 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Replays; @@ -11,25 +12,25 @@ using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset where T : HitObject { - protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; - - public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; - - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap)); + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); } public abstract class ModAutoplay : Mod, IApplicableFailOverride { public override string Name => "Autoplay"; public override string Acronym => "AT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; + public override IconUsage Icon => OsuIcon.ModAuto; public override ModType Type => ModType.Automation; public override string Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 1; public bool AllowFail => false; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + + public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; + + public virtual Score CreateReplayScore(IBeatmap beatmap) => new Score { Replay = new Replay() }; } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 6f8eed4a0a..3c6a3a54aa 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Cinema"; public override string Acronym => "CN"; public override bool HasImplementation => false; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + public override IconUsage Icon => OsuIcon.ModCinema; public override string Description => "Watch the video without visual distractions."; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 2eea6a237c..7e6d959119 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Graphics.Sprites; using osu.Framework.Timing; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { @@ -11,13 +11,13 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Daycore"; public override string Acronym => "DC"; - public override FontAwesome Icon => FontAwesome.fa_question; + public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; public override void ApplyToClock(IAdjustableClock clock) { if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust = 0.75; + pitchAdjust.PitchAdjust *= RateAdjust; else base.ApplyToClock(clock); } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index b69019cd91..a5e76e32b1 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -2,24 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Timing; +using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : Mod, IApplicableToClock + public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock { public override string Name => "Double Time"; public override string Acronym => "DT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override IconUsage Icon => OsuIcon.ModDoubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 1.5; - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); + + protected override double RateAdjust => 1.5; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index ef4de0e300..56ec0bec06 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Easy"; public override string Acronym => "EZ"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override IconUsage Icon => OsuIcon.ModEasy; public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index dfada5614a..0ad99d13ff 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Flashlight"; public override string Acronym => "FL"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override IconUsage Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; public override bool Ranked => true; @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Mods } } - public abstract class ModFlashlight : ModFlashlight, IApplicableToRulesetContainer, IApplicableToScoreProcessor + public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor where T : HitObject { public const double FLASHLIGHT_FADE_DURATION = 800; @@ -45,15 +46,15 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); flashlight.Combo = Combo; flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; - rulesetContainer.KeyBindingInputManager.Add(flashlight); + drawableRuleset.KeyBindingInputManager.Add(flashlight); - flashlight.Breaks = rulesetContainer.Beatmap.Breaks; + flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } public abstract Flashlight CreateFlashlight(); @@ -61,7 +62,7 @@ namespace osu.Game.Rulesets.Mods public abstract class Flashlight : Drawable { internal BindableInt Combo; - private Shader shader; + private IShader shader; protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); @@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Mods private class FlashlightDrawNode : DrawNode { - public Shader Shader; + public IShader Shader; public Quad ScreenSpaceDrawQuad; public Vector2 FlashlightPosition; public Vector2 FlashlightSize; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 1cffa37b04..27369f4c30 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -2,24 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Timing; +using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : Mod, IApplicableToClock + public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock { public override string Name => "Half Time"; public override string Acronym => "HT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override IconUsage Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 0.75; - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); + + protected override double RateAdjust => 0.75; } } diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 4b8792098e..2044cbeae2 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hard Rock"; public override string Acronym => "HR"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override IconUsage Icon => OsuIcon.ModHardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index e526125947..c7e3f0a78f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods { @@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hidden"; public override string Acronym => "HD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override IconUsage Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index e6bd532f72..dc0fc33088 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Graphics.Sprites; using osu.Framework.Timing; using osu.Game.Graphics; @@ -11,13 +12,13 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Nightcore"; public override string Acronym => "NC"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; + public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; public override void ApplyToClock(IAdjustableClock clock) { if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust = 1.5; + pitchAdjust.PitchAdjust *= RateAdjust; else base.ApplyToClock(clock); } diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 5bcba289c6..1ee1f92d8c 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "No Fail"; public override string Acronym => "NF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override IconUsage Icon => OsuIcon.ModNofail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 5145f85124..e984fb8574 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Perfect"; public override string Acronym => "PF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; + public override IconUsage Icon => OsuIcon.ModPerfect; public override string Description => "SS or quit."; protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index ee59810a94..4feb89186c 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Relax"; public override string Acronym => "RX"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; + public override IconUsage Icon => OsuIcon.ModRelax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 26223b24d1..6a82050d26 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Sudden Death"; public override string Acronym => "SD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override IconUsage Icon => OsuIcon.ModSuddendeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs new file mode 100644 index 0000000000..513883f552 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.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; +using osu.Framework.Timing; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModTimeAdjust : Mod + { + public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; + + protected abstract double RateAdjust { get; } + + public virtual void ApplyToClock(IAdjustableClock clock) + { + if (clock is IHasTempoAdjust tempo) + tempo.TempoAdjust *= RateAdjust; + else + clock.Rate *= RateAdjust; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs new file mode 100644 index 0000000000..62407907c1 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Audio; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModTimeRamp : Mod + { + public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; + + protected abstract double FinalRateAdjustment { get; } + } + + public abstract class ModTimeRamp : ModTimeRamp, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap + where T : HitObject + { + private double finalRateTime; + + private double beginRampTime; + + private IAdjustableClock clock; + + /// + /// The point in the beatmap at which the final ramping rate should be reached. + /// + private const double final_rate_progress = 0.75f; + + public virtual void ApplyToClock(IAdjustableClock clock) + { + this.clock = clock; + + lastAdjust = 1; + + // for preview purposes. during gameplay, Update will overwrite this setting. + applyAdjustment(1); + } + + public virtual void ApplyToBeatmap(Beatmap beatmap) + { + HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + + beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; + finalRateTime = final_rate_progress * ((lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0); + } + + public virtual void Update(Playfield playfield) + { + applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); + } + + private double lastAdjust = 1; + + /// + /// Adjust the rate along the specified ramp + /// + /// The amount of adjustment to apply (from 0..1). + private void applyAdjustment(double amount) + { + double adjust = 1 + (Math.Sign(FinalRateAdjustment) * MathHelper.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + + switch (clock) + { + case IHasPitchAdjust pitch: + pitch.PitchAdjust /= lastAdjust; + pitch.PitchAdjust *= adjust; + break; + case IHasTempoAdjust tempo: + tempo.TempoAdjust /= lastAdjust; + tempo.TempoAdjust *= adjust; + break; + default: + clock.Rate /= lastAdjust; + clock.Rate *= adjust; + break; + } + + lastAdjust = adjust; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs new file mode 100644 index 0000000000..5d71c8950b --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModWindDown.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 System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + public class ModWindDown : ModTimeRamp + where T : HitObject + { + public override string Name => "Wind Down"; + public override string Acronym => "WD"; + public override string Description => "Sloooow doooown..."; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown; + public override double ScoreMultiplier => 1.0; + + protected override double FinalRateAdjustment => -0.25; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); + } +} diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs new file mode 100644 index 0000000000..aae85cec19 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModWindUp.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 System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + public class ModWindUp : ModTimeRamp + where T : HitObject + { + public override string Name => "Wind Up"; + public override string Acronym => "WU"; + public override string Description => "Can you keep up?"; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp; + public override double ScoreMultiplier => 1.0; + + protected override double FinalRateAdjustment => 0.5; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e1e76f109d..a7cfbd3300 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -120,6 +120,8 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 41281e805e..0089d1eb88 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,12 +8,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasCombo + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition, IHasCombo { public double EndTime { get; set; } public double Duration => EndTime - StartTime; + public float X => 256; // Required for CatchBeatmapConverter + public bool NewCombo { get; set; } public int ComboOffset { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b9177a325c..8d6bb8bd3f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -5,7 +5,6 @@ using osuTK; using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; @@ -46,9 +45,11 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] split = text.Split(','); - Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture)); + Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); - ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]); + double startTime = Parsing.ParseDouble(split[2]) + Offset; + + ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; type &= ~ConvertHitObjectType.ComboOffset; @@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Objects.Legacy bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); type &= ~ConvertHitObjectType.NewCombo; - var soundType = (LegacySoundType)int.Parse(split[4]); + var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); HitObject result = null; @@ -107,7 +108,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } string[] temp = t.Split(':'); - points[pointIndex++] = new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos; + points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; } // osu-stable special-cased colinear perfect curves to a CurveType.Linear @@ -116,7 +117,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) pathType = PathType.Linear; - int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); + int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); @@ -125,7 +126,7 @@ namespace osu.Game.Rulesets.Objects.Legacy repeatCount = Math.Max(0, repeatCount - 1); if (split.Length > 7) - length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); + length = Math.Max(0, Parsing.ParseDouble(split[7])); if (split.Length > 10) readCustomSampleBanks(split[10], bankInfo); @@ -184,7 +185,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -193,12 +196,12 @@ namespace osu.Game.Rulesets.Objects.Legacy { // Note: Hold is generated by BMS converts - double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { string[] ss = split[5].Split(':'); - endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); + endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } @@ -211,7 +214,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return null; } - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; + result.StartTime = startTime; if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); @@ -222,8 +225,14 @@ namespace osu.Game.Rulesets.Objects.Legacy } catch (FormatException) { - throw new FormatException("One or more hit objects were malformed."); + Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } + catch (OverflowException) + { + Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + } + + return null; } private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) @@ -233,8 +242,8 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); - var bank = (LegacyBeatmapDecoder.LegacySampleBank)int.Parse(split[0]); - var addbank = (LegacyBeatmapDecoder.LegacySampleBank)int.Parse(split[1]); + var bank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[0]); + var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[1]); string stringBank = bank.ToString().ToLowerInvariant(); if (stringBank == @"none") @@ -247,10 +256,10 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; if (split.Length > 2) - bankInfo.CustomSampleBank = int.Parse(split[2]); + bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]); if (split.Length > 3) - bankInfo.Volume = int.Parse(split[3]); + bankInfo.Volume = Math.Max(0, Parsing.ParseInt(split[3])); bankInfo.Filename = split.Length > 4 ? split[4] : null; } @@ -301,7 +310,16 @@ namespace osu.Game.Rulesets.Objects.Legacy { // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario if (!string.IsNullOrEmpty(bankInfo.Filename)) - return new List { new FileSampleInfo { Filename = bankInfo.Filename } }; + { + return new List + { + new FileSampleInfo + { + Filename = bankInfo.Filename, + Volume = bankInfo.Volume + } + }; + } var soundTypes = new List { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs new file mode 100644 index 0000000000..7cda7485f9 --- /dev/null +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -0,0 +1,148 @@ +// 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 osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public static class SliderEventGenerator + { + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) + { + // A very lenient maximum length of a slider for ticks to be generated. + // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. + const double max_length = 100000; + + var length = Math.Min(max_length, totalDistance); + tickDistance = MathHelper.Clamp(tickDistance, 0, length); + + var minDistanceFromEnd = velocity * 10; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Head, + SpanIndex = 0, + SpanStartTime = startTime, + Time = startTime, + PathProgress = 0, + }; + + if (tickDistance != 0) + { + for (var span = 0; span < spanCount; span++) + { + var spanStartTime = startTime + span * spanDuration; + var reversed = span % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d >= length - minDistanceFromEnd) + break; + + var pathProgress = d / length; + var timeProgress = reversed ? 1 - pathProgress : pathProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Tick, + SpanIndex = span, + SpanStartTime = spanStartTime, + Time = spanStartTime + timeProgress * spanDuration, + PathProgress = pathProgress, + }; + } + + if (span < spanCount - 1) + { + yield return new SliderEventDescriptor + { + Type = SliderEventType.Repeat, + SpanIndex = span, + SpanStartTime = startTime + span * spanDuration, + Time = spanStartTime + spanDuration, + PathProgress = (span + 1) % 2, + }; + } + } + } + + double totalDuration = spanCount * spanDuration; + + // Okay, I'll level with you. I made a mistake. It was 2007. + // Times were simpler. osu! was but in its infancy and sliders were a new concept. + // A hack was made, which has unfortunately lived through until this day. + // + // This legacy tick is used for some calculations and judgements where audio output is not required. + // Generally we are keeping this around just for difficulty compatibility. + // Optimistically we do not want to ever use this for anything user-facing going forwards. + + int finalSpanIndex = spanCount - 1; + double finalSpanStartTime = startTime + finalSpanIndex * spanDuration; + double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0)); + double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration; + + if (spanCount % 2 == 0) finalProgress = 1 - finalProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.LegacyLastTick, + SpanIndex = finalSpanIndex, + SpanStartTime = finalSpanStartTime, + Time = finalSpanEndTime, + PathProgress = finalProgress, + }; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Tail, + SpanIndex = finalSpanIndex, + SpanStartTime = startTime + (spanCount - 1) * spanDuration, + Time = startTime + totalDuration, + PathProgress = spanCount % 2, + }; + } + } + + /// + /// Describes a point in time on a slider given special meaning. + /// Should be used by rulesets to visualise the slider. + /// + public struct SliderEventDescriptor + { + /// + /// The type of event. + /// + public SliderEventType Type; + + /// + /// The time of this event. + /// + public double Time; + + /// + /// The zero-based index of the span. In the case of repeat sliders, this will increase after each . + /// + public int SpanIndex; + + /// + /// The time at which the contained begins. + /// + public double SpanStartTime; + + /// + /// The progress along the slider's at which this event occurs. + /// + public double PathProgress; + } + + public enum SliderEventType + { + Tick, + LegacyLastTick, + Head, + Tail, + Repeat + } +} diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index efca0a5883..1d4cdbf04c 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Objects; using osu.Game.Beatmaps; using osu.Game.Replays; namespace osu.Game.Rulesets.Replays { - public abstract class AutoGenerator : IAutoGenerator - where T : HitObject + public abstract class AutoGenerator : IAutoGenerator { /// /// Creates the auto replay and returns it. @@ -21,11 +19,11 @@ namespace osu.Game.Rulesets.Replays /// /// The beatmap we're making. /// - protected Beatmap Beatmap; + protected IBeatmap Beatmap; #endregion - protected AutoGenerator(Beatmap beatmap) + protected AutoGenerator(IBeatmap beatmap) { Beatmap = beatmap; } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index c89ac59e10..3830fa5cbe 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Replays { @@ -22,12 +22,37 @@ namespace osu.Game.Rulesets.Replays protected List Frames => replay.Frames; - public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex]; - public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex]; + public TFrame CurrentFrame + { + get + { + if (!HasFrames || !currentFrameIndex.HasValue) + return null; - private int currentFrameIndex; + return (TFrame)Frames[currentFrameIndex.Value]; + } + } - private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1); + public TFrame NextFrame + { + get + { + if (!HasFrames) + return null; + + if (!currentFrameIndex.HasValue) + return (TFrame)Frames[0]; + + if (currentDirection > 0) + return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex.Value + 1]; + else + return currentFrameIndex == 0 ? null : (TFrame)Frames[nextFrameIndex]; + } + } + + private int? currentFrameIndex; + + private int nextFrameIndex => currentFrameIndex.HasValue ? MathHelper.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; protected FramedReplayInputHandler(Replay replay) { @@ -47,12 +72,12 @@ namespace osu.Game.Rulesets.Replays public override List GetPendingInputs() => new List(); - public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; - public bool AtFirstFrame => currentFrameIndex == 0; - private const double sixty_frame_time = 1000.0 / 60; - protected double CurrentTime { get; private set; } + protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; + + protected double? CurrentTime { get; private set; } + private int currentDirection; /// @@ -63,14 +88,24 @@ namespace osu.Game.Rulesets.Replays protected bool HasFrames => Frames.Count > 0; - private bool inImportantSection => - HasFrames && FrameAccuratePlayback && - //a button is in a pressed state - IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && - //the next frame is within an allowable time span - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2; + private bool inImportantSection + { + get + { + if (!HasFrames || !FrameAccuratePlayback) + return false; - protected virtual bool IsImportant(TFrame frame) => false; + var frame = currentDirection > 0 ? CurrentFrame : NextFrame; + + if (frame == null) + return false; + + return IsImportant(frame) && //a button is in a pressed state + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; //the next frame is within an allowable time span + } + } + + protected virtual bool IsImportant([NotNull] TFrame frame) => false; /// /// Update the current frame based on an incoming time value. @@ -81,47 +116,36 @@ namespace osu.Game.Rulesets.Replays /// The usable time value. If null, we should not advance time as we do not have enough data. public override double? SetFrameFromTime(double time) { - currentDirection = time.CompareTo(CurrentTime); - if (currentDirection == 0) currentDirection = 1; + if (!CurrentTime.HasValue) + { + currentDirection = 1; + } + else + { + currentDirection = time.CompareTo(CurrentTime); + if (currentDirection == 0) currentDirection = 1; + } if (HasFrames) { - // check if the next frame is in the "future" for the current playback direction - if (currentDirection != time.CompareTo(NextFrame.Time)) + // check if the next frame is valid for the current playback direction. + // validity is if the next frame is equal or "earlier" + var compare = time.CompareTo(NextFrame?.Time); + + if (compare == 0 || compare == currentDirection) + { + if (advanceFrame()) + return CurrentTime = CurrentFrame.Time; + } + else { // if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null. if (inImportantSection) return null; } - else if (advanceFrame()) - { - // If going backwards, we need to execute once _before_ the frame time to reverse any judgements - // that would occur as a result of this frame in forward playback - if (currentDirection == -1) - return CurrentTime = CurrentFrame.Time - 1; - - return CurrentTime = CurrentFrame.Time; - } } return CurrentTime = time; } - - protected class ReplayMouseState : osu.Framework.Input.States.MouseState - { - public ReplayMouseState(Vector2 position) - { - Position = position; - } - } - - protected class ReplayKeyboardState : osu.Framework.Input.States.KeyboardState - { - public ReplayKeyboardState(List keys) - { - foreach (var key in keys) - Keys.Add(key); - } - } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ffab0abebf..cdfe02b60b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -5,9 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets /// An enumerable of constructed s public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => new Mod[] { }; - public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); + public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); protected Ruleset(RulesetInfo rulesetInfo = null) { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap); /// /// Creates a to convert a to one that is applicable for this . @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; public abstract string Description { get; } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63e1c93dd5..0fddb19a6c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -210,15 +210,15 @@ namespace osu.Game.Rulesets.Scoring { } - public ScoreProcessor(RulesetContainer rulesetContainer) + public ScoreProcessor(DrawableRuleset drawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); - rulesetContainer.OnNewResult += applyResult; - rulesetContainer.OnRevertResult += revertResult; + drawableRuleset.OnNewResult += applyResult; + drawableRuleset.OnRevertResult += revertResult; - ApplyBeatmap(rulesetContainer.Beatmap); - SimulateAutoplay(rulesetContainer.Beatmap); + ApplyBeatmap(drawableRuleset.Beatmap); + SimulateAutoplay(drawableRuleset.Beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs similarity index 60% rename from osu.Game/Rulesets/UI/RulesetContainer.cs rename to osu.Game/Rulesets/UI/DrawableRuleset.cs index 1c29cf4e2b..27137d9f98 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.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 osu.Framework.Allocation; @@ -16,23 +16,25 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; +using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Graphics.Cursor; using osu.Game.Input.Handlers; using osu.Game.Overlays; using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { /// - /// Base RulesetContainer. Doesn't hold objects. - /// - /// Should not be derived - derive instead. - /// + /// Displays an interactive ruleset gameplay instance. /// - public abstract class RulesetContainer : Container + /// The type of HitObject contained by this DrawableRuleset. + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + where TObject : HitObject { /// /// The selected variant. @@ -40,27 +42,11 @@ namespace osu.Game.Rulesets.UI public virtual int Variant => 0; /// - /// The input manager for this RulesetContainer. - /// - internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; - - /// - /// The key conversion input manager for this RulesetContainer. + /// The key conversion input manager for this DrawableRuleset. /// public PassThroughInputManager KeyBindingInputManager; - /// - /// Whether a replay is currently loaded. - /// - public readonly BindableBool HasReplayLoaded = new BindableBool(); - - public abstract IEnumerable Objects { get; } - - /// - /// The point in time at which gameplay starts, including any required lead-in for display purposes. - /// Defaults to two seconds before the first . Override as necessary. - /// - public virtual double GameplayStartTime => Objects.First().StartTime - 2000; + public override double GameplayStartTime => Objects.First().StartTime - 2000; private readonly Lazy playfield; @@ -72,26 +58,54 @@ namespace osu.Game.Rulesets.UI /// /// Place to put drawables above hit objects but below UI. /// - public Container Overlays { get; protected set; } + public Container Overlays { get; private set; } /// - /// The cursor provided by this . May be null if no cursor is provided. + /// Invoked when a has been applied by a . /// - public readonly CursorContainer Cursor; + public event Action OnNewResult; - public readonly Ruleset Ruleset; + /// + /// Invoked when a is being reverted by a . + /// + public event Action OnRevertResult; + + /// + /// The beatmap. + /// + public Beatmap Beatmap; + + public override IEnumerable Objects => Beatmap.HitObjects; protected IRulesetConfigManager Config { get; private set; } + /// + /// The mods which are to be applied. + /// + private readonly IEnumerable mods; + + private FrameStabilityContainer frameStabilityContainer; + private OnScreenDisplay onScreenDisplay; /// - /// A visual representation of a . + /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// - /// The ruleset being repesented. - protected RulesetContainer(Ruleset ruleset) + /// The ruleset being represented. + /// The beatmap to create the hit renderer for. + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) + : base(ruleset) { - Ruleset = ruleset; + Debug.Assert(workingBeatmap != null, "DrawableRuleset initialized with a null beatmap."); + + RelativeSizeAxes = Axes.Both; + + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + + mods = workingBeatmap.Mods.Value; + applyBeatmapMods(mods); + + KeyBindingInputManager = CreateInputManager(); playfield = new Lazy(CreatePlayfield); IsPaused.ValueChanged += paused => @@ -101,8 +115,6 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager.UseParentInput = !paused.NewValue; }; - - Cursor = CreateCursor(); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -121,159 +133,124 @@ namespace osu.Game.Rulesets.UI return dependencies; } - public abstract ScoreProcessor CreateScoreProcessor(); + public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + InternalChildren = new Drawable[] + { + frameStabilityContainer = new FrameStabilityContainer + { + Child = KeyBindingInputManager + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(Playfield) + ) + }, + Overlays = new Container { RelativeSizeAxes = Axes.Both } + }; + + if ((ResumeOverlay = CreateResumeOverlay()) != null) + { + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); + } + + applyRulesetMods(mods, config); + + loadObjects(); + } + + /// + /// Creates and adds drawable representations of hit objects to the play field. + /// + private void loadObjects() + { + foreach (TObject h in Beatmap.HitObjects) + addHitObject(h); + + Playfield.PostProcess(); + + foreach (var mod in mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); + } + + public override void RequestResume(Action continueResume) + { + if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + { + ResumeOverlay.GameplayCursor = Cursor; + ResumeOverlay.ResumeAction = continueResume; + ResumeOverlay.Show(); + } + else + continueResume(); + } + + public ResumeOverlay ResumeOverlay { get; private set; } + + protected virtual ResumeOverlay CreateResumeOverlay() => null; + + /// + /// Creates and adds the visual representation of a to this . + /// + /// The to add the visual representation for. + private void addHitObject(TObject hitObject) + { + var drawableObject = CreateDrawableRepresentation(hitObject); + + if (drawableObject == null) + return; + + drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); + drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); + + Playfield.Add(drawableObject); + } + + public override void SetReplayScore(Score replayScore) + { + if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + + var handler = (ReplayScore = replayScore) != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + replayInputManager.ReplayInputHandler = handler; + frameStabilityContainer.ReplayInputHandler = handler; + + HasReplayLoaded.Value = replayInputManager.ReplayInputHandler != null; + + if (replayInputManager.ReplayInputHandler != null) + replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + } + + /// + /// Creates a DrawableHitObject from a HitObject. + /// + /// The HitObject to make drawable. + /// The DrawableHitObject. + public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); + + public void Attach(KeyCounterDisplay keyCounter) => + (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// /// The input manager. - public abstract PassThroughInputManager CreateInputManager(); + protected abstract PassThroughInputManager CreateInputManager(); protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - public Score ReplayScore { get; private set; } - - /// - /// Whether the game is paused. Used to block user input. - /// - public readonly BindableBool IsPaused = new BindableBool(); - - /// - /// Sets a replay to be used, overriding local input. - /// - /// The replay, null for local input. - public virtual void SetReplayScore(Score replayScore) - { - if (ReplayInputManager == null) - throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); - - ReplayScore = replayScore; - ReplayInputManager.ReplayInputHandler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; - - HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; - } - - /// - /// Creates the cursor. May be null if the doesn't provide a custom cursor. - /// - protected virtual CursorContainer CreateCursor() => null; - /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (Config != null) - { - onScreenDisplay?.StopTracking(this, Config); - Config = null; - } - } - } - - /// - /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield - /// and does not load drawable hit objects. - /// - /// Should not be derived - derive instead. - /// - /// - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - { - /// - /// Invoked when a has been applied by a . - /// - public event Action OnNewResult; - - /// - /// Invoked when a is being reverted by a . - /// - public event Action OnRevertResult; - - /// - /// The Beatmap - /// - public Beatmap Beatmap; - - /// - /// All the converted hit objects contained by this hit renderer. - /// - public override IEnumerable Objects => Beatmap.HitObjects; - - /// - /// The mods which are to be applied. - /// - protected IEnumerable Mods; - - /// - /// The this was created with. - /// - protected readonly WorkingBeatmap WorkingBeatmap; - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - protected override Container Content => content; - private Container content; - - /// - /// Whether to assume the beatmap passed into this is for the current ruleset. - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) - : base(ruleset) - { - Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); - - WorkingBeatmap = workingBeatmap; - // ReSharper disable once PossibleNullReferenceException - Mods = workingBeatmap.Mods.Value; - - RelativeSizeAxes = Axes.Both; - - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); - - KeyBindingInputManager = CreateInputManager(); - KeyBindingInputManager.RelativeSizeAxes = Axes.Both; - - applyBeatmapMods(Mods); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - KeyBindingInputManager.AddRange(new Drawable[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - Playfield - }); - - if (Cursor != null) - KeyBindingInputManager.Add(Cursor); - - InternalChildren = new Drawable[] - { - KeyBindingInputManager, - Overlays = new Container { RelativeSizeAxes = Axes.Both } - }; - - // Apply mods - applyRulesetMods(Mods, config); - - loadObjects(); - } - /// /// Applies the active mods to the Beatmap. /// @@ -288,7 +265,7 @@ namespace osu.Game.Rulesets.UI } /// - /// Applies the active mods to this RulesetContainer. + /// Applies the active mods to this DrawableRuleset. /// /// private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) @@ -296,83 +273,110 @@ namespace osu.Game.Rulesets.UI if (mods == null) return; - foreach (var mod in mods.OfType>()) - mod.ApplyToRulesetContainer(this); + foreach (var mod in mods.OfType>()) + mod.ApplyToDrawableRuleset(this); foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } - public override void SetReplayScore(Score replayScore) + #region IProvideCursor + + protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + + public override GameplayCursorContainer Cursor => Playfield.Cursor; + + public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; + + #endregion + + protected override void Dispose(bool isDisposing) { - base.SetReplayScore(replayScore); + base.Dispose(isDisposing); - if (ReplayInputManager?.ReplayInputHandler != null) - ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + if (Config != null) + { + onScreenDisplay?.StopTracking(this, Config); + Config = null; + } } - - /// - /// Creates and adds drawable representations of hit objects to the play field. - /// - private void loadObjects() - { - foreach (TObject h in Beatmap.HitObjects) - AddRepresentation(h); - - Playfield.PostProcess(); - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); - } - - /// - /// Creates and adds the visual representation of a to this . - /// - /// The to add the visual representation for. - internal void AddRepresentation(TObject hitObject) - { - var drawableObject = GetVisualRepresentation(hitObject); - - if (drawableObject == null) - return; - - drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); - drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); - - Playfield.Add(drawableObject); - } - - /// - /// Creates a DrawableHitObject from a HitObject. - /// - /// The HitObject to make drawable. - /// The DrawableHitObject. - public abstract DrawableHitObject GetVisualRepresentation(TObject h); } /// - /// A derivable RulesetContainer that manages the Playfield and HitObjects. + /// Displays an interactive ruleset gameplay instance. + /// + /// This type is required only for adding non-generic type to the draw hierarchy. + /// Once IDrawable is a thing, this can also become an interface. + /// /// - /// The type of Playfield contained by this RulesetContainer. - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - where TPlayfield : Playfield + public abstract class DrawableRuleset : CompositeDrawable { /// - /// The playfield. + /// Whether a replay is currently loaded. /// - protected new TPlayfield Playfield => (TPlayfield)base.Playfield; + public readonly BindableBool HasReplayLoaded = new BindableBool(); /// - /// Creates a hit renderer for a beatmap. + /// Whether the game is paused. Used to block user input. /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public readonly BindableBool IsPaused = new BindableBool(); + + /// ~ + /// The associated ruleset. + /// + public readonly Ruleset Ruleset; + + /// + /// Creates a ruleset visualisation for the provided ruleset. + /// + /// The ruleset. + internal DrawableRuleset(Ruleset ruleset) { + Ruleset = ruleset; } + + /// + /// All the converted hit objects contained by this hit renderer. + /// + public abstract IEnumerable Objects { get; } + + /// + /// The point in time at which gameplay starts, including any required lead-in for display purposes. + /// Defaults to two seconds before the first . Override as necessary. + /// + public abstract double GameplayStartTime { get; } + + /// + /// The currently loaded replay. Usually null in the case of a local player. + /// + public Score ReplayScore { get; protected set; } + + /// + /// The cursor being displayed by the . May be null if no cursor is provided. + /// + public abstract GameplayCursorContainer Cursor { get; } + + /// + /// Sets a replay to be used, overriding local input. + /// + /// The replay, null for local input. + public abstract void SetReplayScore(Score replayScore); + + /// + /// Invoked when the interactive user requests resuming from a paused state. + /// Allows potentially delaying the resume process until an interaction is performed. + /// + /// The action to run when resuming is to be completed. + public abstract void RequestResume(Action continueResume); + + /// + /// Create a for the associated ruleset and link with this + /// . + /// + /// A score processor. + public abstract ScoreProcessor CreateScoreProcessor(); } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs new file mode 100644 index 0000000000..deec2b8eac --- /dev/null +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -0,0 +1,156 @@ +// 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.Timing; +using osu.Game.Input.Handlers; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which consumes a parent gameplay clock and standardises frame counts for children. + /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. + /// + public class FrameStabilityContainer : Container, IHasReplayHandler + { + public FrameStabilityContainer() + { + RelativeSizeAxes = Axes.Both; + gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + } + + private readonly ManualClock manualClock; + + private readonly FramedClock framedClock; + + [Cached] + private GameplayClock gameplayClock; + + private IFrameBasedClock parentGameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) + { + parentGameplayClock = clock; + gameplayClock.IsPaused.BindTo(clock.IsPaused); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + setClock(); + } + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + private bool requireMoreUpdateLoops; + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + private bool validState; + + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + + private bool isAttached => ReplayInputHandler != null; + + private const int max_catch_up_updates_per_frame = 50; + + private const double sixty_frame_time = 1000.0 / 60; + + public override bool UpdateSubTree() + { + requireMoreUpdateLoops = true; + validState = !gameplayClock.IsPaused.Value; + + int loops = 0; + + while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + { + updateClock(); + + if (validState) + { + base.UpdateSubTree(); + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); + } + } + + return true; + } + + private void updateClock() + { + if (parentGameplayClock == null) + setClock(); // LoadComplete may not be run yet, but we still want the clock. + + validState = true; + + manualClock.Rate = parentGameplayClock.Rate; + manualClock.IsRunning = parentGameplayClock.IsRunning; + + var newProposedTime = parentGameplayClock.CurrentTime; + + try + { + if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = manualClock.Rate > 0 + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + } + + if (!isAttached) + { + manualClock.CurrentTime = newProposedTime; + } + else + { + double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); + + if (newTime == null) + { + // we shouldn't execute for this time value. probably waiting on more replay data. + validState = false; + + requireMoreUpdateLoops = true; + manualClock.CurrentTime = newProposedTime; + return; + } + + manualClock.CurrentTime = newTime.Value; + } + + requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; + } + finally + { + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + framedClock.ProcessFrame(); + } + } + + private void setClock() + { + // in case a parent gameplay clock isn't available, just use the parent clock. + if (parentGameplayClock == null) + parentGameplayClock = Clock; + + Clock = gameplayClock; + ProcessCustomClock = false; + } + + public ReplayInputHandler ReplayInputHandler { get; set; } + } +} diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs new file mode 100644 index 0000000000..de73c08809 --- /dev/null +++ b/osu.Game/Rulesets/UI/GameplayCursorContainer.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.Containers; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Rulesets.UI +{ + public class GameplayCursorContainer : CursorContainer + { + /// + /// 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. + /// + public Visibility LastFrameState; + + protected override void Update() + { + base.Update(); + LastFrameState = State; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 9f80dea9f7..f9f6b5cc2f 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -7,6 +7,7 @@ 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.Game.Graphics; using osu.Game.Rulesets.Mods; using osuTK; @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public FontAwesome Icon + public IconUsage Icon { get => modIcon.Icon; set => modIcon.Icon = value; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.UI Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(size), - Icon = FontAwesome.fa_osu_mod_bg, + Icon = OsuIcon.ModBg, Y = -6.5f, Shadow = true, }, diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 3b8a7353c6..48b950c070 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -63,6 +63,10 @@ namespace osu.Game.Rulesets.UI private void load(IBindable beatmap) { this.beatmap = beatmap.Value; + + Cursor = CreateCursor(); + if (Cursor != null) + AddInternal(Cursor); } /// @@ -82,6 +86,18 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject to remove. public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); + /// + /// The cursor currently being used by this . May be null if no cursor is provided. + /// + public GameplayCursorContainer Cursor { get; private set; } + + /// + /// 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; + /// /// Registers a as a nested . /// This does not add the to the draw hierarchy. diff --git a/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..fff4a450e5 --- /dev/null +++ b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which handles sizing of the and any other components that need to match their size. + /// + public class PlayfieldAdjustmentContainer : Container + { + public PlayfieldAdjustmentContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index fc61d41ab4..b4271085f5 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; @@ -36,11 +34,21 @@ namespace osu.Game.Rulesets.UI protected readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => KeyBindingContainer; + protected override Container Content => content; + + private readonly Container content; protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { - InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); + InternalChild = KeyBindingContainer = + CreateKeyBindingContainer(ruleset, variant, unique) + .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config) + { + mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } #region Action mapping (for replays) @@ -84,128 +92,10 @@ namespace osu.Game.Rulesets.UI #endregion - #region Clock control - - private ManualClock clock; - private IFrameBasedClock parentClock; - - protected override void LoadComplete() - { - base.LoadComplete(); - - //our clock will now be our parent's clock, but we want to replace this to allow manual control. - parentClock = Clock; - - ProcessCustomClock = false; - Clock = new FramedClock(clock = new ManualClock - { - CurrentTime = parentClock.CurrentTime, - Rate = parentClock.Rate, - }); - } - - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; - - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - - private bool isAttached => replayInputHandler != null && !UseParentInput; - - private const int max_catch_up_updates_per_frame = 50; - - private const double sixty_frame_time = 1000.0 / 60; - - public override bool UpdateSubTree() - { - requireMoreUpdateLoops = true; - validState = true; - - int loops = 0; - - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) - { - updateClock(); - - if (validState) - { - base.UpdateSubTree(); - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } - } - - return true; - } - - private void updateClock() - { - if (parentClock == null) return; - - clock.Rate = parentClock.Rate; - clock.IsRunning = parentClock.IsRunning; - - var newProposedTime = parentClock.CurrentTime; - - try - { - if (Math.Abs(clock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) - { - newProposedTime = clock.Rate > 0 - ? Math.Min(newProposedTime, clock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, clock.CurrentTime - sixty_frame_time); - } - - if (!isAttached) - { - clock.CurrentTime = newProposedTime; - } - else - { - double? newTime = replayInputHandler.SetFrameFromTime(newProposedTime); - - if (newTime == null) - { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - - requireMoreUpdateLoops = true; - clock.CurrentTime = newProposedTime; - return; - } - - clock.CurrentTime = newTime.Value; - } - - requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; - } - finally - { - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - Clock.ProcessFrame(); - } - } - - #endregion - #region Setting application (disables etc.) private Bindable mouseDisabled; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - } - protected override bool Handle(UIEvent e) { switch (e) @@ -229,18 +119,19 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public void Attach(KeyCounterCollection keyCounter) + public void Attach(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); - Add(receptor); - keyCounter.SetReceptor(receptor); + KeyBindingContainer.Add(receptor); + + keyCounter.SetReceptor(receptor); keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); } - public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler + public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { - public ActionReceptor(KeyCounterCollection target) + public ActionReceptor(KeyCounterDisplay target) : base(target) { } @@ -273,12 +164,12 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching a . + /// Supports attaching a . /// Keys will be populated automatically and a receptor will be injected inside. /// public interface ICanAttachKeyCounter { - void Attach(KeyCounterCollection keyCounter); + void Attach(KeyCounterDisplay keyCounter); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs similarity index 89% rename from osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs rename to osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 7a60e0b021..3b368652f2 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -20,12 +21,11 @@ using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { /// - /// A type of that supports a . - /// s inside this will scroll within the playfield. + /// A type of that supports a . + /// s inside this will scroll within the playfield. /// - public abstract class ScrollingRulesetContainer : RulesetContainer, IKeyBindingHandler + public abstract class DrawableScrollingRuleset : DrawableRuleset, IKeyBindingHandler where TObject : HitObject - where TPlayfield : ScrollingPlayfield { /// /// The default span of time visible by the length of the scrolling axes. @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Provides the default s that adjust the scrolling rate of s - /// inside this . + /// inside this . /// /// private readonly SortedList controlPoints = new SortedList(Comparer.Default); @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { scrollingInfo = new LocalScrollingInfo(); @@ -167,6 +167,14 @@ namespace osu.Game.Rulesets.UI.Scrolling return false; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!(Playfield is ScrollingPlayfield)) + throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); + } + public bool OnReleased(GlobalAction action) => false; private class LocalScrollingInfo : IScrollingInfo diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs new file mode 100644 index 0000000000..df80f848e3 --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring.Legacy +{ + public class LegacyScoreInfo : ScoreInfo + { + private int countGeki; + + public int CountGeki + { + get => countGeki; + set + { + countGeki = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 3: + Statistics[HitResult.Perfect] = value; + break; + } + } + } + + private int count300; + + public int Count300 + { + get => count300; + set + { + count300 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 1: + case 3: + Statistics[HitResult.Great] = value; + break; + case 2: + Statistics[HitResult.Perfect] = value; + break; + } + } + } + + private int countKatu; + + public int CountKatu + { + get => countKatu; + set + { + countKatu = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 3: + Statistics[HitResult.Good] = value; + break; + } + } + } + + private int count100; + + public int Count100 + { + get => count100; + set + { + count100 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 1: + Statistics[HitResult.Good] = value; + break; + case 3: + Statistics[HitResult.Ok] = value; + break; + } + } + } + + private int count50; + + public int Count50 + { + get => count50; + set + { + count50 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 3: + Statistics[HitResult.Meh] = value; + break; + } + } + } + + public int CountMiss + { + get => Statistics[HitResult.Miss]; + set => Statistics[HitResult.Miss] = value; + } + } +} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index ace8892330..3491a5779a 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.IO.Legacy; using osu.Game.Replays; @@ -34,7 +35,9 @@ namespace osu.Game.Scoring.Legacy using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); - score.ScoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + + score.ScoreInfo = scoreInfo; var version = sr.ReadInt32(); @@ -43,66 +46,39 @@ namespace osu.Game.Scoring.Legacy throw new BeatmapNotFoundException(); currentBeatmap = workingBeatmap.Beatmap; - score.ScoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; - score.ScoreInfo.User = new User { Username = sr.ReadString() }; + scoreInfo.User = new User { Username = sr.ReadString() }; // MD5Hash sr.ReadString(); - var count300 = (int)sr.ReadUInt16(); - var count100 = (int)sr.ReadUInt16(); - var count50 = (int)sr.ReadUInt16(); - var countGeki = (int)sr.ReadUInt16(); - var countKatu = (int)sr.ReadUInt16(); - var countMiss = (int)sr.ReadUInt16(); + scoreInfo.Count300 = sr.ReadUInt16(); + scoreInfo.Count100 = sr.ReadUInt16(); + scoreInfo.Count50 = sr.ReadUInt16(); + scoreInfo.CountGeki = sr.ReadUInt16(); + scoreInfo.CountKatu = sr.ReadUInt16(); + scoreInfo.CountMiss = sr.ReadUInt16(); - switch (currentRuleset.LegacyID) - { - case 0: - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = count100; - score.ScoreInfo.Statistics[HitResult.Meh] = count50; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - case 1: - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = count100; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - case 2: - score.ScoreInfo.Statistics[HitResult.Perfect] = count300; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - case 3: - score.ScoreInfo.Statistics[HitResult.Perfect] = countGeki; - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = countKatu; - score.ScoreInfo.Statistics[HitResult.Ok] = count100; - score.ScoreInfo.Statistics[HitResult.Meh] = count50; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - } - - score.ScoreInfo.TotalScore = sr.ReadInt32(); - score.ScoreInfo.MaxCombo = sr.ReadUInt16(); + scoreInfo.TotalScore = sr.ReadInt32(); + scoreInfo.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ sr.ReadBoolean(); - score.ScoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); /* score.HpGraphString = */ sr.ReadString(); - score.ScoreInfo.Date = sr.ReadDateTime(); + scoreInfo.Date = sr.ReadDateTime(); var compressedReplay = sr.ReadByteArray(); if (version >= 20140721) - score.ScoreInfo.OnlineScoreID = sr.ReadInt64(); + scoreInfo.OnlineScoreID = sr.ReadInt64(); else if (version >= 20121008) - score.ScoreInfo.OnlineScoreID = sr.ReadInt32(); + scoreInfo.OnlineScoreID = sr.ReadInt32(); if (compressedReplay?.Length > 0) { @@ -169,6 +145,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 1: { int totalHits = count50 + count100 + count300 + countMiss; @@ -191,6 +168,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 2: { int totalHits = count50 + count100 + count300 + countMiss + countKatu; @@ -210,6 +188,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 3: { int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; @@ -249,7 +228,7 @@ namespace osu.Game.Scoring.Legacy continue; } - var diff = float.Parse(split[0]); + var diff = Parsing.ParseFloat(split[0]); lastTime += diff; // Todo: At some point we probably want to rewind and play back the negative-time frames @@ -257,7 +236,10 @@ namespace osu.Game.Scoring.Legacy if (diff < 0) continue; - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); + replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, + Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), + Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), + (ReplayButtonState)Parsing.ParseInt(split[3])))); } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 6ae51796e8..d36f963016 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -178,5 +178,7 @@ namespace osu.Game.Scoring { public string Acronym { get; set; } } + + public override string ToString() => $"{User} playing {Beatmap}"; } } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index b010a70e66..5f82329496 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -11,6 +11,7 @@ namespace osu.Game.Screens public class BackgroundScreenStack : ScreenStack { public BackgroundScreenStack() + : base(false) { Scale = new Vector2(1.06f); RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 13d1ff39ef..6df418753c 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,8 +12,10 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenBeatmap : BlurrableBackgroundScreen + public class BackgroundScreenBeatmap : BackgroundScreen { + protected Background Background; + private WorkingBeatmap beatmap; /// @@ -22,11 +25,34 @@ namespace osu.Game.Screens.Backgrounds public readonly Bindable StoryboardReplacesBackground = new Bindable(); + /// + /// The amount of blur to be applied in addition to user-specified blur. + /// + public readonly Bindable BlurAmount = new Bindable(); + private readonly UserDimContainer fadeContainer; protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both }; - public virtual WorkingBeatmap Beatmap + public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) + { + Beatmap = beatmap; + InternalChild = fadeContainer = CreateFadeContainer(); + fadeContainer.EnableUserDim.BindTo(EnableUserDim); + fadeContainer.BlurAmount.BindTo(BlurAmount); + } + + [BackgroundDependencyLoader] + private void load() + { + var background = new BeatmapBackground(beatmap); + LoadComponent(background); + switchBackground(background); + } + + private CancellationTokenSource cancellationSource; + + public WorkingBeatmap Beatmap { get => beatmap; set @@ -38,54 +64,51 @@ namespace osu.Game.Screens.Backgrounds Schedule(() => { - LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => - { - float newDepth = 0; - if (Background != null) - { - newDepth = Background.Depth + 1; - Background.FinishTransforms(); - Background.FadeOut(250); - Background.Expire(); - } + if ((Background as BeatmapBackground)?.Beatmap == beatmap) + return; - b.Depth = newDepth; - fadeContainer.Add(Background = b); - Background.BlurSigma = BlurTarget; - StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); - })); + cancellationSource?.Cancel(); + LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token); }); } } - public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) + private void switchBackground(BeatmapBackground b) { - Beatmap = beatmap; - InternalChild = fadeContainer = CreateFadeContainer(); - fadeContainer.EnableUserDim.BindTo(EnableUserDim); + float newDepth = 0; + if (Background != null) + { + newDepth = Background.Depth + 1; + Background.FinishTransforms(); + Background.FadeOut(250); + Background.Expire(); + } + + b.Depth = newDepth; + fadeContainer.Background = Background = b; + StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); } public override bool Equals(BackgroundScreen other) { - var otherBeatmapBackground = other as BackgroundScreenBeatmap; - if (otherBeatmapBackground == null) return false; + if (!(other is BackgroundScreenBeatmap otherBeatmapBackground)) return false; return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; } protected class BeatmapBackground : Background { - private readonly WorkingBeatmap beatmap; + public readonly WorkingBeatmap Beatmap; public BeatmapBackground(WorkingBeatmap beatmap) { - this.beatmap = beatmap; + Beatmap = beatmap; } [BackgroundDependencyLoader] private void load(TextureStore textures) { - Sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); + Sprite.Texture = Beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 87a6b5d591..7092ac0c4a 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -13,8 +13,10 @@ using osu.Game.Users; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenDefault : BlurrableBackgroundScreen + public class BackgroundScreenDefault : BackgroundScreen { + private Background background; + private int currentDisplay; private const int background_count = 5; @@ -34,15 +36,15 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - Next(); + display(createBackground()); } private void display(Background newBackground) { - Background?.FadeOut(800, Easing.InOutSine); - Background?.Expire(); + background?.FadeOut(800, Easing.InOutSine); + background?.Expire(); - AddInternal(Background = newBackground); + AddInternal(background = newBackground); currentDisplay++; } @@ -51,19 +53,21 @@ namespace osu.Game.Screens.Backgrounds public void Next() { nextTask?.Cancel(); - nextTask = Scheduler.AddDelayed(() => - { - Background background; + nextTask = Scheduler.AddDelayed(() => { LoadComponentAsync(createBackground(), display); }, 100); + } - if (user.Value?.IsSupporter ?? false) - background = new SkinnedBackground(skin.Value, backgroundName); - else - background = new Background(backgroundName); + private Background createBackground() + { + Background newBackground; - background.Depth = currentDisplay; + if (user.Value?.IsSupporter ?? false) + newBackground = new SkinnedBackground(skin.Value, backgroundName); + else + newBackground = new Background(backgroundName); - LoadComponentAsync(background, display); - }, 100); + newBackground.Depth = currentDisplay; + + return newBackground; } private class SkinnedBackground : Background diff --git a/osu.Game/Screens/BlurrableBackgroundScreen.cs b/osu.Game/Screens/BlurrableBackgroundScreen.cs deleted file mode 100644 index d19e699acb..0000000000 --- a/osu.Game/Screens/BlurrableBackgroundScreen.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; -using osu.Game.Graphics.Backgrounds; -using osuTK; - -namespace osu.Game.Screens -{ - public abstract class BlurrableBackgroundScreen : BackgroundScreen - { - protected Background Background; - - protected Vector2 BlurTarget; - - public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) - { - BlurTarget = sigma; - return Background?.BlurTo(BlurTarget, duration, easing); - } - } -} diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 227ad29000..f5c9f74f62 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Timing; @@ -38,7 +39,7 @@ namespace osu.Game.Screens.Edit.Components Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Icon = FontAwesome.fa_play_circle_o, + Icon = FontAwesome.Regular.PlayCircle, Action = togglePause, Padding = new MarginPadding { Left = 20 } }, @@ -88,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); - playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } private class PlaybackTabControl : OsuTabControl diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 3f7672ae08..9a7ac8dfd0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -94,13 +94,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { new DivisorButton { - Icon = FontAwesome.fa_chevron_left, + Icon = FontAwesome.Solid.ChevronLeft, Action = beatDivisor.Previous }, new DivisorText(beatDivisor), new DivisorButton { - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, Action = beatDivisor.Next } }, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 3b24925f2c..863a120fc3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -4,6 +4,7 @@ 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.UserInterface; using osuTK; @@ -90,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.fa_search_plus, + Icon = FontAwesome.Solid.SearchPlus, Action = () => changeZoom(1) }, new TimelineButton @@ -99,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.fa_search_minus, + Icon = FontAwesome.Solid.SearchMinus, Action = () => changeZoom(-1) }, } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 5ded97393b..49e97e698b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -5,6 +5,7 @@ using System; 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.UserInterface; using osuTK; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action Action; public readonly BindableBool Enabled = new BindableBool(true); - public FontAwesome Icon + public IconUsage Icon { get => button.Icon; set => button.Icon = value; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f2d2381d20..0ba1e74aca 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit { this.host = host; - // TODO: should probably be done at a RulesetContainer level to share logic with Player. + // TODO: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index e665f401d9..b25bcdeab1 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens /// /// Whether a top-level component should be allowed to exit the current screen to, for example, - /// complete an import. + /// complete an import. Note that this can be overridden by a user if they specifically request. /// bool AllowExternalScreenChange { get; } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 0db0f1a1a3..fbe4b6311e 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -36,6 +36,7 @@ namespace osu.Game.Screens logo.BeatMatching = false; logo.Triangles = false; + logo.RelativePositionAxes = Axes.None; logo.Origin = Anchor.BottomRight; logo.Anchor = Anchor.BottomRight; logo.Position = new Vector2(-40); @@ -89,7 +90,7 @@ namespace osu.Game.Screens /// public class ShaderPrecompiler : Drawable { - private readonly List loadTargets = new List(); + private readonly List loadTargets = new List(); public bool FinishedCompiling { get; private set; } @@ -106,7 +107,7 @@ namespace osu.Game.Screens loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); } - protected virtual bool AllLoaded => loadTargets.All(s => s.Loaded); + protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded); protected override void Update() { diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index a02c2a37fa..794fc093d3 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample; 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; using osuTK.Graphics; @@ -17,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.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Menu public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); - public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) + public Button(string text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) { this.sampleName = sampleName; this.clickAction = clickAction; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1f68818669..d3cf23dab8 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -11,6 +11,7 @@ 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.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; @@ -79,8 +80,8 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new[] { - new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) + 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, }, @@ -97,7 +98,7 @@ namespace osu.Game.Screens.Menu private OsuGame game { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } @@ -105,17 +106,17 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { - buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); - buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); + buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); + buttonsPlay.Add(new Button(@"chart", @"button-generic-select", OsuIcon.Charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); + buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); + buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); @@ -134,7 +135,7 @@ namespace osu.Game.Screens.Menu notifications?.Post(new SimpleNotification { Text = "You gotta be logged in to multi 'yo!", - Icon = FontAwesome.fa_globe + Icon = FontAwesome.Solid.Globe }); return; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 89f4f92092..0130a5143b 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -2,11 +2,12 @@ // 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.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; @@ -25,16 +26,17 @@ namespace osu.Game.Screens.Menu private SpriteIcon icon; private Color4 iconColour; private LinkFlowContainer textFlow; + private LinkFlowContainer supportFlow; public override bool HideOverlaysOnEnter => true; public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; public override bool CursorVisible => false; - private readonly List supporterDrawables = new List(); private Drawable heart; private const float icon_y = -85; + private const float icon_size = 30; private readonly Bindable currentUser = new Bindable(); @@ -44,7 +46,7 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { InternalChildren = new Drawable[] { @@ -52,20 +54,40 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_warning, - Size = new Vector2(30), + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(icon_size), Y = icon_y, }, - textFlow = new LinkFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(50), - TextAnchor = Anchor.TopCentre, - Y = -110, + Direction = FillDirection.Vertical, + Y = icon_y + icon_size, Anchor = Anchor.Centre, Origin = Anchor.TopCentre, - Spacing = new Vector2(0, 2), + Children = new Drawable[] + { + textFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(0, 2), + }, + supportFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + Spacing = new Vector2(0, 2), + }, + } } }; @@ -88,28 +110,45 @@ namespace osu.Game.Screens.Menu textFlow.NewParagraph(); textFlow.NewParagraph(); - supporterDrawables.AddRange(textFlow.AddText("Consider becoming an ", format)); - supporterDrawables.AddRange(textFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format)); - supporterDrawables.AddRange(textFlow.AddText(" to help support the game", format)); - - supporterDrawables.Add(heart = textFlow.AddIcon(FontAwesome.fa_heart, t => - { - t.Padding = new MarginPadding { Left = 5 }; - t.Font = t.Font.With(size: 12); - t.Colour = colours.Pink; - t.Origin = Anchor.Centre; - }).First()); - iconColour = colours.Yellow; currentUser.BindTo(api.LocalUser); currentUser.BindValueChanged(e => { + supportFlow.Children.ForEach(d => d.FadeOut().Expire()); + if (e.NewValue.IsSupporter) - supporterDrawables.ForEach(d => d.FadeOut(500, Easing.OutQuint).Expire()); + { + supportFlow.AddText("Thank you for supporting osu!", format); + } + else + { + supportFlow.AddText("Consider becoming an ", format); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); + supportFlow.AddText(" to help support the game", format); + } + + heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => + { + t.Padding = new MarginPadding { Left = 5 }; + t.Font = t.Font.With(size: 12); + t.Origin = Anchor.Centre; + t.Colour = colours.Pink; + }).First(); + + if (IsLoaded) + animateHeart(); + + if (supportFlow.IsPresent) + supportFlow.FadeInFromZero(500); }, true); } + private void animateHeart() + { + heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -128,7 +167,9 @@ namespace osu.Game.Screens.Menu .MoveToY(icon_y, 160, Easing.InCirc) .RotateTo(0, 160, Easing.InCirc); - supporterDrawables.ForEach(d => d.FadeOut().Delay(2000).FadeIn(500)); + supportFlow.FadeOut().Delay(2000).FadeIn(500); + + animateHeart(); this .FadeInFromZero(500) @@ -136,8 +177,6 @@ namespace osu.Game.Screens.Menu .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) .Finally(d => this.Push(intro)); - - heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); } } } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index e930f924be..8283bf7ea2 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -12,9 +12,13 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Menu { @@ -63,21 +67,28 @@ namespace osu.Game.Screens.Menu private readonly float[] frequencyAmplitudes = new float[256]; - private Shader shader; + private IShader shader; private readonly Texture texture; + private Bindable user; + private Bindable skin; + public LogoVisualisation() { texture = Texture.WhitePixel; - AccentColour = new Color4(1, 1, 1, 0.2f); Blending = BlendingMode.Additive; } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IBindable beatmap) + private void load(ShaderManager shaders, IBindable beatmap, IAPIProvider api, SkinManager skinManager) { this.beatmap.BindTo(beatmap); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } private void updateAmplitudes() @@ -107,6 +118,16 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(updateAmplitudes, time_between_updates); } + private void updateColour() + { + Color4 defaultColour = Color4.White.Opacity(0.2f); + + if (user.Value?.IsSupporter ?? false) + AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour; + else + AccentColour = defaultColour; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -131,8 +152,6 @@ namespace osu.Game.Screens.Menu protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(); - private readonly VisualiserSharedData sharedData = new VisualiserSharedData(); - protected override void ApplyDrawNode(DrawNode node) { base.ApplyDrawNode(node); @@ -142,29 +161,23 @@ namespace osu.Game.Screens.Menu visNode.Shader = shader; visNode.Texture = texture; visNode.Size = DrawSize.X; - visNode.Shared = sharedData; visNode.Colour = AccentColour; visNode.AudioData = frequencyAmplitudes; } - private class VisualiserSharedData - { - public readonly QuadBatch VertexBatch = new QuadBatch(100, 10); - } - private class VisualisationDrawNode : DrawNode { - public Shader Shader; + public IShader Shader; public Texture Texture; - public VisualiserSharedData Shared; - //Asuming the logo is a circle, we don't need a second dimension. public float Size; public Color4 Colour; public float[] AudioData; + private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + public override void Draw(Action vertexAction) { base.Draw(vertexAction); @@ -209,7 +222,7 @@ namespace osu.Game.Screens.Menu rectangle, colourInfo, null, - Shared.VertexBatch.AddAction, + vertexBatch.AddAction, //barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. Vector2.Divide(inflation, barSize.Yx)); } @@ -218,6 +231,13 @@ namespace osu.Game.Screens.Menu Shader.Unbind(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); + } } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index f717e52db0..65d8546b87 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -37,7 +37,9 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); + private BackgroundScreenDefault background; + + protected override BackgroundScreen CreateBackground() => background; [BackgroundDependencyLoader(true)] private void load(OsuGame game = null) @@ -86,6 +88,7 @@ namespace osu.Game.Screens.Menu buttons.OnDirect = game.ToggleDirect; } + LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index ce0a38ba8d..95d0bf04b4 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -12,6 +12,9 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using System; using osu.Framework.Bindables; @@ -32,6 +35,12 @@ namespace osu.Game.Screens.Menu private const double box_fade_in_time = 65; private const int box_width = 200; + private Bindable user; + private Bindable skin; + + [Resolved] + private OsuColour colours { get; set; } + public MenuSideFlashes() { EarlyActivationMilliseconds = box_fade_in_time; @@ -42,13 +51,12 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours) + private void load(IBindable beatmap, IAPIProvider api, SkinManager skinManager) { this.beatmap.BindTo(beatmap); - // linear colour looks better in this case, so let's use it for now. - Color4 gradientDark = colours.Blue.Opacity(0).ToLinear(); - Color4 gradientLight = colours.Blue.Opacity(0.6f).ToLinear(); + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); Children = new Drawable[] { @@ -62,8 +70,7 @@ namespace osu.Game.Screens.Menu // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, Alpha = 0, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark) + Blending = BlendingMode.Additive }, rightBox = new Box { @@ -74,10 +81,12 @@ namespace osu.Game.Screens.Menu Height = 1.5f, X = box_width, Alpha = 0, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight) + Blending = BlendingMode.Additive } }; + + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) @@ -97,5 +106,20 @@ namespace osu.Game.Screens.Menu .Then() .FadeOut(beatLength, Easing.In); } + + private void updateColour() + { + Color4 baseColour = colours.Blue; + + if (user.Value?.IsSupporter ?? false) + baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour; + + // linear colour looks better in this case, so let's use it for now. + Color4 gradientDark = baseColour.Opacity(0).ToLinear(); + Color4 gradientLight = baseColour.Opacity(0.6f).ToLinear(); + + leftBox.Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark); + rightBox.Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight); + } } } diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 8ab23a620b..6080458aec 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Multi.Components private const float height = 30; private const float transition_duration = 100; - private Container rulesetContainer; + private Container drawableRuleset; public ModeTypeInfo() { @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Multi.Components LayoutDuration = 100, Children = new[] { - rulesetContainer = new Container + drawableRuleset = new Container { AutoSizeAxes = Axes.Both, }, @@ -55,11 +55,11 @@ namespace osu.Game.Screens.Multi.Components { if (item?.Beatmap != null) { - rulesetContainer.FadeIn(transition_duration); - rulesetContainer.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; + drawableRuleset.FadeIn(transition_duration); + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; } else - rulesetContainer.FadeOut(transition_duration); + drawableRuleset.FadeOut(transition_duration); } } } diff --git a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs index 512f79942d..968fa6e72e 100644 --- a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs +++ b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs @@ -9,6 +9,13 @@ namespace osu.Game.Screens.Multi.Components { public class MultiplayerBackgroundSprite : MultiplayerComposite { + private readonly BeatmapSetCoverType beatmapSetCoverType; + + public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) + { + this.beatmapSetCoverType = beatmapSetCoverType; + } + [BackgroundDependencyLoader] private void load() { @@ -19,6 +26,6 @@ namespace osu.Game.Screens.Multi.Components CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true); } - protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; } } diff --git a/osu.Game/Screens/Multi/Components/ParticipantCount.cs b/osu.Game/Screens/Multi/Components/ParticipantCountDisplay.cs similarity index 100% rename from osu.Game/Screens/Multi/Components/ParticipantCount.cs rename to osu.Game/Screens/Multi/Components/ParticipantCountDisplay.cs diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 0e958bf523..7924086389 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -7,10 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList; -using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Multi @@ -19,11 +17,11 @@ namespace osu.Game.Screens.Multi { public const float HEIGHT = 121; - private readonly OsuSpriteText screenType; private readonly HeaderBreadcrumbControl breadcrumbs; public Header(ScreenStack stack) { + MultiHeaderTitle title; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -40,39 +38,11 @@ namespace osu.Game.Screens.Multi Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - new FillFlowContainer + title = new MultiHeaderTitle { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - Position = new Vector2(-35f, 5f), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Size = new Vector2(25), - Icon = FontAwesome.fa_osu_multi, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "multiplayer ", - Font = OsuFont.GetFont(size: 25) - }, - screenType = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Light, size: 25) - }, - }, - }, - }, + X = -35, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { @@ -84,10 +54,10 @@ namespace osu.Game.Screens.Multi }, }; - breadcrumbs.Current.ValueChanged += scren => + breadcrumbs.Current.ValueChanged += screen => { - if (scren.NewValue is IMultiplayerSubScreen multiScreen) - screenType.Text = multiScreen.ShortTitle.ToLowerInvariant(); + if (screen.NewValue is IMultiplayerSubScreen multiScreen) + title.Screen = multiScreen; }; breadcrumbs.Current.TriggerChange(); @@ -96,10 +66,25 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - screenType.Colour = colours.Yellow; breadcrumbs.StripColour = colours.Green; } + private class MultiHeaderTitle : ScreenTitle + { + public IMultiplayerSubScreen Screen + { + set => Section = value.ShortTitle.ToLowerInvariant(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Title = "multiplayer"; + Icon = OsuIcon.Multi; + AccentColour = colours.Yellow; + } + } + private class HeaderBreadcrumbControl : ScreenBreadcrumbControl { public HeaderBreadcrumbControl(ScreenStack stack) diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index e41238a7ff..dce597b276 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -76,6 +77,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } + public bool FilteringActive { get; set; } + public DrawableRoom(Room room) { Room = room; @@ -137,7 +140,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components Width = cover_width, Masking = true, Margin = new MarginPadding { Left = side_strip_width }, - Child = new MultiplayerBackgroundSprite { RelativeSizeAxes = Axes.Both } + Child = new MultiplayerBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both } }, new Container { diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index fd9c8d7b35..5798fce457 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -246,7 +246,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } private GetRoomScoresRequest request; diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index dd1e060125..7cbae611ea 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -107,6 +107,14 @@ namespace osu.Game.Screens.Multi.Lounge Filter.Search.HoldFocus = false; } + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + + if (currentRoom.Value?.RoomID.Value == null) + currentRoom.Value = new Room(); + } + private void joinRequested(Room room) { processingOverlay.Show(); diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index 6a6a1f274c..e1592532a3 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -2,6 +2,7 @@ // 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; @@ -13,6 +14,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Overlays.SearchableList; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Play.HUD; using osuTK; @@ -108,7 +110,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods, true); + CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty(), true); beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs index bd8e4c7335..f8b64a54ef 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs @@ -28,13 +28,15 @@ namespace osu.Game.Screens.Multi.Match.Components { base.LoadComplete(); - roomId.BindValueChanged(_ => updateChannel(), true); + channelId.BindValueChanged(_ => updateChannel(), true); } private void updateChannel() { - if (roomId.Value != null) - Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#mp_{roomId.Value}" }); + if (roomId.Value == null || channelId.Value == 0) + return; + + Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" }); } } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index ed09203f96..92074abe4b 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; @@ -25,9 +25,9 @@ namespace osu.Game.Screens.Multi.Match.Components protected override IEnumerable GetStatistics(ScoreInfo model) => new[] { - new LeaderboardScoreStatistic(FontAwesome.fa_crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), - new LeaderboardScoreStatistic(FontAwesome.fa_refresh, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), - new LeaderboardScoreStatistic(FontAwesome.fa_check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), + new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), }; } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index b310e62d7c..586a986111 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -316,8 +316,12 @@ namespace osu.Game.Screens.Multi.Match.Components private class SettingsTextBox : OsuTextBox { - protected override Color4 BackgroundUnfocused => Color4.Black; - protected override Color4 BackgroundFocused => Color4.Black; + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = Color4.Black; + BackgroundFocused = Color4.Black; + } } private class SettingsNumberTextBox : SettingsTextBox @@ -327,8 +331,12 @@ namespace osu.Game.Screens.Multi.Match.Components private class SettingsPasswordTextBox : OsuPasswordTextBox { - protected override Color4 BackgroundUnfocused => Color4.Black; - protected override Color4 BackgroundFocused => Color4.Black; + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = Color4.Black; + BackgroundFocused = Color4.Black; + } } private class SectionContainer : FillFlowContainer
diff --git a/osu.Game/Screens/Multi/Match/Components/Participants.cs b/osu.Game/Screens/Multi/Match/Components/Participants.cs index 2d6099c65d..09d25572ec 100644 --- a/osu.Game/Screens/Multi/Match/Components/Participants.cs +++ b/osu.Game/Screens/Multi/Match/Components/Participants.cs @@ -52,12 +52,18 @@ namespace osu.Game.Screens.Multi.Match.Components Participants.BindValueChanged(participants => { - usersFlow.Children = participants.NewValue.Select(u => new UserPanel(u) + usersFlow.Children = participants.NewValue.Select(u => { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 300, - OnLoadComplete = d => d.FadeInFromZero(60), + var panel = new UserPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + }; + + panel.OnLoadComplete += d => d.FadeInFromZero(60); + + return panel; }).ToList(); }, true); } diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index b299e70962..86b4cfbfa7 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi.Match.Components hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null; } - private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) + private void beatmapAdded(BeatmapSetInfo model, bool existing) { if (Beatmap.Value == null) return; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 229b9bde6a..a71106872e 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -202,7 +202,7 @@ namespace osu.Game.Screens.Multi.Match /// /// Handle the case where a beatmap is imported (and can be used by this match). /// - private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) => Schedule(() => + private void beatmapAdded(BeatmapSetInfo model, bool existing) => Schedule(() => { if (Beatmap.Value != beatmapManager.DefaultBeatmap) return; diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index dd01ae4160..5e019a7b3a 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi private OsuGameBase game { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved(CanBeNull = true)] private OsuLogo logo { get; set; } @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both } + Child = screenStack = new OsuScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both } }, new Header(screenStack), createButton = new HeaderButton @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Multi this.Push(new PlayerLoader(player)); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { if (state != APIState.Online) forcefullyExit(); diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index ad72072981..65e501b114 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi { public override bool DisallowExternalBeatmapRulesetChanges => false; - public override bool RemoveWhenNotAlive => false; - public virtual string ShortTitle => Title; [Resolved(CanBeNull = true)] diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 6b88403b9e..d5b8f1f0c8 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Play private readonly PlaylistItem playlistItem; [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private IBindable ruleset { get; set; } diff --git a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs index 6cc13f88a5..dcfad8458f 100644 --- a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs +++ b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking.Pages; using osu.Game.Screens.Ranking; @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi.Ranking.Types this.beatmap = beatmap; } - public FontAwesome Icon => FontAwesome.fa_users; + public IconUsage Icon => FontAwesome.Solid.Users; public string Name => "Room Leaderboard"; diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index c15a8471a1..385cbe20e5 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Multi private Bindable currentFilter { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private RulesetStore rulesets { get; set; } diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs new file mode 100644 index 0000000000..0844e32d46 --- /dev/null +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens +{ + public class OsuScreenStack : ScreenStack + { + [Cached] + private BackgroundScreenStack backgroundScreenStack; + + private ParallaxContainer parallaxContainer; + + protected float ParallaxAmount => parallaxContainer.ParallaxAmount; + + public OsuScreenStack() + { + initializeStack(); + } + + public OsuScreenStack(IScreen baseScreen) + : base(baseScreen) + { + initializeStack(); + } + + private void initializeStack() + { + InternalChild = parallaxContainer = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + }; + + ScreenPushed += onScreenChange; + ScreenExited += onScreenChange; + } + + private void onScreenChange(IScreen prev, IScreen next) + { + parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f; + } + } +} diff --git a/osu.Game/Screens/Play/Break/BlurredIcon.cs b/osu.Game/Screens/Play/Break/BlurredIcon.cs index 53b968959c..a88112a0db 100644 --- a/osu.Game/Screens/Play/Break/BlurredIcon.cs +++ b/osu.Game/Screens/Play/Break/BlurredIcon.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -13,7 +14,7 @@ namespace osu.Game.Screens.Play.Break { private readonly SpriteIcon icon; - public FontAwesome Icon + public IconUsage Icon { set => icon.Icon = value; get => icon.Icon; diff --git a/osu.Game/Screens/Play/Break/BreakArrows.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs index 9d9f0ab898..4b96fa666a 100644 --- a/osu.Game/Screens/Play/Break/BreakArrows.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osuTK; @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreRight, X = -glow_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, X = glow_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, + Icon = FontAwesome.Solid.ChevronLeft, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -67,7 +68,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreRight, Alpha = 0.7f, X = -blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, @@ -77,7 +78,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreLeft, Alpha = 0.7f, X = blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, + Icon = FontAwesome.Solid.ChevronLeft, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, diff --git a/osu.Game/Screens/Play/Break/GlowIcon.cs b/osu.Game/Screens/Play/Break/GlowIcon.cs index 8d918cd225..2810389619 100644 --- a/osu.Game/Screens/Play/Break/GlowIcon.cs +++ b/osu.Game/Screens/Play/Break/GlowIcon.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -30,7 +31,7 @@ namespace osu.Game.Screens.Play.Break set => blurredIcon.BlurSigma = value; } - public FontAwesome Icon + public IconUsage Icon { get => spriteIcon.Icon; set => spriteIcon.Icon = blurredIcon.Icon = value; diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 024ce01dc6..d390787090 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -40,13 +41,7 @@ namespace osu.Game.Screens.Play private readonly BreakInfo info; private readonly BreakArrows breakArrows; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) - : this(letterboxing) - { - bindProcessor(scoreProcessor); - } - - public BreakOverlay(bool letterboxing) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) { RelativeSizeAxes = Axes.Both; Child = fadeContainer = new Container @@ -98,6 +93,14 @@ namespace osu.Game.Screens.Play } } }; + + if (scoreProcessor != null) bindProcessor(scoreProcessor); + } + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) Clock = clock; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs new file mode 100644 index 0000000000..3efcfa0f65 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClock.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.Bindables; +using osu.Framework.Timing; + +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 . + /// + /// 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. + /// + /// + public class GameplayClock : IFrameBasedClock + { + private readonly IFrameBasedClock underlyingClock; + + public readonly BindableBool IsPaused = new BindableBool(); + + public GameplayClock(IFrameBasedClock underlyingClock) + { + this.underlyingClock = underlyingClock; + } + + public double CurrentTime => underlyingClock.CurrentTime; + + public double Rate => underlyingClock.Rate; + + public bool IsRunning => underlyingClock.IsRunning; + + public void ProcessFrame() + { + // we do not want to process the underlying clock. + // this is handled by PauseContainer. + } + + public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + + public double FramesPerSecond => underlyingClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + } +} diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs new file mode 100644 index 0000000000..c13222c6de --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -0,0 +1,161 @@ +// 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.Tasks; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.Play +{ + /// + /// Encapsulates gameplay timing logic and provides a for children. + /// + public class GameplayClockContainer : Container + { + private readonly WorkingBeatmap beatmap; + + /// + /// The original source (usually a 's track). + /// + private readonly IAdjustableClock sourceClock; + + public readonly BindableBool IsPaused = new BindableBool(); + + /// + /// The decoupled clock used for gameplay. Should be used for seeks and clock control. + /// + private readonly DecoupleableInterpolatingFramedClock adjustableClock; + + public readonly Bindable UserPlaybackRate = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.1, + }; + + /// + /// The final clock which is exposed to underlying components. + /// + [Cached] + public readonly GameplayClock GameplayClock; + + private Bindable userAudioOffset; + + private readonly FramedOffsetClock offsetClock; + + public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) + { + this.beatmap = beatmap; + + RelativeSizeAxes = Axes.Both; + + sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); + + adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + + adjustableClock.Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)); + + adjustableClock.ProcessFrame(); + + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; + + // the final usable gameplay clock with user-set offsets applied. + offsetClock = new FramedOffsetClock(platformOffsetClock); + + // the clock to be exposed via DI to children. + GameplayClock = new GameplayClock(offsetClock); + + GameplayClock.IsPaused.BindTo(IsPaused); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); + userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true); + + UserPlaybackRate.ValueChanged += _ => updateRate(); + } + + public void Restart() + { + Task.Run(() => + { + sourceClock.Reset(); + + Schedule(() => + { + adjustableClock.ChangeSource(sourceClock); + updateRate(); + + this.Delay(750).Schedule(() => + { + if (!IsPaused.Value) + { + adjustableClock.Start(); + } + }); + }); + }); + } + + public void Start() + { + // 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); + adjustableClock.Start(); + IsPaused.Value = false; + } + + public void Seek(double time) => adjustableClock.Seek(time); + + public void Stop() + { + adjustableClock.Stop(); + IsPaused.Value = true; + } + + public void ResetLocalAdjustments() + { + // In the case of replays, we may have changed the playback rate. + UserPlaybackRate.Value = 1; + } + + protected override void Update() + { + if (!IsPaused.Value) + offsetClock.ProcessFrame(); + + base.Update(); + } + + private void updateRate() + { + if (sourceClock == null) return; + + sourceClock.ResetSpeedAdjustments(); + + if (sourceClock is IHasTempoAdjust tempo) + tempo.TempoAdjust = UserPlaybackRate.Value; + else + sourceClock.Rate = UserPlaybackRate.Value; + + foreach (var mod in beatmap.Mods.Value.OfType()) + mod.ApplyToClock(sourceClock); + } + } +} diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 5d210446c3..2fac8de799 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -38,9 +38,15 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Children.Last().Click(); + protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); + + /// + /// Action that is invoked when is triggered. + /// + protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click(); public abstract string Header { get; } + public abstract string Description { get; } protected internal FillFlowContainer InternalButtons; @@ -229,16 +235,30 @@ namespace osu.Game.Screens.Play public bool OnPressed(GlobalAction action) { - if (action == GlobalAction.Back) + switch (action) { - BackAction.Invoke(); - return true; + case GlobalAction.Back: + BackAction.Invoke(); + return true; + case GlobalAction.Select: + SelectAction.Invoke(); + return true; } return false; } - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + case GlobalAction.Select: + return true; + } + + return false; + } private void buttonSelectionChanged(DialogButton button, bool isSelected) { @@ -288,15 +308,6 @@ namespace osu.Game.Screens.Play Selected.Value = true; return base.OnMouseMove(e); } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat || e.Key != Key.Enter || !Selected.Value) - return false; - - Click(); - return true; - } } } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index ca4cce8929..6883f246e4 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -92,30 +93,6 @@ namespace osu.Game.Screens.Play.HUD public Action HoverGained; public Action HoverLost; - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - BeginConfirm(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - AbortConfirm(); - return true; - } - - return false; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -152,7 +129,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.fa_close + Icon = FontAwesome.Solid.TimesCircle }, } }; @@ -178,7 +155,7 @@ namespace osu.Game.Screens.Play.HUD // avoid starting a new confirm call until we finish animating. pendingAnimation = true; - Progress.Value = 0; + AbortConfirm(); overlayCircle.ScaleTo(0, 100) .Then().FadeOut().ScaleTo(1).FadeIn(500) @@ -207,6 +184,31 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + if (!pendingAnimation) + BeginConfirm(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + AbortConfirm(); + return true; + } + + return false; + } + protected override bool OnMouseDown(MouseDownEvent e) { if (!pendingAnimation && e.CurrentState.Mouse.Buttons.Count() == 1) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index d3dba88281..e99f6d836e 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -19,7 +19,9 @@ namespace osu.Game.Screens.Play.HUD public readonly PlaybackSettings PlaybackSettings; public readonly VisualSettings VisualSettings; + //public readonly CollectionSettings CollectionSettings; + //public readonly DiscussionSettings DiscussionSettings; public PlayerSettingsOverlay() diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 130d2ecc95..a7b7f96e7a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -1,12 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play { private const int duration = 100; - public readonly KeyCounterCollection KeyCounter; + public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; public readonly ScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; @@ -40,7 +40,9 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock) + public Action RequestSeek; + + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working) { RelativeSizeAxes = Axes.Both; @@ -81,23 +83,20 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { - KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), + KeyCounter = CreateKeyCounter(), HoldToQuit = CreateHoldForMenuButton(), } } }; BindProcessor(scoreProcessor); - BindRulesetContainer(rulesetContainer); + BindDrawableRuleset(drawableRuleset); - Progress.Objects = rulesetContainer.Objects; - Progress.AudioClock = offsetClock; - Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value; - Progress.OnSeek = pos => adjustableClock.Seek(pos); + Progress.Objects = drawableRuleset.Objects; + Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; + Progress.RequestSeek = time => RequestSeek(time); ModDisplay.Current.BindTo(working.Mods); - - PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock; } [BackgroundDependencyLoader(true)] @@ -144,13 +143,13 @@ namespace osu.Game.Screens.Play } } - protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer) + protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - Progress.BindRulestContainer(rulesetContainer); + Progress.BindDrawableRuleset(drawableRuleset); } protected override bool OnKeyDown(KeyDownEvent e) @@ -202,13 +201,12 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection + protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), - AudioClock = offsetClock }; protected virtual SongProgress CreateProgress() => new SongProgress diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 0e1f938137..0626c40334 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -71,9 +71,12 @@ namespace osu.Game.Screens.Play Name = name; } - [BackgroundDependencyLoader] - private void load(TextureStore textures) + [BackgroundDependencyLoader(true)] + private void load(TextureStore textures, GameplayClock clock) { + if (clock != null) + Clock = clock; + Children = new Drawable[] { buttonSprite = new Sprite diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs similarity index 90% rename from osu.Game/Screens/Play/KeyCounterCollection.cs rename to osu.Game/Screens/Play/KeyCounterDisplay.cs index 0259258636..d5967f5899 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -8,21 +8,20 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Configuration; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Play { - public class KeyCounterCollection : FillFlowContainer + public class KeyCounterDisplay : FillFlowContainer { private const int duration = 100; public readonly Bindable Visible = new Bindable(true); private readonly Bindable configVisibility = new Bindable(); - public KeyCounterCollection() + public KeyCounterDisplay() { Direction = FillDirection.Horizontal; AutoSizeAxes = Axes.Both; @@ -37,9 +36,6 @@ namespace osu.Game.Screens.Play key.FadeTime = FadeTime; key.KeyDownTextColor = KeyDownTextColor; key.KeyUpTextColor = KeyUpTextColor; - // Use the same clock object as SongProgress for saving KeyCounter state - if (AudioClock != null) - key.Clock = AudioClock; } public void ResetCount() @@ -125,8 +121,6 @@ namespace osu.Game.Screens.Play public override bool HandleNonPositionalInput => receptor == null; public override bool HandlePositionalInput => receptor == null; - public IFrameBasedClock AudioClock { get; set; } - private Receptor receptor; public Receptor GetReceptor() @@ -144,9 +138,9 @@ namespace osu.Game.Screens.Play public class Receptor : Drawable { - protected readonly KeyCounterCollection Target; + protected readonly KeyCounterDisplay Target; - public Receptor(KeyCounterCollection target) + public Receptor(KeyCounterDisplay target) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs deleted file mode 100644 index 8961d91763..0000000000 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - /// - /// A container which handles pausing children, displaying a pause overlay with choices etc. - /// This alleviates a lot of the intricate pause logic from being in - /// - public class PauseContainer : Container - { - public readonly BindableBool IsPaused = new BindableBool(); - - public Func CheckCanPause; - - private const double pause_cooldown = 1000; - private double lastPauseActionTime; - - private readonly PauseOverlay pauseOverlay; - - private readonly Container content; - - protected override Container Content => content; - - public int Retries - { - set => pauseOverlay.Retries = value; - } - - public bool CanPause => (CheckCanPause?.Invoke() ?? true) && Time.Current >= lastPauseActionTime + pause_cooldown; - public bool IsResuming { get; private set; } - - public Action OnRetry; - public Action OnQuit; - - private readonly FramedClock framedClock; - private readonly DecoupleableInterpolatingFramedClock decoupledClock; - - /// - /// Creates a new . - /// - /// The gameplay clock. This is the clock that will process frames. - /// The seekable clock. This is the clock that will be paused and resumed. - public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock) - { - this.framedClock = framedClock; - this.decoupledClock = decoupledClock; - - RelativeSizeAxes = Axes.Both; - - AddInternal(content = new Container - { - Clock = this.framedClock, - ProcessCustomClock = false, - RelativeSizeAxes = Axes.Both - }); - - AddInternal(pauseOverlay = new PauseOverlay - { - OnResume = () => - { - IsResuming = true; - this.Delay(400).Schedule(Resume); - }, - OnRetry = () => OnRetry(), - OnQuit = () => OnQuit(), - }); - } - - public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. - { - if (!CanPause && !force) return; - - if (IsPaused.Value) return; - - // stop the seekable clock (stops the audio eventually) - decoupledClock.Stop(); - IsPaused.Value = true; - - pauseOverlay.Show(); - - lastPauseActionTime = Time.Current; - }); - - public void Resume() - { - if (!IsPaused.Value) return; - - IsPaused.Value = false; - IsResuming = false; - lastPauseActionTime = Time.Current; - - // 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 - decoupledClock.Seek(decoupledClock.CurrentTime); - decoupledClock.Start(); - - pauseOverlay.Hide(); - } - - private OsuGameBase game; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - this.game = game; - } - - protected override void Update() - { - // eagerly pause when we lose window focus (if we are locally playing). - if (!game.IsActive.Value && CanPause) - Pause(); - - if (!IsPaused.Value) - framedClock.ProcessFrame(); - - base.Update(); - } - - public class PauseOverlay : GameplayMenuOverlay - { - public Action OnResume; - - public override string Header => "paused"; - public override string Description => "you're not going to do what i think you're going to do, are ya?"; - - protected override Action BackAction => () => InternalButtons.Children.First().Click(); - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddButton("Continue", colours.Green, () => OnResume?.Invoke()); - AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); - AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - } - } - } -} diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs new file mode 100644 index 0000000000..6cc6027a03 --- /dev/null +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class PauseOverlay : GameplayMenuOverlay + { + public Action OnResume; + + public override string Header => "paused"; + public override string Description => "you're not going to do what i think you're going to do, are ya?"; + + protected override Action BackAction => () => InternalButtons.Children.First().Click(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddButton("Continue", colours.Green, () => OnResume?.Invoke()); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bb2211a533..0eebefec86 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -3,24 +3,19 @@ using System; using System.Linq; -using System.Threading.Tasks; -using osu.Framework; 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.Cursor; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -34,7 +29,7 @@ using osu.Game.Storyboards.Drawables; namespace osu.Game.Screens.Play { - public class Player : ScreenWithBeatmapBackground, IProvideCursor + public class Player : ScreenWithBeatmapBackground { protected override bool AllowBackButton => false; // handled by HoldForMenuButton @@ -48,179 +43,108 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; private set; } - public bool AllowPause { get; set; } = true; - public bool AllowLeadIn { get; set; } = true; - public bool AllowResults { get; set; } = true; + public bool PauseOnFocusLost { get; set; } = true; private Bindable mouseWheelDisabled; - private Bindable userAudioOffset; private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; - public CursorContainer Cursor => RulesetContainer.Cursor; - public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value; - - private IAdjustableClock sourceClock; - - /// - /// The decoupled clock used for gameplay. Should be used for seeks and clock control. - /// - private DecoupleableInterpolatingFramedClock adjustableClock; - [Resolved] private ScoreManager scoreManager { get; set; } - protected PauseContainer PauseContainer { get; private set; } - private RulesetInfo ruleset; - private APIAccess api; + private IAPIProvider api; private SampleChannel sampleRestart; protected ScoreProcessor ScoreProcessor { get; private set; } - protected RulesetContainer RulesetContainer { get; private set; } + protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } - private FailOverlay failOverlay; - private DrawableStoryboard storyboard; - protected UserDimContainer StoryboardContainer { get; private set; } + public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) + protected GameplayClockContainer GameplayClockContainer { get; private set; } + + private readonly bool allowPause; + private readonly bool showResults; + + /// + /// Create a new player instance. + /// + /// Whether pausing should be allowed. If not allowed, attempting to pause will quit. + /// Whether results screen should be pushed on completion. + public Player(bool allowPause = true, bool showResults = true) { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - EnableUserDim = { Value = true } - }; - - public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; + this.allowPause = allowPause; + this.showResults = showResults; + } [BackgroundDependencyLoader] - private void load(AudioManager audio, APIAccess api, OsuConfigManager config) + private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) { this.api = api; - WorkingBeatmap working = Beatmap.Value; - if (working is DummyWorkingBeatmap) + WorkingBeatmap working = loadBeatmap(); + + if (working == null) return; sampleRestart = audio.Sample.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); + showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - IBeatmap beatmap; - - try - { - beatmap = working.Beatmap; - - if (beatmap == null) - throw new InvalidOperationException("Beatmap was not loaded"); - - ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; - var rulesetInstance = ruleset.CreateInstance(); - - try - { - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working); - } - catch (BeatmapInvalidForRulesetException) - { - // we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset - // let's try again forcing the beatmap's ruleset. - ruleset = beatmap.BeatmapInfo.Ruleset; - rulesetInstance = ruleset.CreateInstance(); - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value); - } - - if (!RulesetContainer.Objects.Any()) - { - Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); - return; - } - } - catch (Exception e) - { - Logger.Error(e, "Could not load beatmap sucessfully!"); - //couldn't load, hard abort! - return; - } - - sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); - adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - - adjustableClock.Seek(AllowLeadIn - ? Math.Min(0, RulesetContainer.GameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn) - : RulesetContainer.GameplayStartTime); - - adjustableClock.ProcessFrame(); - - // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. - // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; - - // the final usable gameplay clock with user-set offsets applied. - var offsetClock = new FramedOffsetClock(platformOffsetClock); - - userAudioOffset.ValueChanged += offset => offsetClock.Offset = offset.NewValue; - userAudioOffset.TriggerChange(); - - ScoreProcessor = RulesetContainer.CreateScoreProcessor(); + ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChildren = new Drawable[] + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, DrawableRuleset.GameplayStartTime); + + GameplayClockContainer.Children = new[] { - PauseContainer = new PauseContainer(offsetClock, adjustableClock) + StoryboardContainer = CreateStoryboardContainer(), + new ScalingContainer(ScalingMode.Gameplay) { - Retries = RestartCount, - OnRetry = Restart, - OnQuit = performUserRequestedExit, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, - Children = new Container[] + Child = new LocalSkinOverrideContainer(working.Skin) { - StoryboardContainer = CreateStoryboardContainer(), - new ScalingContainer(ScalingMode.Gameplay) - { - Child = new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Child = RulesetContainer - } - }, - new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - ProcessCustomClock = false, - Breaks = beatmap.Breaks - }, - new ScalingContainer(ScalingMode.Gameplay) - { - Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - }, - HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) - { - Clock = Clock, // hud overlay doesn't want to use the audio clock directly - ProcessCustomClock = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - new SkipOverlay(RulesetContainer.GameplayStartTime) - { - Clock = Clock, // skip button doesn't want to use the audio clock directly - ProcessCustomClock = false, - AdjustableClock = adjustableClock, - FramedClock = offsetClock, - }, + RelativeSizeAxes = Axes.Both, + Child = DrawableRuleset } }, - failOverlay = new FailOverlay + new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = working.Beatmap.Breaks + }, + // display the cursor above some HUD elements. + DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, working) + { + HoldToQuit = { Action = performUserRequestedExit }, + PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, + KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, + RequestSeek = GameplayClockContainer.Seek, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + new SkipOverlay(DrawableRuleset.GameplayStartTime) + { + RequestSeek = GameplayClockContainer.Seek + }, + FailOverlay = new FailOverlay + { + OnRetry = Restart, + OnQuit = performUserRequestedExit, + }, + PauseOverlay = new PauseOverlay + { + OnResume = Resume, + Retries = RestartCount, OnRetry = Restart, OnQuit = performUserRequestedExit, }, @@ -236,13 +160,11 @@ namespace osu.Game.Screens.Play } }; - HUDOverlay.HoldToQuit.Action = performUserRequestedExit; - HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); + // bind clock into components that require it + DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); - RulesetContainer.IsPaused.BindTo(PauseContainer.IsPaused); - - if (ShowStoryboard.Value) - initializeStoryboard(false); + // load storyboard as part of player's load if we can + initializeStoryboard(false); // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; @@ -252,13 +174,49 @@ namespace osu.Game.Screens.Play mod.ApplyToScoreProcessor(ScoreProcessor); } - private void applyRateFromMods() + private WorkingBeatmap loadBeatmap() { - if (sourceClock == null) return; + WorkingBeatmap working = Beatmap.Value; + if (working is DummyWorkingBeatmap) + return null; - sourceClock.Rate = 1; - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) - mod.ApplyToClock(sourceClock); + try + { + var beatmap = working.Beatmap; + + if (beatmap == null) + throw new InvalidOperationException("Beatmap was not loaded"); + + ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; + var rulesetInstance = ruleset.CreateInstance(); + + try + { + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working); + } + catch (BeatmapInvalidForRulesetException) + { + // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset + // let's try again forcing the beatmap's ruleset. + ruleset = beatmap.BeatmapInfo.Ruleset; + rulesetInstance = ruleset.CreateInstance(); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value); + } + + if (!DrawableRuleset.Objects.Any()) + { + Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); + return null; + } + } + catch (Exception e) + { + Logger.Error(e, "Could not load beatmap sucessfully!"); + //couldn't load, hard abort! + return null; + } + + return working; } private void performUserRequestedExit() @@ -288,7 +246,7 @@ namespace osu.Game.Screens.Play ValidForResume = false; - if (!AllowResults) return; + if (!showResults) return; using (BeginDelayedSequence(1000)) { @@ -297,8 +255,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; var score = CreateScore(); - if (RulesetContainer.ReplayScore == null) - scoreManager.Import(score, true); + if (DrawableRuleset.ReplayScore == null) + scoreManager.Import(score); this.Push(CreateResults(score)); @@ -309,7 +267,7 @@ namespace osu.Game.Screens.Play protected virtual ScoreInfo CreateScore() { - var score = RulesetContainer.ReplayScore?.ScoreInfo ?? new ScoreInfo + var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, @@ -322,19 +280,148 @@ namespace osu.Game.Screens.Play return score; } + protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; + + protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + + #region Storyboard + + private DrawableStoryboard storyboard; + protected UserDimContainer StoryboardContainer { get; private set; } + + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + EnableUserDim = { Value = true } + }; + + private Bindable showStoryboard; + + private void initializeStoryboard(bool asyncLoad) + { + if (StoryboardContainer == null || storyboard != null) + return; + + if (!showStoryboard.Value) + return; + + var beatmap = Beatmap.Value; + + storyboard = beatmap.Storyboard.CreateDrawable(); + storyboard.Masking = true; + + if (asyncLoad) + LoadComponentAsync(storyboard, StoryboardContainer.Add); + else + StoryboardContainer.Add(storyboard); + } + + #endregion + + #region Fail Logic + + protected FailOverlay FailOverlay { get; private set; } + private bool onFail() { if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - adjustableClock.Stop(); + GameplayClockContainer.Stop(); HasFailed = true; - failOverlay.Retries = RestartCount; - failOverlay.Show(); + + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) + // could process an extra frame after the GameplayClock is stopped. + // In such cases we want the fail state to precede a user triggered pause. + if (PauseOverlay.State == Visibility.Visible) + PauseOverlay.Hide(); + + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); return true; } + #endregion + + #region Pause Logic + + public bool IsResuming { get; private set; } + + /// + /// The amount of gameplay time after which a second pause is allowed. + /// + private const double pause_cooldown = 1000; + + protected PauseOverlay PauseOverlay { get; private set; } + + private double? lastPauseActionTime; + + private bool canPause => + // must pass basic screen conditions (beatmap loaded, instance allows pause) + LoadedBeatmapSuccessfully && allowPause && ValidForResume + // replays cannot be paused and exit immediately + && !DrawableRuleset.HasReplayLoaded.Value + // cannot pause if we are already in a fail state + && !HasFailed + // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); + + private bool pauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + + private bool canResume => + // cannot resume from a non-paused state + GameplayClockContainer.IsPaused.Value + // cannot resume if we are already in a fail state + && !HasFailed + // 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; + + IsResuming = false; + GameplayClockContainer.Stop(); + PauseOverlay.Show(); + lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; + } + + public void Resume() + { + if (!canResume) return; + + IsResuming = true; + PauseOverlay.Hide(); + + // time-based conditions may allow instant resume. + if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + completeResume(); + else + DrawableRuleset.RequestResume(completeResume); + + void completeResume() + { + GameplayClockContainer.Start(); + IsResuming = false; + } + } + + #endregion + + #region Screen Logic + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -349,39 +436,18 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - ShowStoryboard.ValueChanged += enabled => - { - if (enabled.NewValue) initializeStoryboard(true); - }; + showStoryboard.ValueChanged += _ => initializeStoryboard(true); Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; - Task.Run(() => - { - sourceClock.Reset(); - - Schedule(() => - { - adjustableClock.ChangeSource(sourceClock); - applyRateFromMods(); - - this.Delay(750).Schedule(() => - { - if (!PauseContainer.IsPaused.Value) - { - adjustableClock.Start(); - } - }); - }); - }); - - PauseContainer.Alpha = 0; - PauseContainer.FadeIn(750, Easing.OutQuint); + GameplayClockContainer.Restart(); + GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); } public override void OnSuspending(IScreen next) @@ -399,18 +465,20 @@ namespace osu.Game.Screens.Play return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PauseContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PauseContainer?.IsResuming ?? true)) + if (canPause) { - // In the case of replays, we may have changed the playback rate. - applyRateFromMods(); - fadeOut(); - return base.OnExiting(next); + Pause(); + return true; } - if (LoadedBeatmapSuccessfully) - PauseContainer?.Pause(); + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return true; - return true; + GameplayClockContainer.ResetLocalAdjustments(); + + fadeOut(); + return base.OnExiting(next); } private void fadeOut(bool instant = false) @@ -422,24 +490,6 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = false; } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PauseContainer.IsPaused.Value; - - private void initializeStoryboard(bool asyncLoad) - { - if (StoryboardContainer == null || storyboard != null) - return; - - var beatmap = Beatmap.Value; - - storyboard = beatmap.Storyboard.CreateDrawable(); - storyboard.Masking = true; - - if (asyncLoad) - LoadComponentAsync(storyboard, StoryboardContainer.Add); - else - StoryboardContainer.Add(storyboard); - } - - protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + #endregion } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5c9acade7b..e9ee5d3fa8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; +using osu.Framework.Input; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; @@ -17,6 +17,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; using osuTK; using osuTK.Graphics; @@ -25,8 +26,9 @@ namespace osu.Game.Screens.Play { public class PlayerLoader : ScreenWithBeatmapBackground { + protected const float BACKGROUND_BLUR = 15; + private readonly Func createPlayer; - private static readonly Vector2 background_blur = new Vector2(15); private Player player; @@ -41,6 +43,8 @@ namespace osu.Game.Screens.Play private Task loadTask; + private InputManager inputManager; + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -132,6 +136,7 @@ namespace osu.Game.Screens.Play base.OnEntering(last); content.ScaleTo(0.7f); + Background?.FadeColour(Color4.White, 800, Easing.OutQuint); contentIn(); @@ -150,43 +155,20 @@ namespace osu.Game.Screens.Play logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); } + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + base.LoadComplete(); + } + private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; + // Hhere 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; - protected override bool OnHover(HoverEvent e) - { - // restore our screen defaults - if (this.IsCurrentScreen()) - { - InitializeBackgroundElements(); - Background.EnableUserDim.Value = false; - } - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true) - { - // Update background elements is only being called here because blur logic still exists in Player. - // Will need to be removed when resolving https://github.com/ppy/osu/issues/4322 - UpdateBackgroundElements(); - if (this.IsCurrentScreen()) - Background.EnableUserDim.Value = true; - } - - base.OnHoverLost(e); - } - - protected override void InitializeBackgroundElements() - { - Background?.FadeColour(Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); - Background?.BlurTo(background_blur, BACKGROUND_FADE_DURATION, Easing.OutQuint); - } - private void pushWhenLoaded() { if (!this.IsCurrentScreen()) return; @@ -265,6 +247,29 @@ namespace osu.Game.Screens.Play } } + protected override void Update() + { + base.Update(); + + if (!this.IsCurrentScreen()) + return; + + // We need to perform this check here rather than in OnHover as any number of children of VisualSettings + // may also be handling the hover events. + if (inputManager.HoveredDrawables.Contains(VisualSettings)) + { + // Preview user-defined background dim and blur when hovered on the visual settings panel. + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; + } + else + { + // Returns background dim and blur to the values specified by PlayerLoader. + Background.EnableUserDim.Value = false; + Background.BlurAmount.Value = BACKGROUND_BLUR; + } + } + private class BeatmapMetadataDisplay : Container { private class MetadataLine : Container @@ -296,6 +301,7 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private LoadingAnimation loading; private Sprite backgroundSprite; + private ModDisplay modDisplay; public bool Loading { @@ -322,7 +328,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); AutoSizeAxes = Axes.Both; Children = new Drawable[] @@ -391,6 +397,14 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 20 }, + Current = beatmap.Mods + } }, } }; diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index ebbed299f7..c691d161ed 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -16,7 +15,13 @@ namespace osu.Game.Screens.Play.PlayerSettings protected override string Title => @"playback"; - public IAdjustableClock AdjustableClock { set; get; } + public readonly Bindable UserPlaybackRate = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.1, + }; private readonly PlayerSliderBar rateSlider; @@ -47,31 +52,13 @@ namespace osu.Game.Screens.Play.PlayerSettings } }, }, - rateSlider = new PlayerSliderBar - { - Bindable = new BindableDouble(1) - { - Default = 1, - MinValue = 0.5, - MaxValue = 2, - Precision = 0.1, - }, - } + rateSlider = new PlayerSliderBar { Bindable = UserPlaybackRate } }; } protected override void LoadComplete() { base.LoadComplete(); - - if (AdjustableClock == null) - return; - - var clockRate = AdjustableClock.Rate; - - // can't trigger this line instantly as the underlying clock may not be ready to accept adjustments yet. - rateSlider.Bindable.ValueChanged += multiplier => AdjustableClock.Rate = clockRate * multiplier.NewValue; - rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); } } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index efaeeea79f..90424ec007 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-15, 0), - Icon = FontAwesome.fa_bars, + Icon = FontAwesome.Solid.Bars, Scale = new Vector2(0.75f), Action = () => Expanded = !Expanded, }, diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3190139378..a9c0ee3a15 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -9,7 +9,8 @@ namespace osu.Game.Screens.Play { private readonly Score score; - public ReplayPlayer(Score score) + public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) { this.score = score; } @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer?.SetReplayScore(score); + DrawableRuleset?.SetReplayScore(score); } protected override ScoreInfo CreateScore() => score.ScoreInfo; diff --git a/osu.Game/Screens/Play/ResumeOverlay.cs b/osu.Game/Screens/Play/ResumeOverlay.cs new file mode 100644 index 0000000000..2ef76069c2 --- /dev/null +++ b/osu.Game/Screens/Play/ResumeOverlay.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// An overlay which can be used to require further user actions before gameplay is resumed. + /// + public abstract class ResumeOverlay : OverlayContainer + { + public CursorContainer GameplayCursor { get; set; } + + /// + /// The action to be performed to complete resuming. + /// + public Action ResumeAction { private get; set; } + + public virtual CursorContainer LocalCursor => null; + + protected const float TRANSITION_TIME = 500; + + protected override bool BlockPositionalInput => false; + + protected abstract string Message { get; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected ResumeOverlay() + { + RelativeSizeAxes = Axes.Both; + } + + protected void Resume() + { + ResumeAction?.Invoke(); + Hide(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRange(new Drawable[] + { + new OsuSpriteText + { + RelativePositionAxes = Axes.Both, + Y = 0.4f, + Text = Message, + Font = OsuFont.GetFont(size: 30), + Spacing = new Vector2(5, 0), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = colours.Yellow, + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f) + } + }); + } + + protected override void PopIn() => this.FadeIn(TRANSITION_TIME, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(TRANSITION_TIME, Easing.OutQuint); + } +} diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 328aa1d18e..d7d2c97598 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -1,13 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Screens; -using osu.Game.Configuration; using osu.Game.Screens.Backgrounds; -using osuTK; namespace osu.Game.Screens.Play { @@ -16,50 +10,5 @@ namespace osu.Game.Screens.Play protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; - - protected const float BACKGROUND_FADE_DURATION = 800; - - #region User Settings - - protected Bindable BlurLevel; - protected Bindable ShowStoryboard; - - #endregion - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - BlurLevel = config.GetBindable(OsuSetting.BlurLevel); - ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - } - - public override void OnEntering(IScreen last) - { - base.OnEntering(last); - BlurLevel.ValueChanged += _ => UpdateBackgroundElements(); - InitializeBackgroundElements(); - } - - public override void OnResuming(IScreen last) - { - base.OnResuming(last); - InitializeBackgroundElements(); - } - - /// - /// Called once on entering screen. By Default, performs a full call. - /// - protected virtual void InitializeBackgroundElements() => UpdateBackgroundElements(); - - /// - /// Called when background elements require updates, usually due to a user changing a setting. - /// - /// - protected virtual void UpdateBackgroundElements() - { - if (!this.IsCurrentScreen()) return; - - Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint); - } } } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 010d9228de..738877232d 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -9,13 +9,13 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Ranking; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -27,8 +27,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public IAdjustableClock AdjustableClock; - public IFrameBasedClock FramedClock; + public Action RequestSeek; private Button button; private Box remainingTimeBox; @@ -54,16 +53,13 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { var baseClock = Clock; - if (FramedClock != null) - { - Clock = FramedClock; - ProcessCustomClock = false; - } + if (clock != null) + Clock = clock; Children = new Drawable[] { @@ -111,7 +107,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time); + button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time); displayTime = Time.Current; @@ -263,9 +259,9 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new[] { - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, } }, new OsuSpriteText diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 3c7e3b2067..94b25e04a3 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -10,7 +10,6 @@ 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; @@ -29,18 +28,11 @@ namespace osu.Game.Screens.Play private readonly SongProgressGraph graph; private readonly SongProgressInfo info; - public Action OnSeek; + public Action RequestSeek; public override bool HandleNonPositionalInput => AllowSeeking; public override bool HandlePositionalInput => AllowSeeking; - private IClock audioClock; - - public IClock AudioClock - { - set => audioClock = info.AudioClock = value; - } - private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; private double firstHitTime => objects.First().StartTime; @@ -63,9 +55,14 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private GameplayClock gameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { + if (clock != null) + gameplayClock = clock; + graph.FillColour = bar.FillColour = colours.BlueLighter; } @@ -99,7 +96,7 @@ namespace osu.Game.Screens.Play Alpha = 0, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - OnSeek = position => OnSeek?.Invoke(position), + OnSeek = time => RequestSeek?.Invoke(time), }, }; } @@ -112,9 +109,9 @@ namespace osu.Game.Screens.Play replayLoaded.TriggerChange(); } - public void BindRulestContainer(RulesetContainer rulesetContainer) + public void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } private bool allowSeeking; @@ -157,14 +154,11 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double position = audioClock?.CurrentTime ?? Time.Current; - double progress = (position - firstHitTime) / (lastHitTime - firstHitTime); + double position = gameplayClock?.CurrentTime ?? Time.Current; + double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime)); - if (progress < 1) - { - bar.CurrentTime = position; - graph.Progress = (int)(graph.ColumnCount * progress); - } + bar.CurrentTime = position; + graph.Progress = (int)(graph.ColumnCount * progress); } } } diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs index 369abb53c8..7441c335d2 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System; @@ -27,8 +26,6 @@ namespace osu.Game.Screens.Play private const int margin = 10; - public IClock AudioClock; - public double StartTime { set => startTime = value; @@ -39,9 +36,14 @@ namespace osu.Game.Screens.Play set => endTime = value; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private GameplayClock gameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { + if (clock != null) + gameplayClock = clock; + Children = new Drawable[] { timeCurrent = new OsuSpriteText @@ -80,7 +82,9 @@ namespace osu.Game.Screens.Play { base.Update(); - double songCurrentTime = AudioClock.CurrentTime - startTime; + var time = gameplayClock?.CurrentTime ?? Time.Current; + + double songCurrentTime = time - startTime; int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0); @@ -93,7 +97,7 @@ namespace osu.Game.Screens.Play if (currentSecond != previousSecond && songCurrentTime < songLength) { timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond)); - timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime)); + timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - time)); previousSecond = currentSecond; } diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 95ac35baa7..d10034d552 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -242,7 +242,11 @@ namespace osu.Game.Screens.Play // Reverse drawableRows so when iterating through them they start at the bottom drawableRows.Reverse(); + } + protected override void LoadComplete() + { + base.LoadComplete(); fillActive(); } diff --git a/osu.Game/Screens/Ranking/IResultPageInfo.cs b/osu.Game/Screens/Ranking/IResultPageInfo.cs index 5e0bec21f3..cc86e7441a 100644 --- a/osu.Game/Screens/Ranking/IResultPageInfo.cs +++ b/osu.Game/Screens/Ranking/IResultPageInfo.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Ranking { public interface IResultPageInfo { - FontAwesome Icon { get; } + IconUsage Icon { get; } string Name { get; } diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index b1fd8f9fde..109d0195db 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -11,12 +11,13 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Ranking { public class ResultModeButton : TabItem, IHasTooltip { - private readonly FontAwesome icon; + private readonly IconUsage icon; private Color4 activeColour; private Color4 inactiveColour; private CircularContainer colouredPart; diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 47ac472b9e..dafb4c0aad 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Ranking { public abstract class Results : OsuScreen { + protected const float BACKGROUND_BLUR = 20; + private Container circleOuterBackground; private Container circleOuter; private Container circleInner; @@ -38,8 +40,6 @@ namespace osu.Game.Screens.Ranking private Container currentPage; - private static readonly Vector2 background_blur = new Vector2(20); - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); private const float overscan = 1.3f; @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking public override void OnEntering(IScreen last) { base.OnEntering(last); - (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint); + ((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR; Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => diff --git a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs index e8a11ab1a4..fe183c5f89 100644 --- a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs +++ b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Pages; @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Types this.beatmap = beatmap; } - public FontAwesome Icon => FontAwesome.fa_user; + public IconUsage Icon => FontAwesome.Solid.User; public string Name => @"Local Leaderboard"; diff --git a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs index d8e5e9b135..424dbff6f6 100644 --- a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs +++ b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Pages; @@ -10,7 +10,7 @@ namespace osu.Game.Screens.Ranking.Types { public class ScoreOverviewPageInfo : IResultPageInfo { - public FontAwesome Icon => FontAwesome.fa_asterisk; + public IconUsage Icon => FontAwesome.Solid.Asterisk; public string Name => "Overview"; private readonly ScoreInfo score; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index b222b91221..d6766c2b49 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -14,6 +14,7 @@ using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens { @@ -112,7 +113,7 @@ namespace osu.Game.Screens { new SpriteIcon { - Icon = FontAwesome.fa_universal_access, + Icon = FontAwesome.Solid.UniversalAccess, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Size = new Vector2(50), @@ -188,7 +189,7 @@ namespace osu.Game.Screens { public ChildModeButton() { - Icon = FontAwesome.fa_osu_right_o; + Icon = OsuIcon.RightCircle; Anchor = Anchor.BottomRight; Origin = Anchor.BottomRight; } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 389f614ef2..d7240a40ad 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -55,9 +55,9 @@ namespace osu.Game.Screens.Select public override bool HandlePositionalInput => AllowSelection; /// - /// Used to avoid firing null selections before the initial beatmaps have been loaded via . + /// Whether carousel items have completed asynchronously loaded. /// - private bool initialLoadComplete; + public bool BeatmapSetsLoaded { get; private set; } private IEnumerable beatmapSets => root.Children.OfType(); @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select Schedule(() => { BeatmapSetsChanged?.Invoke(); - initialLoadComplete = true; + BeatmapSetsLoaded = true; }); })); } @@ -327,6 +327,9 @@ namespace osu.Game.Screens.Select private void select(CarouselItem item) { + if (!AllowSelection) + return; + if (item == null) return; item.State.Value = CarouselItemState.Selected; @@ -577,7 +580,7 @@ namespace osu.Game.Screens.Select else { float y = currentY; - d.OnLoadComplete = _ => performMove(y, setY); + d.OnLoadComplete += _ => performMove(y, setY); } break; @@ -593,7 +596,7 @@ namespace osu.Game.Screens.Select currentY += DrawHeight / 2; scrollableContent.Height = currentY; - if (initialLoadComplete && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) + if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { selectedBeatmapSet = null; SelectionChanged?.Invoke(null); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index a37327f2c3..c9b6ca7bb3 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -3,12 +3,12 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Dialog; using osu.Game.Scoring; using System; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion) { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.fa_eraser; + Icon = FontAwesome.Solid.Eraser; HeaderText = @"Clearing all local scores. Are you sure?"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index f2c1940ed8..5fb72e4151 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 604d7a132b..a78ab97960 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select private readonly FailRetryGraph failRetryGraph; private readonly DimmedLoadingAnimation loading; - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingBeatmapSwitch; @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } @@ -175,21 +175,22 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - if (Beatmap == null) + advanced.Beatmap = Beatmap; + description.Text = Beatmap?.Version; + source.Text = Beatmap?.Metadata?.Source; + tags.Text = Beatmap?.Metadata?.Tags; + + // metrics may have been previously fetched + if (Beatmap?.Metrics != null) { - clearStats(); + updateMetrics(Beatmap.Metrics); return; } - ratingsContainer.FadeIn(transition_duration); - advanced.Beatmap = Beatmap; - description.Text = Beatmap.Version; - source.Text = Beatmap.Metadata.Source; - tags.Text = Beatmap.Metadata.Tags; - - var requestedBeatmap = Beatmap; - if (requestedBeatmap.Metrics == null) + // metrics may not be fetched but can be + if (Beatmap?.OnlineBeatmapID != null) { + var requestedBeatmap = Beatmap; var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); lookup.Success += res => { @@ -198,39 +199,34 @@ namespace osu.Game.Screens.Select return; requestedBeatmap.Metrics = res; - Schedule(() => displayMetrics(res)); + Schedule(() => updateMetrics(res)); }; - lookup.Failure += e => Schedule(() => displayMetrics(null)); - + lookup.Failure += e => Schedule(() => updateMetrics()); api.Queue(lookup); loading.Show(); + return; } - displayMetrics(requestedBeatmap.Metrics, false); + updateMetrics(); } - private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + private void updateMetrics(BeatmapMetrics metrics = null) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); - if (failOnMissing) loading.Hide(); - if (hasRatings) { ratings.Metrics = metrics; - ratings.FadeIn(transition_duration); + ratingsContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { ratings.Metrics = new BeatmapMetrics { Ratings = new int[10], }; - } - else - { - ratings.FadeTo(0.25f, transition_duration); + ratingsContainer.FadeTo(0.25f, transition_duration); } if (hasRetriesFails) @@ -238,41 +234,17 @@ namespace osu.Game.Screens.Select failRetryGraph.Metrics = metrics; failRetryContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { failRetryGraph.Metrics = new BeatmapMetrics { Fails = new int[100], Retries = new int[100], }; + failRetryContainer.FadeOut(transition_duration); } - else - { - failRetryContainer.FadeTo(0.25f, transition_duration); - } - } - - private void clearStats() - { - description.Text = null; - source.Text = null; - tags.Text = null; - - advanced.Beatmap = new BeatmapInfo - { - StarDifficulty = 0, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 0, - DrainRate = 0, - OverallDifficulty = 0, - ApproachRate = 0, - }, - }; loading.Hide(); - ratingsContainer.FadeOut(transition_duration); - failRetryContainer.FadeOut(transition_duration); } private class DetailBox : Container diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 32e7215e69..d32387c1d3 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -22,6 +22,7 @@ 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.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; using osu.Game.Rulesets.UI; @@ -292,14 +293,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - Icon = FontAwesome.fa_clock_o, + Icon = FontAwesome.Regular.Clock, Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - Icon = FontAwesome.fa_circle, + Icon = FontAwesome.Regular.Circle, Content = getBPMRange(b), })); @@ -377,7 +378,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"441288"), - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Solid.Square, Rotation = 45, }, new SpriteIcon diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e01149ebc8..51ca9902d2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -49,11 +49,16 @@ namespace osu.Game.Screens.Select.Carousel Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => - new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + { + var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint), - }, 300, 5000 + }; + + background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return background; + }, 300, 5000 ), new FillFlowContainer { diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 537736a4ec..54e4c096f6 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select HeaderText = @"You have no beatmaps!"; BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?"; - Icon = FontAwesome.fa_plane; + Icon = FontAwesome.Solid.Plane; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 61370f7ce3..ebb1d78ba0 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select.Leaderboards private IBindable ruleset { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index f6d758df29..fa5dc4c1d1 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -60,9 +61,12 @@ namespace osu.Game.Screens.Select if (base.OnExiting(next)) return true; - Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value?.Beatmap); - Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value?.RequiredMods; - Ruleset.Value = CurrentItem.Value?.Ruleset; + if (CurrentItem.Value != null) + { + Ruleset.Value = CurrentItem.Value.Ruleset; + Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); + Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); + } Beatmap.Disabled = true; Ruleset.Disabled = true; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 758e1c24c3..a9616ee535 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Select.Options set => background.Colour = value; } - public FontAwesome Icon + public IconUsage Icon { get => iconText.Icon; set => iconText.Icon = value; @@ -140,7 +141,7 @@ namespace osu.Game.Screens.Select.Options Anchor = Anchor.TopCentre, Size = new Vector2(30), Shadow = true, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, Margin = new MarginPadding { Bottom = 5, diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 5fedb2f8cc..669264cef0 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -6,7 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Select.Options /// Lower depth to be put on the left, and higher to be put on the right. /// Notice this is different to ! /// - public void AddButton(string firstLine, string secondLine, FontAwesome icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) + public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) { var button = new BeatmapOptionsButton { diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index d06436c92e..340ceb6864 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Screens.Play; @@ -20,7 +21,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuColour colours) { - BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.fa_pencil, colours.Yellow, () => + BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => { ValidForResume = false; Edit(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 866c7e0926..b60e693cbf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.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 osu.Framework.Allocation; @@ -32,13 +32,14 @@ using osuTK.Input; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - private static readonly Vector2 background_blur = new Vector2(20); + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; public readonly FilterControl FilterControl; @@ -61,7 +62,11 @@ namespace osu.Game.Screens.Select ///
protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); + protected override BackgroundScreen CreateBackground() + { + var background = new BackgroundScreenBeatmap(); + return background; + } protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; @@ -85,13 +90,12 @@ namespace osu.Game.Screens.Select protected SongSelect() { const float carousel_width = 640; - const float filter_height = 100; AddRangeInternal(new Drawable[] { new ParallaxContainer { - Padding = new MarginPadding { Top = filter_height }, + Masking = true, ParallaxAmount = 0.005f, RelativeSizeAxes = Axes.Both, Children = new[] @@ -150,7 +154,7 @@ namespace osu.Game.Screens.Select FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, - Height = filter_height, + Height = 100, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, Exit = () => @@ -224,9 +228,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); + 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); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); } if (this.beatmaps == null) @@ -296,6 +300,10 @@ namespace osu.Game.Screens.Select /// Whether to trigger . public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) { + // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. + if (!Carousel.BeatmapSetsLoaded) + return; + // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). Carousel.FlushPendingFilterOperations(); @@ -369,6 +377,13 @@ namespace osu.Game.Screens.Select var beatmap = beatmapNoDebounce; var ruleset = rulesetNoDebounce; + selectionChangedDebounce?.Cancel(); + + if (beatmap == null) + run(); + else + selectionChangedDebounce = Scheduler.AddDelayed(run, 200); + void run() { Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); @@ -398,7 +413,6 @@ namespace osu.Game.Screens.Select { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); if (beatmap != null) @@ -410,16 +424,10 @@ namespace osu.Game.Screens.Select } } - if (this.IsCurrentScreen()) ensurePlayingSelected(preview); + if (this.IsCurrentScreen()) + ensurePlayingSelected(); UpdateBeatmap(Beatmap.Value); } - - selectionChangedDebounce?.Cancel(); - - if (beatmap == null) - run(); - else - selectionChangedDebounce = Scheduler.AddDelayed(run, 200); } private void triggerRandom() @@ -556,7 +564,7 @@ namespace osu.Game.Screens.Select if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint); + backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR; backgroundModeBeatmap.FadeColour(Color4.White, 250); } @@ -568,39 +576,28 @@ namespace osu.Game.Screens.Select beatmap.Track.Looping = true; } - private void ensurePlayingSelected(bool preview = false) + private void ensurePlayingSelected(bool restart = false) { Track track = Beatmap.Value.Track; - if (!track.IsRunning) + if (!track.IsRunning || restart) { // Ensure the track is added to the TrackManager, since it is removed after the player finishes the map. // Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once. Game.Audio.Track.AddItemToList(track); - if (preview) track.Seek(Beatmap.Value.Metadata.PreviewTime); - track.Start(); + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.Restart(); } } - private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s); + private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void carouselBeatmapsLoaded() { - if (rulesetNoDebounce == null) - { - // manual binding to parent ruleset to allow for delayed load in the incoming direction. - rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; - Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); - - decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; - decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; - - Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); - Beatmap.BindValueChanged(workingBeatmapChanged); - } + bindBindables(); if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) @@ -614,6 +611,26 @@ namespace osu.Game.Screens.Select } } + private bool boundLocalBindables; + + private void bindBindables() + { + if (boundLocalBindables) + return; + + // manual binding to parent ruleset to allow for delayed load in the incoming direction. + rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); + + decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; + decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; + + Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); + Beatmap.BindValueChanged(workingBeatmapChanged); + + boundLocalBindables = true; + } + private void delete(BeatmapSetInfo beatmap) { if (beatmap == null || beatmap.ID <= 0) return; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 96a9116c51..a655c884be 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -16,12 +16,10 @@ namespace osu.Game.Skinning { line = StripComments(line); + var pair = SplitKeyVal(line); switch (section) { case Section.General: - { - var pair = SplitKeyVal(line); - switch (pair.Key) { case @"Name": @@ -36,11 +34,8 @@ namespace osu.Game.Skinning } break; - } - case Section.Fonts: - { - var pair = SplitKeyVal(line); + case Section.Fonts: switch (pair.Key) { case "HitCirclePrefix": @@ -52,7 +47,6 @@ namespace osu.Game.Skinning } break; - } } base.ParseLine(skin, section, line); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index f5e1f612ca..1182cacfc1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Game.IO; +using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -55,9 +56,12 @@ namespace osu.Game.Storyboards.Drawables }); } - [BackgroundDependencyLoader] - private void load(FileStore fileStore) + [BackgroundDependencyLoader(true)] + private void load(FileStore fileStore, GameplayClock clock) { + if (clock != null) + Clock = clock; + dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 9fa481b8b6..ffd238d4e1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -25,7 +25,7 @@ namespace osu.Game.Storyboards.Drawables public DrawableStoryboardSample(StoryboardSample sample) { this.sample = sample; - LifetimeStart = sample.Time; + LifetimeStart = sample.StartTime; } [BackgroundDependencyLoader] @@ -43,27 +43,27 @@ namespace osu.Game.Storyboards.Drawables base.Update(); // TODO: this logic will need to be consolidated with other game samples like hit sounds. - if (Time.Current < sample.Time) + if (Time.Current < sample.StartTime) { // We've rewound before the start time of the sample channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) - LifetimeStart = sample.Time; + LifetimeStart = sample.StartTime; LifetimeEnd = double.MaxValue; } - else if (Time.Current - Time.Elapsed < sample.Time) + else if (Time.Current - Time.Elapsed < sample.StartTime) { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future - if (Time.Current - sample.Time < allowable_late_start) + if (Time.Current - sample.StartTime < allowable_late_start) channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) LifetimeStart = double.MinValue; - LifetimeEnd = sample.Time; + LifetimeEnd = sample.StartTime; } } } diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index 454db2afc2..c4c150a8a4 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -10,6 +10,8 @@ namespace osu.Game.Storyboards string Path { get; } bool IsDrawable { get; } + double StartTime { get; } + Drawable CreateDrawable(); } } diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index daf03b00b4..d15f771534 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -13,8 +13,7 @@ namespace osu.Game.Storyboards public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; - private readonly List elements = new List(); - public IEnumerable Elements => elements; + public List Elements = new List(); public StoryboardLayer(string name, int depth) { @@ -24,7 +23,7 @@ namespace osu.Game.Storyboards public void Add(IStoryboardElement element) { - elements.Add(element); + Elements.Add(element); } public DrawableStoryboardLayer CreateDrawable() diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index 1bdf774e74..24231cdca6 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -11,13 +11,14 @@ namespace osu.Game.Storyboards public string Path { get; set; } public bool IsDrawable => true; - public double Time; + public double StartTime { get; } + public float Volume; public StoryboardSample(string path, double time, float volume) { Path = path; - Time = time; + StartTime = time; Volume = volume; } diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index d1263984ac..b77a8508ad 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.IO; using System.Text; using osu.Game.Beatmaps; @@ -21,6 +22,18 @@ namespace osu.Game.Tests.Beatmaps HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; + BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; + BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; + BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo + { + Status = BeatmapSetOnlineStatus.Ranked, + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg", + Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg", + List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg" + } + }; } private static Beatmap createTestBeatmap() diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 90b5178169..78f9103a74 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osuTK; namespace osu.Game.Tests.Beatmaps { @@ -68,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps public override bool Seek(double seek) { - offset = MathHelper.Clamp(seek, 0, Length); + offset = Math.Min(seek, Length); lastReferenceTime = null; return true; } diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 746dd936de..bfbf7bb9da 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -13,6 +13,11 @@ namespace osu.Game.Tests public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true) : base(gameName, bindIPC, realtime) { + } + + protected override void SetupForRun() + { + base.SetupForRun(); Storage.DeleteDirectory(string.Empty); } } diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs new file mode 100644 index 0000000000..4ef9b346b0 --- /dev/null +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.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.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +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 + { + 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(() => + { + if (p?.IsLoaded == true) + { + p = null; + return true; + } + + return false; + }, "player loaded"); + + AddCheckSteps(); + } + } + + protected abstract void AddCheckSteps(); + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) => + new TestWorkingBeatmap(beatmap, Clock); + + private Player loadPlayerFor(RulesetInfo ri) + { + Ruleset.Value = ri; + var r = ri.CreateInstance(); + + var beatmap = CreateBeatmap(r); + var working = CreateWorkingBeatmap(beatmap, Clock); + + Beatmap.Value = working; + Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + + Player?.Exit(); + Player = null; + + Player = CreatePlayer(r); + + LoadScreen(Player); + + return Player; + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + } +} diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs index 67a1cb6de3..96e70e018e 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual { Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo, null); - LoadComponentAsync(new Editor(), LoadScreen); + LoadScreen(new Editor()); } } } diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs index 7c7c5938aa..f14ac833e4 100644 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs @@ -14,8 +14,7 @@ namespace osu.Game.Tests.Visual protected ManualInputManagerTestCase() { - base.Content.Add(InputManager = new ManualInputManager()); - ReturnUserInput(); + base.Content.Add(InputManager = new ManualInputManager { UseParentInput = true }); } /// diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 90bd86d0ed..dc0d8fad31 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual protected OsuTestCase() { - localStorage = new Lazy(() => new DesktopStorage($"{GetType().Name}-{Guid.NewGuid()}", null)); + localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); } [BackgroundDependencyLoader] diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs new file mode 100644 index 0000000000..3bf707fade --- /dev/null +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.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.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 + { + private readonly Ruleset ruleset; + + protected Player Player; + + protected PlayerTestCase(Ruleset ruleset) + { + this.ruleset = ruleset; + } + + [BackgroundDependencyLoader] + private void load() + { + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep(ruleset.RulesetInfo.Name, loadPlayer); + AddUntilStep(() => Player.IsLoaded, "player loaded"); + } + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + protected virtual bool AllowFail => false; + + private void loadPlayer() + { + var beatmap = CreateBeatmap(ruleset); + + Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); + + if (!AllowFail) + Beatmap.Value.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); + } +} diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs index 14b4af1e7d..3b3adb4d76 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - Beatmap.Value.Track.Rate = Clock.Rate; + Beatmap.Value.Track.TempoAdjust = Clock.Rate; } } } diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index 79c57ad9f4..4fd4c7c207 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Screens; +using osu.Framework.Graphics.Containers; using osu.Game.Screens; namespace osu.Game.Tests.Visual @@ -11,20 +10,21 @@ 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 : OsuTestCase + public abstract class ScreenTestCase : ManualInputManagerTestCase { - private readonly ScreenStack stack; + private readonly OsuScreenStack stack; - [Cached] - private BackgroundScreenStack backgroundStack; + private readonly Container content; + + protected override Container Content => content; protected ScreenTestCase() { - Children = new Drawable[] + base.Content.AddRange(new Drawable[] { - backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, - stack = new ScreenStack { RelativeSizeAxes = Axes.Both } - }; + stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + content = new Container { RelativeSizeAxes = Axes.Both } + }); } protected void LoadScreen(OsuScreen screen) diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs deleted file mode 100644 index 5ff798c40d..0000000000 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; -using osu.Framework.Screens; -using osu.Game.Beatmaps; -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 TestCasePlayer : RateAdjustedBeatmapTestCase - { - private readonly Ruleset ruleset; - - protected Player Player; - - protected TestCasePlayer(Ruleset ruleset) - { - this.ruleset = ruleset; - } - - protected TestCasePlayer() - { - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - - if (ruleset != null) - { - Player p = null; - AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset)); - AddCheckSteps(() => p); - } - else - { - foreach (var r in rulesets.AvailableRulesets) - { - Player p = null; - AddStep(r.Name, () => p = loadPlayerFor(r)); - AddCheckSteps(() => p); - - AddUntilStep(() => - { - p = null; - - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - workingWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked beatmaps"); - - AddUntilStep(() => - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - playerWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked players"); - } - } - } - - protected virtual void AddCheckSteps(Func player) - { - AddUntilStep(() => player().IsLoaded, "player loaded"); - } - - protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); - - private readonly WeakList workingWeakReferences = new WeakList(); - private readonly WeakList playerWeakReferences = new WeakList(); - - private Player loadPlayerFor(RulesetInfo ri) - { - Ruleset.Value = ri; - return loadPlayerFor(ri.CreateInstance()); - } - - private Player loadPlayerFor(Ruleset r) - { - var beatmap = CreateBeatmap(r); - var working = new TestWorkingBeatmap(beatmap, Clock); - - workingWeakReferences.Add(working); - - Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; - - Player?.Exit(); - - var player = CreatePlayer(r); - - playerWeakReferences.Add(player); - - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); - - return player; - } - - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; - } -} diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index cefb91797b..7259468674 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -57,9 +57,9 @@ namespace osu.Game.Users var avatar = new Avatar(user) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }; + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); avatar.OpenOnClick.BindTo(OpenOnClick); Add(displayedAvatar = new DelayedLoadWrapper(avatar)); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 4dfde04e07..e745aa54c8 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Profile.Header; @@ -59,6 +60,8 @@ namespace osu.Game.Users FillFlowContainer infoContainer; + UserCoverBackground coverBackground; + AddInternal(content = new Container { RelativeSizeAxes = Axes.Both, @@ -73,13 +76,12 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(new UserCoverBackground(user) + new DelayedLoadWrapper(coverBackground = new UserCoverBackground(user) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) }, 300) { RelativeSizeAxes = Axes.Both }, new Box { @@ -164,7 +166,7 @@ namespace osu.Game.Users { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.Regular.Circle, Shadow = true, Size = new Vector2(14), }, @@ -181,6 +183,8 @@ namespace osu.Game.Users } }); + coverBackground.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); + if (user.IsSupporter) { infoContainer.Add(new SupporterIcon diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index acc3ab28b5..7f4faa60ae 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -20,14 +20,14 @@ namespace osu.Game.Utils private readonly List tasks = new List(); - private Exception lastException; - public RavenLogger(OsuGame game) { raven.Release = game.Version; if (!game.IsDeployedBuild) return; + Exception lastException = null; + Logger.NewEntry += entry => { if (entry.Level < LogLevel.Verbose) return; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8f9a7cd14b..f3c648cf02 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -11,13 +11,13 @@ - - - + + + - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 499878347b..7ce7329246 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,12 +105,12 @@ - - + + - + diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index 8a5cfcdbe8..cb75e5c159 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -13,4 +13,3 @@ namespace osu.iOS } } } - diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 3c6a6dd2a9..71cbd83e3e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -111,6 +111,7 @@ HINT WARNING WARNING + HINT WARNING WARNING WARNING @@ -262,6 +263,7 @@ IL IP IPC + JIT LTRB MD5 NS