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/appveyor.yml b/appveyor.yml index 1f485485da..4dcaa7b45e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2017 +image: Previous Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 diff --git a/build/build.cake b/build/build.cake index 81deeb3bc7..1d2588de49 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,5 +1,5 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.21" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); @@ -46,7 +46,9 @@ Task("InspectCode") OutputFile = "inspectcodereport.xml", }); - StartProcess(nVikaToolPath, @"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + if (returnCode != 0) + throw new Exception($"inspectcode failed with return code {returnCode}"); }); Task("CodeFileSanity") diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 7e5b003f03..00cabbadf7 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() @@ -73,6 +77,7 @@ namespace osu.Desktop if (versionManager != null) versionManager.State = Visibility.Visible; break; + default: if (versionManager != null) versionManager.State = Visibility.Hidden; @@ -83,6 +88,7 @@ namespace osu.Desktop public override void SetHost(GameHost host) { base.SetHost(host); + if (host.Window is DesktopGameWindow desktopWindow) { desktopWindow.CursorState |= CursorState.Hidden; @@ -139,8 +145,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..e9c5d06f3c 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -95,6 +95,7 @@ namespace osu.Desktop.Overlays var version = game.Version; var lastVersion = config.Get(OsuSetting.Version); + if (game.IsDeployedBuild && version != lastVersion) { config.Set(OsuSetting.Version, version); @@ -110,7 +111,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/Program.cs b/osu.Desktop/Program.cs index ff9972ac48..29554df64c 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -31,6 +31,7 @@ namespace osu.Desktop var importer = new ArchiveImportIPCChannel(host); // Restore the cwd so relative paths given at the command line work correctly Directory.SetCurrentDirectory(cwd); + foreach (var file in args) { Console.WriteLine(@"Importing {0}", file); diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index ab05888408..5184791de1 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)); @@ -78,6 +78,7 @@ namespace osu.Desktop.Updater case RuntimeInfo.Platform.Windows: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); break; + case RuntimeInfo.Platform.MacOsx: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 6400fd776d..9681350ade 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), } @@ -174,7 +175,7 @@ namespace osu.Desktop.Updater public SquirrelLogger() { - var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SquirrelSetupUpdater.log"); + var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? Directory.GetCurrentDirectory()), "SquirrelSetupUpdater.log"); if (File.Exists(file)) File.Delete(file); path = file; } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 874f73da6d..aa8848c55f 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,9 +26,9 @@ - - - + + + diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 7f85d4ccce..e45ed8c6f4 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -36,11 +36,13 @@ namespace osu.Game.Rulesets.Catch.Tests yield return new ConvertValue((CatchHitObject)nested); break; + case BananaShower shower: foreach (var nested in shower.NestedHitObjects) yield return new ConvertValue((CatchHitObject)nested); break; + default: yield return new ConvertValue((CatchHitObject)hitObject); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index fbb2db33b0..102afa9ca6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch.Tests/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 78b5a510b2..645cb5701a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps initialiseHyperDash((List)Beatmap.HitObjects); int index = 0; + foreach (var obj in Beatmap.HitObjects.OfType()) { obj.IndexInBeatmap = index++; @@ -58,6 +59,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } break; + case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects) { @@ -103,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext); + if (distanceToHyper < 0) { currentObject.HyperDashTarget = nextObject; diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 5140135f80..ea9f225cc1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -9,6 +9,7 @@ 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; @@ -17,12 +18,13 @@ 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 DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); @@ -85,6 +87,7 @@ namespace osu.Game.Rulesets.Catch new CatchModNoFail(), new MultiMod(new CatchModHalfTime(), new CatchModDaycore()) }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -94,17 +97,20 @@ namespace osu.Game.Rulesets.Catch new CatchModHidden(), new CatchModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new CatchModAutoplay(), new ModCinema()), new CatchModRelax(), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } @@ -114,10 +120,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 b4998347f4..bd647fd667 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty lastObject = hitObject; break; + case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) { 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/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index 4e64753a65..374dd50c11 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 1100; } @@ -27,8 +28,9 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: - return 8; + return 0.008; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index 2598dee156..f1399bb5c0 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 30; } @@ -23,9 +24,10 @@ namespace osu.Game.Rulesets.Catch.Judgements switch (result) { default: - return 0; + return base.HealthIncreaseFor(result); + case HitResult.Perfect: - return 7; + return 0.007; } } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 5d7ef04dd2..8fd9ac92ba 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 300; } @@ -27,9 +28,10 @@ namespace osu.Game.Rulesets.Catch.Judgements switch (result) { default: - return 0; + return -0.02; + case HitResult.Perfect: - return 10.2; + return 0.01; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index b8c51b7b60..3829b5e94f 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 10; } @@ -26,8 +27,9 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: - return 4; + return 0.004; } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/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/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 294fd97d59..2f8ccec48b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable case ArmedState.Miss: this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); break; + case ArmedState.Hit: this.FadeOut().Expire(); break; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/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..77407def54 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; @@ -42,7 +43,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 +71,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } } }, - }; + }); if (HitObject.HyperDash) { @@ -105,6 +106,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { default: return new Container(); + case FruitVisualRepresentation.Raspberry: return new Container { @@ -143,6 +145,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pineapple: return new Container { @@ -181,6 +184,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pear: return new Container { @@ -213,6 +217,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Grape: return new Container { @@ -245,6 +250,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Banana: return new Container { @@ -282,19 +288,25 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable default: case FruitVisualRepresentation.Pear: return new Color4(17, 136, 170, 255); + case FruitVisualRepresentation.Grape: return new Color4(204, 102, 0, 255); + case FruitVisualRepresentation.Raspberry: return new Color4(121, 9, 13, 255); + case FruitVisualRepresentation.Pineapple: return new Color4(102, 136, 0, 255); + case FruitVisualRepresentation.Banana: switch (RNG.Next(0, 3)) { default: return new Color4(255, 240, 0, 255); + case 1: return new Color4(255, 192, 0, 255); + case 2: return new Color4(214, 221, 28, 255); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/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/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index 2e18c5f2ad..b9b6d5b924 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -3,7 +3,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2adc156efd..a9fd34455a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; + case SliderEventType.Head: case SliderEventType.Tail: case SliderEventType.Repeat: diff --git a/osu.Game.Rulesets.Catch/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 af614f95d0..99b22b2d56 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; @@ -27,20 +26,16 @@ namespace osu.Game.Rulesets.Catch.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - private const double harshness = 0.01; - - protected override void ApplyResult(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) { - base.ApplyResult(result); - - if (result.Type == HitResult.Miss) + switch (result.Type) { - if (!result.Judgement.IsBonus) - Health.Value -= hpDrainRate * (harshness * 2); - return; - } + case HitResult.Miss: + return hpDrainRate; - Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness; + default: + return 10.2 - hpDrainRate; // Award less HP as drain rate is increased + } } public override HitWindows CreateHitWindows() => new CatchHitWindows(); diff --git a/osu.Game.Rulesets.Catch/UI/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 c6dd0a86a0..e7c7fd77df 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 { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.UI if (result.IsHit && fruit.CanBePlated) { - var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); + var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); if (caughtFruit == null) return; @@ -292,6 +292,7 @@ namespace osu.Game.Rulesets.Catch.UI const float hyper_dash_transition_length = 180; bool previouslyHyperDashing = HyperDashing; + if (modifier <= 1 || X == targetPosition) { hyperDashModifier = 1; @@ -325,9 +326,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection--; return true; + case CatchAction.MoveRight: currentDirection++; return true; + case CatchAction.Dash: Dashing = true; return true; @@ -343,9 +346,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection++; return true; + case CatchAction.MoveRight: currentDirection--; return true; + case CatchAction.Dash: Dashing = false; return true; diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 406dc10eea..f48b84e344 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -10,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -23,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); @@ -34,24 +36,31 @@ 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 PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); - public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) { switch (h) { case Banana banana: return new DrawableBanana(banana); + 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: return new DrawableDroplet(droplet); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs index 13bbe87513..9ad22498a9 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,6 +10,7 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -21,6 +24,9 @@ namespace osu.Game.Rulesets.Mania.Tests { private readonly Column column; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + protected ManiaPlacementBlueprintTestCase() { Add(column = new Column(0) diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs index b14f999f61..d46b661eea 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Mania.Tests typeof(ColumnHitObjectArea) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List columns = new List(); public TestCaseColumn() diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs index 0bc2c3ea28..2220873d89 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs @@ -168,11 +168,13 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var nested in obj.NestedHitObjects) { double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration; + switch (direction) { case ScrollingDirection.Up: nested.Y = (float)(finalPosition * content.DrawHeight); break; + case ScrollingDirection.Down: nested.Y = (float)(-finalPosition * content.DrawHeight); break; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs index ac430037e4..9a7a3d1c5c 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests { private const int columns = 4; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List stages = new List(); private FillFlowContainer fill; 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/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 71df68c087..704deba78b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (IsForCurrentRuleset) { TargetColumns = (int)Math.Max(1, roundedCircleSize); + if (TargetColumns >= 10) { TargetColumns = TargetColumns / 2; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index ed52bdd23f..1b6ff16388 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -179,6 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; int nextColumn = GetRandomColumn(); + for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) { // Find available column @@ -217,6 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); int lastColumn = nextColumn; + for (int i = 0; i < noteCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -299,6 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i <= spanCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -341,16 +344,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p3 = 0; p4 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; break; + case 4: p2 = Math.Min(p2, 0.3); p3 = Math.Min(p3, 0.04); p4 = 0; break; + case 5: p2 = Math.Min(p2, 0.34); p3 = Math.Min(p3, 0.1); @@ -440,6 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); var rowPattern = new Pattern(); + for (int i = 0; i <= spanCount; i++) { if (!(ignoreHead && startTime == HitObject.StartTime)) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 0bf6c055ac..9e95be35fa 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -38,9 +38,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: addToPattern(pattern, 0, generateHold); break; + case 8: addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold); break; + default: if (TotalColumns > 0) addToPattern(pattern, GetRandomColumn(), generateHold); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 34f5f5c415..d13b21183b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -233,6 +233,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i < noteCount; i++) { nextColumn = allowStacking @@ -303,6 +304,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int nextColumn = GetRandomColumn(upperBound: columnLimit); + for (int i = 0; i < noteCount; i++) { nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern); @@ -340,18 +342,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p4 = 0; p5 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; p5 = 0; break; + case 4: p2 = Math.Min(p2, 0.23); p3 = Math.Min(p3, 0.04); p4 = 0; p5 = 0; break; + case 5: p3 = Math.Min(p3, 0.15); p4 = Math.Min(p4, 0.03); @@ -384,20 +389,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p2 = 0; p3 = 0; break; + case 3: centreProbability = Math.Min(centreProbability, 0.03); p2 = 0; p3 = 0; break; + case 4: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.2); p3 = 0; break; + case 5: centreProbability = Math.Min(centreProbability, 0.03); p3 = 0; break; + case 6: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.5); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index b702291c5d..fba52dfc32 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -158,6 +158,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // Ensure that we have at least one free column, so that an endless loop is avoided bool hasValidColumns = false; + for (int i = lowerBound.Value; i < upperBound.Value; i++) { hasValidColumns = isValid(i); diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index acafaffee6..e5f379f608 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics; using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -14,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 56c9471462..2729621ab3 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -41,9 +42,9 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) { - DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it dependencies.CacheAs(DrawableRuleset.ScrollingInfo); @@ -65,6 +66,7 @@ namespace osu.Game.Rulesets.Mania.Edit { case DrawableNote note: return new NoteSelectionBlueprint(note); + case DrawableHoldNote holdNote: return new HoldNoteSelectionBlueprint(holdNote); } diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 015eb1310e..b9c6e3a7f7 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -10,5 +10,17 @@ namespace osu.Game.Rulesets.Mania.Judgements public override bool AffectsCombo => false; protected override int NumericResultFor(HitResult result) => 20; + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return 0; + + default: + return 0.040; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index b6fb37f054..0e4c811945 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -14,16 +14,47 @@ namespace osu.Game.Rulesets.Mania.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Ok: return 100; + case HitResult.Good: return 200; + case HitResult.Great: case HitResult.Perfect: return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.125; + + case HitResult.Meh: + return 0.005; + + case HitResult.Ok: + return 0.010; + + case HitResult.Good: + return 0.035; + + case HitResult.Great: + return 0.055; + + case HitResult.Perfect: + return 0.065; + + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a4a10f1742..d83033f9c6 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -10,6 +10,7 @@ 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; @@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); @@ -116,6 +117,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModNoFail(), new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -125,6 +127,7 @@ namespace osu.Game.Rulesets.Mania new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), }; + case ModType.Conversion: return new Mod[] { @@ -141,16 +144,19 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new ManiaModAutoplay(), new ModCinema()), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } @@ -160,7 +166,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); @@ -213,6 +219,7 @@ namespace osu.Game.Rulesets.Mania SpecialAction = ManiaAction.Special1, NormalActionStart = ManiaAction.Key1, }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: int keys = getDualStageKeyCount(variant); @@ -270,6 +277,7 @@ namespace osu.Game.Rulesets.Mania { default: return $"{variant}K"; + case PlayfieldType.Dual: { var keys = getDualStageKeyCount(variant); diff --git a/osu.Game.Rulesets.Mania/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..9a29273282 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; Size = new Vector2(1); - InternalChildren = new[] + AddRangeInternal(new[] { glowContainer = new CircularContainer { @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } } - }; + }); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a78524011f..0873f753be 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables case ArmedState.Miss: this.FadeOut(150, Easing.In).Expire(); break; + case ArmedState.Hit: this.FadeOut(150, Easing.OutQuint).Expire(); break; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 7ef90cdb9c..afd7777861 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,7 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osuTK.Graphics; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; @@ -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/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 2baf1ad520..8102718edf 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; @@ -144,6 +145,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces const float animation_length = 50; Foreground.ClearTransforms(false, nameof(Foreground.Colour)); + if (hitting) { // wait for the next sync point diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs index b146a33fd3..1d25a0c966 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 65b7d54cd2..e5669816fa 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mania.Replays var normalAction = ManiaAction.Key1; var specialAction = ManiaAction.Special1; int totalCounter = 0; + foreach (var stage in Beatmap.Stages) { for (int i = 0; i < stage.Columns; i++) @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Replays var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); + foreach (var group in pointGroups) { foreach (var point in group) @@ -60,6 +62,7 @@ namespace osu.Game.Rulesets.Mania.Replays case HitPoint _: actions.Add(columnActions[point.Column]); break; + case ReleasePoint _: actions.Remove(columnActions[point.Column]); break; diff --git a/osu.Game.Rulesets.Mania/Replays/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/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 81a76c93e6..f7277d3669 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Replays int activeColumns = (int)(legacyFrame.MouseX ?? 0); int counter = 0; + while (activeColumns > 0) { var isSpecial = stage.IsSpecialColumn(counter); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 5c914d8eac..5caf08fb1e 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -28,36 +27,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_max = 1; - /// - /// The default BAD hit HP increase. - /// - private const double hp_increase_bad = 0.005; - - /// - /// The default OK hit HP increase. - /// - private const double hp_increase_ok = 0.010; - - /// - /// The default GOOD hit HP increase. - /// - private const double hp_increase_good = 0.035; - - /// - /// The default tick hit HP increase. - /// - private const double hp_increase_tick = 0.040; - - /// - /// The default GREAT hit HP increase. - /// - private const double hp_increase_great = 0.055; - - /// - /// The default PERFECT hit HP increase. - /// - private const double hp_increase_perfect = 0.065; - /// /// The MISS HP multiplier at OD = 0. /// @@ -73,11 +42,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_miss_max = 1; - /// - /// The default MISS HP increase. - /// - private const double hp_increase_miss = -0.125; - /// /// The MISS HP multiplier. This is multiplied to the miss hp increase. /// @@ -88,10 +52,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - public ManiaScoreProcessor() - { - } - public ManiaScoreProcessor(DrawableRuleset drawableRuleset) : base(drawableRuleset) { @@ -122,42 +82,8 @@ namespace osu.Game.Rulesets.Mania.Scoring } } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - bool isTick = result.Judgement is HoldNoteTickJudgement; - - if (isTick) - { - if (result.IsHit) - Health.Value += hpMultiplier * hp_increase_tick; - } - else - { - switch (result.Type) - { - case HitResult.Miss: - Health.Value += hpMissMultiplier * hp_increase_miss; - break; - case HitResult.Meh: - Health.Value += hpMultiplier * hp_increase_bad; - break; - case HitResult.Ok: - Health.Value += hpMultiplier * hp_increase_ok; - break; - case HitResult.Good: - Health.Value += hpMultiplier * hp_increase_good; - break; - case HitResult.Great: - Health.Value += hpMultiplier * hp_increase_great; - break; - case HitResult.Perfect: - Health.Value += hpMultiplier * hp_increase_perfect; - break; - } - } - } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 89e8cd9b5a..a0d713067d 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index 03b55cbead..85880222d7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index a019401d5b..c8aeda8fe4 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.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; @@ -19,6 +18,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Mania.UI double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; int index = 0; + for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) { barLines.Add(new BarLine @@ -89,11 +90,9 @@ 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); @@ -101,14 +100,16 @@ namespace osu.Game.Rulesets.Mania.UI protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) { switch (h) { case HoldNote holdNote: return new DrawableHoldNote(holdNote); + case Note note: return new DrawableNote(note); + default: return null; } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index f5a9978f77..0ec1fc38d2 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -4,6 +4,7 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 81888d2773..5ab07416a6 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 { @@ -40,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.UI var normalColumnAction = ManiaAction.Key1; var specialColumnAction = ManiaAction.Special1; int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) { var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); @@ -94,6 +93,7 @@ namespace osu.Game.Rulesets.Mania.UI private ManiaStage getStageByColumn(int column) { int sum = 0; + foreach (var stage in stages) { sum = sum + stage.Columns.Count; diff --git a/osu.Game.Rulesets.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/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index f7d1ff4db1..f98d63e6c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests yield return createConvertValue(nested); break; + default: yield return createConvertValue(hitObject); diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index e2f6b2164c..e8b99e86f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Text; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -22,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Tests using (var reader = new StreamReader(stream)) { var beatmap = Decoder.GetDecoder(reader).Decode(reader); - var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo); + var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()); var objects = converted.HitObjects.ToList(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index 5c1e775c01..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 @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests [BackgroundDependencyLoader] private void load() { - Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both }); + Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs index e1e854e8dc..31f3146046 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); public TestCaseHitCircle() { @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs index 26d9b5ae91..7391c0f11a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestCaseHitCircleHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs new file mode 100644 index 0000000000..1e72591b87 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseOsuFlashlight : TestCaseOsuPlayer + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/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/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index 35e8f3e17e..0f02050605 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); public TestCaseSlider() { @@ -292,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawable.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs index ba5bd48c51..65a8005407 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestCaseSliderHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 2f33982d41..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 += _ => { @@ -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/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs index e8b534bba9..ab33d1e518 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); public TestCaseSpinner() { @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs index 6136ce1639..24e3bcb47b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestCaseSpinnerHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } 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/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 59a5f90fd0..b2beda18f4 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -44,12 +44,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); int extendedEndIndex = endIndex; + if (endIndex < beatmap.HitObjects.Count - 1) { // Extend the end index to include objects they are stacked on for (int i = endIndex; i >= startIndex; i--) { int stackBaseIndex = i; + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) { OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; @@ -87,6 +89,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps //Reverse pass for stack calculation. int extendedStartIndex = startIndex; + for (int i = extendedEndIndex; i > startIndex; i--) { int n = i; @@ -138,6 +141,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { int offset = objectI.StackHeight - objectN.StackHeight + 1; + for (int j = n + 1; j <= i; j++) { //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index f6edd062e9..f76635a932 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -16,15 +16,16 @@ namespace osu.Game.Rulesets.Osu.Configuration protected override void InitialiseDefaults() { base.InitialiseDefaults(); - Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); + Set(OsuRulesetSetting.ShowCursorTrail, true); } } public enum OsuRulesetSetting { SnakingInSliders, - SnakingOutSliders + SnakingOutSliders, + ShowCursorTrail } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0dce5208dd..093081b6a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); double approachRateFactor = 1.0f; + if (Attributes.ApproachRate > 10.33f) approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); else if (Attributes.ApproachRate < 8.0f) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 37276a3432..eacac7ae6a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. float scalingFactor = normalized_radius / (float)BaseObject.Radius; + if (BaseObject.Radius < 30) { float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 46a81a9480..01f2fb8dc8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -42,9 +42,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); double angleBonus = 1.0; + if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) { angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57; + if (osuCurrent.Angle.Value < pi_over_2) { angleBonus = 1.28; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs index 9d164ebe0b..2ecfea2e3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case SliderPosition.Start: Position = slider.StackedPosition + slider.Path.PositionAt(0); break; + case SliderPosition.End: Position = slider.StackedPosition + slider.Path.PositionAt(1); break; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 989a53db1f..55de626d7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: HitObject.Position = e.MousePosition; return true; + case PlacementState.Body: cursor = e.MousePosition - HitObject.Position; return true; @@ -77,6 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: beginCurve(); break; + case PlacementState.Body: switch (e.Button) { diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 50b3eabcf4..bcb6099cfb 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -10,19 +12,18 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor { Size = Vector2.One }; + protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; private class OsuPlayfieldNoCursor : OsuPlayfield { - public OsuPlayfieldNoCursor() - { - Cursor?.Expire(); - } + protected override GameplayCursorContainer CreateCursor() => null; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index dd3925e04f..c5452ae0aa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -2,18 +2,16 @@ // 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; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; 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 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - => new DrawableOsuEditRuleset(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { @@ -38,16 +36,16 @@ 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) { case DrawableHitCircle circle: return new HitCircleSelectionBlueprint(circle); + case DrawableSlider slider: return new SliderSelectionBlueprint(slider); + case DrawableSpinner spinner: return new SpinnerSelectionBlueprint(spinner); } diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 81fedf9f4a..7a5b98864c 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -16,13 +16,33 @@ namespace osu.Game.Rulesets.Osu.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Good: return 100; + case HitResult.Great: return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.02; + + case HitResult.Meh: + case HitResult.Good: + case HitResult.Great: + return 0.01; + + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/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/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index a1f4dfe1da..445f81c6d4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -8,11 +8,11 @@ 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; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods 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; @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Mods scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + /// /// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency. /// diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 2c40d18f1b..c0332fbf60 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,23 +1,37 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight + public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObjects { public override double ScoreMultiplier => 1.12; private const float default_flashlight_size = 180; - public override Flashlight CreateFlashlight() => new OsuFlashlight(); + private OsuFlashlight flashlight; + + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var s in drawables.OfType()) + { + s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + } + } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { @@ -26,6 +40,12 @@ namespace osu.Game.Rulesets.Osu.Mods FlashlightSize = new Vector2(0, getSizeFor(0)); } + public void OnSliderTrackingChange(ValueChangedEvent e) + { + // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. + this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50); + } + protected override bool OnMouseMove(MouseMoveEvent e) { FlashlightPosition = e.MousePosition; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 65e9eb7a1d..a2da2bbf53 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; @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableSpinner _: continue; + default: drawable.ApplyCustomUpdateState += ApplyCustomState; break; @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSliderTail _: // special cases we should *not* be scaling. break; + case DrawableSlider _: case DrawableHitCircle _: { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 1a30b2c944..ddf708d0f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -59,11 +59,13 @@ namespace osu.Game.Rulesets.Osu.Mods circle.FadeOut(fadeOutDuration); break; + case DrawableSlider slider: using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) slider.Body.FadeOut(longFadeDuration, Easing.Out); break; + case DrawableSliderTick sliderTick: // slider ticks fade out over up to one second var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); @@ -72,6 +74,7 @@ namespace osu.Game.Rulesets.Osu.Mods sliderTick.FadeOut(tickFadeOutDuration); break; + case DrawableSpinner spinner: // hide elements we don't care about. spinner.Disc.Hide(); diff --git a/osu.Game.Rulesets.Osu/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/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 3c64fe57d4..aacf3ee08d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -6,6 +6,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 8f9d487d49..7569626230 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return; OsuHitObject prevHitObject = null; + foreach (var currHitObject in hitObjects) { if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index decd0ce073..fef0bfdc2c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) { Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss)); @@ -158,11 +159,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); break; + case ArmedState.Miss: ApproachCircle.FadeOut(50); this.FadeOut(100); Expire(); break; + case ArmedState.Hit: ApproachCircle.FadeOut(50); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 22d2034fe0..cce6dfe106 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) }; } @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (repeatPoint.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); } protected override void UpdatePreemptState() @@ -64,9 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(animDuration); break; + case ArmedState.Hit: this.FadeOut(animDuration, Easing.OutQuint) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 57ea0abdd8..c1a4c1981f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -130,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public bool Tracking; + public readonly Bindable Tracking = new Bindable(); protected override void Update() { base.Update(); - Tracking = Ball.Tracking; + Tracking.Value = Ball.Tracking; double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index b5ce36f889..72b648bfd0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -67,10 +67,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(ANIM_DURATION); this.FadeColour(Color4.Red, ANIM_DURATION / 2); break; + case ArmedState.Hit: this.FadeOut(ANIM_DURATION, Easing.OutQuint); this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 789af4f49b..1794da54b7 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, }, } @@ -221,9 +222,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: Expire(true); break; + case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; + case ArmedState.Miss: sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 93ac8748dd..84034d3ee9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index e41c568403..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; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index c982f53c2b..77228e28af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -4,6 +4,7 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index f47617bcf6..9219fab830 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1afbacc01e..a8aec005d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -183,6 +183,7 @@ namespace osu.Game.Rulesets.Osu.Objects Samples = sampleList }); break; + case SliderEventType.Head: AddNested(HeadCircle = new SliderCircle { @@ -194,6 +195,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.LegacyLastTick: // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. @@ -206,6 +208,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.Repeat: AddNested(new RepeatPoint { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7d3aff7801..83d29c156d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -9,6 +9,7 @@ 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; @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); @@ -103,6 +104,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModHalfTime(), new OsuModDaycore()), new OsuModSpunOut(), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -112,11 +114,13 @@ namespace osu.Game.Rulesets.Osu new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), }; + case ModType.Conversion: return new Mod[] { new OsuModTarget(), }; + case ModType.Automation: return new Mod[] { @@ -124,6 +128,7 @@ namespace osu.Game.Rulesets.Osu new OsuModRelax(), new OsuModAutopilot(), }; + case ModType.Fun: return new Mod[] { @@ -132,12 +137,13 @@ namespace osu.Game.Rulesets.Osu 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); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index c1aaa7767e..690263c6a0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -199,6 +199,7 @@ namespace osu.Game.Rulesets.Osu.Replays // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); + if (waitTime > lastFrame.Time) { lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; @@ -292,7 +293,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; @@ -315,9 +315,8 @@ 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); @@ -326,7 +325,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/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 2c8bf11016..cf0565c6da 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts.Clear(); } - private const double harshness = 0.01; - protected override void ApplyResult(JudgementResult result) { base.ApplyResult(result); @@ -47,28 +45,29 @@ namespace osu.Game.Rulesets.Osu.Scoring if (result.Type != HitResult.None) comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; + } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + { switch (result.Type) { case HitResult.Great: - Health.Value += (10.2 - hpDrainRate) * harshness; - break; + return 10.2 - hpDrainRate; case HitResult.Good: - Health.Value += (8 - hpDrainRate) * harshness; - break; + return 8 - hpDrainRate; case HitResult.Meh: - Health.Value += (4 - hpDrainRate) * harshness; - break; + return 4 - hpDrainRate; - /*case HitResult.SliderTick: - Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01; - break;*/ + // case HitResult.SliderTick: + // return Math.Max(7 - hpDrainRate, 0) * 0.01; case HitResult.Miss: - Health.Value -= hpDrainRate * (harshness * 2); - break; + return hpDrainRate; + + default: + return 0; } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 03dbf7ac63..1b8fa0de01 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -43,22 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly InputResampler resampler = new InputResampler(); - protected override DrawNode CreateDrawNode() => new TrailDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - TrailDrawNode tNode = (TrailDrawNode)node; - tNode.Shader = shader; - tNode.Texture = texture; - tNode.Size = size; - tNode.Time = time; - - for (int i = 0; i < parts.Length; ++i) - if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) - tNode.Parts[i] = parts[i]; - } + protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); public CursorTrail() { @@ -167,33 +152,53 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private class TrailDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new CursorTrail Source => (CursorTrail)base.Source; - public float Time; + private IShader shader; + private Texture texture; - public readonly TrailPart[] Parts = new TrailPart[max_sprites]; - public Vector2 Size; + private float time; + + private readonly TrailPart[] parts = new TrailPart[max_sprites]; + private Vector2 size; private readonly VertexBuffer vertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); - public TrailDrawNode() + public TrailDrawNode(CursorTrail source) + : base(source) { for (int i = 0; i < max_sprites; i++) { - Parts[i].InvalidationID = 0; - Parts[i].WasUpdated = false; + parts[i].InvalidationID = 0; + parts[i].WasUpdated = false; + } + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.size; + time = Source.time; + + for (int i = 0; i < Source.parts.Length; ++i) + { + if (Source.parts[i].InvalidationID > parts[i].InvalidationID) + parts[i] = Source.parts[i]; } } public override void Draw(Action vertexAction) { - Shader.GetUniform("g_FadeClock").UpdateValue(ref Time); + shader.GetUniform("g_FadeClock").UpdateValue(ref time); int updateStart = -1, updateEnd = 0; - for (int i = 0; i < Parts.Length; ++i) + + for (int i = 0; i < parts.Length; ++i) { - if (Parts[i].WasUpdated) + if (parts[i].WasUpdated) { if (updateStart == -1) updateStart = i; @@ -202,22 +207,22 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor int start = i * 4; int end = start; - Vector2 pos = Parts[i].Position; - float time = Parts[i].Time; + Vector2 pos = parts[i].Position; + float localTime = parts[i].Time; - Texture.DrawQuad( - new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), + texture.DrawQuad( + new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), DrawColourInfo.Colour, null, v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex { Position = v.Position, TexturePosition = v.TexturePosition, - Time = time + 1, + Time = localTime + 1, Colour = v.Colour, }); - Parts[i].WasUpdated = false; + parts[i].WasUpdated = false; } else if (updateStart != -1) { @@ -232,12 +237,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor base.Draw(vertexAction); - Shader.Bind(); + shader.Bind(); - Texture.TextureGL.Bind(); + texture.TextureGL.Bind(); vertexBuffer.Draw(); - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs deleted file mode 100644 index 8c6723f5be..0000000000 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.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 GameplayCursorContainer : CursorContainer, IKeyBindingHandler - { - protected override Drawable CreateCursor() => new OsuCursor(); - - protected override Container Content => fadeContainer; - - private readonly Container fadeContainer; - - public GameplayCursorContainer() - { - 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(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/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs new file mode 100644 index 0000000000..27546fa424 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -0,0 +1,149 @@ +// 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.Effects; +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..d72c334ed3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Osu.Configuration; +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; + + private readonly Bindable showTrail = new Bindable(true); + + private readonly CursorTrail cursorTrail; + + public OsuCursorContainer() + { + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + cursorTrail = new CursorTrail { Depth = 1 } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuRulesetConfigManager config) + { + config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + + showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true); + } + + 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/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b632e0fb05..17fffbd974 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; @@ -14,6 +17,7 @@ using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.UI { @@ -21,8 +25,8 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } @@ -32,14 +36,20 @@ namespace osu.Game.Rulesets.Osu.UI 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) { case HitCircle circle: return new DrawableHitCircle(circle); + case Slider slider: return new DrawableSlider(slider); + case Spinner spinner: return new DrawableSpinner(spinner); } @@ -54,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI get { var first = (OsuHitObject)Objects.First(); - return first.StartTime - first.TimePreempt; + return first.StartTime - Math.Max(2000, first.TimePreempt); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 51733c3c01..0cbe0cca85 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; -using osu.Framework.Graphics.Cursor; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; @@ -24,41 +23,28 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - private readonly PlayfieldAdjustmentContainer adjustmentContainer; - - protected override Container CursorTargetContainer => adjustmentContainer; - - protected override CursorContainer CreateCursor() => new GameplayCursorContainer(); + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); public OsuPlayfield() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Size = new Vector2(0.75f); - - InternalChild = adjustmentContainer = 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/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index ce3432c73d..88adf72551 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = "Snaking out sliders", Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) }, + new SettingsCheckbox + { + LabelText = "Cursor trail", + Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) + }, }; } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 369cdd49d2..8cd5918fc5 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -11,6 +11,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; @@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Array.Empty()) } }); } @@ -100,15 +101,19 @@ namespace osu.Game.Rulesets.Taiko.Tests case 1: addCentreHit(false); break; + case 2: addCentreHit(true); break; + case 3: addDrumRoll(false); break; + case 4: addDrumRoll(true); break; + case 5: addSwell(); delay = scroll_time - 100; @@ -121,6 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Tests default: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); break; + case 6: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); break; diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 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/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 3e0e2624bf..f8672037cd 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -120,6 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { List currentSamples = allSamples[i]; diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index e5ebd5c647..604daa929f 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return 0; + default: return base.HealthIncreaseFor(result); } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 32d4b77ca4..a617028f1c 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Great: return 200; + default: return 0; } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Great: return 0.15; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 427d38aaa7..eb5f443365 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -16,8 +16,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Good: return 100; + case HitResult.Great: return 300; + default: return 0; } @@ -29,10 +31,13 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return -1.0; + case HitResult.Good: return 1.1; + case HitResult.Great: return 3.0; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index f0f621d12b..29be5e0eac 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return -0.65; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/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/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index d3837946c9..4c8d5d5204 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables circlePiece?.FlashBox.FinishTransforms(); var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; + using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) { switch (State.Value) @@ -108,15 +109,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables UnproxyContent(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); break; + case ArmedState.Miss: this.FadeOut(100) .Expire(); break; + case ArmedState.Hit: // If we're far enough away from the left stage, we should bring outselves in front of it ProxyContent(); var flash = circlePiece?.FlashBox; + if (flash != null) { flash.FadeTo(0.9f); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 9211eccc40..5ec9dc61e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true)) targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); break; + case ArmedState.Miss: case ArmedState.Hit: this.FadeOut(out_transition_time, Easing.Out); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f755c7cc3..119940536e 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 } - }; + }); } /// @@ -111,6 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.KiaiMode = HitObject.Kiai; var strongObject = HitObject.NestedHitObjects.OfType().FirstOrDefault(); + if (strongObject != null) { var strongHit = CreateStrongHit(strongObject); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 53dbe5d08e..b7db819717 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Backgrounds; using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Taiko/Objects/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/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 6f3bdca6fb..1d25735fe3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects return; bool first = true; + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { AddNested(new DrumRollTick diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs index ce841fff80..f232919cbf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects case HitResult.Good: case HitResult.Miss: return true; + default: return false; } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 01ba53e07b..422ba748e3 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Replays int count = 0; int req = swell.RequiredHits; double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); + for (double j = h.StartTime; j < endTime; j += hitRate) { TaikoAction action; @@ -62,12 +63,15 @@ namespace osu.Game.Rulesets.Taiko.Replays case 0: action = TaikoAction.LeftCentre; break; + case 1: action = TaikoAction.LeftRim; break; + case 2: action = TaikoAction.RightCentre; break; + case 3: action = TaikoAction.RightRim; break; diff --git a/osu.Game.Rulesets.Taiko/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 442cca49f8..68ddf2db19 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -46,19 +46,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - double hpIncrease = result.Judgement.HealthIncreaseFor(result); - - if (result.Type == HitResult.Miss) - hpIncrease *= hpMissMultiplier; - else - hpIncrease *= hpMultiplier; - - Health.Value += hpIncrease; - } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; protected override void Reset(bool storeResults) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 3e94775eb6..a67004e9c7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -9,6 +9,7 @@ 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; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] @@ -85,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModNoFail(), new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -94,17 +96,20 @@ namespace osu.Game.Rulesets.Taiko new TaikoModHidden(), new TaikoModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new TaikoModAutoplay(), new ModCinema()), new TaikoModRelax(), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } @@ -114,7 +119,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 943adaed4b..f91bbb14e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI case HitResult.Good: JudgementBody.Colour = colours.GreenLight; break; + case HitResult.Great: JudgementBody.Colour = colours.BlueLight; break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 899b91863e..ec3a56e9c7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; @@ -16,6 +17,7 @@ using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI @@ -26,8 +28,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; TimeRange.Value = 7000; @@ -52,9 +54,11 @@ namespace osu.Game.Rulesets.Taiko.UI int currentIndex = 0; int currentBeat = 0; double time = timingPoints[currentIndex].Time; + while (time <= lastHitTime) { int nextIndex = currentIndex + 1; + if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) { currentIndex = nextIndex; @@ -81,20 +85,25 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + 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) { case CentreHit centreHit: return new DrawableCentreHit(centreHit); + case RimHit rimHit: return new DrawableRimHit(rimHit); + case DrumRoll drumRoll: return new DrawableDrumRoll(drumRoll); + case Swell swell: return new DrawableSwell(swell); } diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index bed2c554ec..e80b463481 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -5,6 +5,7 @@ using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 35b941b52b..7427a3235d 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -55,143 +56,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, } }; } @@ -217,6 +212,7 @@ namespace osu.Game.Rulesets.Taiko.UI case DrawableBarLine barline: barlineContainer.Add(barline.CreateProxy()); break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; @@ -237,6 +233,7 @@ namespace osu.Game.Rulesets.Taiko.UI if (result.IsHit) hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).MainObject)?.VisualiseSecondHit(); break; + default: judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) { diff --git a/osu.Game.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 02dff6993d..3c1f48b7a4 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using NUnit.Framework; using osuTK; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu; @@ -39,7 +41,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion); } } @@ -47,6 +49,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapGeneral() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -70,6 +73,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEditor() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -95,6 +99,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapMetadata() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -119,6 +124,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapDifficulty() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -137,6 +143,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEvents() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -155,6 +162,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapTimingPoints() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -190,6 +198,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapColours() { var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -215,6 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsOsu() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { @@ -237,6 +247,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsCatch() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { @@ -259,6 +270,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapHitObjects() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -286,6 +298,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeControlPointCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { @@ -307,6 +320,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { @@ -324,6 +338,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectFileSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu")) using (var stream = new StreamReader(resStream)) { @@ -343,6 +358,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeSliderSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("slider-samples.osu")) using (var stream = new StreamReader(resStream)) { @@ -386,6 +402,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectNullAdditionBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 136d1de930..971518909d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeStoryboardEvents() { var decoder = new LegacyStoryboardDecoder(); + using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) using (var stream = new StreamReader(resStream)) { @@ -29,28 +30,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 +63,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 +71,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); @@ -91,6 +92,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeVariableWithSuffix() { var decoder = new LegacyStoryboardDecoder(); + using (var resStream = TestResources.OpenResource("variable-with-suffix.osb")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index a867ddebae..39b7735a55 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -151,6 +151,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var sr = new StreamReader(stream)) { var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 17197aff27..37e0565df0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -75,6 +75,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var osz = TestResources.GetTestBeatmapStream()) { var reader = new ZipArchiveReader(osz); + using (var stream = new StreamReader( reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) { diff --git a/osu.Game.Tests/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/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 2a97519e21..24ef9e4535 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -18,6 +18,7 @@ namespace osu.Game.Tests.Skins public void TestDecodeSkinColours(bool hasColours) { var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs rename to osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index de34ff822e..81fab5b4b3 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 @@ -54,8 +53,6 @@ namespace osu.Game.Tests.Visual private BeatmapManager manager; private RulesetStore rulesets; - private ScreenStackCacheContainer screenStackContainer; - [BackgroundDependencyLoader] private void load(GameHost host) { @@ -82,8 +79,10 @@ namespace osu.Game.Tests.Visual [SetUp] public virtual void SetUp() => Schedule(() => { - Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both }; - screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect()); + Child = new OsuScreenStack(songSelect = new DummySongSelect()) + { + RelativeSizeAxes = Axes.Both + }; }); /// @@ -93,7 +92,7 @@ namespace osu.Game.Tests.Visual public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer()))); + 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", () => @@ -184,10 +183,10 @@ namespace osu.Game.Tests.Visual public void PauseTest() { performFullSetup(true); - AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause()); + AddStep("Pause", () => player.Pause()); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); - AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume()); + AddStep("Unpause", () => player.Resume()); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } @@ -256,14 +255,8 @@ namespace osu.Game.Tests.Visual { setupUserSettings(); - AddStep("Start player loader", () => - { - songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer - { - AllowPause = allowPause, - Ready = true, - })); - }); + 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("Wait for player to load", () => player.IsLoaded); @@ -274,7 +267,7 @@ namespace osu.Game.Tests.Visual AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }); + Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); @@ -349,46 +342,36 @@ namespace osu.Game.Tests.Visual }; } - public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer; - 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); 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 TestPlayerLoader : PlayerLoader { public VisualSettings VisualSettingsPos => VisualSettings; diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseIdleTracker.cs rename to osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs index a7a1831ba7..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 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/Editor/TestCaseWaveform.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs new file mode 100644 index 0000000000..ce6ca08a61 --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs @@ -0,0 +1,107 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestCaseWaveform : OsuTestCase + { + private WorkingBeatmap waveformBeatmap; + + [BackgroundDependencyLoader] + private void load() + { + waveformBeatmap = new WaveformTestBeatmap(); + } + + [TestCase(1f)] + [TestCase(1f / 2)] + [TestCase(1f / 4)] + [TestCase(1f / 8)] + [TestCase(1f / 16)] + [TestCase(0f)] + public void TestResolution(float resolution) + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new Drawable[] + { + graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Resolution = resolution, + Waveform = waveformBeatmap.Waveform, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.75f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"Resolution: {resolution:0.00}" + } + } + } + } + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + [Test] + public void TestDefaultBeatmap() + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Child = graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Waveform = new DummyWorkingBeatmap().Waveform, + }, + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + public class TestWaveformGraph : WaveformGraph + { + public new Waveform ResampledWaveform => base.ResampledWaveform; + } + } +} diff --git a/osu.Game.Tests/Visual/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 77% rename from osu.Game.Tests/Visual/TestCaseAutoplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index 4d6a0ae7b8..624e5f08bd 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -7,20 +7,15 @@ 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 : 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, - }; + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + return new ScoreAccessiblePlayer(); } protected override void AddCheckSteps() @@ -33,6 +28,11 @@ namespace osu.Game.Tests.Visual { 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/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs new file mode 100644 index 0000000000..5cd01fe9a8 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -0,0 +1,150 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCaseFrameStabilityContainer : OsuTestCase + { + private readonly ManualClock manualClock; + + private readonly Container mainContainer; + + private ClockConsumingChild consumer; + + public TestCaseFrameStabilityContainer() + { + Child = mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(manualClock = new ManualClock()), + }; + } + + [Test] + public void TestLargeJumps() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(100000); + + confirmSeek(100000); + checkFrameCount(6000); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(12000); + } + + [Test] + public void TestSmallJumps() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(40); + + confirmSeek(40); + checkFrameCount(3); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(6); + } + + [Test] + public void TestSingleFrameJump() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(8); + confirmSeek(8); + checkFrameCount(1); + + seekManualTo(16); + confirmSeek(16); + checkFrameCount(2); + } + + [Test] + public void TestInitialSeek() + { + seekManualTo(100000); + createStabilityContainer(); + + confirmSeek(100000); + checkFrameCount(0); + } + + private void createStabilityContainer() => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer().WithChild(consumer = new ClockConsumingChild())); + + private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); + + private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time); + + private void checkFrameCount(int frames) => + AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames); + + public class ClockConsumingChild : CompositeDrawable + { + private readonly OsuSpriteText text; + private readonly OsuSpriteText text2; + private readonly OsuSpriteText text3; + + public ClockConsumingChild() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text2 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text3 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + } + + public int ElapsedFrames; + + protected override void Update() + { + base.Update(); + + if (Clock.ElapsedFrameTime != 0) + ElapsedFrames++; + + text.Text = $"current time: {Clock.CurrentTime:F0}"; + if (Clock.ElapsedFrameTime != 0) + text2.Text = $"last elapsed frame time: {Clock.ElapsedFrameTime:F0}"; + text3.Text = $"total frames: {ElapsedFrames:F0}"; + } + } + } +} diff --git a/osu.Game.Tests/Visual/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 93a059d214..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(PausableGameplayContainer) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; private FailOverlay failOverlay; - private PausableGameplayContainer.PauseOverlay pauseOverlay; + private PauseOverlay pauseOverlay; + + private GlobalActionContainer globalActionContainer; [BackgroundDependencyLoader] - private void load() + private void load(OsuGameBase game) { - Add(pauseOverlay = new PausableGameplayContainer.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 97% rename from osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs index a4fadbd3db..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 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..b9c7fd14bd --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -0,0 +1,206 @@ +// 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; + + protected override void LoadComplete() + { + base.LoadComplete(); + HUDOverlay.HoldToQuit.PauseOnFocusLost = false; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs new file mode 100644 index 0000000000..15c38e6d18 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -0,0 +1,131 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCasePlayerLoader : ManualInputManagerTestCase + { + private PlayerLoader loader; + private OsuScreenStack stack; + + [SetUp] + public void Setup() => Schedule(() => + { + InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + Beatmap.Value = new TestWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), Clock); + }); + + [Test] + public void TestLoadContinuation() + { + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); + AddStep("load slow dummy beatmap", () => + { + SlowLoadPlayer slow = null; + + stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false))); + + Scheduler.AddDelayed(() => slow.Ready = true, 5000); + }); + + AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); + } + + [Test] + public void TestModReinstantiation() + { + TestPlayer player = null; + TestMod gameMod = null; + TestMod playerMod1 = null; + TestMod playerMod2 = null; + + AddStep("load player", () => + { + Mods.Value = new[] { gameMod = new TestMod() }; + stack.Push(loader = new PlayerLoader(() => player = new TestPlayer())); + }); + + AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod1 = (TestMod)player.Mods.Value.Single()); + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player mods applied", () => playerMod1.Applied); + + AddStep("restart player", () => + { + var lastPlayer = player; + player = null; + lastPlayer.Restart(); + }); + + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod2 = (TestMod)player.Mods.Value.Single()); + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player has different mods", () => playerMod1 != playerMod2); + AddAssert("player mods applied", () => playerMod2.Applied); + } + + private class TestMod : Mod, IApplicableToScoreProcessor + { + public override string Name => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => 1; + + public bool Applied { get; private set; } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + Applied = true; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + } + + private class TestPlayer : Player + { + public new Bindable> Mods => base.Mods; + + public TestPlayer() + : base(false, false) + { + } + } + + protected class SlowLoadPlayer : Player + { + public bool Ready; + + public SlowLoadPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + + [BackgroundDependencyLoader] + private void load() + { + while (!Ready) + Thread.Sleep(1); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs rename to osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs index 3e009ae080..5937d489f2 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerReferenceLeaking : AllPlayersTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCaseReplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs index 3a7e2352f8..263070ab21 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -1,21 +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; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +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); + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } 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 95% rename from osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs index 5ebb9b270f..6998f238c9 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs @@ -4,25 +4,30 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; 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 { public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4]; @@ -124,12 +129,15 @@ namespace osu.Game.Tests.Visual case ScrollingDirection.Up: obj.Anchor = Anchor.TopCentre; break; + case ScrollingDirection.Down: obj.Anchor = Anchor.BottomCentre; break; + case ScrollingDirection.Left: obj.Anchor = Anchor.CentreLeft; break; + case ScrollingDirection.Right: obj.Anchor = Anchor.CentreRight; break; @@ -170,12 +178,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) { @@ -184,6 +192,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.X; Height = 2; break; + case ScrollingDirection.Left: case ScrollingDirection.Right: RelativeSizeAxes = Axes.Y; @@ -205,7 +214,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 92% rename from osu.Game.Tests/Visual/TestCaseSkinReloadable.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs index 94f01e9d32..56ab70b400 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 { @@ -120,12 +120,8 @@ namespace osu.Game.Tests.Visual } } - private class SecondarySource : ISkinSource + private class SecondarySource : ISkin { - public event Action SourceChanged; - - public void TriggerSourceChanged() => SourceChanged?.Invoke(); - public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); @@ -135,12 +131,8 @@ namespace osu.Game.Tests.Visual public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } - private class SkinSourceContainer : Container, ISkinSource + private class SkinSourceContainer : Container, ISkin { - public event Action SourceChanged; - - public void TriggerSourceChanged() => SourceChanged?.Invoke(); - public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Visual/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 98% rename from osu.Game.Tests/Visual/TestCaseSongProgress.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs index 511272a5ae..e17dcef19c 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs @@ -10,7 +10,7 @@ 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 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/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseDisclaimer.cs rename to osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs index f08a2a54ca..68a1ceec16 100644 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API; using osu.Game.Screens.Menu; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { public class TestCaseDisclaimer : ScreenTestCase { @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { + Add(api); + AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); AddStep("toggle support", () => 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/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs similarity index 53% rename from osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs rename to osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs index 3803764194..eb275cbceb 100644 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; @@ -9,62 +11,92 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseLoaderAnimation : OsuTestCase + public class TestCaseLoaderAnimation : ScreenTestCase { private TestLoader loader; - protected override void LoadComplete() + [Cached] + private OsuLogo logo; + + public TestCaseLoaderAnimation() { - base.LoadComplete(); - - // required to preload the logo in a headless run (so it doesn't delay the loading itself). - Add(new OsuLogo()); + Child = logo = new OsuLogo + { + Alpha = 0, + Depth = float.MinValue + }; + } + [Test] + public void TestInstantLoad() + { bool logoVisible = false; - AddStep("almost instant display", () => Child = loader = new TestLoader(250)); - AddUntilStep("loaded", () => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }); - AddAssert("logo not visible", () => !logoVisible); - AddStep("short load", () => Child = loader = new TestLoader(800)); + AddStep("begin loading", () => + { + loader = new TestLoader(); + loader.AllowLoad.Set(); + + LoadScreen(loader); + }); + AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; }); - AddAssert("logo visible", () => logoVisible); + + 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); + } - AddStep("longer load", () => Child = loader = new TestLoader(1400)); - AddUntilStep("loaded", () => + [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; - return loader.Logo != null && loader.ScreenLoaded; + loader.AllowLoad.Set(); }); - AddAssert("logo visible", () => logoVisible); + + AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); + AddAssert("logo was visible", () => logoVisible); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); } private class TestLoader : Loader { - private readonly double delay; + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); 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; @@ -72,25 +104,18 @@ namespace osu.Game.Tests.Visual } protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen(); - protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(delay); + protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(AllowLoad); private class TestShaderPrecompiler : ShaderPrecompiler { - private readonly double delay; - private double startTime; + private readonly ManualResetEventSlim allowLoad; - public TestShaderPrecompiler(double delay) + public TestShaderPrecompiler(ManualResetEventSlim allowLoad) { - this.delay = delay; + this.allowLoad = allowLoad; } - protected override void LoadComplete() - { - base.LoadComplete(); - startTime = Time.Current; - } - - protected override bool AllLoaded => Time.Current > startTime + delay; + protected override bool AllLoaded => allowLoad.IsSet; } private class TestScreen : OsuScreen diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseToolbar.cs rename to osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs index cb5f33911b..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 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 97% rename from osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs index 484a212a38..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 { 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 99% rename from osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs index 11c7d3ef70..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 { 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/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..8363f8be04 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs @@ -1,9 +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 NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; @@ -13,8 +10,11 @@ using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osu.Game.Users; +using System; +using System.Collections.Generic; +using System.Linq; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseBeatmapSetOverlay : OsuTestCase @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(Header), - typeof(ClickableUsername), - typeof(DrawableScore), + typeof(ScoreTable), + typeof(ScoreTableRowBackground), typeof(DrawableTopScore), typeof(ScoresContainer), typeof(AuthorInfo), diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseChannelTabControl.cs rename to osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs index e90b5f5372..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 { 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 99% rename from osu.Game.Tests/Visual/TestCaseChatLink.cs rename to osu.Game.Tests/Visual/Online/TestCaseChatLink.cs index ecab64ccf3..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 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/TestCaseDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseDirectPanel.cs rename to osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs index beb88ac56c..fbda531792 100644 --- a/osu.Game.Tests/Visual/TestCaseDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseDirectPanel : OsuTestCase { 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 97% rename from osu.Game.Tests/Visual/TestCaseRankGraph.cs rename to osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs index f41033c0be..a92b788e83 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.Overlays.Profile.Header.Components; 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/Online/TestCaseScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs new file mode 100644 index 0000000000..1ef4558691 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs @@ -0,0 +1,185 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestCaseScoresContainer : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableTopScore), + typeof(TopScoreUserSection), + typeof(TopScoreStatisticsSection), + typeof(ScoreTable), + typeof(ScoreTableRowBackground), + }; + + private readonly Box background; + + public TestCaseScoresContainer() + { + ScoresContainer scoresContainer; + + Child = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = 0.8f, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + scoresContainer = new ScoresContainer(), + } + }; + + var scores = new List + { + new ScoreInfo + { + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + new OsuModHardRock(), + }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, + }, + new ScoreInfo + { + User = new User + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + }, + Rank = ScoreRank.S, + PP = 190, + MaxCombo = 1234, + TotalScore = 1234789, + Accuracy = 0.9997, + }, + new ScoreInfo + { + User = new User + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + }, + Rank = ScoreRank.B, + PP = 180, + MaxCombo = 1234, + TotalScore = 12345678, + Accuracy = 0.9854, + }, + new ScoreInfo + { + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + }, + Rank = ScoreRank.C, + PP = 170, + MaxCombo = 1234, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + new ScoreInfo + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.F, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + }; + + foreach (var s in scores) + { + s.Statistics.Add(HitResult.Great, RNG.Next(2000)); + s.Statistics.Add(HitResult.Good, RNG.Next(2000)); + s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); + } + + scoresContainer.Scores = scores; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray2; + } + } +} 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 96% rename from osu.Game.Tests/Visual/TestCaseUserPanel.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs index 8caa608d5e..b015418d78 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 @@ -38,6 +38,7 @@ namespace osu.Game.Tests.Visual Country = new Country { FlagName = @"AU" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, + SupportLevel = 3, }) { Width = 300 }, }, }); diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs similarity index 54% rename from osu.Game.Tests/Visual/TestCaseUserProfile.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs index aa0bd37449..0789c14b32 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs @@ -6,20 +6,23 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Profile; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserProfile : OsuTestCase { private readonly TestUserProfileOverlay profile; - private IAPIProvider api; + + [Resolved] + private IAPIProvider api { get; set; } public override IReadOnlyList RequiredTypes => new[] { @@ -27,7 +30,46 @@ namespace osu.Game.Tests.Visual typeof(UserProfileOverlay), typeof(RankGraph), typeof(LineGraph), - typeof(BadgeContainer) + typeof(SectionsContainer<>), + typeof(SupporterIcon) + }; + + public static readonly User TEST_USER = new User + { + Username = @"Somebody", + Id = 1, + Country = new Country { FullName = @"Alien" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + JoinDate = DateTimeOffset.Now.AddDays(-1), + LastVisit = DateTimeOffset.Now, + ProfileOrder = new[] { "me" }, + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + PP = 4567.89m, + Level = new UserStatistics.LevelInfo + { + Current = 727, + Progress = 69, + } + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, + Badges = new[] + { + new Badge + { + AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), + Description = "Outstanding help by being a voluntary test subject.", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" + } + }, + Title = "osu!volunteer", + Colour = "ff0000", + Achievements = new User.UserAchievement[0], }; public TestCaseUserProfile() @@ -35,47 +77,11 @@ namespace osu.Game.Tests.Visual Add(profile = new TestUserProfileOverlay()); } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - this.api = api; - } - protected override void LoadComplete() { base.LoadComplete(); - AddStep("Show offline dummy", () => profile.ShowUser(new User - { - Username = @"Somebody", - Id = 1, - Country = new Country { FullName = @"Alien" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - JoinDate = DateTimeOffset.Now.AddDays(-1), - LastVisit = DateTimeOffset.Now, - ProfileOrder = new[] { "me" }, - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, - PP = 4567.89m, - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() - }, - Badges = new[] - { - new Badge - { - AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), - Description = "Outstanding help by being a voluntary test subject.", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" - } - } - }, false)); - - checkSupporterTag(false); + AddStep("Show offline dummy", () => profile.ShowUser(TEST_USER, false)); AddStep("Show null dummy", () => profile.ShowUser(new User { @@ -92,8 +98,6 @@ namespace osu.Game.Tests.Visual CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" }, api.IsLoggedIn)); - checkSupporterTag(true); - AddStep("Show flyte", () => profile.ShowUser(new User { Username = @"flyte", @@ -106,15 +110,6 @@ namespace osu.Game.Tests.Visual AddStep("Show without reload", profile.Show); } - private void checkSupporterTag(bool isSupporter) - { - AddUntilStep("wait for load", () => profile.Header.User != null); - if (isSupporter) - AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); - else - AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); - } - private class TestUserProfileOverlay : UserProfileOverlay { public new ProfileHeader Header => base.Header; diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs new file mode 100644 index 0000000000..5f5ba89186 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestCaseUserProfileHeader : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(RankGraph), + typeof(LineGraph), + typeof(ProfileHeaderTabControl), + typeof(CentreHeaderContainer), + typeof(BottomHeaderContainer), + typeof(DetailHeaderContainer), + typeof(ProfileHeaderButton) + }; + + [Resolved] + private IAPIProvider api { get; set; } + + private readonly ProfileHeader header; + + public TestCaseUserProfileHeader() + { + header = new ProfileHeader(); + Add(header); + + AddStep("Show offline dummy", () => header.User.Value = TestCaseUserProfile.TEST_USER); + + AddStep("Show null dummy", () => header.User.Value = new User + { + Username = "Null" + }); + + addOnlineStep("Show ppy", new User + { + Username = @"peppy", + Id = 2, + IsSupporter = true, + Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" + }); + + addOnlineStep("Show flyte", new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }); + } + + private void addOnlineStep(string name, User fallback) + { + AddStep(name, () => + { + if (api.IsLoggedIn) + { + var request = new GetUserRequest(fallback.Id); + request.Success += user => header.User.Value = user; + api.Queue(request); + } + else + header.User.Value = fallback; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/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 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs index 956d84618c..1ffc164026 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 @@ -508,6 +508,7 @@ namespace osu.Game.Tests.Visual }, Beatmaps = new List(), }; + for (int b = 1; b < 101; b++) { toReturn.Beatmaps.Add(new BeatmapInfo diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs index 6cc3982f9c..722a63f2b0 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] 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 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs index 0d77ac666b..36a7eba37f 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 @@ -83,15 +83,19 @@ namespace osu.Game.Tests.Visual case OsuRuleset _: testInfoLabels(5); break; + case TaikoRuleset _: testInfoLabels(5); break; + case CatchRuleset _: testInfoLabels(5); break; + case ManiaRuleset _: testInfoLabels(4); break; + default: testInfoLabels(2); break; diff --git a/osu.Game.Tests/Visual/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/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 93% rename from osu.Game.Tests/Visual/TestCasePlaySongSelect.cs rename to osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index 4a2cf24c6d..7e33f6ce02 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 @@ -35,13 +35,9 @@ namespace osu.Game.Tests.Visual private WorkingBeatmap defaultBeatmap; private DatabaseContextFactory factory; - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); - public override IReadOnlyList RequiredTypes => new[] { - typeof(SongSelect), + typeof(Screens.Select.SongSelect), typeof(BeatmapCarousel), typeof(CarouselItem), @@ -175,19 +171,19 @@ namespace osu.Game.Tests.Visual AddStep("change ruleset", () => { - songSelect.CurrentBeatmap.Mods.ValueChanged += onModChange; + Mods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - songSelect.CurrentBeatmap.Mods.ValueChanged -= onModChange; + Mods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !selectedMods.Value.Any()); + AddAssert("empty mods", () => !Mods.Value.Any()); - void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; + void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--; } @@ -218,7 +214,7 @@ namespace osu.Game.Tests.Visual private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => selectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game.Tests/Visual/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/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs deleted file mode 100644 index 892c87653d..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Overlays.Profile.Header; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBadgeContainer : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(BadgeContainer) }; - - public TestCaseBadgeContainer() - { - BadgeContainer badgeContainer; - - Child = badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.Both - }; - - AddStep("Show 1 badge", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show 2 badges", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Contributed to osu!lazer testing", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.png", - }, - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show many badges", () => badgeContainer.ShowBadges(Enumerable.Range(1, 20).Select(i => new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = $"Contributed to osu!lazer testing {i} times", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg", - }).ToArray())); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs deleted file mode 100644 index bb55c0b1e8..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ /dev/null @@ -1,313 +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.Shapes; -using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Rulesets.Mods; -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; - -namespace osu.Game.Tests.Visual -{ - [System.ComponentModel.Description("in BeatmapOverlay")] - public class TestCaseBeatmapScoresContainer : OsuTestCase - { - private readonly Box background; - - public TestCaseBeatmapScoresContainer() - { - Container container; - ScoresContainer scoresContainer; - - Child = container = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.8f, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - scoresContainer = new ScoresContainer(), - } - }; - - IEnumerable scores = new[] - { - new APIScoreInfo - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScoreInfo - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScoreInfo - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScoreInfo - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - new APIScoreInfo - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - }; - - foreach (var s in scores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - IEnumerable anotherScores = new[] - { - new APIScoreInfo - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScoreInfo - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScoreInfo - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - new APIScoreInfo - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScoreInfo - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - }; - foreach (var s in anotherScores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - var topScoreInfo = new APIScoreInfo - { - User = new User - { - Id = 2705430, - Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.B, - TotalScore = 987654321, - Accuracy = 0.8487, - }; - topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000)); - topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000)); - topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - - AddStep("scores pack 1", () => scoresContainer.Scores = scores); - AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); - AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo }); - AddStep("remove scores", () => scoresContainer.Scores = null); - AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); - AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); - AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray2; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseCharLookup.cs b/osu.Game.Tests/Visual/TestCaseCharLookup.cs new file mode 100644 index 0000000000..0b9413f332 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseCharLookup.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseCharLookup : OsuTestCase + { + public TestCaseCharLookup() + { + AddStep("null", () => { }); + AddStep("display acharacter", () => Add(new OsuSpriteText { Text = "振込申請" })); + } + } +} 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/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs deleted file mode 100644 index 2bc416f7f4..0000000000 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Screens; -using osu.Game.Beatmaps; -using osu.Game.Screens; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - public class TestCasePlayerLoader : ManualInputManagerTestCase - { - private PlayerLoader loader; - private readonly ScreenStack stack; - - [Cached] - private BackgroundScreenStack backgroundStack; - - public TestCasePlayerLoader() - { - InputManager.Add(backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }); - InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both }); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - Beatmap.Value = new DummyWorkingBeatmap(game); - - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }))); - - AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - - AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); - - AddStep("exit loader", () => loader.Exit()); - - AddUntilStep("wait for no longer alive", () => !loader.IsAlive); - - AddStep("load slow dummy beatmap", () => - { - SlowLoadPlayer slow = null; - - stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - })); - - Scheduler.AddDelayed(() => slow.Ready = true, 5000); - }); - - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); - } - - protected class SlowLoadPlayer : Player - { - public bool Ready; - - [BackgroundDependencyLoader] - private void load() - { - while (!Ready) - Thread.Sleep(1); - } - } - } -} 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/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs deleted file mode 100644 index 0981b482a1..0000000000 --- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; -using osu.Game.Tests.Beatmaps.IO; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase - { - private TestUpdateableBeatmapBackgroundSprite backgroundSprite; - - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) - { - Bindable beatmapBindable = new Bindable(); - - var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); - - Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; - - backgroundSprite.Beatmap.BindTo(beatmapBindable); - - var req = new GetBeatmapSetRequest(1); - api.Queue(req); - - AddStep("load null beatmap", () => beatmapBindable.Value = null); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First()); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - - if (api.IsLoggedIn) - { - AddUntilStep("wait for api response", () => req.Result != null); - AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo - { - BeatmapSet = req.Result?.ToBeatmapSet(rulesets) - }); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - } - else - { - AddStep("online (login first)", () => { }); - } - } - - private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite - { - public int ChildCount => InternalChildren.Count; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs deleted file mode 100644 index 9330070392..0000000000 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using 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; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseWaveform : OsuTestCase - { - [BackgroundDependencyLoader] - private void load() - { - Beatmap.Value = new WaveformTestBeatmap(); - - FillFlowContainer flow; - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - }; - - for (int i = 1; i <= 16; i *= 2) - { - var newDisplay = new WaveformGraph - { - RelativeSizeAxes = Axes.Both, - Resolution = 1f / i, - Waveform = Beatmap.Value.Waveform, - }; - - flow.Add(new Container - { - RelativeSizeAxes = Axes.X, - Height = 100, - Children = new Drawable[] - { - newDisplay, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.75f - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = $"Resolution: {1f / i:0.00}" - } - } - } - } - }); - } - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseDrawings.cs rename to osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs index aad135b71f..53fb60bcb6 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs @@ -3,15 +3,17 @@ using System.Collections.Generic; using System.ComponentModel; +using osu.Framework.Allocation; using osu.Game.Screens.Tournament; using osu.Game.Screens.Tournament.Teams; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Tournament { [Description("for tournament use")] public class TestCaseDrawings : ScreenTestCase { - public TestCaseDrawings() + [BackgroundDependencyLoader] + private void load() { LoadScreen(new Drawings { 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 91% rename from osu.Game.Tests/Visual/TestCaseButtonSystem.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs index 8ea2ab9dde..04aa8bce7e 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 @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, }, buttons = new ButtonSystem(), - logo = new OsuLogo() + logo = new OsuLogo { RelativePositionAxes = Axes.Both } }; buttons.SetOsuLogo(logo); diff --git a/osu.Game.Tests/Visual/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 97% rename from osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs index c9a7e9c39f..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 { 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/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs new file mode 100644 index 0000000000..e45e2e24da --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -0,0 +1,299 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestCaseLogoTrackingContainer : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PlayerLoader), + typeof(Player), + typeof(LogoTrackingContainer), + typeof(ButtonSystem), + typeof(ButtonSystemState), + typeof(Menu), + typeof(MainMenu) + }; + + private OsuLogo logo; + private TestLogoTrackingContainer trackingContainer; + private Container transferContainer; + private Box visualBox; + private Box transferContainerBox; + private Drawable logoFacade; + private bool randomPositions; + + private const float visual_box_size = 72; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Clear facades", () => + { + Clear(); + Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.Both }); + trackingContainer = null; + transferContainer = null; + }); + } + + /// + /// Move the facade to 0,0, then move it to a random new location while the logo is still transforming to it. + /// Check if the logo is still tracking the facade. + /// + [Test] + public void TestMoveFacade() + { + AddToggleStep("Toggle move continuously", b => randomPositions = b); + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + waitForMove(); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + /// + /// Check if the facade is removed from the container, the logo stops tracking. + /// + [Test] + public void TestRemoveFacade() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + AddStep("Remove facade from FacadeContainer", removeFacade); + waitForMove(); + AddAssert("Logo is not tracking", () => !trackingContainer.IsLogoTracking); + } + + /// + /// Check if the facade gets added to a new container, tracking starts on the new facade. + /// + [Test] + public void TestTransferFacade() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + AddStep("Remove facade from FacadeContainer", removeFacade); + AddStep("Transfer facade to a new container", () => + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); + }); + + waitForMove(); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + /// + /// Add a facade to a flow container, move the logo to the center of the screen, then start tracking on the facade. + /// + [Test] + public void TestFlowContainer() + { + FillFlowContainer flowContainer; + + AddStep("Create new flow container with facade", () => + { + Add(trackingContainer = new TestLogoTrackingContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Child = flowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + } + }); + flowContainer.Children = new Drawable[] + { + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(visual_box_size) + }, + new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + visualBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + trackingContainer.LogoFacade, + } + }, + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(visual_box_size) + }, + }; + }); + + AddStep("Perform logo movements", () => + { + trackingContainer.StopTracking(); + logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); + + visualBox.Colour = Color4.White; + + Scheduler.AddDelayed(() => + { + trackingContainer.StartTracking(logo, 1000, Easing.InOutExpo); + visualBox.Colour = Color4.Tomato; + }, 700); + }); + + waitForMove(8); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + [Test] + public void TestSetFacadeSize() + { + bool failed = false; + + AddStep("Set up scenario", () => + { + failed = false; + addFacadeContainers(); + }); + + AddStep("Try setting facade size", () => + { + try + { + logoFacade.Size = new Vector2(0, 0); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + + AddAssert("Exception thrown", () => failed); + } + + [Test] + public void TestSetMultipleContainers() + { + bool failed = false; + LogoTrackingContainer newContainer = new LogoTrackingContainer(); + + AddStep("Set up scenario", () => + { + failed = false; + newContainer = new LogoTrackingContainer(); + addFacadeContainers(); + moveLogoFacade(); + }); + + AddStep("Try tracking new container", () => + { + try + { + newContainer.StartTracking(logo); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + + AddAssert("Exception thrown", () => failed); + } + + private void addFacadeContainers() + { + AddRange(new Drawable[] + { + trackingContainer = new TestLogoTrackingContainer + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Child = visualBox = new Box + { + Colour = Color4.Tomato, + RelativeSizeAxes = Axes.Both, + } + }, + transferContainer = new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Child = transferContainerBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }, + }); + + trackingContainer.Add(logoFacade = trackingContainer.LogoFacade); + trackingContainer.StartTracking(logo, 1000); + } + + private void waitForMove(int count = 5) => AddWaitStep("Wait for transforms to finish", count); + + private void removeFacade() + { + trackingContainer.Remove(logoFacade); + visualBox.Colour = Color4.White; + moveLogoFacade(); + } + + private void moveLogoFacade() + { + if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) + { + Random random = new Random(); + trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + } + + if (randomPositions) + Schedule(moveLogoFacade); + } + + private class TestLogoTrackingContainer : LogoTrackingContainer + { + /// + /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. + /// + public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, ComputeLogoTrackingPosition()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseMods.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs index cb7e783bee..fd003c7ea2 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 @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual private class TestModSelectOverlay : ModSelectOverlay { - public new Bindable> SelectedMods => base.SelectedMods; + public new Bindable> SelectedMods => base.SelectedMods; public ModButton GetModButton(Mod mod) { diff --git a/osu.Game.Tests/Visual/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 99% rename from osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs index 9e70df91b6..faf80d22ff 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 @@ -86,12 +86,15 @@ namespace osu.Game.Tests.Visual case 0: sendHelloNotification(); break; + case 1: sendAmazingNotification(); break; + case 2: sendUploadProgress(); break; + case 3: sendDownloadProgress(); break; 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 96% rename from osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs index dad684689e..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[] { 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/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs new file mode 100644 index 0000000000..a4dd7b83e2 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Tests.Beatmaps.IO; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase + { + private BeatmapSetInfo testBeatmap; + private IAPIProvider api; + private RulesetStore rulesets; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu); + } + + [Test] + public void TestNullBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }); + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestLocalBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load local beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = testBeatmap.Beatmaps.First() } + }; + }); + + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestOnlineBeatmap() + { + if (api.IsLoggedIn) + { + var req = new GetBeatmapSetRequest(1); + api.Queue(req); + + AddUntilStep("wait for api response", () => req.Result != null); + + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load online beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } } + }; + }); + + AddUntilStep("wait for load", () => background.ContentLoaded); + } + else + AddStep("online (login first)", () => { }); + } + + [Test] + public void TestUnloadAndReload() + { + var backgrounds = new List(); + ScrollContainer scrollContainer = null; + + AddStep("create backgrounds hierarchy", () => + { + FillFlowContainer backgroundFlow; + + Child = scrollContainer = new ScrollContainer + { + Size = new Vector2(500), + Child = backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 250 } + } + }; + + for (int i = 0; i < 25; i++) + { + var background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + + if (i % 2 == 0) + background.Beatmap.Value = testBeatmap.Beatmaps.First(); + + backgroundFlow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Child = background + }); + + backgrounds.Add(background); + } + }); + + var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); + + int initialLoadCount = 0; + + AddUntilStep("some loaded", () => (initialLoadCount = loadedBackgrounds.Count()) > 0); + AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); + AddUntilStep("some unloaded", () => loadedBackgrounds.Count() < initialLoadCount); + } + + private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite + { + protected override double UnloadDelay => 2000; + + public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false; + } + } +} diff --git a/osu.Game.Tests/Visual/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/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs index fdcae65e3c..8ab93257a5 100644 --- a/osu.Game/Audio/IPreviewTrackOwner.cs +++ b/osu.Game/Audio/IPreviewTrackOwner.cs @@ -4,7 +4,7 @@ namespace osu.Game.Audio { /// - /// Interface for objects that can own s. + /// Interface for objects that can own s. /// /// /// s can cancel the currently playing through the diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b6fa6674f6..7922843626 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps protected abstract IEnumerable ValidConversionTypes { get; } /// - /// Creates the that will be returned by this . + /// Creates the that will be returned by this . /// protected virtual Beatmap CreateBeatmap() => new Beatmap(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9caa64ec96..798bca3ada 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -117,6 +117,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineBeatmapSetID != null) { var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + if (existingOnlineId != null) { Delete(existingOnlineId); @@ -217,7 +218,7 @@ namespace osu.Game.Beatmaps { request.Perform(api); } - catch (Exception e) + catch { // no need to handle here as exceptions will filter down to request.Failure above. } @@ -325,6 +326,7 @@ namespace osu.Game.Beatmaps { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + if (string.IsNullOrEmpty(mapName)) { Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database); @@ -382,7 +384,6 @@ namespace osu.Game.Beatmaps /// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status. /// /// The beatmap to populate. - /// The other beatmaps contained within this set. /// Whether to re-query if the provided beatmap already has populated values. /// True if population was successful. private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index a2e43e5a97..0bdab22dd2 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps protected override Storyboard GetStoryboard() { Storyboard storyboard; + try { using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) @@ -131,6 +132,7 @@ namespace osu.Game.Beatmaps protected override Skin GetSkin() { Skin skin; + try { skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager); diff --git a/osu.Game/Beatmaps/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/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs index 657dc06297..27bad65062 100644 --- a/osu.Game/Beatmaps/BindableBeatmap.cs +++ b/osu.Game/Beatmaps/BindableBeatmap.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps { /// /// A for the beatmap. - /// This should be used sparingly in-favour of . + /// This should be used sparingly in-favour of . /// public abstract class BindableBeatmap : NonNullableBindable { @@ -67,6 +67,6 @@ namespace osu.Game.Beatmaps /// If you are further binding to events of the retrieved , ensure a local reference is held. /// [NotNull] - public abstract BindableBeatmap GetBoundCopy(); + public new abstract BindableBeatmap GetBoundCopy(); } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index 3adfcb85ea..d0db7765c2 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -32,9 +32,11 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapSetCoverType.Cover: resource = set.OnlineInfo.Covers.Cover; break; + case BeatmapSetCoverType.Card: resource = set.OnlineInfo.Covers.Card; break; + case BeatmapSetCoverType.List: resource = set.OnlineInfo.Covers.List; break; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index f1607ad749..26ffcca1ec 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -63,15 +63,20 @@ namespace osu.Game.Beatmaps.Drawables { case DifficultyRating.Easy: return palette.Green; + default: case DifficultyRating.Normal: return palette.Blue; + case DifficultyRating.Hard: return palette.Yellow; + case DifficultyRating.Insane: return palette.Pink; + case DifficultyRating.Expert: return palette.Purple; + case DifficultyRating.ExpertPlus: return palette.Gray0; } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index dd8cdb862e..0a0ad28fdf 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -6,8 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osuTK; @@ -60,7 +61,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 ce7811fc0d..bd80919851 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -26,6 +26,11 @@ namespace osu.Game.Beatmaps.Drawables this.beatmapSetCoverType = beatmapSetCoverType; } + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 10000; + private BeatmapInfo lastModel; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) @@ -34,13 +39,13 @@ namespace osu.Game.Beatmaps.Drawables { // If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was // previously UNLOADED and thus its children have been disposed of, so we need to recreate them here. - if (lastModel == Beatmap.Value && Beatmap.Value != null) + if (lastModel == Beatmap.Value) return CreateDrawable(Beatmap.Value); // If the model has changed since the previous unload (or if there was no load), then we can safely use the given content lastModel = Beatmap.Value; return content; - }, timeBeforeLoad, 10000); + }, timeBeforeLoad, UnloadDelay); } protected override Drawable CreateDrawable(BeatmapInfo model) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 73aa12a3db..7d25ca3ede 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; @@ -52,7 +53,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) { throw new NotImplementedException(); } @@ -73,9 +74,18 @@ namespace osu.Game.Beatmaps private class DummyBeatmapConverter : IBeatmapConverter { public event Action> ObjectConverted; + public IBeatmap Beatmap { get; set; } + public bool CanConvert => true; - public IBeatmap Convert() => Beatmap; + + public IBeatmap Convert() + { + foreach (var obj in Beatmap.HitObjects) + ObjectConverted?.Invoke(obj, obj.Yield()); + + return Beatmap; + } } } } diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index a895ba3d63..953e50eadc 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -49,6 +49,7 @@ namespace osu.Game.Beatmaps.Formats throw new IOException(@"Unknown decoder type"); string line; + do { line = stream.ReadLine()?.Trim(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index a27126ad9c..b489b5e6d9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -70,21 +70,27 @@ namespace osu.Game.Beatmaps.Formats case Section.General: handleGeneral(strippedLine); return; + case Section.Editor: handleEditor(strippedLine); return; + case Section.Metadata: handleMetadata(line); return; + case Section.Difficulty: handleDifficulty(strippedLine); return; + case Section.Events: handleEvent(strippedLine); return; + case Section.TimingPoints: handleTimingPoint(strippedLine); return; + case Section.HitObjects: handleHitObject(strippedLine); return; @@ -98,29 +104,37 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"AudioFilename": metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; + case @"AudioLeadIn": beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); break; + case @"PreviewTime": metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value)); break; + case @"Countdown": beatmap.BeatmapInfo.Countdown = Parsing.ParseInt(pair.Value) == 1; break; + case @"SampleSet": defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; + case @"SampleVolume": defaultSampleVolume = Parsing.ParseInt(pair.Value); break; + case @"StackLeniency": beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); break; + case @"Mode": beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); @@ -129,24 +143,30 @@ namespace osu.Game.Beatmaps.Formats case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 1: parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 2: parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 3: parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; } break; + case @"LetterboxInBreaks": beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; + case @"SpecialStyle": beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; + case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; @@ -162,15 +182,19 @@ namespace osu.Game.Beatmaps.Formats case @"Bookmarks": beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; + case @"DistanceSpacing": beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; + case @"BeatDivisor": beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); break; + case @"GridSize": beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); break; + case @"TimelineZoom": beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; @@ -182,35 +206,45 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"Title": metadata.Title = pair.Value; break; + case @"TitleUnicode": metadata.TitleUnicode = pair.Value; break; + case @"Artist": metadata.Artist = pair.Value; break; + case @"ArtistUnicode": metadata.ArtistUnicode = pair.Value; break; + case @"Creator": metadata.AuthorString = pair.Value; break; + case @"Version": beatmap.BeatmapInfo.Version = pair.Value; break; + case @"Source": beatmap.BeatmapInfo.Metadata.Source = pair.Value; break; + case @"Tags": beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; + case @"BeatmapID": beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); break; + case @"BeatmapSetID": beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; break; @@ -222,23 +256,29 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + switch (pair.Key) { case @"HPDrainRate": difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; + case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); break; + case @"OverallDifficulty": difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); break; + case @"ApproachRate": difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); break; + case @"SliderMultiplier": difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; + case @"SliderTickRate": difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; @@ -259,6 +299,7 @@ namespace osu.Game.Beatmaps.Formats string filename = split[2].Trim('"'); beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; + case EventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); @@ -308,6 +349,7 @@ namespace osu.Game.Beatmaps.Formats bool kiaiMode = false; bool omitFirstBarSignature = false; + if (split.Length >= 8) { EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 040f582e3b..ad5089958c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -26,6 +26,7 @@ namespace osu.Game.Beatmaps.Formats Section section = Section.None; string line; + while ((line = stream.ReadLine()) != null) { if (ShouldSkipLine(line)) @@ -95,7 +96,7 @@ namespace osu.Game.Beatmaps.Formats { colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); } - catch (Exception e) + catch { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 9584b10ef5..f6e2bf6966 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) @@ -49,6 +54,7 @@ namespace osu.Game.Beatmaps.Formats case Section.Events: handleEvents(line); return; + case Section.Variables: handleVariables(line); return; @@ -60,6 +66,7 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(string line) { var depth = 0; + while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal)) { ++depth; @@ -89,8 +96,9 @@ namespace osu.Game.Beatmaps.Formats var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Animation: { var layer = parseLayer(split[1]); @@ -103,8 +111,9 @@ namespace osu.Game.Beatmaps.Formats var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Sample: { var time = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -112,8 +121,8 @@ namespace osu.Game.Beatmaps.Formats var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } break; + } } } else @@ -122,6 +131,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.TimelineGroup; var commandType = split[0]; + switch (commandType) { case "T": @@ -133,6 +143,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); } break; + case "L": { var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -140,6 +151,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); } break; + default: { if (string.IsNullOrEmpty(split[3])) @@ -158,6 +170,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } break; + case "S": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -165,6 +178,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); } break; + case "V": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -174,6 +188,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); } break; + case "R": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -181,6 +196,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); } break; + case "M": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -191,6 +207,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); } break; + case "MX": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -198,6 +215,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); } break; + case "MY": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -205,6 +223,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); } break; + case "C": { var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -218,23 +237,28 @@ namespace osu.Game.Beatmaps.Formats new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); } break; + case "P": { var type = split[4]; + switch (type) { case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break; + case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break; + case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break; } } break; + default: throw new InvalidDataException($@"Unknown command type: {commandType}"); } @@ -249,26 +273,36 @@ namespace osu.Game.Beatmaps.Formats private Anchor parseOrigin(string value) { var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) { case LegacyOrigins.TopLeft: return Anchor.TopLeft; + case LegacyOrigins.TopCentre: return Anchor.TopCentre; + case LegacyOrigins.TopRight: return Anchor.TopRight; + case LegacyOrigins.CentreLeft: return Anchor.CentreLeft; + case LegacyOrigins.Centre: return Anchor.Centre; + case LegacyOrigins.CentreRight: return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: return Anchor.BottomRight; + default: return Anchor.TopLeft; } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 2e36d87024..4b0720d867 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -11,7 +11,6 @@ using osu.Framework.IO.File; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Bindables; using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -28,16 +27,12 @@ namespace osu.Game.Beatmaps public readonly BeatmapMetadata Metadata; - public readonly Bindable> Mods = new Bindable>(new Mod[] { }); - protected WorkingBeatmap(BeatmapInfo beatmapInfo) { BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapInfo.BeatmapSet; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - Mods.ValueChanged += _ => applyRateAdjustments(); - beatmap = new RecyclableLazy(() => { var b = GetBeatmap() ?? new Beatmap(); @@ -51,14 +46,7 @@ namespace osu.Game.Beatmaps return b; }); - track = new RecyclableLazy(() => - { - // we want to ensure that we always have a track, even if it's a fake one. - var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap); - applyRateAdjustments(t); - return t; - }); - + track = new RecyclableLazy(() => GetTrack() ?? new VirtualBeatmapTrack(Beatmap)); background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); @@ -85,9 +73,10 @@ namespace osu.Game.Beatmaps /// /// /// The to create a playable for. + /// The s to apply to the . /// The converted . /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); @@ -98,19 +87,19 @@ namespace osu.Game.Beatmaps throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); // Apply conversion mods - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToBeatmapConverter(converter); // Convert IBeatmap converted = converter.Convert(); // Apply difficulty mods - if (Mods.Value.Any(m => m is IApplicableToDifficulty)) + if (mods.Any(m => m is IApplicableToDifficulty)) { converted.BeatmapInfo = converted.BeatmapInfo.Clone(); converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); } @@ -122,7 +111,7 @@ namespace osu.Game.Beatmaps foreach (var obj in converted.HitObjects) obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) foreach (var obj in converted.HitObjects) mod.ApplyToHitObject(obj); @@ -188,16 +177,6 @@ namespace osu.Game.Beatmaps /// public void RecycleTrack() => track.Recycle(); - private void applyRateAdjustments(Track t = null) - { - if (t == null && track.IsResultAvailable) t = Track; - if (t == null) return; - - t.ResetSpeedAdjustments(); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(t); - } - public class RecyclableLazy { private Lazy lazy; diff --git a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs index cb1be82c69..1e237a2b53 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs @@ -26,9 +26,11 @@ namespace osu.Game.Beatmaps case null: Length = excess_length; break; + case IHasEndTime endTime: Length = endTime.EndTime + excess_length; break; + default: Length = lastObject.StartTime + excess_length; break; diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index f547a7d3e1..8f1780cab5 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -44,6 +44,7 @@ namespace osu.Game.Configuration base.AddBindable(lookup, bindable); var setting = databasedSettings.Find(s => (int)s.Key == (int)(object)lookup); + if (setting != null) { bindable.Parse(setting.Value); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 2d8cfa12ee..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); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3805921ac2..ba348c4090 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Database private void flushEvents(bool perform) { Action[] events; + lock (queuedEvents) { events = queuedEvents.ToArray(); @@ -147,6 +148,7 @@ namespace osu.Game.Database List imported = new List(); int current = 0; + foreach (string path in paths) { if (notification.State == ProgressNotificationState.Cancelled) @@ -563,7 +565,7 @@ namespace osu.Game.Database /// /// Check whether an existing model already exists for a new import item. /// - /// The new model proposed for import. + /// The new model proposed for import. /// An existing model which matches the criteria to skip importing, else null. protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index f6250732d9..554337c477 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; @@ -67,7 +66,7 @@ namespace osu.Game.Database context = threadContexts.Value; } } - catch (Exception e) + catch { // retrieval of a context could trigger a fatal error. Monitor.Exit(writeLock); diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 17efe2c839..d31d7cbff7 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -60,6 +60,7 @@ namespace osu.Game.Database this.connectionString = connectionString; var connection = Database.GetDbConnection(); + try { connection.Open(); @@ -70,7 +71,7 @@ namespace osu.Game.Database cmd.ExecuteNonQuery(); } } - catch (Exception e) + catch { connection.Close(); throw; @@ -170,9 +171,11 @@ namespace osu.Game.Database default: frameworkLogLevel = Framework.Logging.LogLevel.Debug; break; + case LogLevel.Warning: frameworkLogLevel = Framework.Logging.LogLevel.Important; break; + case LogLevel.Error: case LogLevel.Critical: frameworkLogLevel = Framework.Logging.LogLevel.Error; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index c67d779c37..e2c7693700 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -178,64 +178,68 @@ namespace osu.Game.Graphics.Backgrounds /// The colour. protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); - protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var trianglesNode = (TrianglesDrawNode)node; - - trianglesNode.Shader = shader; - trianglesNode.Texture = texture; - trianglesNode.Size = DrawSize; - - trianglesNode.Parts.Clear(); - trianglesNode.Parts.AddRange(parts); - } + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this); private class TrianglesDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new Triangles Source => (Triangles)base.Source; - public readonly List Parts = new List(); - public Vector2 Size; + private IShader shader; + private Texture texture; + + private readonly List parts = new List(); + private Vector2 size; private readonly LinearBatch vertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + public TrianglesDrawNode(Triangles source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize; + + parts.Clear(); + parts.AddRange(Source.parts); + } + public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); + texture.TextureGL.Bind(); Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; - foreach (TriangleParticle particle in Parts) + foreach (TriangleParticle particle in parts) { var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); - var size = new Vector2(2 * offset.X, offset.Y); var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + offset, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - Texture.DrawTriangle( + texture.DrawTriangle( triangle, colourInfo, null, vertexBatch.AddAction, - Vector2.Divide(localInflationAmount, size)); + Vector2.Divide(localInflationAmount, new Vector2(2 * offset.X, offset.Y))); } - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index c1811f37d5..9d3f342a70 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; namespace osu.Game.Graphics.Containers @@ -34,6 +35,7 @@ namespace osu.Game.Graphics.Containers protected override void Update() { base.Update(); + if (InternalChildren.Count > 0 && InternalChild.DrawSize.X > 0) { // We're modifying scale here for a few reasons diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 37afefb7f8..15068d81c0 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Users; namespace osu.Game.Graphics.Containers { @@ -35,7 +36,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, }); } @@ -51,6 +52,7 @@ namespace osu.Game.Graphics.Containers } int previousLinkEnd = 0; + foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); @@ -75,6 +77,9 @@ namespace osu.Game.Graphics.Containers return createLink(text, null, url, linkType, linkArgument, tooltipText); } + public IEnumerable AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) @@ -90,33 +95,39 @@ namespace osu.Game.Graphics.Containers if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) game?.ShowBeatmap(beatmapId); break; + case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) game?.ShowBeatmapSet(setId); break; + case LinkAction.OpenChannel: try { channelManager?.OpenChannel(linkArgument); } - catch (ChannelNotFoundException e) + catch (ChannelNotFoundException) { Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); } break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: showNotImplementedError?.Invoke(); break; + case LinkAction.External: game?.OpenUrlExternally(url); break; + case LinkAction.OpenUserProfile: if (long.TryParse(linkArgument, out long userId)) game?.ShowUser(userId); break; + default: throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); } diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs new file mode 100644 index 0000000000..23015e8bf5 --- /dev/null +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that handles tracking of an through different layout scenarios. + /// + public class LogoTrackingContainer : Container + { + public Facade LogoFacade => facade; + + protected OsuLogo Logo { get; private set; } + + private readonly InternalFacade facade = new InternalFacade(); + + private Easing easing; + private Vector2? startPosition; + private double? startTime; + private double duration; + + /// + /// Assign the logo that should track the facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) + { + if (logo == null) + throw new ArgumentNullException(nameof(logo)); + + if (logo.IsTracking && Logo == null) + throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + + if (Logo != logo && Logo != null) + { + // If we're replacing the logo to be tracked, the old one no longer has a tracking container + Logo.IsTracking = false; + } + + Logo = logo; + Logo.IsTracking = true; + + this.duration = duration; + this.easing = easing; + + startTime = null; + startPosition = null; + } + + /// + /// Stops the logo assigned in from tracking the facade's position. + /// + public void StopTracking() + { + if (Logo != null) + { + Logo.IsTracking = false; + Logo = null; + } + } + + /// + /// Gets the position that the logo should move to with respect to the . + /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. + /// + /// Will only be correct if the logo's are set to Axes.Both + protected Vector2 ComputeLogoTrackingPosition() + { + var absolutePos = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); + + return new Vector2(absolutePos.X / Logo.Parent.RelativeToAbsoluteFactor.X, + absolutePos.Y / Logo.Parent.RelativeToAbsoluteFactor.Y); + } + + protected override void Update() + { + base.Update(); + + if (Logo == null) + return; + + if (Logo.RelativePositionAxes != Axes.Both) + throw new InvalidOperationException($"Tracking logo must have {nameof(RelativePositionAxes)} = Axes.Both"); + + // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. + facade.SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); + + var localPos = ComputeLogoTrackingPosition(); + + if (LogoFacade.Parent != null && Logo.Position != localPos) + { + // If this is our first update since tracking has started, initialize our starting values for interpolation + if (startTime == null || startPosition == null) + { + startTime = Time.Current; + startPosition = Logo.Position; + } + + if (duration != 0) + { + double elapsedDuration = (double)(Time.Current - startTime); + + var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); + + // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. + Logo.Position = Vector2.Lerp(startPosition.Value, localPos, amount); + } + else + { + Logo.Position = localPos; + } + } + } + + protected override void Dispose(bool isDisposing) + { + if (Logo != null) + Logo.IsTracking = false; + + base.Dispose(isDisposing); + } + + private class InternalFacade : Facade + { + public new void SetSize(Vector2 size) + { + base.SetSize(size); + } + } + + /// + /// A dummy object used to denote another object's location. + /// + public abstract class Facade : Drawable + { + public override Vector2 Size + { + get => base.Size; + set => throw new InvalidOperationException($"Cannot set the Size of a {typeof(Facade)} outside of a {typeof(LogoTrackingContainer)}"); + } + + protected void SetSize(Vector2 size) + { + base.Size = size; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index c6ee91f961..3f84f77081 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -84,6 +84,7 @@ namespace osu.Game.Graphics.Containers case GlobalAction.Back: State = Visibility.Hidden; return true; + case GlobalAction.Select: return true; } @@ -107,6 +108,7 @@ namespace osu.Game.Graphics.Containers State = Visibility.Hidden; break; + case Visibility.Hidden: if (PlaySamplesOnStateChange) samplePopOut?.Play(); if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this); diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 276b0f9dd1..d5ae7cba57 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -1,17 +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.Collections.Generic; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osuTK.Graphics; +using System.Collections.Generic; namespace osu.Game.Graphics.Containers { public class OsuHoverContainer : OsuClickableContainer { + protected const float FADE_DURATION = 500; + protected Color4 HoverColour; protected Color4 IdleColour = Color4.White; @@ -20,20 +22,21 @@ namespace osu.Game.Graphics.Containers protected override bool OnHover(HoverEvent e) { - EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); + EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint)); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); + EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint)); base.OnHoverLost(e); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - HoverColour = colours.Yellow; + if (HoverColour == default) + HoverColour = colours.Yellow; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/Containers/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/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 6bbab4766d..a8d0e03c01 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -142,6 +142,17 @@ namespace osu.Game.Graphics.Containers public void ScrollToTop() => scrollContainer.ScrollTo(0); + public override void InvalidateFromChild(Invalidation invalidation, Drawable source = null) + { + base.InvalidateFromChild(invalidation, source); + + if ((invalidation & Invalidation.DrawSize) != 0) + { + if (source == ExpandableHeader) //We need to recalculate the positions if the ExpandableHeader changed its size + lastKnownScroll = -1; + } + } + private float lastKnownScroll; protected override void UpdateAfterChildren() @@ -150,6 +161,7 @@ namespace osu.Game.Graphics.Containers float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); float footerH = Footer?.LayoutSize.Y ?? 0; + if (headerH != headerHeight || footerH != footerHeight) { headerHeight = headerH; @@ -181,6 +193,7 @@ namespace osu.Game.Graphics.Containers foreach (var section in Children) { float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); + if (diff < minDiff) { minDiff = diff; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index b078f40420..fe9eb7baf4 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -8,6 +8,7 @@ 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; diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 48131d7e86..464682a0ad 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 059beeca4d..092a23e787 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -45,10 +45,12 @@ namespace osu.Game.Graphics.Cursor { var position = e.MousePosition; var distance = Vector2Extensions.Distance(position, positionMouseDown); + // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. if (dragRotationState == DragRotationState.DragStarted && distance > 30) dragRotationState = DragRotationState.Rotating; + // don't rotate when distance is zero to avoid NaN if (dragRotationState == DragRotationState.Rotating && distance > 0) { diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 4e0ce4a3e1..cfcda892fd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -6,8 +6,8 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; @@ -37,6 +37,7 @@ namespace osu.Game.Graphics.Cursor if (value == text.Text) return; text.Text = value; + if (IsPresent) { AutoSizeDuration = 250; diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 3ae1033f5d..125c994c92 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -54,9 +54,11 @@ namespace osu.Game.Graphics var diffToNow = DateTimeOffset.Now.Subtract(Date); double timeUntilNextUpdate = 1000; + if (Math.Abs(diffToNow.TotalSeconds) > 120) { timeUntilNextUpdate *= 60; + if (Math.Abs(diffToNow.TotalMinutes) > 120) { timeUntilNextUpdate *= 60; diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 712dc4c444..e1e33b4c57 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -20,12 +20,14 @@ namespace osu.Game.Graphics { default: throw new ArgumentException(@"Invalid hex string length!"); + case 3: return new Color4( (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), 255); + case 6: return new Color4( Convert.ToByte(hex.Substring(0, 2), 16), @@ -92,5 +94,15 @@ namespace osu.Game.Graphics public readonly Color4 ChatBlue = FromHex(@"17292e"); public readonly Color4 ContextMenuGray = FromHex(@"223034"); + + public readonly Color4 CommunityUserGreenLight = FromHex(@"deff87"); + public readonly Color4 CommunityUserGreen = FromHex(@"05ffa2"); + public readonly Color4 CommunityUserGreenDark = FromHex(@"a6cc00"); + public readonly Color4 CommunityUserGrayGreenLighter = FromHex(@"9ebab1"); + public readonly Color4 CommunityUserGrayGreenLight = FromHex(@"77998e"); + public readonly Color4 CommunityUserGrayGreen = FromHex(@"4e7466"); + public readonly Color4 CommunityUserGrayGreenDark = FromHex(@"33413c"); + public readonly Color4 CommunityUserGrayGreenDarker = FromHex(@"2c3532"); + public readonly Color4 CommunityUserGrayGreenDarkest = FromHex(@"1e2422"); } } diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index dc660fd159..22250d4a56 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,7 @@ namespace osu.Game.Graphics { case Typeface.Exo: return "Exo2.0"; - case Typeface.FontAwesome: - return "FontAwesome"; + case Typeface.Venera: return "Venera"; } @@ -63,9 +62,9 @@ namespace osu.Game.Graphics /// /// Retrieves the string representation of a . /// - /// The . + /// The family string. /// The . - /// The string representation of in the specified . + /// The string representation of in the specified . public static string GetWeightString(string family, FontWeight weight) { string weightString = weight.ToString(); @@ -83,6 +82,7 @@ namespace osu.Game.Graphics /// /// Creates a new by applying adjustments to this . /// + /// The base . /// The font typeface. If null, the value is copied from this . /// The text size. If null, the value is copied from this . /// The font weight. If null, the value is copied from this . @@ -101,7 +101,6 @@ namespace osu.Game.Graphics public enum Typeface { Exo, - FontAwesome, Venera, } 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/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a2ac71de93..24a98e6dc9 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -104,9 +104,11 @@ namespace osu.Game.Graphics case ScreenshotFormat.Png: image.SaveAsPng(stream); break; + case ScreenshotFormat.Jpg: image.SaveAsJpeg(stream); break; + default: throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); } diff --git a/osu.Game/Graphics/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/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 58058c9d4c..953f3985f9 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -25,6 +25,7 @@ namespace osu.Game.Graphics.UserInterface { direction = value; base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) { bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); @@ -41,6 +42,7 @@ namespace osu.Game.Graphics.UserInterface set { List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) { float length = MaxValue ?? value.Max(); diff --git a/osu.Game/Graphics/UserInterface/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/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index dbbe5b4258..b50bf14bab 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; diff --git a/osu.Game/Graphics/UserInterface/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 74025b71ff..757a9a349c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -9,6 +9,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -63,6 +64,12 @@ namespace osu.Game.Graphics.UserInterface } } + public Color4 LineColour + { + get => maskingContainer.Colour; + set => maskingContainer.Colour = value; + } + public LineGraph() { Add(maskingContainer = new Container @@ -86,6 +93,7 @@ namespace osu.Game.Graphics.UserInterface protected override void Update() { base.Update(); + if (!pathCached.IsValid) { applyPath(); diff --git a/osu.Game/Graphics/UserInterface/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/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 1f5195eaf1..82b09e0821 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index d64068f74c..a8041c79fc 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index c72d11b57e..cea8427296 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -5,7 +5,7 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/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/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 9b5755ea8b..32994be78a 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -88,9 +88,11 @@ namespace osu.Game.Graphics.UserInterface case MenuItemType.Standard: text.Colour = Color4.White; break; + case MenuItemType.Destructive: text.Colour = Color4.Red; break; + case MenuItemType.Highlighted: text.Colour = OsuColour.FromHex(@"ffcc22"); break; diff --git a/osu.Game/Graphics/UserInterface/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/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index c558fd7c7b..c3c447ef83 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -203,6 +203,7 @@ namespace osu.Game.Graphics.UserInterface private int findPrecision(decimal d) { int precision = 0; + while (d != Math.Round(d)) { d *= 10; diff --git a/osu.Game/Graphics/UserInterface/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/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index f564a4b5a8..3e0a6c3265 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -8,7 +8,7 @@ using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface { /// - /// A which follows the active screen (and allows navigation) in a stack. + /// A which follows the active screen (and allows navigation) in a stack. /// public class ScreenBreadcrumbControl : BreadcrumbControl { diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs new file mode 100644 index 0000000000..b9d9b5427d --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.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 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; + public const float ICON_WIDTH = icon_size + icon_spacing; + private const float icon_size = 25, icon_spacing = 10; + + 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(icon_spacing, 0), + Children = new Drawable[] + { + iconSprite = new SpriteIcon + { + Size = new Vector2(icon_size), + }, + 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..3ee572602b 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 { @@ -99,6 +100,7 @@ namespace osu.Game.Graphics.UserInterface public void StopAnimation() { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); @@ -119,6 +121,7 @@ namespace osu.Game.Graphics.UserInterface private void transformCount(float newValue) { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); @@ -142,7 +145,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..36a9aca412 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; @@ -149,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface }; } - public FontAwesome Icon + public IconUsage Icon { set => bouncingIcon.Icon = value; } @@ -207,7 +208,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/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 95ee5aea6b..7a84c11930 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -85,6 +85,7 @@ namespace osu.Game.IO.Legacy for (int i = 0; i < count; i++) { T obj = new T(); + try { obj.ReadFromStream(sr); @@ -129,44 +130,63 @@ namespace osu.Game.IO.Legacy public object ReadObject() { ObjType t = (ObjType)ReadByte(); + switch (t) { case ObjType.boolType: return ReadBoolean(); + case ObjType.byteType: return ReadByte(); + case ObjType.uint16Type: return ReadUInt16(); + case ObjType.uint32Type: return ReadUInt32(); + case ObjType.uint64Type: return ReadUInt64(); + case ObjType.sbyteType: return ReadSByte(); + case ObjType.int16Type: return ReadInt16(); + case ObjType.int32Type: return ReadInt32(); + case ObjType.int64Type: return ReadInt64(); + case ObjType.charType: return ReadChar(); + case ObjType.stringType: return base.ReadString(); + case ObjType.singleType: return ReadSingle(); + case ObjType.doubleType: return ReadDouble(); + case ObjType.decimalType: return ReadDecimal(); + case ObjType.dateTimeType: return ReadDateTime(); + case ObjType.byteArrayType: return ReadByteArray(); + case ObjType.charArrayType: return ReadCharArray(); + case ObjType.otherType: return DynamicDeserializer.Deserialize(BaseStream); + default: return null; } @@ -241,6 +261,7 @@ namespace osu.Game.IO.Legacy string toAssemblyName = assemblyName.Split(',')[0]; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly a in assemblies) { if (a.FullName.Split(',')[0] == toAssemblyName) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index 695767c822..f30e4492af 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -111,6 +111,7 @@ namespace osu.Game.IO.Legacy else { Write(d.Count); + foreach (KeyValuePair kvp in d) { WriteObject(kvp.Key); diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 13be4be0c6..6d244bff60 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -65,6 +65,7 @@ namespace osu.Game.IO.Serialization.Converters var lookupTable = new List(); var objects = new List(); + foreach (var item in list) { var type = item.GetType(); @@ -75,6 +76,7 @@ namespace osu.Game.IO.Serialization.Converters typeString += $", {assemblyName.Version}"; int typeId = lookupTable.IndexOf(typeString); + if (typeId == -1) { lookupTable.Add(typeString); diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 91e2456ef7..cbc446a126 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -62,6 +62,7 @@ namespace osu.Game.Input case MouseUpEvent _: case MouseMoveEvent _: return updateLastInteractionTime(); + default: return base.Handle(e); } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 3d861e44bf..d62b53088a 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -113,6 +113,7 @@ namespace osu.Game.Online.API } break; + case APIState.Offline: case APIState.Connecting: //work to restore a connection... @@ -253,7 +254,7 @@ namespace osu.Game.Online.API handleWebException(we); return false; } - catch (Exception e) + catch { return false; } @@ -266,20 +267,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); + }); } } @@ -302,6 +301,7 @@ namespace osu.Game.Online.API case HttpStatusCode.Unauthorized: Logout(); return true; + case HttpStatusCode.RequestTimeout: failureCount++; log.Add($@"API failure count is now {failureCount}"); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 0cb49951f7..99fde10309 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.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 { @@ -20,7 +23,23 @@ namespace osu.Game.Online.API public string Endpoint => "http://localhost"; - public APIState State => LocalUser.Value.Id == 1 ? APIState.Offline : APIState.Online; + 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) { @@ -28,28 +47,36 @@ namespace osu.Game.Online.API public void Register(IOnlineComponent component) { - // todo: add support + Scheduler.Add(delegate { components.Add(component); }); + component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) { - // todo: add support + Scheduler.Add(delegate { components.Remove(component); }); } public void Login(string username, string password) { LocalUser.Value = new User { - Username = @"Dummy", + 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) => null; + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) + { + Thread.Sleep(200); + return null; + } } } diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/API/Requests/GetRoomsRequest.cs index d7c66707e4..8f1497ef33 100644 --- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomsRequest.cs @@ -26,12 +26,15 @@ namespace osu.Game.Online.API.Requests { case PrimaryFilter.Open: break; + case PrimaryFilter.Owned: target += "/owned"; break; + case PrimaryFilter.Participated: target += "/participated"; break; + case PrimaryFilter.RecentlyEnded: target += "/ended"; break; diff --git a/osu.Game/Online/API/Requests/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 81% rename from osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index ded4ca71ee..3060300077 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -7,13 +7,12 @@ using System.Linq; 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 @@ -71,32 +70,45 @@ namespace osu.Game.Online.API.Requests.Responses { foreach (var kvp in value) { - HitResult newKey; switch (kvp.Key) { + case @"count_geki": + CountGeki = kvp.Value; + break; + case @"count_300": - 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 79% rename from osu.Game/Online/API/Requests/Responses/APIScores.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index a867d86d9b..c629caaa6f 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 List Scores; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIMod.cs b/osu.Game/Online/API/Requests/Responses/APIMod.cs index d7dda07b33..b9da4f49ee 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMod.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMod.cs @@ -8,5 +8,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIMod : IMod { public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 8177f99abe..4614fe29b7 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -10,16 +10,16 @@ namespace osu.Game.Online.API.Requests.Responses public class APIUserMostPlayedBeatmap { [JsonProperty("beatmap_id")] - public int BeatmapID; + public int BeatmapID { get; set; } [JsonProperty("count")] - public int PlayCount; + public int PlayCount { get; set; } [JsonProperty] - private BeatmapInfo beatmap; + private BeatmapInfo beatmap { get; set; } [JsonProperty] - private APIBeatmapSet beatmapSet; + private APIBeatmapSet beatmapSet { get; set; } public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) { diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d35dc07368..e1fc65da6c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -51,6 +51,7 @@ namespace osu.Game.Online.Chat private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) { int captureOffset = 0; + foreach (Match m in regex.Matches(result.Text, startIndex)) { var index = m.Index - captureOffset; @@ -114,51 +115,63 @@ namespace osu.Game.Online.Chat case "b": case "beatmaps": return new LinkDetails(LinkAction.OpenBeatmap, args[3]); + case "s": case "beatmapsets": case "d": return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); + case "u": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } return new LinkDetails(LinkAction.External, null); + case "osu": // every internal link also needs some kind of argument if (args.Length < 3) return new LinkDetails(LinkAction.External, null); LinkAction linkType; + switch (args[1]) { case "chan": linkType = LinkAction.OpenChannel; break; + case "edit": linkType = LinkAction.OpenEditorTimestamp; break; + case "b": linkType = LinkAction.OpenBeatmap; break; + case "s": case "dl": linkType = LinkAction.OpenBeatmapSet; break; + case "spectate": linkType = LinkAction.Spectate; break; + case "u": linkType = LinkAction.OpenUserProfile; break; + default: linkType = LinkAction.External; break; } return new LinkDetails(linkType, args[2]); + case "osump": return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); + default: return new LinkDetails(LinkAction.External, null); } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 438bf231c4..ae4a056033 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -27,8 +27,6 @@ namespace osu.Game.Online.Chat protected ChannelManager ChannelManager; - private ScrollContainer scroll; - private DrawableChannel drawableChannel; private readonly bool postingTextbox; diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 9155df69b4..ce64395dde 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -43,7 +43,24 @@ namespace osu.Game.Online.Leaderboards private void updateTexture() { - rankSprite.Texture = textures.Get($@"Grades/{Rank.GetDescription()}"); + string textureName; + + switch (Rank) + { + default: + textureName = Rank.GetDescription(); + break; + + case ScoreRank.SH: + textureName = "SPlus"; + break; + + case ScoreRank.XH: + textureName = "SSPlus"; + break; + } + + rankSprite.Texture = textures.Get($@"Grades/{textureName}"); } public void UpdateRank(ScoreRank newRank) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index ac1666f8ed..3ce71cccba 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -74,6 +74,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.Add(scrollFlow); int i = 0; + foreach (var s in scrollFlow.Children) { using (s.BeginDelayedSequence(i++ * 50, true)) @@ -138,18 +139,23 @@ namespace osu.Game.Online.Leaderboards OnRetry = UpdateScores, }); break; + case PlaceholderState.Unavailable: replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; + case PlaceholderState.NoScores: replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; + case PlaceholderState.NotLoggedIn: replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); break; + case PlaceholderState.NotSupporter: replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; + default: replacePlaceholder(null); break; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c5602fc4ad..c6db939f6b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -258,8 +259,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) @@ -353,7 +354,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 @@ -378,11 +379,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/OsuGame.cs b/osu.Game/OsuGame.cs index cf231f19ce..7b2a8184d1 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; @@ -114,8 +112,8 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); + [Cached(typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(Array.Empty()); public OsuGame(string[] args = null) { @@ -256,6 +254,12 @@ namespace osu.Game if (menuScreen.IsCurrentScreen()) menuScreen.LoadToSolo(); + // we might even already be at the song + if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash) + { + return; + } + // Use first beatmap available for current ruleset, else switch ruleset. var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); @@ -268,11 +272,11 @@ namespace osu.Game /// Present a score's replay immediately. /// The user should have already requested this interactively. /// - /// The beatmap to select. public void PresentScore(ScoreInfo score) { var databasedScore = ScoreManager.GetScore(score); var databasedScoreInfo = databasedScore.ScoreInfo; + if (databasedScore.Replay == null) { Logger.Log("The loaded score has no replay data.", LoggingTarget.Information); @@ -280,6 +284,7 @@ namespace osu.Game } var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + if (databasedBeatmap == null) { Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information); @@ -289,9 +294,8 @@ namespace osu.Game performFromMainMenu(() => { ruleset.Value = databasedScoreInfo.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; + mods.Value = databasedScoreInfo.Mods; menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); @@ -390,92 +394,78 @@ 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 }, + rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + leftFloatingOverlayContent = 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(volume = new VolumeOverlay(), leftFloatingOverlayContent.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 - { - 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(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, - Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, floatingOverlayContent.Add); + }, rightFloatingOverlayContent.Add); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay - { - Depth = -6, - }, floatingOverlayContent.Add); + loadComponentSingleFile(screenshotManager, Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay - { - Depth = -7, - }, floatingOverlayContent.Add); + //overlay elements + loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add); + loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add); + loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); + loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add); + loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add); + loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener + loadComponentSingleFile(loginOverlay = new LoginOverlay { - Depth = -8, - }, floatingOverlayContent.Add); + GetToolbarHeight = () => ToolbarOffset, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, rightFloatingOverlayContent.Add); + + loadComponentSingleFile(musicController = new MusicController + { + GetToolbarHeight = () => ToolbarOffset, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, rightFloatingOverlayContent.Add); + + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); + loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); dependencies.CacheAs(idleTracker); dependencies.Cache(settings); @@ -488,6 +478,7 @@ namespace osu.Game dependencies.Cache(musicController); dependencies.Cache(beatmapSetOverlay); dependencies.Cache(notifications); + dependencies.Cache(loginOverlay); dependencies.Cache(dialogOverlay); dependencies.Cache(accountCreation); @@ -594,7 +585,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), })); } @@ -602,7 +593,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 = () => { @@ -675,9 +666,11 @@ namespace osu.Game case GlobalAction.ToggleChat: chatOverlay.ToggleVisibility(); return true; + case GlobalAction.ToggleSocial: social.ToggleVisibility(); return true; + case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); @@ -688,15 +681,19 @@ namespace osu.Game frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; + case GlobalAction.ToggleToolbar: Toolbar.ToggleVisibility(); return true; + case GlobalAction.ToggleSettings: settings.ToggleVisibility(); return true; + case GlobalAction.ToggleDirect: direct.ToggleVisibility(); return true; + case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; @@ -719,7 +716,11 @@ namespace osu.Game private Container overlayContent; - private Container floatingOverlayContent; + private Container rightFloatingOverlayContent; + + private Container leftFloatingOverlayContent; + + private Container topMostOverlayContent; private FrameworkConfigManager frameworkConfig; private ScalingContainer screenContainer; @@ -770,6 +771,7 @@ namespace osu.Game case Intro intro: introScreen = intro; break; + case MainMenu menu: menuScreen = menu; break; @@ -777,8 +779,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 7d55d19e50..44776bb2a8 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")); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 13d8df098f..e136fc1403 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -209,6 +209,7 @@ namespace osu.Game.Overlays.AccountCreation private bool focusNextTextbox() { var nextTextbox = nextUnfilledTextbox(); + if (nextTextbox != null) { Schedule(() => GetContainingInputManager().ChangeFocus(nextTextbox)); diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index e8e44c206e..52d2917677 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Graphics; @@ -103,8 +104,10 @@ namespace osu.Game.Overlays case APIState.Offline: case APIState.Failing: break; + case APIState.Connecting: break; + case APIState.Online: State = Visibility.Hidden; break; diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 18de87e7b4..abe954aa80 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -11,6 +11,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; 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 bbbcff0558..0a159507fe 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; @@ -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 }, }, @@ -122,6 +123,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; break; + case DownloadState.Downloaded: textSprites.Children = new Drawable[] { @@ -132,9 +134,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; break; + case DownloadState.LocallyAvailable: this.FadeOut(200); break; + case DownloadState.NotDownloaded: textSprites.Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSet/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/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 95cf9e9d04..a0f71d05c0 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -225,11 +226,13 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.Y }; break; + case DownloadState.Downloading: case DownloadState.Downloaded: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); break; + default: downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 4d974a0b63..44827f0a0c 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs deleted file mode 100644 index 31af251877..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs +++ /dev/null @@ -1,58 +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.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ClickableUsername : OsuHoverContainer - { - private readonly OsuSpriteText text; - private UserProfileOverlay profile; - - private User user; - - public User User - { - get => user; - set - { - if (user == value) return; - - user = value; - - text.Text = user.Username; - } - } - - public float TextSize - { - get => text.Font.Size; - set => text.Font = text.Font.With(size: value); - } - - public ClickableUsername() - { - AutoSizeAxes = Axes.Both; - Child = text = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) - { - this.profile = profile; - } - - protected override bool OnClick(ClickEvent e) - { - profile?.ShowUser(user); - return true; - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs deleted file mode 100644 index c6c8315aeb..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ /dev/null @@ -1,141 +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 osuTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -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.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class DrawableScore : Container - { - private const int fade_duration = 100; - private const float side_margin = 20; - - private readonly Box background; - - public DrawableScore(int index, APIScoreInfo score) - { - ScoreModsContainer modsContainer; - - RelativeSizeAxes = Axes.X; - Height = 30; - CornerRadius = 3; - Masking = true; - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"#{index + 1}", - Font = OsuFont.GetFont(weight: FontWeight.Regular, italics: true), - Margin = new MarginPadding { Left = side_margin } - }, - new DrawableFlag(score.User.Country) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - Margin = new MarginPadding { Left = 60 } - }, - new ClickableUsername - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - User = score.User, - Margin = new MarginPadding { Left = 100 } - }, - modsContainer = new ScoreModsContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.06f, - RelativePositionAxes = Axes.X, - X = 0.42f - }, - new DrawableRank(score.Rank) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - FillMode = FillMode.Fit, - RelativePositionAxes = Axes.X, - X = 0.55f - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $@"{score.TotalScore:N0}", - Font = OsuFont.Numeric.With(fixedWidth: true), - RelativePositionAxes = Axes.X, - X = 0.75f, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $@"{score.Accuracy:P2}", - Font = OsuFont.GetFont(weight: FontWeight.Regular, italics: true), - RelativePositionAxes = Axes.X, - X = 0.85f - }, - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}", - Font = OsuFont.GetFont(weight: FontWeight.Regular, italics: true), - Margin = new MarginPadding { Right = side_margin } - }, - }; - - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.35f), - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray4; - } - - protected override bool OnHover(HoverEvent e) - { - background.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - background.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - - protected override bool OnClick(ClickEvent e) => true; - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 78e560cdbe..8e806c6747 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -1,240 +1,125 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; 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; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class DrawableTopScore : Container + public class DrawableTopScore : CompositeDrawable { private const float fade_duration = 100; - private const float height = 200; - private const float avatar_size = 80; - private const float margin = 10; + + private Color4 backgroundIdleColour; + private Color4 backgroundHoveredColour; private readonly Box background; - private readonly Box bottomBackground; - private readonly Box middleLine; - private readonly UpdateableAvatar avatar; - private readonly DrawableFlag flag; - private readonly ClickableUsername username; - private readonly OsuSpriteText rankText; - private readonly OsuSpriteText date; - private readonly DrawableRank rank; - private readonly InfoColumn totalScore; - private readonly InfoColumn accuracy; - private readonly InfoColumn statistics; - private readonly ScoreModsContainer modsContainer; - - private APIScoreInfo score; - - public APIScoreInfo Score - { - get => score; - set - { - if (score == value) return; - - score = value; - - avatar.User = username.User = score.User; - flag.Country = score.User.Country; - date.Text = $@"achieved {score.Date:MMM d, yyyy}"; - rank.UpdateRank(score.Rank); - - totalScore.Value = $@"{score.TotalScore:N0}"; - accuracy.Value = $@"{score.Accuracy:P2}"; - statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}"; - - modsContainer.Clear(); - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.45f), - }); - } - } + private readonly TopScoreUserSection userSection; + private readonly TopScoreStatisticsSection statisticsSection; public DrawableTopScore() { RelativeSizeAxes = Axes.X; - Height = height; - CornerRadius = 5; - BorderThickness = 4; + AutoSizeAxes = Axes.Y; + Masking = true; - Children = new Drawable[] + CornerRadius = 10; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 1, + Offset = new Vector2(0, 1), + }; + + InternalChildren = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, //used for correct border representation - }, - avatar = new UpdateableAvatar - { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - Radius = 1, - }, - Margin = new MarginPadding { Top = margin, Left = margin } - }, - flag = new DrawableFlag - { - Size = new Vector2(30, 20), - Position = new Vector2(margin * 2 + avatar_size, height / 4), - }, - username = new ClickableUsername - { - Origin = Anchor.BottomLeft, - TextSize = 30, - Position = new Vector2(margin * 2 + avatar_size, height / 4), - Margin = new MarginPadding { Bottom = 4 } - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.BottomRight, - Text = "#1", - Font = OsuFont.GetFont(size: 40, weight: FontWeight.Bold, italics: true), - Y = height / 4, - Margin = new MarginPadding { Right = margin } - }, - date = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = height / 4, - Margin = new MarginPadding { Right = margin } }, new Container { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), Children = new Drawable[] { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - middleLine = new Box + new AutoSizingGrid { RelativeSizeAxes = Axes.X, - Height = 1, - }, - rank = new DrawableRank(ScoreRank.F) - { - Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size, 40), - FillMode = FillMode.Fit, - Y = height / 4, - Margin = new MarginPadding { Left = margin } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Position = new Vector2(height / 2, height / 4), - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new[] + Content = new[] { - totalScore = new InfoColumn("Score"), - accuracy = new InfoColumn("Accuracy"), - statistics = new InfoColumn("300/100/50"), + new Drawable[] + { + userSection = new TopScoreUserSection + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + null, + statisticsSection = new TopScoreStatisticsSection + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }, }, - }, - modsContainer = new ScoreModsContainer - { - AutoSizeAxes = Axes.Y, - Width = 80, - Position = new Vector2(height / 2, height / 4), + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, 20) }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, } } - }, + } }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = bottomBackground.Colour = colours.Gray4; - middleLine.Colour = colours.Gray2; - date.Colour = colours.Gray9; - BorderColour = rankText.Colour = colours.Yellow; + backgroundIdleColour = colours.Gray3; + backgroundHoveredColour = colours.Gray4; + + background.Colour = backgroundIdleColour; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + userSection.Score = value; + statisticsSection.Score = value; + } } protected override bool OnHover(HoverEvent e) { - background.FadeIn(fade_duration, Easing.OutQuint); + background.FadeColour(backgroundHoveredColour, fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeOut(fade_duration, Easing.OutQuint); + background.FadeColour(backgroundIdleColour, fade_duration, Easing.OutQuint); base.OnHoverLost(e); } - private class InfoColumn : FillFlowContainer + private class AutoSizingGrid : GridContainer { - private readonly OsuSpriteText headerText; - private readonly OsuSpriteText valueText; - - public string Value + public AutoSizingGrid() { - set - { - if (valueText.Text == value) - return; - - valueText.Text = value; - } - get => valueText.Text; - } - - public InfoColumn(string header) - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 3); - Children = new Drawable[] - { - headerText = new OsuSpriteText - { - Text = header, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) - }, - valueText = new OsuSpriteText { Font = OsuFont.GetFont(size: 25, weight: FontWeight.Regular, italics: true) } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - headerText.Colour = colours.Gray9; + AutoSizeAxes = Axes.Y; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs new file mode 100644 index 0000000000..693ce958dd --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -0,0 +1,192 @@ +// 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 System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + private Color4 highAccuracyColour; + + public ScoreTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + highAccuracyColour = colours.GreenLight; + } + + public IReadOnlyList Scores + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value == null || !value.Any()) + return; + + for (int i = 0; i < value.Count; i++) + backgroundFlow.Add(new ScoreTableRowBackground(i)); + + Columns = createHeaders(value[0]); + Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders(ScoreInfo score) + { + var columns = new List + { + new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade + new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)), + new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90)) + }; + + foreach (var statistic in score.Statistics) + columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70))); + + columns.AddRange(new[] + { + new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70)), + new TableColumn("mods", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + }); + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, ScoreInfo score) + { + var content = new List + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new DrawableRank(score.Rank) + { + Size = new Vector2(30, 20) + }, + new OsuSpriteText + { + Margin = new MarginPadding { Right = horizontal_inset }, + Text = $@"{score.TotalScore:N0}", + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) + }, + new OsuSpriteText + { + Margin = new MarginPadding { Right = horizontal_inset }, + Text = $@"{score.Accuracy:P2}", + Font = OsuFont.GetFont(size: text_size), + Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White + }, + }; + + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: text_size)) { AutoSizeAxes = Axes.Both }; + username.AddUserLink(score.User); + + content.AddRange(new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Right = horizontal_inset }, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new DrawableFlag(score.User.Country) { Size = new Vector2(20, 13) }, + username + } + }, + new OsuSpriteText + { + Text = $@"{score.MaxCombo:N0}x", + Font = OsuFont.GetFont(size: text_size) + } + }); + + foreach (var kvp in score.Statistics) + { + content.Add(new OsuSpriteText + { + Text = $"{kvp.Value}", + Font = OsuFont.GetFont(size: text_size), + Colour = kvp.Value == 0 ? Color4.Gray : Color4.White + }); + } + + content.AddRange(new Drawable[] + { + new OsuSpriteText + { + Text = $@"{score.PP:N0}", + Font = OsuFont.GetFont(size: text_size) + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f) + }) + }, + }); + + return content.ToArray(); + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs new file mode 100644 index 0000000000..d820f4d89d --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTableRowBackground : CompositeDrawable + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + private readonly Box background; + + public ScoreTableRowBackground(int index) + { + RelativeSizeAxes = Axes.X; + Height = 25; + + CornerRadius = 3; + Masking = true; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + + if (index % 2 != 0) + background.Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Gray4; + background.Colour = colours.Gray3; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3dd03fcea6..8ef3f71fe3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,38 +1,91 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osuTK; using System.Collections.Generic; 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 { - public class ScoresContainer : Container + public class ScoresContainer : CompositeDrawable { private const int spacing = 15; private const int fade_duration = 200; - private readonly FillFlowContainer flow; + private readonly Box background; + private readonly ScoreTable scoreTable; + private readonly DrawableTopScore topScore; private readonly LoadingAnimation loadingAnimation; + [Resolved] + private IAPIProvider api { get; set; } + + public ScoresContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.95f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Margin = new MarginPadding { Vertical = spacing }, + Children = new Drawable[] + { + topScore = new DrawableTopScore(), + scoreTable = new ScoreTable + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } + }, + loadingAnimation = new LoadingAnimation + { + Alpha = 0, + Margin = new MarginPadding(20) + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray2; + updateDisplay(); + } + private bool loading { set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; - private BeatmapInfo beatmap; + private GetScoresRequest getScoresRequest; + private IReadOnlyList scores; - public IEnumerable Scores + public IReadOnlyList Scores { get => scores; set @@ -44,8 +97,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private GetScoresRequest getScoresRequest; - private IAPIProvider api; + private BeatmapInfo beatmap; public BeatmapInfo Beatmap { @@ -71,68 +123,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { loading = false; - var scoreCount = scores?.Count() ?? 0; + scoreTable.Scores = scores?.Count > 1 ? scores : new List(); - if (scoreCount == 0) + if (scores?.Any() == true) { - topScore.Hide(); - flow.Clear(); - return; + topScore.Score = scores.FirstOrDefault(); + topScore.Show(); } - - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); - - flow.Clear(); - - if (scoreCount < 2) - return; - - for (int i = 1; i < scoreCount; i++) - flow.Add(new DrawableScore(i, scores.ElementAt(i))); - } - - public ScoresContainer() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.95f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), - Margin = new MarginPadding { Vertical = spacing }, - Children = new Drawable[] - { - topScore = new DrawableTopScore(), - flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 1), - }, - } - }, - loadingAnimation = new LoadingAnimation - { - Alpha = 0, - Margin = new MarginPadding(20) - }, - }; - } - - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - this.api = api; - updateDisplay(); + else + topScore.Hide(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs new file mode 100644 index 0000000000..6761d0f710 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -0,0 +1,204 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class TopScoreStatisticsSection : CompositeDrawable + { + private const float margin = 10; + + private readonly FontUsage smallFont = OsuFont.GetFont(size: 20); + private readonly FontUsage largeFont = OsuFont.GetFont(size: 25); + + private readonly TextColumn totalScoreColumn; + private readonly TextColumn accuracyColumn; + private readonly TextColumn maxComboColumn; + private readonly TextColumn ppColumn; + + private readonly FillFlowContainer statisticsColumns; + private readonly ModsInfoColumn modsColumn; + + public TopScoreStatisticsSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + statisticsColumns = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + }, + ppColumn = new TextColumn("pp", smallFont), + modsColumn = new ModsInfoColumn(), + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + totalScoreColumn = new TextColumn("total score", largeFont), + accuracyColumn = new TextColumn("accuracy", largeFont), + maxComboColumn = new TextColumn("max combo", largeFont) + } + }, + } + }; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + totalScoreColumn.Text = $@"{value.TotalScore:N0}"; + accuracyColumn.Text = $@"{value.Accuracy:P2}"; + maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; + ppColumn.Text = $@"{value.PP:N0}"; + + statisticsColumns.ChildrenEnumerable = value.Statistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); + modsColumn.Mods = value.Mods; + } + } + + private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont) + { + Text = count.ToString() + }; + + private class InfoColumn : CompositeDrawable + { + private readonly Box separator; + + public InfoColumn(string title, Drawable content) + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), + Text = title.ToUpper() + }, + separator = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2 + }, + content + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + separator.Colour = colours.Gray5; + } + } + + private class TextColumn : InfoColumn + { + private readonly SpriteText text; + + public TextColumn(string title, FontUsage font) + : this(title, new OsuSpriteText { Font = font }) + { + } + + private TextColumn(string title, SpriteText text) + : base(title, text) + { + this.text = text; + } + + public LocalisedString Text + { + set => text.Text = value; + } + } + + private class ModsInfoColumn : InfoColumn + { + private readonly FillFlowContainer modsContainer; + + public ModsInfoColumn() + : this(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal + }) + { + } + + private ModsInfoColumn(FillFlowContainer modsContainer) + : base("mods", modsContainer) + { + this.modsContainer = modsContainer; + } + + public IEnumerable Mods + { + set + { + modsContainer.Clear(); + + foreach (Mod mod in value) + { + modsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f), + }); + } + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs new file mode 100644 index 0000000000..e70bf4c572 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -0,0 +1,128 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class TopScoreUserSection : CompositeDrawable + { + private readonly SpriteText rankText; + private readonly DrawableRank rank; + private readonly UpdateableAvatar avatar; + private readonly LinkFlowContainer usernameText; + private readonly SpriteText date; + private readonly DrawableFlag flag; + + public TopScoreUserSection() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + rankText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "#1", + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) + }, + rank = new DrawableRank(ScoreRank.F) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40), + FillMode = FillMode.Fit, + }, + avatar = new UpdateableAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(80), + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + Radius = 1, + }, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + usernameText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + date = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) + }, + flag = new DrawableFlag + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20, 13), + }, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + rankText.Colour = colours.Yellow; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + avatar.User = value.User; + flag.Country = value.User.Country; + date.Text = $@"achieved {value.Date.Humanize()}"; + + usernameText.Clear(); + usernameText.AddUserLink(value.User); + + rank.UpdateRank(value.Rank); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index c49268bc16..82bac71f5e 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 908ec5f026..66a6672ab1 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index e3df81e455..aec78b962f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,6 +99,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); + if (found != null) { Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); diff --git a/osu.Game/Overlays/Chat/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/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 52260506fe..c26ecfd86f 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Chat.Tabs } [BackgroundDependencyLoader] - private new void load(OsuColour colour) + private void load(OsuColour colour) { BackgroundInactive = colour.Gray2; BackgroundActive = colour.Gray3; diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 7a43ca4b8c..67d9356b76 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), @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Chat.Tabs { default: return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + case ChannelType.PM: return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 95c5fbf8fa..7f820e4ff7 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -117,7 +118,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 8111ac7394..b8165e70cb 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -7,8 +7,8 @@ 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; using osu.Game.Users; using osuTK; @@ -17,10 +17,7 @@ namespace osu.Game.Overlays.Chat.Tabs { public class PrivateChannelTabItem : ChannelTabItem { - 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) 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 77f88ab4e7..221fd35576 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -205,6 +205,7 @@ namespace osu.Game.Overlays Scheduler.Add(() => channelTabControl.Current.Value = e.NewValue); var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); + if (loaded == null) { currentChannelContainer.FadeOut(500, Easing.OutQuint); @@ -288,6 +289,7 @@ namespace osu.Game.Overlays case Key.Number9: selectTab((int)e.Key - (int)Key.Number1); return true; + case Key.Number0: selectTab(9); return true; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 72e3cc4f6a..5949f1fcd4 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -6,7 +6,9 @@ using System.Linq; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -38,7 +40,7 @@ namespace osu.Game.Overlays.Dialog private bool actionInvoked; - public FontAwesome Icon + public IconUsage Icon { get => icon.Icon; set => icon.Icon = value; @@ -71,6 +73,7 @@ namespace osu.Game.Overlays.Dialog set { buttonsContainer.ChildrenEnumerable = value; + foreach (PopupDialogButton b in value) { var action = b.Action; @@ -165,7 +168,7 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(50), }, }, @@ -221,6 +224,7 @@ namespace osu.Game.Overlays.Dialog // press button at number if 1-9 on number row or keypad are pressed var k = e.Key; + if (k >= Key.Number1 && k <= Key.Number9) { pressButtonAtIndex(k - Key.Number1); diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index b35dbde639..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,8 +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), - 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 @@ -205,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 d857a0f042..d645fd3bda 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -11,6 +11,7 @@ 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; @@ -160,8 +161,8 @@ namespace osu.Game.Overlays.Direct Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0), - 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, @@ -210,12 +211,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_film) { IconSize = new Vector2(20) }); + statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_image) { IconSize = new Vector2(20) }); + statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); } statusContainer.Add(new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 3867886f6d..f413dc3771 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -166,7 +167,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..3f44d854e5 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, } } } @@ -84,9 +85,11 @@ namespace osu.Game.Overlays.Direct case DownloadState.Downloaded: shakeContainer.Shake(); break; + case DownloadState.LocallyAvailable: game.PresentBeatmap(BeatmapSet.Value); break; + default: beatmaps.Download(BeatmapSet.Value, noVideo); break; @@ -109,9 +112,11 @@ namespace osu.Game.Overlays.Direct icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); break; + case DownloadState.Downloaded: background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); break; + case DownloadState.LocallyAvailable: background.FadeColour(colours.Green, 500, Easing.InOutExpo); icon.MoveToX(-8, 500, Easing.InOutExpo); diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/Direct/DownloadProgressBar.cs index 9c2b1e5b63..57500b3531 100644 --- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs +++ b/osu.Game/Overlays/Direct/DownloadProgressBar.cs @@ -43,10 +43,12 @@ namespace osu.Game.Overlays.Direct progressBar.Current.Value = 0; progressBar.FadeOut(500); break; + case DownloadState.Downloading: progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); break; + case DownloadState.Downloaded: progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); @@ -54,6 +56,7 @@ namespace osu.Game.Overlays.Direct progressBar.Current.Value = 1; progressBar.FillColour = colours.Yellow; break; + case DownloadState.LocallyAvailable: progressBar.FadeOut(500); break; diff --git a/osu.Game/Overlays/Direct/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 e7f516f449..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; @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Direct private readonly Container iconContainer; - public IconPill(FontAwesome icon) + public IconPill(IconUsage icon) { AutoSizeAxes = Axes.Both; Masking = true; 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 34edbbcc8b..40c4c90fca 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -58,6 +58,7 @@ namespace osu.Game.Overlays var artists = new List(); var songs = new List(); var tags = new List(); + foreach (var s in beatmapSets) { artists.Add(s.Metadata.Artist); @@ -210,6 +211,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; + default: return new DirectListPanel(b); } diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index 154aff605a..fb38ddcbd1 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays { /// /// An overlay which will display a black screen that dims over a period before confirming an exit action. - /// Action is BYO (derived class will need to call and from a user event). + /// Action is BYO (derived class will need to call and from a user event). /// public abstract class HoldToConfirmOverlay : HoldToConfirmContainer { diff --git a/osu.Game/Overlays/KeyBinding/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..9a707adaea 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -43,6 +44,8 @@ namespace osu.Game.Overlays.KeyBinding } } + public bool FilteringActive { get; set; } + private OsuSpriteText text; private OsuTextFlowContainer pressAKey; @@ -115,6 +118,7 @@ namespace osu.Game.Overlays.KeyBinding public void RestoreDefaults() { int i = 0; + foreach (var d in Defaults) { var button = buttons[i++]; diff --git a/osu.Game/Overlays/KeyBinding/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/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index 61dd51d16f..39bfdfd4d6 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -55,6 +55,7 @@ namespace osu.Game.Overlays SectionsContainer.FadeOut(300, Easing.OutQuint); ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint); break; + case Visibility.Hidden: Background.FadeTo(0.6f, 500, Easing.OutQuint); Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index a5703eba92..6d82db5603 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures; using osuTK.Input; using osu.Framework.Graphics.Shapes; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.MathUtils; diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 431ae98c2c..f1ae5d64f5 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -156,11 +156,13 @@ namespace osu.Game.Overlays.MedalSplash case DisplayState.None: medalContainer.ScaleTo(0); break; + case DisplayState.Icon: medalContainer .FadeIn(duration) .ScaleTo(1, duration, Easing.OutElastic); break; + case DisplayState.MedalUnlocked: medalContainer .FadeTo(1) @@ -170,6 +172,7 @@ namespace osu.Game.Overlays.MedalSplash this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); unlocked.FadeInFromZero(duration); break; + case DisplayState.Full: medalContainer .FadeTo(1) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 23b75caedc..fa1ee500a8 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -81,6 +81,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); backgroundIcon.Icon = modAfter.Icon; + using (BeginDelayedSequence(mod_switch_duration, true)) { foregroundIcon @@ -139,6 +140,7 @@ namespace osu.Game.Overlays.Mods } createIcons(); + if (Mods.Length > 0) { displayMod(Mods[0]); @@ -168,6 +170,7 @@ namespace osu.Game.Overlays.Mods case MouseButton.Left: SelectNext(1); break; + case MouseButton.Right: SelectNext(-1); break; @@ -219,6 +222,7 @@ namespace osu.Game.Overlays.Mods private void createIcons() { iconsContainer.Clear(); + if (Mods.Length > 1) { iconsContainer.AddRange(new[] diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index a118357f21..50400e254f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.Mods public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { int delay = 0; + foreach (var button in buttons) { Mod selected = button.SelectedMod; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index aa41723ca6..97769fe5aa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -42,19 +42,19 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); protected readonly IBindable Ruleset = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> selectedMods) + private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; UnrankedLabel.Colour = colours.Blue; Ruleset.BindTo(ruleset); - if (selectedMods != null) SelectedMods.BindTo(selectedMods); + if (mods != null) SelectedMods.BindTo(mods); sampleOn = audio.Sample.Get(@"UI/check-on"); sampleOff = audio.Sample.Get(@"UI/check-off"); @@ -87,14 +87,14 @@ namespace osu.Game.Overlays.Mods // attempt to re-select any already selected mods. // this may be the first time we are receiving the ruleset, in which case they will still match. - selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); + selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); // write the mods back to the SelectedMods bindable in the case a change was not applicable. // this generally isn't required as the previous line will perform deselection; just here for safety. refreshSelectedMods(); } - private void selectedModsChanged(ValueChangedEvent> e) + private void selectedModsChanged(ValueChangedEvent> e) { foreach (ModSection section in ModSectionsContainer.Children) section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList()); diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index aa93e349e8..4f59b053b6 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -6,7 +6,7 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Music/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 7846e31725..89d166b788 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -190,6 +190,7 @@ namespace osu.Game.Overlays.Music // the item positions as they are being transformed float heightAccumulator = 0; int dstIndex = 0; + for (; dstIndex < items.Count; dstIndex++) { // Using BoundingBox here takes care of scale, paddings, etc... @@ -231,6 +232,11 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive + { + set { } + } + public IEnumerable FilterableChildren => Children; public ItemSearchContainer() diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 8cbea63fe3..4431288a1a 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -81,6 +82,7 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit = (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c1b742e4e5..ea3e1ca00c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -22,6 +23,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Music; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -49,12 +51,20 @@ namespace osu.Game.Overlays private BeatmapManager beatmaps; private List beatmapSets; - private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; - private readonly Bindable beatmap = new Bindable(); + [Resolved] + private Bindable beatmap { get; set; } + + [Resolved] + private IBindable> mods { get; set; } + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; public MusicController() { @@ -68,7 +78,6 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(Bindable beatmap, BeatmapManager beatmaps, OsuColour colours) { - this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; Children = new Drawable[] @@ -143,7 +152,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 +161,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 +177,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(), }, } @@ -226,6 +235,7 @@ namespace osu.Game.Overlays { beatmap.BindValueChanged(beatmapChanged, true); beatmap.BindDisabledChanged(beatmapDisabledChanged, true); + mods.BindValueChanged(_ => updateAudioAdjustments(), true); base.LoadComplete(); } @@ -244,6 +254,8 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); Height = dragContainer.Height; + + dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected override void Update() @@ -257,13 +269,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; } } @@ -289,6 +301,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Prev; var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -302,6 +315,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Next; var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -347,10 +361,23 @@ namespace osu.Game.Overlays progressBar.CurrentTime = 0; updateDisplay(current, direction); + updateAudioAdjustments(); queuedDirection = null; } + private void updateAudioAdjustments() + { + var track = current?.Track; + if (track == null) + return; + + track.ResetSpeedAdjustments(); + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToClock(track); + } + private void currentTrackCompleted() => Schedule(() => { if (!current.Track.Looping && !beatmap.Disabled && beatmapSets.Any()) @@ -392,6 +419,7 @@ namespace osu.Game.Overlays newBackground.MoveToX(0, 500, Easing.OutCubic); background.MoveToX(-400, 500, Easing.OutCubic); break; + case TransformDirection.Prev: newBackground.Position = new Vector2(-400, 0); newBackground.MoveToX(0, 500, Easing.OutCubic); diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index ea6e250556..2dc6b39a92 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -7,10 +7,12 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -174,7 +176,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/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 75e70b18ea..857a0bda9e 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -54,11 +54,13 @@ namespace osu.Game.Overlays.Notifications Light.Pulsate = false; progressBar.Active = false; break; + case ProgressNotificationState.Active: Light.Colour = colourActive; Light.Pulsate = true; progressBar.Active = true; break; + case ProgressNotificationState.Cancelled: Light.Colour = colourCancelled; Light.Pulsate = false; @@ -145,6 +147,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Cancelled: base.Close(); break; + case ProgressNotificationState.Active: case ProgressNotificationState.Queued: if (CancelRequested?.Invoke() != false) diff --git a/osu.Game/Overlays/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/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 5e45fbf081..88a1edddc5 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Transforms; using osu.Framework.Threading; using osu.Game.Configuration; @@ -188,6 +189,7 @@ namespace osu.Game.Overlays optionCount = 1; if (val) selectedOption = 0; break; + case Enum _: var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; diff --git a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs deleted file mode 100644 index 93f08768f7..0000000000 --- a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Profile.Components -{ - public class DrawableJoinDate : DrawableDate - { - public DrawableJoinDate(DateTimeOffset date) - : base(date) - { - } - - protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}"; - - public override string TooltipText => $"{Date:MMMM d, yyyy}"; - } -} diff --git a/osu.Game/Overlays/Profile/Components/GradeBadge.cs b/osu.Game/Overlays/Profile/Components/GradeBadge.cs deleted file mode 100644 index ca56780663..0000000000 --- a/osu.Game/Overlays/Profile/Components/GradeBadge.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays.Profile.Components -{ - public class GradeBadge : Container - { - private const float width = 50; - private readonly string grade; - private readonly Sprite badge; - private readonly SpriteText numberText; - - public int DisplayCount - { - set => numberText.Text = value.ToString(@"#,0"); - } - - public GradeBadge(string grade) - { - this.grade = grade; - Width = width; - Height = 41; - Add(badge = new Sprite - { - Width = width, - Height = 26 - }); - Add(numberText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) - }); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - badge.Texture = textures.Get($"Grades/{grade}"); - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs deleted file mode 100644 index ff4d7a10dc..0000000000 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class BadgeContainer : Container - { - private static readonly Vector2 badge_size = new Vector2(86, 40); - private static readonly MarginPadding outer_padding = new MarginPadding(3); - - private OsuSpriteText badgeCountText; - private FillFlowContainer badgeFlowContainer; - private FillFlowContainer outerBadgeContainer; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Child = new Container - { - Masking = true, - CornerRadius = 4, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray3 - }, - outerBadgeContainer = new OuterBadgeContainer(onOuterHover, onOuterHoverLost) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - Padding = outer_padding, - Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - badgeCountText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Alpha = 0, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Child = badgeFlowContainer = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - } - } - } - }, - } - }; - - Scheduler.AddDelayed(rotateBadges, 3000, true); - } - - private void rotateBadges() - { - if (outerBadgeContainer.IsHovered) return; - - visibleBadge = (visibleBadge + 1) % badgeCount; - - badgeFlowContainer.MoveToX(-DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge, 500, Easing.InOutQuad); - } - - private int visibleBadge; - private int badgeCount; - - public void ShowBadges(Badge[] badges) - { - if (badges == null || badges.Length == 0) - { - Hide(); - return; - } - - badgeCount = badges.Length; - - badgeCountText.FadeTo(badgeCount > 1 ? 1 : 0); - badgeCountText.Text = $"{badges.Length} badges"; - - Show(); - visibleBadge = 0; - - badgeFlowContainer.Clear(); - for (var index = 0; index < badges.Length; index++) - { - int displayIndex = index; - LoadComponentAsync(new DrawableBadge(badges[index]) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, asyncBadge => - { - badgeFlowContainer.Add(asyncBadge); - - // load in stable order regardless of async load order. - badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); - }); - } - } - - private void onOuterHover() - { - badgeFlowContainer.ClearTransforms(); - badgeFlowContainer.X = 0; - badgeFlowContainer.Direction = FillDirection.Full; - outerBadgeContainer.AutoSizeAxes = Axes.Both; - - badgeFlowContainer.MaximumSize = new Vector2(ChildSize.X, float.MaxValue); - } - - private void onOuterHoverLost() - { - badgeFlowContainer.X = -DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge; - badgeFlowContainer.Direction = FillDirection.Horizontal; - outerBadgeContainer.AutoSizeAxes = Axes.Y; - outerBadgeContainer.Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal; - } - - private class OuterBadgeContainer : FillFlowContainer - { - private readonly Action hoverAction; - private readonly Action hoverLostAction; - - public OuterBadgeContainer(Action hoverAction, Action hoverLostAction) - { - this.hoverAction = hoverAction; - this.hoverLostAction = hoverLostAction; - } - - protected override bool OnHover(HoverEvent e) - { - hoverAction(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) => hoverLostAction(); - } - - private class DrawableBadge : Container, IHasTooltip - { - public static readonly Vector2 DRAWABLE_BADGE_SIZE = badge_size + outer_padding.Total; - - private readonly Badge badge; - - public DrawableBadge(Badge badge) - { - this.badge = badge; - Padding = outer_padding; - Size = DRAWABLE_BADGE_SIZE; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - Child = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(badge.ImageUrl), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Child.FadeInFromZero(200); - } - - public string TooltipText => badge.Description; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs new file mode 100644 index 0000000000..52336dcd30 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class BottomHeaderContainer : CompositeDrawable + { + public readonly Bindable User = new Bindable(); + + private LinkFlowContainer topLinkContainer; + private LinkFlowContainer bottomLinkContainer; + + private Color4 iconColour; + + public BottomHeaderContainer() + { + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + iconColour = colours.CommunityUserGrayGreenLighter; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarker, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + topLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + bottomLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + } + }; + + User.BindValueChanged(user => updateDisplay(user.NewValue)); + } + + private void updateDisplay(User user) + { + topLinkContainer.Clear(); + bottomLinkContainer.Clear(); + + if (user == null) return; + + if (user.JoinDate.ToUniversalTime().Year < 2008) + topLinkContainer.AddText("Here since the beginning"); + else + { + topLinkContainer.AddText("Joined "); + topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden); + } + + addSpacer(topLinkContainer); + + if (user.PlayStyles?.Length > 0) + { + topLinkContainer.AddText("Plays with "); + topLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), embolden); + + addSpacer(topLinkContainer); + } + + if (user.LastVisit.HasValue) + { + topLinkContainer.AddText("Last seen "); + topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden); + + addSpacer(topLinkContainer); + } + + topLinkContainer.AddText("Contributed "); + topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); + + string websiteWithoutProtcol = user.Website; + + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) + { + if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) + { + websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment; + websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/'); + } + } + + tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); + tryAddInfo(OsuIcon.Heart, user.Interests); + tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); + bottomLinkContainer.NewLine(); + if (!string.IsNullOrEmpty(user.Twitter)) + tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfo(FontAwesome.Brands.Discord, user.Discord); + tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); + } + + private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); + + private void tryAddInfo(IconUsage icon, string content, string link = null) + { + if (string.IsNullOrEmpty(content)) return; + + bottomLinkContainer.AddIcon(icon, text => + { + text.Font = text.Font.With(size: 10); + text.Colour = iconColour; + }); + + if (link != null) + bottomLinkContainer.AddLink(" " + content, link, creationParameters: embolden); + else + bottomLinkContainer.AddText(" " + content, embolden); + + addSpacer(bottomLinkContainer); + } + + private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold); + } +} diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs new file mode 100644 index 0000000000..b441775393 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -0,0 +1,150 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class CentreHeaderContainer : CompositeDrawable + { + public readonly BindableBool DetailsVisible = new BindableBool(true); + public readonly Bindable User = new Bindable(); + + private OverlinedInfoContainer hiddenDetailGlobal; + private OverlinedInfoContainer hiddenDetailCountry; + + public CentreHeaderContainer() + { + Height = 60; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + Container hiddenDetailContainer; + Container expandedDetailContainer; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDark + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Vertical = 10 }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new AddFriendButton + { + RelativeSizeAxes = Axes.Y, + User = { BindTarget = User } + }, + new MessageUserButton + { + User = { BindTarget = User } + }, + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 10 }, + Width = UserProfileOverlay.CONTENT_X_MARGIN, + Child = new ExpandDetailsButton + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DetailsVisible = { BindTarget = DetailsVisible } + }, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Children = new Drawable[] + { + new LevelBadge + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(40), + User = { BindTarget = User } + }, + expandedDetailContainer = new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + Height = 6, + Margin = new MarginPadding { Right = 50 }, + Child = new LevelProgressBar + { + RelativeSizeAxes = Axes.Both, + User = { BindTarget = User } + } + }, + hiddenDetailContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Right = 50 }, + Children = new[] + { + hiddenDetailGlobal = new OverlinedInfoContainer + { + Title = "Global Ranking", + LineColour = colours.Yellow + }, + hiddenDetailCountry = new OverlinedInfoContainer + { + Title = "Country Ranking", + LineColour = colours.Yellow + }, + } + } + } + } + }; + + DetailsVisible.BindValueChanged(visible => + { + hiddenDetailContainer.FadeTo(visible.NewValue ? 0 : 1, 200, Easing.OutQuint); + expandedDetailContainer.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + }); + + User.BindValueChanged(user => updateDisplay(user.NewValue)); + } + + private void updateDisplay(User user) + { + hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs new file mode 100644 index 0000000000..2e4fd6fe3d --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class AddFriendButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "friends"; + + private OsuSpriteText followerText; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + followerText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Bold) + } + } + }; + + User.BindValueChanged(user => updateFollowers(user.NewValue), true); + } + + private void updateFollowers(User user) => followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs new file mode 100644 index 0000000000..ea259fe49a --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class DrawableBadge : CompositeDrawable, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChild = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InternalChild.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs new file mode 100644 index 0000000000..089228b2cd --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class ExpandDetailsButton : ProfileHeaderButton + { + public readonly BindableBool DetailsVisible = new BindableBool(); + + public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + + private SpriteIcon icon; + + public ExpandDetailsButton() + { + Action = () => DetailsVisible.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.CommunityUserGrayGreen; + HoverColour = colours.CommunityUserGrayGreen.Darken(0.2f); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20, 12) + }; + + DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + } + + private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs new file mode 100644 index 0000000000..8069937810 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class LevelBadge : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private OsuSpriteText levelText; + + public LevelBadge() + { + TooltipText = "Level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + InternalChildren = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("Profile/levelbadge"), + Colour = colours.Yellow, + }, + levelText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20) + } + }; + + User.BindValueChanged(user => updateLevel(user.NewValue)); + } + + private void updateLevel(User user) + { + levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs new file mode 100644 index 0000000000..6a6532764f --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class LevelProgressBar : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private Bar levelProgressBar; + private OsuSpriteText levelProgressText; + + public LevelProgressBar() + { + TooltipText = "Progress to next level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = levelProgressBar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.Black, + Direction = BarDirection.LeftToRight, + AccentColour = colours.Yellow + } + }, + levelProgressText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) + } + }; + + User.BindValueChanged(user => updateProgress(user.NewValue)); + } + + private void updateProgress(User user) + { + levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; + levelProgressText.Text = user?.Statistics?.Level.Progress.ToString("0'%'"); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs new file mode 100644 index 0000000000..cc6edcdd6a --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class MessageUserButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "send message"; + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved] + private IAPIProvider apiProvider { get; set; } + + public MessageUserButton() + { + Content.Alpha = 0; + RelativeSizeAxes = Axes.Y; + + Child = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Envelope, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }; + + Action = () => + { + if (!Content.IsPresent) return; + + channelManager?.OpenPrivateChannel(User.Value); + userOverlay?.Hide(); + chatOverlay?.Show(); + }; + + User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs new file mode 100644 index 0000000000..c40ddca688 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class OverlinedInfoContainer : CompositeDrawable + { + private readonly Circle line; + private readonly OsuSpriteText title; + private readonly OsuSpriteText content; + + public string Title + { + set => title.Text = value; + } + + public string Content + { + set => content.Text = value; + } + + public Color4 LineColour + { + set => line.Colour = value; + } + + public OverlinedInfoContainer(bool big = false, int minimumWidth = 60) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + line = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 4, + }, + title = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 14 : 12, weight: FontWeight.Bold) + }, + content = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) + }, + new Container //Add a minimum size to the FillFlowContainer + { + Width = minimumWidth, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs new file mode 100644 index 0000000000..2c88a83680 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; set; } + + private OverlinedInfoContainer info; + + public OverlinedTotalPlayTime() + { + AutoSizeAxes = Axes.Both; + + TooltipText = "0 hours"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = info = new OverlinedInfoContainer + { + Title = "Total Play Time", + LineColour = colours.Yellow, + }; + + User.BindValueChanged(updateTime, true); + } + + private void updateTime(ValueChangedEvent user) + { + TooltipText = (user.NewValue?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; + info.Content = formatTime(user.NewValue?.Statistics?.PlayTime); + } + + private string formatTime(int? secondsNull) + { + if (secondsNull == null) return "0h 0m"; + + int seconds = secondsNull.Value; + string time = ""; + + int days = seconds / 86400; + seconds -= days * 86400; + if (days > 0) + time += days + "d "; + + int hours = seconds / 3600; + seconds -= hours * 3600; + time += hours + "h "; + + int minutes = seconds / 60; + time += minutes + "m"; + + return time; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs new file mode 100644 index 0000000000..1650f11523 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public abstract class ProfileHeaderButton : OsuHoverContainer, IHasTooltip + { + public abstract string TooltipText { get; } + + private readonly Box background; + private readonly Container content; + + protected override Container Content => content; + + protected override IEnumerable EffectTargets => new[] { background }; + + protected ProfileHeaderButton() + { + AutoSizeAxes = Axes.X; + + IdleColour = Color4.Black; + HoverColour = OsuColour.Gray(0.1f); + + base.Content.Add(new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + } + } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs new file mode 100644 index 0000000000..1dabf167e3 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -0,0 +1,280 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class RankGraph : Container, IHasCustomTooltip + { + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int ranked_days = 88; + + private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; + + private KeyValuePair[] ranks; + private int dayIndex; + public Bindable User = new Bindable(); + + public RankGraph() + { + Padding = new MarginPadding { Vertical = padding }; + Children = new Drawable[] + { + placeholder = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) + }, + graph = new RankChartLineGraph + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Y = -secondary_textsize, + Alpha = 0, + } + }; + + graph.OnBallMove += i => dayIndex = i; + + User.ValueChanged += userChanged; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + graph.LineColour = colours.Yellow; + } + + private void userChanged(ValueChangedEvent e) + { + placeholder.FadeIn(fade_duration, Easing.Out); + + if (e.NewValue?.Statistics?.Ranks.Global == null) + { + graph.FadeOut(fade_duration, Easing.Out); + ranks = null; + return; + } + + int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + placeholder.FadeOut(fade_duration, Easing.Out); + + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); + } + + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + } + + protected override bool OnHover(HoverEvent e) + { + if (ranks?.Length > 1) + { + graph.UpdateBallPosition(e.MousePosition.X); + graph.ShowBall(); + } + + return base.OnHover(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (ranks?.Length > 1) + graph.UpdateBallPosition(e.MousePosition.X); + + return base.OnMouseMove(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (ranks?.Length > 1) + { + graph.HideBall(); + } + + base.OnHoverLost(e); + } + + private class RankChartLineGraph : LineGraph + { + private readonly CircularContainer movingBall; + private readonly Box ballBg; + private readonly Box movingBar; + + public Action OnBallMove; + + public RankChartLineGraph() + { + Add(movingBar = new Box + { + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1.5f, + Alpha = 0, + RelativePositionAxes = Axes.Both, + }); + + Add(movingBall = new CircularContainer + { + Origin = Anchor.Centre, + Size = new Vector2(18), + Alpha = 0, + Masking = true, + BorderThickness = 4, + RelativePositionAxes = Axes.Both, + Child = ballBg = new Box { RelativeSizeAxes = Axes.Both } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ballBg.Colour = colours.CommunityUserGrayGreenDarkest; + movingBall.BorderColour = colours.Yellow; + movingBar.Colour = colours.Yellow; + } + + public void UpdateBallPosition(float mouseXPosition) + { + int index = calculateIndex(mouseXPosition); + movingBall.Position = calculateBallPosition(index); + movingBar.X = movingBall.X; + OnBallMove.Invoke(index); + } + + public void ShowBall() + { + movingBall.FadeIn(fade_duration); + movingBar.FadeIn(fade_duration); + } + + public void HideBall() + { + movingBall.FadeOut(fade_duration); + movingBar.FadeOut(fade_duration); + } + + private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + + private Vector2 calculateBallPosition(int index) + { + float y = GetYPosition(Values.ElementAt(index)); + return new Vector2(index / (float)(DefaultValueCount - 1), y); + } + } + + public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; + + public ITooltip GetCustomTooltip() => new RankGraphTooltip(); + + public class RankGraphTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText globalRankingText, timeText; + private readonly Box background; + + public string TooltipText { get; set; } + + public RankGraphTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "Global Ranking " + }, + globalRankingText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.CommunityUserGrayGreenDarker; + } + + public void Refresh() + { + var info = TooltipText.Split('|'); + globalRankingText.Text = info[0]; + timeText.Text = info[1] == "0" ? "now" : $"{info[1]} days ago"; + } + + private bool instantMove = true; + + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs new file mode 100644 index 0000000000..97454d7327 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class SupporterIcon : CompositeDrawable, IHasTooltip + { + private readonly Box background; + private readonly FillFlowContainer iconContainer; + private readonly CircularContainer content; + + public string TooltipText => "osu!supporter"; + + public int SupportLevel + { + set + { + int count = MathHelper.Clamp(value, 0, 3); + + if (count == 0) + { + content.Hide(); + } + else + { + content.Show(); + iconContainer.Clear(); + + for (int i = 0; i < count; i++) + { + iconContainer.Add(new SpriteIcon + { + Width = 12, + RelativeSizeAxes = Axes.Y, + Icon = FontAwesome.Solid.Heart, + }); + } + + iconContainer.Padding = new MarginPadding { Horizontal = DrawHeight / 2 }; + } + } + } + + public SupporterIcon() + { + AutoSizeAxes = Axes.X; + + InternalChild = content = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Alpha = 0, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + iconContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Pink; + iconContainer.Colour = colours.CommunityUserGrayGreenDark; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs new file mode 100644 index 0000000000..de710c5fcd --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -0,0 +1,231 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class DetailHeaderContainer : CompositeDrawable + { + private readonly Dictionary scoreRankInfos = new Dictionary(); + private OverlinedInfoContainer medalInfo; + private OverlinedInfoContainer ppInfo; + private OverlinedInfoContainer detailGlobalRank; + private OverlinedInfoContainer detailCountryRank; + private FillFlowContainer fillFlow; + private RankGraph rankGraph; + + public readonly Bindable User = new Bindable(); + + private bool expanded = true; + + public bool Expanded + { + set + { + if (expanded == value) return; + + expanded = value; + + if (fillFlow == null) return; + + fillFlow.ClearTransforms(); + + if (expanded) + fillFlow.AutoSizeAxes = Axes.Y; + else + { + fillFlow.AutoSizeAxes = Axes.None; + fillFlow.ResizeHeightTo(0, 200, Easing.OutQuint); + } + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Y; + + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarkest, + }, + fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = expanded ? Axes.Y : Axes.None, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new OverlinedTotalPlayTime + { + User = { BindTarget = User } + }, + medalInfo = new OverlinedInfoContainer + { + Title = "Medals", + LineColour = colours.GreenLight, + }, + ppInfo = new OverlinedInfoContainer + { + Title = "pp", + LineColour = colours.Red, + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Children = new[] + { + scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH), + scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X), + scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH), + scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S), + scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A), + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 130 }, + Children = new Drawable[] + { + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 130, + Anchor = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + detailGlobalRank = new OverlinedInfoContainer(true, 110) + { + Title = "Global Ranking", + LineColour = colours.Yellow, + }, + detailCountryRank = new OverlinedInfoContainer(false, 110) + { + Title = "Country Ranking", + LineColour = colours.Yellow, + }, + } + } + } + }, + } + }, + }; + } + + private void updateDisplay(User user) + { + medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; + ppInfo.Content = user?.Statistics?.PP?.ToString("#,##0") ?? "0"; + + foreach (var scoreRankInfo in scoreRankInfos) + scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; + + detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + + rankGraph.User.Value = user; + } + + private class ScoreRankInfo : CompositeDrawable + { + private readonly ScoreRank rank; + private readonly Sprite rankSprite; + private readonly OsuSpriteText rankCount; + + public int RankCount + { + set => rankCount.Text = value.ToString("#,##0"); + } + + public ScoreRankInfo(ScoreRank rank) + { + this.rank = rank; + + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 56, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + rankSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }, + rankCount = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs new file mode 100644 index 0000000000..25d04195b2 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class MedalHeaderContainer : CompositeDrawable + { + private FillFlowContainer badgeFlowContainer; + + public readonly Bindable User = new Bindable(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Alpha = 0; + AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarkest, + }, + new Container //artificial shadow + { + RelativeSizeAxes = Axes.X, + Height = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = Color4.Black.Opacity(0.2f), + TopRight = Color4.Black.Opacity(0.2f), + BottomLeft = Color4.Black.Opacity(0), + BottomRight = Color4.Black.Opacity(0) + } + }, + }, + badgeFlowContainer = new FillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5 }, + Spacing = new Vector2(10, 10), + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + } + }; + } + + private void updateDisplay(User user) + { + var badges = user.Badges; + badgeFlowContainer.Clear(); + + if (badges?.Length > 0) + { + Show(); + + for (var index = 0; index < badges.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => + { + badgeFlowContainer.Add(asyncBadge); + + // load in stable order regardless of async load order. + badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + }); + } + } + else + { + Hide(); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs new file mode 100644 index 0000000000..3b16b102d5 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -0,0 +1,155 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class ProfileHeaderTabControl : TabControl + { + private readonly Box bar; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + foreach (TabItem tabItem in TabContainer) + { + ((ProfileHeaderTabItem)tabItem).AccentColour = value; + } + } + } + + public new MarginPadding Padding + { + get => TabContainer.Padding; + set => TabContainer.Padding = value; + } + + public ProfileHeaderTabControl() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(15, 0); + + AddInternal(bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + }); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(string value) => new ProfileHeaderTabItem(value) + { + AccentColour = AccentColour + }; + + private class ProfileHeaderTabItem : TabItem + { + private readonly OsuSpriteText text; + private readonly Drawable bar; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + updateState(); + } + } + + public ProfileHeaderTabItem(string value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new[] + { + text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 10 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = value, + Font = OsuFont.GetFont() + }, + bar = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 0, + Origin = Anchor.CentreLeft, + Anchor = Anchor.BottomLeft, + }, + new HoverClickSounds() + }; + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + + updateState(); + + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() + { + if (Active.Value || IsHovered) + { + text.FadeColour(Color4.White, 120, Easing.InQuad); + bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); + + if (Active.Value) + text.Font = text.Font.With(weight: FontWeight.Bold); + } + else + { + text.FadeColour(AccentColour, 120, Easing.InQuad); + bar.ResizeHeightTo(0, 120, Easing.InQuad); + text.Font = text.Font.With(weight: FontWeight.Medium); + } + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs deleted file mode 100644 index 3df0677576..0000000000 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class RankGraph : Container - { - private const float primary_textsize = 25; - private const float secondary_textsize = 13; - private const float padding = 10; - private const float fade_duration = 150; - private const int ranked_days = 88; - - private readonly SpriteText rankText, performanceText, relativeText; - private readonly RankChartLineGraph graph; - private readonly OsuSpriteText placeholder; - - private KeyValuePair[] ranks; - public Bindable User = new Bindable(); - - public RankGraph() - { - Padding = new MarginPadding { Vertical = padding }; - Children = new Drawable[] - { - placeholder = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "No recent plays", - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular, italics: true) - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: primary_textsize, weight: FontWeight.Regular, italics: true), - }, - relativeText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: secondary_textsize, weight: FontWeight.Regular, italics: true), - Y = 25, - }, - performanceText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.GetFont(size: secondary_textsize, weight: FontWeight.Regular, italics: true) - }, - graph = new RankChartLineGraph - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 60, - Y = -secondary_textsize, - Alpha = 0, - } - }; - - graph.OnBallMove += showHistoryRankTexts; - - User.ValueChanged += userChanged; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - graph.Colour = colours.Yellow; - } - - private void userChanged(ValueChangedEvent e) - { - placeholder.FadeIn(fade_duration, Easing.Out); - - if (e.NewValue?.Statistics?.Ranks.Global == null) - { - rankText.Text = string.Empty; - performanceText.Text = string.Empty; - relativeText.Text = string.Empty; - graph.FadeOut(fade_duration, Easing.Out); - ranks = null; - return; - } - - int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; - ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); - - if (ranks.Length > 1) - { - placeholder.FadeOut(fade_duration, Easing.Out); - - graph.DefaultValueCount = ranks.Length; - graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); - graph.SetStaticBallPosition(); - } - - graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); - - updateRankTexts(); - } - - private void updateRankTexts() - { - var user = User.Value; - - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - rankText.Text = user.Statistics.Ranks.Global > 0 ? $"#{user.Statistics.Ranks.Global:#,0}" : "no rank"; - relativeText.Text = user.Country != null && user.Statistics.Ranks.Country > 0 ? $"{user.Country.FullName} #{user.Statistics.Ranks.Country:#,0}" : "no rank"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; - relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; - } - - protected override bool OnHover(HoverEvent e) - { - if (ranks?.Length > 1) - { - graph.UpdateBallPosition(e.MousePosition.X); - graph.ShowBall(); - } - - return base.OnHover(e); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (ranks?.Length > 1) - graph.UpdateBallPosition(e.MousePosition.X); - - return base.OnMouseMove(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (ranks?.Length > 1) - { - graph.HideBall(); - updateRankTexts(); - } - - base.OnHoverLost(e); - } - - private class RankChartLineGraph : LineGraph - { - private readonly CircularContainer staticBall; - private readonly CircularContainer movingBall; - - public Action OnBallMove; - - public RankChartLineGraph() - { - Add(staticBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - Add(movingBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Alpha = 0, - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - } - - public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); - - public void UpdateBallPosition(float mouseXPosition) - { - int index = calculateIndex(mouseXPosition); - movingBall.Position = calculateBallPosition(index); - OnBallMove.Invoke(index); - } - - public void ShowBall() => movingBall.FadeIn(fade_duration); - - public void HideBall() => movingBall.FadeOut(fade_duration); - - private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); - - private Vector2 calculateBallPosition(int index) - { - float y = GetYPosition(Values.ElementAt(index)); - return new Vector2(index / (float)(DefaultValueCount - 1), y); - } - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs deleted file mode 100644 index 722c9c9af2..0000000000 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class SupporterIcon : CircularContainer, IHasTooltip - { - private readonly Box background; - - public string TooltipText => "osu!supporter"; - - public SupporterIcon() - { - Masking = true; - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, - Scale = new Vector2(0.45f), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Pink; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs new file mode 100644 index 0000000000..2ac7f3cc96 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -0,0 +1,202 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class TopHeaderContainer : CompositeDrawable + { + private const float avatar_size = 110; + + public readonly Bindable User = new Bindable(); + + private SupporterIcon supporterTag; + private UpdateableAvatar avatar; + private OsuSpriteText usernameText; + private ExternalLinkButton openUserExternally; + private OsuSpriteText titleText; + private DrawableFlag userFlag; + private OsuSpriteText userCountryText; + private FillFlowContainer userStats; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Height = 150; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarker, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Height = avatar_size, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new[] + { + avatar = new UpdateableAvatar + { + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + OpenOnClick = { Value = false }, + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + new FillFlowContainer + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) + }, + supporterTag = new SupporterIcon + { + Height = 20, + Margin = new MarginPadding { Top = 5 } + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 }, + Colour = colours.CommunityUserGrayGreenLighter, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + userFlag = new DrawableFlag + { + Size = new Vector2(30, 20) + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 40 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Colour = colours.CommunityUserGrayGreenLighter, + } + } + }, + } + } + } + } + } + }, + userStats = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Y, + Width = 300, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Vertical = 15 }, + Spacing = new Vector2(0, 2) + } + }; + + User.BindValueChanged(user => updateUser(user.NewValue)); + } + + private void updateUser(User user) + { + avatar.User = user; + usernameText.Text = user?.Username ?? string.Empty; + openUserExternally.Link = $@"https://osu.ppy.sh/users/{user?.Id ?? 0}"; + userFlag.Country = user?.Country; + userCountryText.Text = user?.Country?.FullName ?? "Alien"; + supporterTag.SupportLevel = user?.SupportLevel ?? 0; + titleText.Text = user?.Title ?? string.Empty; + titleText.Colour = OsuColour.FromHex(user?.Colour ?? "fff"); + + userStats.Clear(); + + if (user?.Statistics != null) + { + userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); + userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); + } + } + + private class UserStatsLine : Container + { + public UserStatsLine(string left, string right) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = left, + }, + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + Text = right, + }, + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c41d977701..2d8c47b11a 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,483 +1,152 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Profile.Components; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -using Humanizer; namespace osu.Game.Overlays.Profile { public class ProfileHeader : Container { - private readonly LinkFlowContainer infoTextLeft; - private readonly LinkFlowContainer infoTextRight; - private readonly FillFlowContainer scoreText, scoreNumberText; - private readonly RankGraph rankGraph; + private readonly UserCoverBackground coverContainer; + private readonly ProfileHeaderTabControl infoTabControl; - public readonly SupporterIcon SupporterTag; - private readonly Container coverContainer; - private readonly Sprite levelBadge; - private readonly SpriteText levelText; - private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; - private readonly Box colourBar; - private readonly DrawableFlag countryFlag; - private readonly BadgeContainer badgeContainer; + private const float cover_height = 150; + private const float cover_info_height = 75; - private const float cover_height = 350; - private const float info_height = 150; - private const float info_width = 220; - private const float avatar_size = 110; - private const float level_position = 30; - private const float level_height = 60; - private const float stats_width = 280; - - public ProfileHeader(User user) + public ProfileHeader() { + CentreHeaderContainer centreHeaderContainer; + DetailHeaderContainer detailHeaderContainer; + RelativeSizeAxes = Axes.X; - Height = cover_height + info_height; + AutoSizeAxes = Axes.Y; Children = new Drawable[] { - coverContainer = new Container + new Container { RelativeSizeAxes = Axes.X, Height = cover_height, Masking = true, Children = new Drawable[] { + coverContainer = new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) + Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f)) }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Bottom = 20, Right = stats_width + UserProfileOverlay.CONTENT_X_MARGIN }, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - new UpdateableAvatar - { - User = user, - Size = new Vector2(avatar_size), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Masking = true, - CornerRadius = 5, - OpenOnClick = { Value = false }, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = avatar_size + 10, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - SupporterTag = new SupporterIcon - { - Alpha = 0, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -75, - Size = new Vector2(25, 25) - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -48, - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Text = user.Username, - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Regular, italics: true) - }, - new ExternalLinkButton($@"https://osu.ppy.sh/users/{user.Id}") - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 3, Bottom = 3 }, //To better lineup with the font - }, - } - }, - countryFlag = new DrawableFlag(user.Country) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Width = 30, - Height = 20 - } - } - }, - badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 5 }, - Alpha = 0, - }, - } - }, - colourBar = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Height = 5, - Width = info_width, - Alpha = 0 - } } }, - new Box // this is a temporary workaround for incorrect masking behaviour of FillMode.Fill used in UserCoverBackground (see https://github.com/ppy/osu-framework/issues/1675) - { - RelativeSizeAxes = Axes.X, - Height = 1, - Y = cover_height, - Colour = OsuColour.Gray(34), - }, - infoTextLeft = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14)) - { - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - infoTextRight = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular, italics: true)) - { - X = UserProfileOverlay.CONTENT_X_MARGIN + info_width + 20, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, new Container { - X = -UserProfileOverlay.CONTENT_X_MARGIN, - RelativeSizeAxes = Axes.Y, - Width = stats_width, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Y = cover_height, + Height = cover_info_height, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomLeft, + Depth = -float.MaxValue, Children = new Drawable[] { - new Container + new ProfileHeaderTitle { - RelativeSizeAxes = Axes.X, - Y = level_position, - Height = level_height, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - levelBadge = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = 50, - Width = 50, - Alpha = 0 - }, - levelText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 11, - Font = OsuFont.GetFont(size: 20) - } - } + X = -ScreenTitle.ICON_WIDTH, }, - new Container + infoTabControl = new ProfileHeaderTabControl { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Y = cover_height, - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Height = cover_height - level_height - level_position - 5, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - scoreText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - scoreNumberText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -64, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSSPlus = new GradeBadge("SSPlus") { Alpha = 0 }, - gradeSS = new GradeBadge("SS") { Alpha = 0 }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -18, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSPlus = new GradeBadge("SPlus") { Alpha = 0 }, - gradeS = new GradeBadge("S") { Alpha = 0 }, - gradeA = new GradeBadge("A") { Alpha = 0 }, - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = info_height - 15, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.25f), - RelativeSizeAxes = Axes.Both - }, - rankGraph = new RankGraph - { - RelativeSizeAxes = Axes.Both - } - } + Height = cover_info_height - 30, + Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN } } } + }, + new FillFlowContainer + { + Margin = new MarginPadding { Top = cover_height }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TopHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + centreHeaderContainer = new CentreHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + detailHeaderContainer = new DetailHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + new MedalHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + new BottomHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + } } }; + + infoTabControl.AddItem("Info"); + infoTabControl.AddItem("Modding"); + + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); + User.ValueChanged += e => updateDisplay(e.NewValue); } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(OsuColour colours) { - levelBadge.Texture = textures.Get(@"Profile/levelbadge"); + infoTabControl.AccentColour = colours.CommunityUserGreen; } - private readonly OsuSpriteText usernameText; + public Bindable User = new Bindable(); - private User user; - - public User User + private void updateDisplay(User user) { - get => user; - set - { - user = value; - loadUser(); - } + coverContainer.User = user; } - private void loadUser() + private class ProfileHeaderTitle : ScreenTitle { - LoadComponentAsync(new UserCoverBackground(user) + public ProfileHeaderTitle() { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - Depth = float.MaxValue, - }, background => - { - coverContainer.Add(background); - background.FadeInFromZero(200); - }); - - if (user.IsSupporter) - SupporterTag.Show(); - - usernameText.Text = user.Username; - - if (!string.IsNullOrEmpty(user.Colour)) - { - colourBar.Colour = OsuColour.FromHex(user.Colour); - colourBar.Show(); + Title = "Player"; + Section = "Info"; } - void boldItalic(SpriteText t) => t.Font = t.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true); - void lightText(SpriteText t) => t.Alpha = 0.8f; - - OsuSpriteText createScoreText(string text) => new OsuSpriteText + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - Font = OsuFont.GetFont(size: 14), - Text = text - }; - - OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = text - }; - - if (user.Country != null) - { - infoTextLeft.AddText("From ", lightText); - infoTextLeft.AddText(user.Country.FullName, boldItalic); - countryFlag.Country = user.Country; + AccentColour = colours.CommunityUserGreen; } - - infoTextLeft.NewParagraph(); - - if (user.JoinDate.ToUniversalTime().Year < 2008) - { - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText); - } - else - { - infoTextLeft.AddText("Joined ", lightText); - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic); - } - - if (user.LastVisit.HasValue) - { - infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen ", lightText); - infoTextLeft.AddText(new DrawableDate(user.LastVisit.Value), boldItalic); - } - - if (user.PlayStyle?.Length > 0) - { - infoTextLeft.NewParagraph(); - infoTextLeft.AddText("Plays with ", lightText); - infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); - } - - infoTextLeft.NewLine(); - infoTextLeft.AddText("Contributed ", lightText); - infoTextLeft.AddLink("forum post".ToQuantity(user.PostCount), url: $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: boldItalic); - - string websiteWithoutProtcol = user.Website; - if (!string.IsNullOrEmpty(websiteWithoutProtcol)) - { - int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); - if (protocolIndex >= 0) - websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); - } - - tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); - tryAddInfoRightLine(FontAwesome.fa_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); - - if (user.Statistics != null) - { - levelBadge.Show(); - levelText.Text = user.Statistics.Level.Current.ToString(); - - scoreText.Add(createScoreText("Ranked Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); - scoreText.Add(createScoreText("Play Count")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Hits")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); - scoreText.Add(createScoreText("Max Combo")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); - scoreText.Add(createScoreText("Replays Watched by Others")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); - - gradeSSPlus.DisplayCount = user.Statistics.GradesCount.SSPlus; - gradeSSPlus.Show(); - gradeSS.DisplayCount = user.Statistics.GradesCount.SS; - gradeSS.Show(); - gradeSPlus.DisplayCount = user.Statistics.GradesCount.SPlus; - gradeSPlus.Show(); - gradeS.DisplayCount = user.Statistics.GradesCount.S; - gradeS.Show(); - gradeA.DisplayCount = user.Statistics.GradesCount.A; - gradeA.Show(); - - rankGraph.User.Value = user; - } - - badgeContainer.ShowBadges(user.Badges); - } - - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) - { - if (string.IsNullOrEmpty(str)) return; - - infoTextRight.AddIcon(icon); - if (url != null) - { - infoTextRight.AddLink(" " + str, url); - } - else - { - infoTextRight.AddText(" " + str); - } - - infoTextRight.NewLine(); } } } diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 6da736432f..4d891384e8 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Profile diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index a93fefdd75..23fe6e9cd5 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 3c69082e9d..aeea5118a7 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 95a18ccfa9..470bed2854 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -54,6 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks default: drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); break; + case ScoreType.Recent: drawableScores = scores.Select(score => new DrawableTotalScore(score)); break; diff --git a/osu.Game/Overlays/SearchableList/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/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs index a16e852902..93b07fbac7 100644 --- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.Settings { /// /// A which provides subclasses with the - /// from the 's . + /// from the 's . /// public abstract class RulesetSettingsSubsection : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/Sections/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 d6738250f9..2f56ace24d 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -16,6 +16,8 @@ using System.ComponentModel; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; @@ -86,6 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } }; break; + case APIState.Failing: case APIState.Connecting: LinkFlowContainer linkFlow; @@ -111,6 +114,7 @@ namespace osu.Game.Overlays.Settings.Sections.General linkFlow.AddLink("cancel", api.Logout, string.Empty); break; + case APIState.Online: Children = new Drawable[] { @@ -159,14 +163,17 @@ namespace osu.Game.Overlays.Settings.Sections.General api.LocalUser.Value.Status.Value = new UserStatusOnline(); dropdown.StatusColour = colours.Green; break; + case UserAction.DoNotDisturb: api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); dropdown.StatusColour = colours.Red; break; + case UserAction.AppearOffline: api.LocalUser.Value.Status.Value = new UserStatusOffline(); dropdown.StatusColour = colours.Gray7; break; + case UserAction.SignOut: api.Logout(); break; @@ -362,7 +369,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 4b0147eb5d..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(); diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 11cdbf6e0a..9f09f251c2 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings { protected override OsuDropdown CreateDropdown() => new DropdownControl(); - protected class DropdownControl : OsuEnumDropdown + protected new class DropdownControl : OsuEnumDropdown { public DropdownControl() { diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index f6517bafd6..4b64f942bf 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -64,6 +65,7 @@ namespace osu.Game.Overlays.Settings { bindable = value; controlWithCurrent?.Current.BindTo(bindable); + if (ShowsDefaultIndicator) { restoreDefaultButton.Bindable = bindable.GetBoundCopy(); @@ -79,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/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 969686e36d..3c18627f23 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -102,6 +102,7 @@ namespace osu.Game.Overlays.Settings default: this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); break; + case ExpandedState.Expanded: this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); break; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index c7736d6047..a94f76e7af 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -46,6 +46,7 @@ namespace osu.Game.Overlays.Settings set { selected = value; + if (selected) { selectionIndicator.FadeIn(50); diff --git a/osu.Game/Overlays/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/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index 738f940484..555527670a 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -5,7 +5,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Game.Users; diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index daf3d1c576..e6d0c2fe40 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -111,6 +111,7 @@ namespace osu.Game.Overlays ChildrenEnumerable = Users.Select(u => { SocialPanel panel; + switch (displayStyle) { case PanelDisplayStyle.Grid: @@ -120,6 +121,7 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre }; break; + default: panel = new SocialListPanel(u); break; @@ -167,6 +169,7 @@ namespace osu.Game.Overlays friendRequest.Success += updateUsers; api.Queue(getUsersRequest = friendRequest); break; + default: var userRequest = new GetUsersRequest(); // TODO filter arguments! userRequest.Success += response => updateUsers(response.Select(r => r.User)); @@ -200,6 +203,7 @@ namespace osu.Game.Overlays case APIState.Online: Scheduler.AddOnce(updateSearch); break; + default: Users = null; clearPanels(); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index dca0226499..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 ToolbarUserArea userArea; + private ToolbarUserButton userButton; protected override bool BlockPositionalInput => false; @@ -75,9 +75,9 @@ 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(), } } @@ -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..2b2b19b73a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -27,13 +28,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/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index ca86ce7aa7..b2ae273e31 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Toolbar set { stateContainer = value; + if (stateContainer != null) { Action = stateContainer.ToggleVisibility; @@ -55,6 +56,7 @@ namespace osu.Game.Overlays.Toolbar case Visibility.Hidden: stateBackground.FadeOut(200); break; + case Visibility.Visible: stateBackground.FadeIn(200); break; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs index f729810fbc..87b18ba9f4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Rulesets; using osuTK.Graphics; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index d01eab4dab..84a41b6547 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Input; using osuTK.Graphics; @@ -71,6 +72,7 @@ namespace osu.Game.Overlays.Toolbar private void load(RulesetStore rulesets, Bindable parentRuleset) { this.rulesets = rulesets; + foreach (var r in rulesets.AvailableRulesets) { modeButtons.Add(new ToolbarRulesetButton diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 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 8d1910fc19..ea15e5498b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Online.API; using osu.Game.Users; @@ -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,10 +42,12 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + [BackgroundDependencyLoader(true)] + private void load(IAPIProvider api, LoginOverlay login) { api.Register(this); + + StateContainer = login; } public void APIStateChanged(IAPIProvider api, APIState state) @@ -56,6 +58,7 @@ namespace osu.Game.Overlays.Toolbar Text = @"Guest"; avatar.User = new User(); break; + case APIState.Online: Text = api.LocalUser.Value.Username; avatar.User = api.LocalUser.Value; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 48ce055975..1c242e3768 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -5,7 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; @@ -31,7 +31,7 @@ namespace osu.Game.Overlays private SectionsContainer sectionsContainer; private ProfileTabControl tabs; - public const float CONTENT_X_MARGIN = 50; + public const float CONTENT_X_MARGIN = 70; public UserProfileOverlay() { @@ -81,7 +81,7 @@ namespace osu.Game.Overlays Show(); - if (user.Id == Header?.User?.Id) + if (user.Id == Header?.User.Value?.Id) return; userReq?.Cancel(); @@ -113,12 +113,10 @@ namespace osu.Game.Overlays Colour = OsuColour.Gray(0.2f) }); - Header = new ProfileHeader(user); - Add(sectionsContainer = new SectionsContainer { RelativeSizeAxes = Axes.Both, - ExpandableHeader = Header, + ExpandableHeader = Header = new ProfileHeader(), FixedHeader = tabs, HeaderBackground = new Box { @@ -169,13 +167,14 @@ namespace osu.Game.Overlays private void userLoadComplete(User user) { - Header.User = user; + Header.User.Value = user; if (user.ProfileOrder != null) { foreach (string id in user.ProfileOrder) { var sec = sections.FirstOrDefault(s => s.Identifier == id); + if (sec != null) { sec.User.Value = user; diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 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/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index e2e480ef53..34b15d958d 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -105,12 +105,14 @@ namespace osu.Game.Overlays else volumeMeterMaster.Decrease(amount, isPrecise); return true; + case GlobalAction.IncreaseVolume: if (State == Visibility.Hidden) Show(); else volumeMeterMaster.Increase(amount, isPrecise); return true; + case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index aad55f8a38..e31c963403 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -39,8 +39,7 @@ namespace osu.Game.Rulesets.Difficulty { mods = mods.Select(m => m.CreateCopy()).ToArray(); - beatmap.Mods.Value = mods; - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); var clock = new StopwatchClock(); mods.OfType().ForEach(m => m.ApplyToClock(clock)); @@ -106,7 +105,7 @@ namespace osu.Game.Rulesets.Difficulty /// public Mod[] CreateDifficultyAdjustmentModCombinations() { - return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); + return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { @@ -117,10 +116,12 @@ namespace osu.Game.Rulesets.Difficulty yield return new ModNoMod(); break; + case 1: yield return currentSet.Single(); break; + default: yield return new MultiMod(currentSet.ToArray()); @@ -166,7 +167,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Creates the s to calculate the difficulty of an . /// - /// The whose difficulty will be calculated.The whose difficulty will be calculated. /// The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap); } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 2b627ee8e6..9ab81b9580 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -26,8 +26,7 @@ namespace osu.Game.Rulesets.Difficulty Ruleset = ruleset; Score = score; - beatmap.Mods.Value = score.Mods; - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods); diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 76a2e7af12..2200caeb20 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract Playfield Playfield { get; } + public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer(); + internal DrawableEditRuleset() { RelativeSizeAxes = Axes.Both; @@ -43,6 +45,8 @@ namespace osu.Game.Rulesets.Edit { public override Playfield Playfield => drawableRuleset.Playfield; + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); + private Ruleset ruleset => drawableRuleset.Ruleset; private Beatmap beatmap => drawableRuleset.Beatmap; @@ -73,7 +77,7 @@ namespace osu.Game.Rulesets.Edit processor?.PostProcess(); // Add visual representation - var drawableObject = drawableRuleset.GetVisualRepresentation(tObject); + var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject); drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 45bf9b8be7..38ec09535d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -63,10 +64,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); @@ -174,11 +175,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 @@ -190,8 +186,8 @@ namespace osu.Game.Rulesets.Edit } internal override DrawableEditRuleset CreateDrawableRuleset() - => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 74aa9ace2d..757c269358 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -108,7 +109,8 @@ namespace osu.Game.Rulesets.Edit } /// - /// Invokes , refreshing and parameters for the . + /// Invokes , + /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); @@ -122,8 +124,10 @@ namespace osu.Game.Rulesets.Edit { case ScrollEvent _: return false; + case MouseEvent _: return true; + default: return false; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 85b6c91a07..e94604554c 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -68,20 +68,25 @@ namespace osu.Game.Rulesets.Edit get => state; set { - if (state == value) return; + if (state == value) + return; state = value; + switch (state) { case SelectionState.Selected: Show(); Selected?.Invoke(this); break; + case SelectionState.NotSelected: Hide(); Deselected?.Invoke(this); break; } + + StateChanged?.Invoke(state); } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 89db954c36..2150726a42 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Judgements { case HitResult.None: break; + case HitResult.Miss: JudgementBody.ScaleTo(1.6f); JudgementBody.ScaleTo(1, 100, Easing.In); @@ -98,6 +99,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); break; + default: ApplyHitAnimations(); break; @@ -113,13 +115,17 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: case HitResult.Great: return colours.Blue; + case HitResult.Ok: case HitResult.Good: return colours.Green; + case HitResult.Meh: return colours.Yellow; + case HitResult.Miss: return colours.Red; + default: return Color4.White; } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index e14eedd3dc..f07f76a2b8 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Judgements /// public int MaxNumericResult => NumericResultFor(MaxResult); + /// + /// The health increase for the maximum achievable result. + /// + public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); + /// /// Retrieves the numeric score representation of a . /// diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index d4ef5750b1..195fe316ac 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Judgements /// public int HighestComboAtJudgement { get; internal set; } + /// + /// The health prior to this occurring. + /// + public double HealthAtJudgement { get; internal set; } + /// /// Whether a miss or hit occurred. /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index eb80fa131a..8cefb02904 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -2,14 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { /// /// Interface for a that applies changes to a . /// - /// The type of converted . public interface IApplicableToBeatmapConverter : IApplicableMod { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index c13b62812b..f7f81c92c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public interface IApplicableToHitObject : IApplicableMod { /// - /// Applies this to a . + /// Applies this to a . /// /// The to apply to. void ApplyToHitObject(HitObject hitObject); diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 1d0ed94ef4..cb00770868 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -10,6 +11,14 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToScoreProcessor : IApplicableMod { + /// + /// Provide a to a mod. Called once on initialisation of a play instance. + /// void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); + + /// + /// Called every time a rank calculation is requested. Allows mods to adjust the final rank. + /// + ScoreRank AdjustRank(ScoreRank rank, double accuracy); } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 448ad0eb30..a5e19f293c 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; namespace osu.Game.Rulesets.Mods { - public interface IMod + public interface IMod : IEquatable { /// /// The shortened name of this mod. diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1f9907caa7..023d37497a 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. @@ -70,5 +70,7 @@ namespace osu.Game.Rulesets.Mods /// Creates a copy of this initialised to a default state. /// public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); + + public bool Equals(IMod other) => GetType() == other?.GetType(); } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 1c76abbc4b..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; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mods { 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; 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 dded688e80..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,7 +11,7 @@ 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) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 9ea9eb76bc..a5e76e32b1 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { 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; 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 23e928d991..e174a25df3 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -9,12 +9,14 @@ 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; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -24,7 +26,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; @@ -45,6 +47,8 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); @@ -63,24 +67,12 @@ namespace osu.Game.Rulesets.Mods internal BindableInt Combo; private IShader shader; - protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); + protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); public override bool RemoveCompletedTransforms => false; public List Breaks; - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var flashNode = (FlashlightDrawNode)node; - - flashNode.Shader = shader; - flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; - flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); - flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); - } - [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -93,14 +85,15 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - - foreach (var breakPeriod in Breaks) + using (BeginAbsoluteSequence(0)) { - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + foreach (var breakPeriod in Breaks) + { + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + } } } @@ -135,27 +128,61 @@ namespace osu.Game.Rulesets.Mods Invalidate(Invalidation.DrawNode); } } - } - private class FlashlightDrawNode : DrawNode - { - public IShader Shader; - public Quad ScreenSpaceDrawQuad; - public Vector2 FlashlightPosition; - public Vector2 FlashlightSize; + private float flashlightDim; - public override void Draw(Action vertexAction) + public float FlashlightDim { - base.Draw(vertexAction); + get => flashlightDim; + set + { + if (flashlightDim == value) return; - Shader.Bind(); + flashlightDim = value; + Invalidate(Invalidation.DrawNode); + } + } - Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); - Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); + private class FlashlightDrawNode : DrawNode + { + protected new Flashlight Source => (Flashlight)base.Source; - Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + private IShader shader; + private Quad screenSpaceDrawQuad; + private Vector2 flashlightPosition; + private Vector2 flashlightSize; + private float flashlightDim; - Shader.Unbind(); + public FlashlightDrawNode(Flashlight source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; + flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); + flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; + flashlightDim = Source.FlashlightDim; + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + + shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); + shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); + shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); + + Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + + shader.Unbind(); + } } } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index fe26c96214..27369f4c30 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { 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; 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..0934992f55 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -7,14 +7,17 @@ using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects + public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects, IApplicableToScoreProcessor { public override string Name => "Hidden"; public override string Acronym => "HD"; - 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; @@ -31,6 +34,27 @@ namespace osu.Game.Rulesets.Mods d.ApplyCustomUpdateState += ApplyHiddenState; } + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ + scoreProcessor.Rank.Value = ScoreRank.XH; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + { + switch (rank) + { + case ScoreRank.X: + return ScoreRank.XH; + + case ScoreRank.S: + return ScoreRank.SH; + + default: + return rank; + } + } + protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a689292ed7..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,7 +12,7 @@ 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) 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..809661db8e 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -2,8 +2,10 @@ // 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; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -11,7 +13,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; @@ -23,6 +25,8 @@ namespace osu.Game.Rulesets.Mods scoreProcessor.FailConditions += FailCondition; } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 62407907c1..a5f96087c0 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -73,10 +73,12 @@ namespace osu.Game.Rulesets.Mods pitch.PitchAdjust /= lastAdjust; pitch.PitchAdjust *= adjust; break; + case IHasTempoAdjust tempo: tempo.TempoAdjust /= lastAdjust; tempo.TempoAdjust *= adjust; break; + default: clock.Rate /= lastAdjust; clock.Rate *= adjust; diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 174070eb85..5d71c8950b 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -3,7 +3,7 @@ using System; using System.Linq; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Wind Down"; public override string Acronym => "WD"; public override string Description => "Sloooow doooown..."; - public override FontAwesome Icon => FontAwesome.fa_chevron_circle_down; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown; public override double ScoreMultiplier => 1.0; protected override double FinalRateAdjustment => -0.25; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index bf9af8a51d..aae85cec19 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -3,7 +3,7 @@ using System; using System.Linq; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Wind Up"; public override string Acronym => "WU"; public override string Description => "Can you keep up?"; - public override FontAwesome Icon => FontAwesome.fa_chevron_circle_up; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp; public override double ScoreMultiplier => 1.0; protected override double FinalRateAdjustment => 0.5; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e1e76f109d..e91100608b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public bool AllJudged => Judged && NestedHitObjects.All(h => h.AllJudged); /// - /// Whether this has been hit. This occurs if is . + /// Whether this has been hit. This occurs if is hit. /// Note: This does NOT include nested hitobjects. /// public bool IsHit => Result?.IsHit ?? false; @@ -97,6 +98,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load() { var judgement = HitObject.CreateJudgement(); + if (judgement != null) { Result = CreateResult(judgement); @@ -120,6 +122,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(); @@ -209,9 +213,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { case HitResult.None: break; + case HitResult.Miss: State.Value = ArmedState.Miss; break; + default: State.Value = ArmedState.Hit; break; @@ -221,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Will called at least once after the of this has been passed. + /// Will called at least once after the of this has been passed. /// internal void OnLifetimeEnd() { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fd542be67d..cede2e50d0 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -53,8 +53,6 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public bool Kiai { get; private set; } - private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - /// /// The hit windows for this . /// @@ -115,7 +113,7 @@ namespace osu.Game.Rulesets.Objects /// Creates the for this . /// This can be null to indicate that the has no . /// - /// This will only be invoked if hasn't been set externally (e.g. from a . + /// This will only be invoked if hasn't been set externally (e.g. from a . /// /// protected virtual HitWindows CreateHitWindows() => new HitWindows(); diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index c5b7686da6..fe099aaee7 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects case HitResult.Perfect: case HitResult.Ok: return false; + default: return true; } @@ -126,16 +127,22 @@ namespace osu.Game.Rulesets.Objects { case HitResult.Perfect: return Perfect / 2; + case HitResult.Great: return Great / 2; + case HitResult.Good: return Good / 2; + case HitResult.Ok: return Ok / 2; + case HitResult.Meh: return Meh / 2; + case HitResult.Miss: return Miss / 2; + default: throw new ArgumentException(nameof(result)); } @@ -143,7 +150,7 @@ namespace osu.Game.Rulesets.Objects /// /// Given a time offset, whether the can ever be hit in the future with a non- result. - /// This happens if is less than what is required for a result. + /// This happens if is less than what is required for . /// /// The time offset. /// Whether the can be hit at any point in the future from this time offset. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8d6bb8bd3f..c14f3b6a42 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Objects.Legacy var points = new Vector2[pointCount]; int pointIndex = 1; + foreach (string t in pointSplit) { if (t.Length == 1) @@ -93,12 +94,15 @@ namespace osu.Game.Rulesets.Objects.Legacy case @"C": pathType = PathType.Catmull; break; + case @"B": pathType = PathType.Bezier; break; + case @"L": pathType = PathType.Linear; break; + case @"P": pathType = PathType.PerfectCurve; break; @@ -143,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 9 && split[9].Length > 0) { string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= sets.Length) @@ -162,6 +167,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 8 && split[8].Length > 0) { string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= adds.Length) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 1e9767a54f..bc9571c85d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Objects path.Clear(); int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } @@ -142,6 +143,7 @@ namespace osu.Game.Rulesets.Objects { case PathType.Linear: return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) @@ -155,6 +157,7 @@ namespace osu.Game.Rulesets.Objects break; return subpath; + case PathType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); } @@ -277,12 +280,5 @@ namespace osu.Game.Rulesets.Objects return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - - return obj is SliderPath other && Equals(other); - } } } diff --git a/osu.Game/Rulesets/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 feac49ca2c..42b1322cae 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; @@ -53,9 +53,10 @@ namespace osu.Game.Rulesets /// Attempt to create a hit renderer for a beatmap /// /// The beatmap to create the hit renderer for. + /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); /// /// Creates a to convert a to one that is applicable for this . @@ -77,7 +78,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 0fddb19a6c..0ca92a8861 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -60,6 +61,11 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt Combo = new BindableInt(); + /// + /// The current selected mods + /// + public readonly Bindable> Mods = new Bindable>(Array.Empty()); + /// /// Create a for this processor. /// @@ -98,7 +104,12 @@ namespace osu.Game.Rulesets.Scoring protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; + Accuracy.ValueChanged += delegate + { + Rank.Value = rankFrom(Accuracy.Value); + foreach (var mod in Mods.Value.OfType()) + Rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); + }; } private ScoreRank rankFrom(double acc) @@ -154,7 +165,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Notifies subscribers of that a new judgement has occurred. /// - /// The judgement to notify subscribers of. /// The judgement scoring result to notify subscribers of. protected void NotifyNewJudgement(JudgementResult result) { @@ -283,7 +293,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. private void revertResult(JudgementResult result) { @@ -301,6 +310,7 @@ namespace osu.Game.Rulesets.Scoring { result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; + result.HealthAtJudgement = Health.Value; JudgedHits++; @@ -313,9 +323,11 @@ namespace osu.Game.Rulesets.Scoring { case HitResult.None: break; + case HitResult.Miss: Combo.Value = 0; break; + default: Combo.Value++; break; @@ -332,17 +344,19 @@ namespace osu.Game.Rulesets.Scoring baseScore += result.Judgement.NumericResultFor(result); rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + + Health.Value += HealthAdjustmentFactorFor(result) * result.Judgement.HealthIncreaseFor(result); } /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) { Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; + Health.Value = result.HealthAtJudgement; JudgedHits--; @@ -358,6 +372,13 @@ namespace osu.Game.Rulesets.Scoring } } + /// + /// An adjustment factor which is multiplied into the health increase provided by a . + /// + /// The for which the adjustment should apply. + /// The adjustment factor. + protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1; + private void updateScore() { if (rollingMaxBaseScore != 0) @@ -373,6 +394,7 @@ namespace osu.Game.Rulesets.Scoring default: case ScoringMode.Standardised: return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore; + case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) / 25); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index abf0fb2696..77d1e60b87 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.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; @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; @@ -82,7 +81,8 @@ namespace osu.Game.Rulesets.UI /// /// The mods which are to be applied. /// - private readonly IEnumerable mods; + [Cached(typeof(IReadOnlyList))] + private readonly IReadOnlyList mods; private FrameStabilityContainer frameStabilityContainer; @@ -93,16 +93,19 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being represented. /// The beatmap to create the hit renderer for. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) + /// The s to apply. + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) : base(ruleset) { - Debug.Assert(workingBeatmap != null, "DrawableRuleset initialized with a null beatmap."); + if (workingBeatmap == null) + throw new ArgumentException("Beatmap cannot be null.", nameof(workingBeatmap)); + + this.mods = mods.ToArray(); RelativeSizeAxes = Axes.Both; - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - mods = workingBeatmap.Mods.Value; applyBeatmapMods(mods); KeyBindingInputManager = CreateInputManager(); @@ -124,6 +127,7 @@ namespace osu.Game.Rulesets.UI onScreenDisplay = dependencies.Get(); Config = dependencies.Get().GetConfigFor(Ruleset); + if (Config != null) { dependencies.Cache(Config); @@ -133,23 +137,30 @@ namespace osu.Game.Rulesets.UI return dependencies; } + public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - KeyBindingInputManager.AddRange(new Drawable[] - { - Playfield - }); - InternalChildren = new Drawable[] { frameStabilityContainer = new FrameStabilityContainer { - Child = KeyBindingInputManager, + 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(); @@ -161,7 +172,7 @@ namespace osu.Game.Rulesets.UI private void loadObjects() { foreach (TObject h in Beatmap.HitObjects) - addRepresentation(h); + addHitObject(h); Playfield.PostProcess(); @@ -169,13 +180,29 @@ namespace osu.Game.Rulesets.UI 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 addRepresentation(TObject hitObject) + private void addHitObject(TObject hitObject) { - var drawableObject = GetVisualRepresentation(hitObject); + var drawableObject = CreateDrawableRepresentation(hitObject); if (drawableObject == null) return; @@ -200,6 +227,12 @@ namespace osu.Game.Rulesets.UI if (replayInputManager.ReplayInputHandler != null) replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + + if (!ProvidingUserCursor) + { + // The cursor is hidden by default (see Playfield.load()), but should be shown when there's a replay + Playfield.Cursor?.Show(); + } } /// @@ -207,9 +240,9 @@ namespace osu.Game.Rulesets.UI /// /// The HitObject to make drawable. /// The DrawableHitObject. - public abstract DrawableHitObject GetVisualRepresentation(TObject h); + public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); - public void Attach(KeyCounterCollection keyCounter) => + public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); /// @@ -232,7 +265,7 @@ namespace osu.Game.Rulesets.UI /// Applies the active mods to the Beatmap. /// /// - private void applyBeatmapMods(IEnumerable mods) + private void applyBeatmapMods(IReadOnlyList mods) { if (mods == null) return; @@ -244,8 +277,9 @@ namespace osu.Game.Rulesets.UI /// /// Applies the active mods to this DrawableRuleset. /// - /// - private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) + /// The s to apply. + /// The to apply. + private void applyRulesetMods(IReadOnlyList mods, OsuConfigManager config) { if (mods == null) return; @@ -261,7 +295,9 @@ namespace osu.Game.Rulesets.UI protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor - public override CursorContainer Cursor => Playfield.Cursor; + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + + public override GameplayCursorContainer Cursor => Playfield.Cursor; public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; @@ -331,7 +367,7 @@ namespace osu.Game.Rulesets.UI /// /// The cursor being displayed by the . May be null if no cursor is provided. /// - public abstract CursorContainer Cursor { get; } + public abstract GameplayCursorContainer Cursor { get; } /// /// Sets a replay to be used, overriding local input. @@ -339,6 +375,13 @@ namespace osu.Game.Rulesets.UI /// 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 /// . diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 161e7aecb4..ad15bcf057 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -36,7 +36,10 @@ namespace osu.Game.Rulesets.UI private void load(GameplayClock clock) { if (clock != null) + { parentGameplayClock = clock; + gameplayClock.IsPaused.BindTo(clock.IsPaused); + } } protected override void LoadComplete() @@ -65,10 +68,12 @@ namespace osu.Game.Rulesets.UI private const double sixty_frame_time = 1000.0 / 60; + private bool firstConsumption = true; + public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = true; + validState = !gameplayClock.IsPaused.Value; int loops = 0; @@ -100,9 +105,20 @@ namespace osu.Game.Rulesets.UI try { - if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + if (firstConsumption) { - newProposedTime = manualClock.Rate > 0 + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. + manualClock.CurrentTime = newProposedTime; + + // do a second process to clear out ElapsedTime + framedClock.ProcessFrame(); + + firstConsumption = false; + } + else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = newProposedTime > manualClock.CurrentTime ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs new file mode 100644 index 0000000000..41edfa0b68 --- /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..86feea09a8 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, }, @@ -75,18 +76,22 @@ namespace osu.Game.Rulesets.UI backgroundColour = colours.Yellow; highlightedColour = colours.YellowLight; break; + case ModType.DifficultyReduction: backgroundColour = colours.Green; highlightedColour = colours.GreenLight; break; + case ModType.Automation: backgroundColour = colours.Blue; highlightedColour = colours.BlueLight; break; + case ModType.Conversion: backgroundColour = colours.Purple; highlightedColour = colours.PurpleLight; break; + case ModType.Fun: backgroundColour = colours.Pink; highlightedColour = colours.PinkLight; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 78d14a27e3..f2e7f51b52 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osuTK; @@ -58,16 +57,24 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(CreateHitObjectContainer); } - private WorkingBeatmap beatmap; + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load() { - this.beatmap = beatmap.Value; - Cursor = CreateCursor(); + if (Cursor != null) - CursorTargetContainer.Add(Cursor); + { + // initial showing of the cursor will be handed by MenuCursorContainer (via DrawableRuleset's IProvideCursor implementation). + Cursor.Hide(); + + AddInternal(Cursor); + } } /// @@ -90,19 +97,13 @@ namespace osu.Game.Rulesets.UI /// /// The cursor currently being used by this . May be null if no cursor is provided. /// - public CursorContainer Cursor { get; private set; } + 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 CursorContainer CreateCursor() => null; - - /// - /// The target container to add the cursor after it is created. - /// - protected virtual Container CursorTargetContainer => null; + protected virtual GameplayCursorContainer CreateCursor() => null; /// /// Registers a as a nested . @@ -129,7 +130,7 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) - foreach (var mod in beatmap.Mods.Value) + foreach (var mod in mods) if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } diff --git a/osu.Game/Rulesets/UI/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 e303166774..e25c3bd0e7 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -34,11 +34,15 @@ 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)] @@ -101,6 +105,7 @@ namespace osu.Game.Rulesets.UI return false; break; + case MouseUpEvent mouseUp: if (!CurrentState.Mouse.IsPressed(mouseUp.Button)) return false; @@ -115,18 +120,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) { } @@ -159,12 +165,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/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs index a104b0629f..b7a5eedc22 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The current time. /// The amount of visible time. /// The absolute spatial length through . - /// The time at which == . + /// The time at which == . double TimeAt(float position, double currentTime, double timeRange, float scrollLength); /// diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 3b368652f2..42ec0b79b9 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; @@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.UI.Scrolling protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential; /// - /// Whether the player can change . + /// Whether the player can change . /// protected virtual bool UserScrollSpeedAdjustment => true; @@ -80,8 +81,8 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); scrollingInfo.Direction.BindTo(Direction); @@ -92,9 +93,11 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollVisualisationMethod.Sequential: scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Overlapping: scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Constant: scrollingInfo.Algorithm = new ConstantScrollAlgorithm(); break; @@ -159,6 +162,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case GlobalAction.IncreaseScrollSpeed: this.TransformBindableTo(TimeRange, TimeRange.Value - time_span_step, 200, Easing.OutQuint); return true; + case GlobalAction.DecreaseScrollSpeed: this.TransformBindableTo(TimeRange, TimeRange.Value + time_span_step, 200, Easing.OutQuint); return true; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index ed3534fb36..069e2d1a0b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: scrollLength = DrawSize.Y; break; + default: scrollLength = DrawSize.X; break; @@ -97,6 +98,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: case ScrollingDirection.Right: hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); @@ -129,12 +131,15 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Up: hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Down: hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Right: hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs new file mode 100644 index 0000000000..e66f93ec8d --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -0,0 +1,118 @@ +// 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..d2c9ce81c3 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) { @@ -113,6 +89,7 @@ namespace osu.Game.Scoring.Legacy throw new IOException("input .lzma is too short"); long outSize = 0; + for (int i = 0; i < 8; i++) { int v = replayInStream.ReadByte(); @@ -169,6 +146,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 1: { int totalHits = count50 + count100 + count300 + countMiss; @@ -191,6 +169,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 2: { int totalHits = count50 + count100 + count300 + countMiss + countKatu; @@ -210,6 +189,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 3: { int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; @@ -249,7 +229,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 +237,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 d36f963016..8bdc30ac94 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -177,6 +177,8 @@ namespace osu.Game.Scoring protected class DeserializedMod : IMod { public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; } public override string ToString() => $"{User} playing {Beatmap}"; diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 82c33748bb..a93d015f1b 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -25,13 +25,13 @@ namespace osu.Game.Scoring [Description(@"S")] S, - [Description(@"SPlus")] + [Description(@"S+")] SH, [Description(@"SS")] X, - [Description(@"SSPlus")] + [Description(@"SS+")] XH, } } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 5f82329496..9c0c5da0fb 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens //public float ParallaxAmount { set => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * value; } - public new void Push(BackgroundScreen screen) + public void Push(BackgroundScreen screen) { if (screen == null) return; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 6df418753c..b6c2d016d2 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -76,6 +76,7 @@ namespace osu.Game.Screens.Backgrounds private void switchBackground(BeatmapBackground b) { float newDepth = 0; + if (Background != null) { newDepth = Background.Depth + 1; diff --git a/osu.Game/Screens/Edit/Components/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/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 1ad69afe91..70c0cf623e 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 3f7672ae08..ebf8c9c309 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 } }, @@ -236,10 +236,12 @@ namespace osu.Game.Screens.Edit.Compose.Components beatDivisor.Next(); OnUserChange(Current.Value); return true; + case Key.Left: beatDivisor.Previous(); OnUserChange(Current.Value); return true; + default: return false; } @@ -307,18 +309,25 @@ namespace osu.Game.Screens.Edit.Compose.Components { case 2: return colours.BlueLight; + case 4: return colours.Blue; + case 8: return colours.BlueDarker; + case 16: return colours.PurpleDark; + case 3: return colours.YellowLight; + case 6: return colours.Yellow; + case 12: return colours.YellowDarker; + default: return Color4.White; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index bcb2bee601..11e649168f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -121,6 +121,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint requesting selection. /// /// The blueprint. + /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) @@ -166,8 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - bool hasSelection = false; - foreach (var blueprint in selectedBlueprints) { topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/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/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 1e94a20dc7..9b00a3998d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -92,13 +92,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override void Update() - { - base.Update(); - - zoomedContent.Width = DrawWidth * currentZoom; - } - protected override bool OnScroll(ScrollEvent e) { if (e.IsPrecise) @@ -138,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly float scrollOffset; /// - /// Transforms to a new value. + /// Transforms to a new value. /// /// The focus point in absolute coordinates local to the content. /// The size of the content. @@ -169,6 +162,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; d.currentZoom = newZoom; + + d.zoomedContent.Width = d.DrawWidth * d.currentZoom; + // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. + // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. + d.Invalidate(Invalidation.DrawSize); d.ScrollTo(targetOffset, false); } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 9ccf974244..5699ef0a84 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Edit.Compose }; var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) { Logger.Log("Beatmap doesn't have a ruleset assigned."); @@ -108,6 +109,7 @@ namespace osu.Game.Screens.Edit.Compose } composer = ruleset.CreateHitObjectComposer(); + if (composer == null) { Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ba1e74aca..cb01e33282 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -65,11 +65,9 @@ namespace osu.Game.Screens.Edit dependencies.Cache(beatDivisor); EditorMenuBar menuBar; - TimeInfoContainer timeInfo; - SummaryTimeline timeline; - PlaybackControl playback; var fileMenuItems = new List(); + if (RuntimeInfo.IsDesktop) { fileMenuItems.Add(new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap)); @@ -173,6 +171,7 @@ namespace osu.Game.Screens.Edit case Key.Left: seek(e, -1); return true; + case Key.Right: seek(e, 1); return true; @@ -218,6 +217,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); + if (Beatmap.Value.Track != null) { Beatmap.Value.Track.Tempo.Value = 1; @@ -238,9 +238,11 @@ namespace osu.Game.Screens.Edit case EditorScreenMode.Compose: currentScreen = new ComposeScreen(); break; + case EditorScreenMode.Design: currentScreen = new DesignScreen(); break; + default: currentScreen = new EditorScreen(); break; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 8f65366650..24fb561f04 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -83,6 +83,7 @@ namespace osu.Game.Screens.Edit if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); + if (direction < 0 && timingPoint.Time == CurrentTime) { // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 50d524d1f5..1c53fc7088 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -60,14 +60,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents set => label.Colour = value; } - public Color4 BackgroundColour - { - get => content.Colour; - set => content.Colour = value; - } - private readonly OsuTextBox textBox; - private readonly Container content; private readonly OsuSpriteText label; public LabelledTextBox() diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index d858cb076a..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); diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index a02c2a37fa..7d48f619d9 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,8 @@ using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; @@ -48,7 +49,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; @@ -63,6 +64,8 @@ namespace osu.Game.Screens.Menu { box = new Container { + // box needs to be always present to ensure the button is always sized correctly for flow + AlwaysPresent = true, Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters @@ -260,6 +263,7 @@ namespace osu.Game.Screens.Menu box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); this.FadeOut(500); break; + case 1: box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); this.FadeOut(800); @@ -267,11 +271,13 @@ namespace osu.Game.Screens.Menu } break; + case ButtonState.Expanded: const int expand_duration = 500; box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); this.FadeIn(expand_duration / 6f); break; + case ButtonState.Exploded: const int explode_duration = 200; box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); @@ -294,10 +300,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Initial: State = ButtonState.Contracted; break; + case ButtonSystemState.EnteringMode: ContractStyle = 1; State = ButtonState.Contracted; break; + default: if (value == VisibleState) State = ButtonState.Expanded; diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index d6e1aef63c..c7650a08fa 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.X, Size = new Vector2(1, BUTTON_AREA_HEIGHT), Alpha = 0, + AlwaysPresent = true, // Always needs to be present for correct tracking on initial -> toplevel state change Children = new Drawable[] { buttonAreaBackground = new ButtonAreaBackground(), @@ -57,6 +58,7 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.EnteringMode: State = Visibility.Hidden; break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: State = Visibility.Visible; @@ -109,6 +111,7 @@ namespace osu.Game.Screens.Menu case ButtonAreaBackgroundState.Flat: this.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); break; + case ButtonAreaBackgroundState.Normal: this.ScaleTo(Vector2.One, 400, Easing.OutQuint); break; @@ -127,6 +130,7 @@ namespace osu.Game.Screens.Menu default: State = ButtonAreaBackgroundState.Normal; break; + case ButtonSystemState.Initial: case ButtonSystemState.Exit: case ButtonSystemState.EnteringMode: diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 3df4ef9059..a098d42c83 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -9,13 +9,16 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -46,6 +49,10 @@ namespace osu.Game.Screens.Menu private OsuLogo logo; + /// + /// Assign the that this ButtonSystem should manage the position of. + /// + /// The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem. public void SetOsuLogo(OsuLogo logo) { this.logo = logo; @@ -59,9 +66,13 @@ namespace osu.Game.Screens.Menu updateLogoState(); } + else + { + // We should stop tracking as the facade is now out of scope. + logoTrackingContainer.StopTracking(); + } } - private readonly Drawable iconFacade; private readonly ButtonArea buttonArea; private readonly Button backButton; @@ -71,26 +82,29 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; + private readonly LogoTrackingContainer logoTrackingContainer; + public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = buttonArea = new ButtonArea(); - - buttonArea.AddRange(new[] + Child = logoTrackingContainer = new LogoTrackingContainer { - 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) + RelativeSizeAxes = Axes.Both, + Child = buttonArea = new ButtonArea() + }; + + buttonArea.AddRange(new Drawable[] + { + new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, - iconFacade = new Container //need a container to make the osu! icon flow properly. - { - Size = new Vector2(0, ButtonArea.BUTTON_AREA_HEIGHT) - } + logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f)) }); - buttonArea.Flow.CentreTarget = iconFacade; + buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; } [Resolved(CanBeNull = true)] @@ -102,24 +116,36 @@ namespace osu.Game.Screens.Menu [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } + [Resolved(CanBeNull = true)] + private LoginOverlay loginOverlay { get; set; } + [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); + buttonArea.ForEach(b => + { + if (b is Button) + { + b.Origin = Anchor.CentreLeft; + b.Anchor = Anchor.CentreLeft; + } + }); + isIdle.ValueChanged += idle => updateIdleState(idle.NewValue); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); @@ -134,7 +160,12 @@ 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, + Activated = () => + { + loginOverlay?.Show(); + return true; + } }); return; @@ -155,9 +186,11 @@ namespace osu.Game.Screens.Menu { case GlobalAction.Back: return goBack(); + case GlobalAction.Select: logo?.Click(); return true; + default: return false; } @@ -173,9 +206,11 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; sampleBack?.Play(); return true; + case ButtonSystemState.Play: backButton.Click(); return true; + default: return false; } @@ -187,12 +222,15 @@ namespace osu.Game.Screens.Menu { default: return true; + case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; return true; + case ButtonSystemState.TopLevel: buttonsTopLevel.First().Click(); return false; + case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; @@ -247,76 +285,57 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = false; + logoTrackingContainer.StopTracking(); game?.Toolbar.Hide(); logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.Both; - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: switch (lastState) { case ButtonSystemState.TopLevel: // coming from toplevel to play break; + case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logo.MoveTo(logoTrackingPosition, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoTrackingContainer.StartTracking(logo, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = true; - if (impact) logo.Impact(); game?.Toolbar.Show(); }, 200); break; + default: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; - logoTracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; + case ButtonSystemState.EnteringMode: - logoTracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); break; } } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); - - private bool logoTracking; - - protected override void Update() - { - base.Update(); - - if (logo != null) - { - if (logoTracking && logo.RelativePositionAxes == Axes.None && iconFacade.IsLoaded) - logo.Position = logoTrackingPosition; - - iconFacade.Width = logo.SizeForFlow * 0.5f; - } - } } public enum ButtonSystemState diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index e6a90f76c0..0130a5143b 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_warning, + Icon = FontAwesome.Solid.ExclamationTriangle, Size = new Vector2(icon_size), Y = icon_y, }, @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Menu supportFlow.AddText(" to help support the game", format); } - heart = supportFlow.AddIcon(FontAwesome.fa_heart, t => + heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => { t.Padding = new MarginPadding { Left = 5 }; t.Font = t.Font.With(size: 12); diff --git a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs index ec7333ec02..8310ab06eb 100644 --- a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs +++ b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Menu if (CentreTarget == null) return base.OriginPosition; - return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2; + return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2 * CentreTarget.Scale; } } } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index a41a12927b..2925689d20 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 { @@ -66,18 +70,25 @@ namespace osu.Game.Screens.Menu 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(); @@ -118,6 +139,7 @@ namespace osu.Game.Screens.Menu base.Update(); float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + for (int i = 0; i < bars_per_visualiser; i++) { //3% of extra bar length to make it a little faster when bar is almost at it's minimum @@ -129,62 +151,67 @@ namespace osu.Game.Screens.Menu Invalidate(Invalidation.DrawNode, shallPropagate: false); } - protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var visNode = (VisualisationDrawNode)node; - - visNode.Shader = shader; - visNode.Texture = texture; - visNode.Size = DrawSize.X; - visNode.Colour = AccentColour; - visNode.AudioData = frequencyAmplitudes; - } + protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); private class VisualisationDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new LogoVisualisation Source => (LogoVisualisation)base.Source; + + private IShader shader; + private Texture texture; //Asuming the logo is a circle, we don't need a second dimension. - public float Size; + private float size; - public Color4 Colour; - public float[] AudioData; + private Color4 colour; + private float[] audioData; private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + public VisualisationDrawNode(LogoVisualisation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize.X; + colour = Source.AccentColour; + audioData = Source.frequencyAmplitudes; + } + public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); + texture.TextureGL.Bind(); Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(Colour); + colourInfo.ApplyChild(colour); - if (AudioData != null) + if (audioData != null) { for (int j = 0; j < visualiser_rounds; j++) { for (int i = 0; i < bars_per_visualiser; i++) { - if (AudioData[i] < amplitude_dead_zone) + if (audioData[i] < amplitude_dead_zone) continue; float rotation = MathHelper.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotationCos = (float)Math.Cos(rotation); float rotationSin = (float)Math.Sin(rotation); //taking the cos and sin to the 0..1 range - var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * Size; + var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - var barSize = new Vector2(Size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * AudioData[i]); + var barSize = new Vector2(size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); //The distance between the position and the sides of the bar. var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); //The distance between the bottom side of the bar and the top side. @@ -197,7 +224,7 @@ namespace osu.Game.Screens.Menu Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) ); - Texture.DrawQuad( + texture.DrawQuad( rectangle, colourInfo, null, @@ -208,7 +235,7 @@ namespace osu.Game.Screens.Menu } } - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 5403f7c702..21fc53be6e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -79,6 +79,7 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Exit: Background.FadeColour(Color4.White, 500, Easing.OutSine); break; + default: Background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); break; 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/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index af697d37bd..4631f4e222 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -54,7 +54,13 @@ namespace osu.Game.Screens.Menu /// public Func Action; - public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f; + /// + /// The size of the logo Sprite with respect to the scale of its hover and bounce containers. + /// + /// Does not account for the scale of this + public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X; + + public bool IsTracking { get; set; } private readonly Sprite ripple; diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs index 23771451bd..d63f2fecd2 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.Chat; using osuTK; namespace osu.Game.Screens.Multi.Components @@ -60,7 +59,7 @@ namespace osu.Game.Screens.Multi.Components if (beatmap != null) { beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); - beatmapAuthor.AddLink(beatmap.Metadata.Author.Username, null, LinkAction.OpenUserProfile, beatmap.Metadata.Author.Id.ToString(), "View Profile"); + beatmapAuthor.AddUserLink(beatmap.Metadata.Author); } }, true); } diff --git a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs index b6b0332cf3..27b5aec4d3 100644 --- a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs +++ b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs @@ -13,15 +13,13 @@ namespace osu.Game.Screens.Multi.Components protected override void AddTabItem(TabItem tab, bool addToDropdown = true) { - if (tab is DisableableTabItem disableable) + if (tab is DisableableTabItem disableable) disableable.Enabled.BindTo(Enabled); base.AddTabItem(tab, addToDropdown); } - protected abstract class DisableableTabItem : TabItem + protected abstract class DisableableTabItem : TabItem { - public readonly BindableBool Enabled = new BindableBool(); - protected DisableableTabItem(T value) : base(value) { diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 0e958bf523..1cbf2a45e7 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 = -ScreenTitle.ICON_WIDTH, }, 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 = "multi"; + 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 4bab68058f..6ec8f2bfe5 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -77,6 +78,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } + public bool FilteringActive { get; set; } + public DrawableRoom(Room room) { Room = room; diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs index 40e59de25d..6570051040 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs @@ -8,7 +8,6 @@ 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.Users; using osuTK; @@ -26,8 +25,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components private void load(OsuColour colours) { OsuSpriteText summary; - OsuSpriteText levelRangeHigher; - OsuSpriteText levelRangeLower; Container flagContainer; LinkFlowContainer hostText; @@ -46,21 +43,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components Width = 22f, RelativeSizeAxes = Axes.Y, }, - /*new Container //todo: team banners - { - Width = 38f, - RelativeSizeAxes = Axes.Y, - CornerRadius = 2f, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"ad387e"), - }, - }, - },*/ hostText = new LinkFlowContainer { Anchor = Anchor.CentreLeft, @@ -95,20 +77,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components if (host.NewValue != null) { hostText.AddText("hosted by "); - hostText.AddLink(host.NewValue.Username, null, LinkAction.OpenUserProfile, host.NewValue.Id.ToString(), "Open profile", - s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + flagContainer.Child = new DrawableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both }; } }, true); ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true); - - /*Participants.BindValueChanged(e => - { - var ranks = v.Select(u => u.Statistics.Ranks.Global); - levelRangeLower.Text = ranks.Min().ToString(); - levelRangeHigher.Text = ranks.Max().ToString(); - });*/ } } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 5798fce457..d597e5bb0f 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: 30), - Current = Name + Current = RoomName }, }, }, @@ -258,6 +258,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components // nice little progressive fade int time = 500; + foreach (var c in fill.Children) { c.Delay(500 - time).FadeOut(time, Easing.Out); diff --git a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs index ccb957734f..b69cb9705d 100644 --- a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs +++ b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Multi.Match.Components AddItem(new GameTypeTimeshift()); } - private class GameTypePickerItem : DisableableTabItem + private class GameTypePickerItem : DisableableTabItem { private const float transition_duration = 200; diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index e1592532a3..2a6074882d 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -110,7 +109,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty(), true); + CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(), true); beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); } diff --git a/osu.Game/Screens/Multi/Match/Components/HostInfo.cs b/osu.Game/Screens/Multi/Match/Components/HostInfo.cs index 02c8929f44..b898cd0466 100644 --- a/osu.Game/Screens/Multi/Match/Components/HostInfo.cs +++ b/osu.Game/Screens/Multi/Match/Components/HostInfo.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; @@ -54,8 +53,7 @@ namespace osu.Game.Screens.Multi.Match.Components { linkContainer.AddText("hosted by"); linkContainer.NewLine(); - linkContainer.AddLink(host.Username, null, LinkAction.OpenUserProfile, host.Id.ToString(), "View Profile", - s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + linkContainer.AddUserLink(host, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); } } } diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs index a944d965bd..a185c4db50 100644 --- a/osu.Game/Screens/Multi/Match/Components/Info.cs +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -30,7 +30,6 @@ namespace osu.Game.Screens.Multi.Match.Components ReadyButton readyButton; ViewBeatmapButton viewBeatmapButton; HostInfo hostInfo; - RoomStatusInfo statusInfo; InternalChildren = new Drawable[] { @@ -63,7 +62,7 @@ namespace osu.Game.Screens.Multi.Match.Components new OsuSpriteText { Font = OsuFont.GetFont(size: 30), - Current = Name + Current = RoomName }, new RoomStatusInfo(), } diff --git a/osu.Game/Screens/Multi/Match/Components/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..359b5824c0 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Multi.Match.Components }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); - Name.BindValueChanged(name => NameField.Text = name.NewValue, true); + RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Multi.Match.Components { hideError(); - Name.Value = NameField.Text; + RoomName.Value = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; Type.Value = TypePicker.Current.Value; @@ -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/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs index 8751e27552..9de4a61cde 100644 --- a/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs +++ b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Multi.Match.Components AddItem(RoomAvailability.InviteOnly); } - private class RoomAvailabilityPickerItem : DisableableTabItem + private class RoomAvailabilityPickerItem : DisableableTabItem { private const float transition_duration = 200; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a71106872e..a80056d164 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -42,9 +41,6 @@ namespace osu.Game.Screens.Multi.Match [Resolved(typeof(Room))] protected Bindable CurrentItem { get; private set; } - [Resolved] - protected Bindable> SelectedMods { get; private set; } - [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -149,6 +145,7 @@ namespace osu.Game.Screens.Multi.Match header.Tabs.Current.BindValueChanged(tab => { const float fade_duration = 500; + if (tab.NewValue is SettingsMatchPage) { settings.Show(); @@ -182,6 +179,9 @@ namespace osu.Game.Screens.Multi.Match public override bool OnExiting(IScreen next) { RoomManager?.PartRoom(); + + Mods.Value = Array.Empty(); + return base.OnExiting(next); } @@ -194,7 +194,7 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); - SelectedMods.Value = e.NewValue?.RequiredMods ?? Enumerable.Empty(); + Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(); if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; } @@ -207,7 +207,7 @@ namespace osu.Game.Screens.Multi.Match if (Beatmap.Value != beatmapManager.DefaultBeatmap) return; - if (Beatmap.Value == null) + if (CurrentItem.Value == null) return; // Try to retrieve the corresponding local beatmap @@ -222,8 +222,6 @@ namespace osu.Game.Screens.Multi.Match private void onStart() { - Beatmap.Value.Mods.Value = SelectedMods.Value.ToArray(); - switch (type.Value) { default: diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index f38fc4e3ff..155665e0d5 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -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 @@ -248,6 +248,7 @@ namespace osu.Game.Screens.Multi if (screenStack.CurrentScreen is MatchSubScreen) { var track = Beatmap.Value.Track; + if (track != null) { track.Looping = true; @@ -276,7 +277,7 @@ namespace osu.Game.Screens.Multi updatePollingRate(isIdle.Value); - if (screenStack.CurrentScreen == null) + if (screenStack.CurrentScreen == null && this.IsCurrentScreen()) this.Exit(); } diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs index da6bba7865..8c09d576ff 100644 --- a/osu.Game/Screens/Multi/MultiplayerComposite.cs +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -16,8 +16,8 @@ namespace osu.Game.Screens.Multi [Resolved(typeof(Room))] protected Bindable RoomID { get; private set; } - [Resolved(typeof(Room))] - protected Bindable Name { get; private set; } + [Resolved(typeof(Room), nameof(Room.Name))] + protected Bindable RoomName { get; private set; } [Resolved(typeof(Room))] protected Bindable Host { get; private set; } diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index ad72072981..ff94f63f01 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -3,24 +3,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Graphics.Containers; -using osu.Game.Input.Bindings; namespace osu.Game.Screens.Multi { - public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen, IKeyBindingHandler + public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen { public override bool DisallowExternalBeatmapRulesetChanges => false; - public override bool RemoveWhenNotAlive => false; - public virtual string ShortTitle => Title; - [Resolved(CanBeNull = true)] - protected OsuGame Game { get; private set; } - [Resolved(CanBeNull = true)] protected IRoomManager RoomManager { get; private set; } @@ -58,21 +51,6 @@ namespace osu.Game.Screens.Multi this.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); } - public override bool OnPressed(GlobalAction action) - { - if (!this.IsCurrentScreen()) return false; - - if (action == GlobalAction.Back) - { - this.Exit(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; - public override string ToString() => Title; } } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index d5b8f1f0c8..88c6fc5e2e 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -14,7 +13,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking; using osu.Game.Screens.Play; @@ -37,9 +35,6 @@ namespace osu.Game.Screens.Multi.Play [Resolved] private IBindable ruleset { get; set; } - [Resolved] - private Bindable> selectedMods { get; set; } - public TimeshiftPlayer(PlaylistItem playlistItem) { this.playlistItem = playlistItem; @@ -61,7 +56,7 @@ namespace osu.Game.Screens.Multi.Play if (ruleset.Value.ID != playlistItem.Ruleset.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!playlistItem.RequiredMods.All(m => selectedMods.Value.Contains(m))) + if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); diff --git a/osu.Game/Screens/Multi/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 385cbe20e5..6f473aaafa 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Multi /// /// Adds a to the list of available rooms. /// - /// The to add.< + /// The to add. private void addRoom(Room room) { var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 5034385969..9d53e43b80 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -14,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { @@ -57,20 +59,25 @@ namespace osu.Game.Screens private SampleChannel sampleExit; + protected virtual bool PlayResumeSound => true; + public virtual float BackgroundParallaxAmount => 1; - public Bindable Beatmap { get; set; } + public Bindable Beatmap { get; private set; } - public Bindable Ruleset { get; set; } + public Bindable Ruleset { get; private set; } + + public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var deps = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, base.CreateChildDependencies(parent)); + var screenDependencies = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, parent); - Beatmap = deps.Beatmap; - Ruleset = deps.Ruleset; + Beatmap = screenDependencies.Beatmap; + Ruleset = screenDependencies.Ruleset; + Mods = screenDependencies.Mods; - return deps; + return base.CreateChildDependencies(screenDependencies); } protected BackgroundScreen Background => backgroundStack?.CurrentScreen as BackgroundScreen; @@ -112,7 +119,8 @@ namespace osu.Game.Screens public override void OnResuming(IScreen last) { - sampleExit?.Play(); + if (PlayResumeSound) + sampleExit?.Play(); applyArrivingDefaults(true); base.OnResuming(last); diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 84e5de76de..115f4b7e1a 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { @@ -14,27 +16,32 @@ namespace osu.Game.Screens public Bindable Ruleset { get; } + public Bindable> Mods { get; } + public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent) : base(parent) { if (requireLease) { Beatmap = parent.Get>()?.GetBoundCopy(); + if (Beatmap == null) - { Cache(Beatmap = parent.Get>().BeginLease(false)); - } Ruleset = parent.Get>()?.GetBoundCopy(); + if (Ruleset == null) - { Cache(Ruleset = parent.Get>().BeginLease(true)); - } + + Mods = parent.Get>>()?.GetBoundCopy(); + if (Mods == null) + Cache(Mods = parent.Get>>().BeginLease(true)); } else { Beatmap = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); Ruleset = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); + Mods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); } } } diff --git a/osu.Game/Screens/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/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 4b07405812..70e7b8f297 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -60,7 +62,13 @@ namespace osu.Game.Screens.Play.Break valueText.Text = newText; } - protected virtual string Format(T count) => count.ToString(); + protected virtual string Format(T count) + { + if (count is Enum countEnum) + return countEnum.GetDescription(); + + return count.ToString(); + } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/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/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 0400bfbc27..b1948d02d5 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -1,13 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using 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 . + /// 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. @@ -17,6 +18,8 @@ namespace osu.Game.Screens.Play { private readonly IFrameBasedClock underlyingClock; + public readonly BindableBool IsPaused = new BindableBool(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3c2cec1d94..c151e598f7 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework; @@ -18,11 +19,12 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play { /// - /// Encapsulates gameplay timing logic and provides a for children. + /// Encapsulates gameplay timing logic and provides a for children. /// public class GameplayClockContainer : Container { private readonly WorkingBeatmap beatmap; + private readonly IReadOnlyList mods; /// /// The original source (usually a 's track). @@ -36,6 +38,8 @@ namespace osu.Game.Screens.Play /// private readonly DecoupleableInterpolatingFramedClock adjustableClock; + private readonly double gameplayStartTime; + public readonly Bindable UserPlaybackRate = new BindableDouble(1) { Default = 1, @@ -48,15 +52,19 @@ namespace osu.Game.Screens.Play /// The final clock which is exposed to underlying components. /// [Cached] - private readonly GameplayClock gameplayClock; + public readonly GameplayClock GameplayClock; private Bindable userAudioOffset; - private readonly FramedOffsetClock offsetClock; + private readonly FramedOffsetClock userOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, bool allowLeadIn, double gameplayStartTime) + private readonly FramedOffsetClock platformOffsetClock; + + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; + this.mods = mods; + this.gameplayStartTime = gameplayStartTime; RelativeSizeAxes = Axes.Both; @@ -64,30 +72,30 @@ namespace osu.Game.Screens.Play adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - adjustableClock.Seek(allowLeadIn - ? Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn) - : 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 }; + 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); + userOffsetClock = new FramedOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - gameplayClock = new GameplayClock(offsetClock); + GameplayClock = new GameplayClock(userOffsetClock); + + GameplayClock.IsPaused.BindTo(IsPaused); } + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); - userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true); + userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); UserPlaybackRate.ValueChanged += _ => updateRate(); + + Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); } public void Restart() @@ -101,13 +109,8 @@ namespace osu.Game.Screens.Play adjustableClock.ChangeSource(sourceClock); updateRate(); - this.Delay(750).Schedule(() => - { - if (!IsPaused.Value) - { - adjustableClock.Start(); - } - }); + if (!IsPaused.Value) + Start(); }); }); } @@ -116,13 +119,33 @@ namespace osu.Game.Screens.Play { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the audio clock source potentially taking time to enter a completely stopped state - adjustableClock.Seek(adjustableClock.CurrentTime); + Seek(GameplayClock.CurrentTime); adjustableClock.Start(); + IsPaused.Value = false; } - public void Seek(double time) => adjustableClock.Seek(time); + /// + /// Seek to a specific time in gameplay. + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// + /// The destination time to seek to. + public void Seek(double time) + { + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + adjustableClock.Seek(time - totalOffset); - public void Stop() => adjustableClock.Stop(); + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); + } + + public void Stop() + { + adjustableClock.Stop(); + IsPaused.Value = true; + } public void ResetLocalAdjustments() { @@ -133,7 +156,7 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) - offsetClock.ProcessFrame(); + userOffsetClock.ProcessFrame(); base.Update(); } @@ -149,7 +172,7 @@ namespace osu.Game.Screens.Play else sourceClock.Rate = UserPlaybackRate.Value; - foreach (var mod in beatmap.Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToClock(sourceClock); } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 5d210446c3..456fb4faf9 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using Humanizer; +using osu.Framework.Graphics.Effects; namespace osu.Game.Screens.Play { @@ -38,9 +39,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; @@ -215,6 +222,7 @@ namespace osu.Game.Screens.Play else selectionIndex--; return true; + case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) selectionIndex = 0; @@ -229,16 +237,31 @@ 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 +311,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 50bc34726a..446df94aca 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -4,9 +4,11 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -69,6 +71,11 @@ namespace osu.Game.Screens.Play.HUD return base.OnMouseMove(e); } + public bool PauseOnFocusLost + { + set => button.PauseOnFocusLost = value; + } + protected override void Update() { base.Update(); @@ -92,8 +99,10 @@ namespace osu.Game.Screens.Play.HUD public Action HoverGained; public Action HoverLost; + private readonly IBindable gameActive = new Bindable(true); + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, Framework.Game game) { Size = new Vector2(60); @@ -128,12 +137,20 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.fa_close + Icon = FontAwesome.Solid.Times }, } }; bind(); + + gameActive.BindTo(game.IsActive); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + gameActive.BindValueChanged(_ => updateActive(), true); } private void bind() @@ -183,6 +200,31 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } + private bool pauseOnFocusLost = true; + + public bool PauseOnFocusLost + { + set + { + if (pauseOnFocusLost == value) + return; + + pauseOnFocusLost = value; + if (IsLoaded) + updateActive(); + } + } + + private void updateActive() + { + if (!pauseOnFocusLost) return; + + if (gameActive.Value) + AbortConfirm(); + else + BeginConfirm(); + } + public bool OnPressed(GlobalAction action) { switch (action) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 2c1293833f..0f3c92a962 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -18,15 +18,17 @@ using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { - public class ModDisplay : Container, IHasCurrentValue> + public class ModDisplay : Container, IHasCurrentValue> { private const int fade_duration = 1000; public bool DisplayUnrankedText = true; - private readonly Bindable> current = new Bindable>(); + public bool AllowExpand = true; - public Bindable> Current + private readonly Bindable> current = new Bindable>(); + + public Bindable> Current { get => current; set @@ -68,6 +70,7 @@ namespace osu.Game.Screens.Play.HUD Current.ValueChanged += mods => { iconsContainer.Clear(); + foreach (Mod mod in mods.NewValue) { iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); @@ -87,7 +90,9 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { base.LoadComplete(); + appearTransform(); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -97,14 +102,17 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - iconsContainer.FinishTransforms(); - iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); expand(); + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } - private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + private void expand() + { + if (AllowExpand) + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + } private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 8f09c2b2bf..315bc27a79 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osuTK; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 285e6eab23..3c1b33297a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -2,16 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; @@ -24,7 +25,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; @@ -42,7 +43,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { RelativeSizeAxes = Axes.Both; @@ -96,7 +97,7 @@ namespace osu.Game.Screens.Play Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); - ModDisplay.Current.BindTo(working.Mods); + ModDisplay.Current.Value = mods; } [BackgroundDependencyLoader(true)] @@ -201,7 +202,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection + protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { FadeTime = 50, Anchor = Anchor.BottomRight, diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 0626c40334..88a62ac8d4 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Play { isLit = value; updateGlowSprite(value); + if (value && IsCounting) { CountPresses++; diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs similarity index 95% rename from osu.Game/Screens/Play/KeyCounterCollection.cs rename to osu.Game/Screens/Play/KeyCounterDisplay.cs index 1b43737731..d5967f5899 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -14,14 +14,14 @@ 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; @@ -138,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/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 13dbe40a8b..95fa58e5c0 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -25,8 +25,10 @@ namespace osu.Game.Screens.Play { default: return button.ToString(); + case MouseButton.Left: return @"M1"; + case MouseButton.Right: return @"M2"; } diff --git a/osu.Game/Screens/Play/PausableGameplayContainer.cs b/osu.Game/Screens/Play/PausableGameplayContainer.cs deleted file mode 100644 index 99f0083b55..0000000000 --- a/osu.Game/Screens/Play/PausableGameplayContainer.cs +++ /dev/null @@ -1,137 +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.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - /// - /// A container which handles pausing children, displaying an overlay blocking its children during paused state. - /// - public class PausableGameplayContainer : 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; - - public Action Stop; - public Action Start; - - /// - /// Creates a new . - /// - public PausableGameplayContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChildren = new[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both - }, - 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) - Stop?.Invoke(); - IsPaused.Value = true; - - pauseOverlay.Show(); - - lastPauseActionTime = Time.Current; - }); - - public void Resume() - { - if (!IsPaused.Value) return; - - IsResuming = false; - lastPauseActionTime = Time.Current; - - IsPaused.Value = false; - - Start?.Invoke(); - - 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(); - - 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 194d09f4b3..fd9ddec314 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -43,10 +44,6 @@ 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; - private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -56,8 +53,6 @@ namespace osu.Game.Screens.Play [Resolved] private ScoreManager scoreManager { get; set; } - protected PausableGameplayContainer PausableGameplayContainer { get; private set; } - private RulesetInfo ruleset; private IAPIProvider api; @@ -68,29 +63,36 @@ namespace osu.Game.Screens.Play protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } - private FailOverlay failOverlay; - - private DrawableStoryboard storyboard; - protected UserDimContainer StoryboardContainer { get; private set; } - - private Bindable showStoryboard; - - protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - EnableUserDim = { Value = true } - }; public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - private GameplayClockContainer gameplayClockContainer; + protected GameplayClockContainer GameplayClockContainer { get; private set; } + + [Cached] + [Cached(Type = typeof(IBindable>))] + protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); + + private readonly bool allowPause; + private readonly bool showResults; + + /// + /// 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) + { + this.allowPause = allowPause; + this.showResults = showResults; + } [BackgroundDependencyLoader] private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) { this.api = api; + Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); + WorkingBeatmap working = loadBeatmap(); if (working == null) @@ -102,58 +104,54 @@ namespace osu.Game.Screens.Play showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); + ScoreProcessor.Mods.BindTo(Mods); + if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); - gameplayClockContainer.Children = new Drawable[] + GameplayClockContainer.Children = new[] { - PausableGameplayContainer = new PausableGameplayContainer + StoryboardContainer = CreateStoryboardContainer(), + new ScalingContainer(ScalingMode.Gameplay) { - Retries = RestartCount, - OnRetry = Restart, - OnQuit = performUserRequestedExit, - Start = gameplayClockContainer.Start, - Stop = gameplayClockContainer.Stop, - IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !DrawableRuleset.HasReplayLoaded.Value, - Children = new[] + Child = new LocalSkinOverrideContainer(working.Skin) { - StoryboardContainer = CreateStoryboardContainer(), - new ScalingContainer(ScalingMode.Gameplay) - { - Child = new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Child = DrawableRuleset - } - }, - 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 - }, + 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, Mods.Value) + { + 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, }, @@ -170,16 +168,16 @@ namespace osu.Game.Screens.Play }; // bind clock into components that require it - DrawableRuleset.IsPaused.BindTo(gameplayClockContainer.IsPaused); + DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.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; ScoreProcessor.Failed += onFail; - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); } @@ -201,7 +199,7 @@ namespace osu.Game.Screens.Play try { - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value); } catch (BeatmapInvalidForRulesetException) { @@ -209,7 +207,7 @@ namespace osu.Game.Screens.Play // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); } if (!DrawableRuleset.Objects.Any()) @@ -255,7 +253,7 @@ namespace osu.Game.Screens.Play ValidForResume = false; - if (!AllowResults) return; + if (!showResults) return; using (BeginDelayedSequence(1000)) { @@ -280,7 +278,7 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, - Mods = Beatmap.Value.Mods.Value.ToArray(), + Mods = Mods.Value.ToArray(), User = api.LocalUser.Value, }; @@ -289,19 +287,139 @@ 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)) + if (Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - gameplayClockContainer.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; + + 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); @@ -316,10 +434,7 @@ 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; @@ -329,10 +444,8 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; - gameplayClockContainer.Restart(); - - PausableGameplayContainer.Alpha = 0; - PausableGameplayContainer.FadeIn(750, Easing.OutQuint); + GameplayClockContainer.Restart(); + GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); } public override void OnSuspending(IScreen next) @@ -350,18 +463,20 @@ namespace osu.Game.Screens.Play return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || DrawableRuleset?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) + if (canPause) { - gameplayClockContainer.ResetLocalAdjustments(); - - fadeOut(); - return base.OnExiting(next); + Pause(); + return true; } - if (LoadedBeatmapSuccessfully) - PausableGameplayContainer?.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) @@ -373,24 +488,6 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = false; } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.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 e9ee5d3fa8..908a95c18b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -14,8 +15,10 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; @@ -32,7 +35,7 @@ namespace osu.Game.Screens.Play private Player player; - private Container content; + private LogoTrackingContainer content; private BeatmapMetadataDisplay info; @@ -41,6 +44,8 @@ namespace osu.Game.Screens.Play public override bool DisallowExternalBeatmapRulesetChanges => true; + protected override bool PlayResumeSound => false; + private Task loadTask; private InputManager inputManager; @@ -59,35 +64,34 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = new Container + InternalChild = (content = new LogoTrackingContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + }).WithChildren(new Drawable[] + { + info = new BeatmapMetadataDisplay(Beatmap.Value, Mods.Value, content.LogoFacade) { - info = new BeatmapMetadataDisplay(Beatmap.Value) + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] - { - VisualSettings = new VisualSettings(), - new InputSettings() - } + VisualSettings = new VisualSettings(), + new InputSettings() } } - }; + }); loadNewPlayer(); } @@ -127,6 +131,9 @@ namespace osu.Game.Screens.Play private void contentOut() { + // Ensure the logo is no longer tracking before we scale the content + content.StopTracking(); + content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } @@ -148,11 +155,27 @@ namespace osu.Game.Screens.Play { base.LogoArriving(logo, resuming); - logo.ScaleTo(new Vector2(0.15f), 300, Easing.In); - logo.MoveTo(new Vector2(0.5f), 300, Easing.In); + const double duration = 300; + + if (!resuming) + { + logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + } + + logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); - logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); + Scheduler.AddDelayed(() => + { + if (this.IsCurrentScreen()) + content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); + }, resuming ? 0 : 500); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + content.StopTracking(); } protected override void LoadComplete() @@ -164,7 +187,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; - // Hhere because IsHovered will not update unless we do so. + // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; @@ -299,9 +322,10 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; + private readonly IReadOnlyList mods; + private readonly Drawable facade; private LoadingAnimation loading; private Sprite backgroundSprite; - private ModDisplay modDisplay; public bool Loading { @@ -320,9 +344,11 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IReadOnlyList mods, Drawable facade) { this.beatmap = beatmap; + this.mods = mods; + this.facade = facade; } [BackgroundDependencyLoader] @@ -339,14 +365,20 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Direction = FillDirection.Vertical, - Children = new Drawable[] + Children = new[] { + facade.With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + }), new OsuSpriteText { Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), Font = OsuFont.GetFont(size: 36, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, }, new OsuSpriteText { @@ -403,7 +435,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 20 }, - Current = beatmap.Mods + Current = { Value = mods } } }, } diff --git a/osu.Game/Screens/Play/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 949b08d98d..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; } 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/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index a6e6009b95..e3c56e1c2c 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -15,6 +15,7 @@ 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; @@ -37,6 +38,10 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override bool BlockPositionalInput => false; + /// + /// Displays a skip overlay, giving the user the ability to skip forward. + /// + /// The time at which gameplay begins to appear. public SkipOverlay(double startTime) { this.startTime = startTime; @@ -86,16 +91,21 @@ namespace osu.Game.Screens.Play }; } - private const double skip_required_cutoff = 3000; + /// + /// Duration before gameplay start time required before skip button displays. + /// + private const double skip_buffer = 1000; + private const double fade_time = 300; - private double beginFadeTime => startTime - skip_required_cutoff - fade_time; + private double beginFadeTime => startTime - fade_time; protected override void LoadComplete() { base.LoadComplete(); - if (startTime < skip_required_cutoff) + // skip is not required if there is no extra "empty" time to skip. + if (Clock.CurrentTime > beginFadeTime - skip_buffer) { Alpha = 0; Expire(); @@ -106,7 +116,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time); + button.Action = () => RequestSeek?.Invoke(beginFadeTime); displayTime = Time.Current; @@ -173,6 +183,7 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(1000)) scheduledHide = Schedule(() => State = Visibility.Hidden); break; + case Visibility.Hidden: this.FadeOut(1000, Easing.OutExpo); break; @@ -258,9 +269,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/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index d10034d552..5b7a9574b6 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -169,6 +169,7 @@ namespace osu.Game.Screens.Play var max = values.Max(); float step = values.Length / (float)ColumnCount; + for (float i = 0; i < values.Length; i += step) { newValues.Add((float)values[(int)i] / max); diff --git a/osu.Game/Screens/Ranking/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/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 043bf55d2b..fab227c7f4 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -180,6 +180,7 @@ namespace osu.Game.Screens.Ranking.Pages scoreCounter.Increment(Score.TotalScore); int delay = 0; + foreach (var s in statisticsContainer.Children) { s.FadeOut() @@ -336,6 +337,7 @@ namespace osu.Game.Screens.Ranking.Pages versionMapper.Colour = colours.Gray8; var creator = beatmap.Metadata.Author?.Username; + if (!string.IsNullOrEmpty(creator)) { versionMapper.Text = $"mapped by {creator}"; diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index b1fd8f9fde..1383511241 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -6,17 +6,19 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osuTK; 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 dafb4c0aad..bebeaee00a 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index 1b17dda563..8776c599dd 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; diff --git a/osu.Game/Screens/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 bfd1d3d236..63ad3b6ab2 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; }); })); } @@ -257,6 +257,7 @@ namespace osu.Game.Screens.Select select(beatmap); return; + case CarouselBeatmapSet set: if (skipDifficulties) select(set); @@ -292,6 +293,7 @@ namespace osu.Game.Screens.Select if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) { var notYetVisitedSets = visibleSets.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { previouslyVisitedRandomSets.RemoveAll(s => visibleSets.Contains(s)); @@ -327,6 +329,9 @@ namespace osu.Game.Screens.Select private void select(CarouselItem item) { + if (!AllowSelection) + return; + if (item == null) return; item.State.Value = CarouselItemState.Selected; @@ -391,13 +396,16 @@ namespace osu.Game.Screens.Select case Key.Up: direction = -1; break; + case Key.Down: direction = 1; break; + case Key.Left: direction = -1; skipDifficulties = true; break; + case Key.Right: direction = 1; skipDifficulties = true; @@ -462,8 +470,10 @@ namespace osu.Game.Screens.Select case LoadState.NotLoaded: LoadComponentAsync(item); break; + case LoadState.Loading: break; + default: scrollableContent.Add(item); break; @@ -554,6 +564,7 @@ namespace osu.Game.Screens.Select set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); set.MoveToY(currentY, 750, Easing.OutExpo); break; + case DrawableCarouselBeatmap beatmap: if (beatmap.Item.State.Value == CarouselItemState.Selected) scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; @@ -593,7 +604,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/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 32e7215e69..1508de2730 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -22,8 +22,11 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Select @@ -292,14 +295,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), })); @@ -308,12 +311,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); } labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); @@ -377,7 +380,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/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 8e7ea8f964..5c334b126c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -38,10 +38,13 @@ namespace osu.Game.Screens.Select.Carousel default: case SortMode.Artist: return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Title: return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Author: return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Difficulty: return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 5d8f4f0ec6..6ebd2d41cc 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -66,6 +66,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; + case CarouselItemState.Selected: InternalChildren.ForEach(c => { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 67e8282b76..045c682dc3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -69,6 +69,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.Selected: updateSelected(item); break; + case CarouselItemState.NotSelected: case CarouselItemState.Collapsed: attemptSelection(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index a0f5969b3c..79c1a4cb6b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private int creationOrder; - protected CarouselItem() { DrawableRepresentation = new Lazy(CreateDrawableRepresentation); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 38ca9a9aed..0a20f2aa6d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -2,6 +2,7 @@ // 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.Colour; @@ -168,15 +169,22 @@ namespace osu.Game.Screens.Select.Carousel base.ApplyState(); } - public MenuItem[] ContextMenuItems => new MenuItem[] + public MenuItem[] ContextMenuItems { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), - new OsuMenuItem("Details", MenuItemType.Standard, () => + get { - if (beatmap.OnlineBeatmapID.HasValue) beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); - }), - }; + List items = new List + { + new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), + new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), + }; + + if (beatmap.OnlineBeatmapID.HasValue) + items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + + return items.ToArray(); + } + } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 3c1b7cc831..f1d6343e72 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.MathUtils; @@ -106,6 +107,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: Deselected(); break; + case CarouselItemState.Selected: Selected(); break; diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 03b9826e9b..6ba29751b0 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,9 +19,6 @@ namespace osu.Game.Screens.Select { private readonly Box modeLight; - private const float play_song_select_button_width = 100; - private const float play_song_select_button_height = 50; - public const float HEIGHT = 50; public const int TRANSITION_LENGTH = 300; @@ -33,57 +29,36 @@ namespace osu.Game.Screens.Select private readonly FillFlowContainer buttons; - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. - /// Action the button does. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) - { - var button = new FooterButton - { - Text = text, - Height = play_song_select_button_height, - Width = play_song_select_button_width, - Depth = depth, - SelectedColour = colour, - DeselectedColour = colour.Opacity(0.5f), - Hotkey = hotkey, - Hovered = updateModeLight, - HoverLost = updateModeLight, - Action = action, - }; - - buttons.Add(button); - buttons.SetLayoutPosition(button, -depth); - } - private readonly List overlays = new List(); - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. + /// THe button to be added. /// The to be toggled by this button. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, OverlayContainer overlay) { overlays.Add(overlay); - AddButton(text, colour, () => + button.Action = () => showOverlay(overlay); + + AddButton(button); + } + + /// Button to be added. + public void AddButton(FooterButton button) + { + button.Hovered = updateModeLight; + button.HoverLost = updateModeLight; + + buttons.Add(button); + } + + private void showOverlay(OverlayContainer overlay) + { + foreach (var o in overlays) { - foreach (var o in overlays) - { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); - } - }, hotkey, depth); + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } } private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9b98e344ce..e18a086a10 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -6,6 +6,7 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -20,11 +21,11 @@ namespace osu.Game.Screens.Select public string Text { - get => spriteText?.Text; + get => SpriteText?.Text; set { - if (spriteText != null) - spriteText.Text = value; + if (SpriteText != null) + SpriteText.Text = value; } } @@ -53,7 +54,8 @@ namespace osu.Game.Screens.Select } } - private readonly SpriteText spriteText; + protected readonly Container TextContainer; + protected readonly SpriteText SpriteText; private readonly Box box; private readonly Box light; @@ -61,8 +63,18 @@ namespace osu.Game.Screens.Select public FooterButton() { + AutoSizeAxes = Axes.Both; Children = new Drawable[] { + TextContainer = new Container + { + Size = new Vector2(100, 50), + Child = SpriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, box = new Box { RelativeSizeAxes = Axes.Both, @@ -78,11 +90,6 @@ namespace osu.Game.Screens.Select EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs new file mode 100644 index 0000000000..c96c5022c0 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; +using osu.Game.Rulesets.Mods; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonMods : FooterButton + { + public FooterButtonMods(Bindable> mods) + { + FooterModDisplay modDisplay; + + Add(new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = modDisplay = new FooterModDisplay + { + DisplayUnrankedText = false, + Scale = new Vector2(0.8f) + }, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = 70 } + }); + + if (mods != null) + modDisplay.Current = mods; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"mods"; + Hotkey = Key.F1; + } + + private class FooterModDisplay : ModDisplay + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + public FooterModDisplay() + { + AllowExpand = false; + } + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs new file mode 100644 index 0000000000..c000d8a8c8 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonOptions : FooterButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Blue; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"options"; + Hotkey = Key.F3; + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs new file mode 100644 index 0000000000..14c9eb2035 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonRandom : FooterButton + { + private readonly SpriteText secondaryText; + private bool secondaryActive; + + public FooterButtonRandom() + { + TextContainer.Add(secondaryText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = @"rewind", + Alpha = 0 + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Green; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"random"; + Hotkey = Key.F2; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyDown(e); + } + + protected override bool OnKeyUp(KeyUpEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyUp(e); + } + + private void updateText() + { + if (secondaryActive) + { + SpriteText.FadeOut(120, Easing.InQuad); + secondaryText.FadeIn(120, Easing.InQuad); + } + else + { + SpriteText.FadeIn(120, Easing.InQuad); + secondaryText.FadeOut(120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Screens/Select/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 ebb1d78ba0..aafa6bb0eb 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Leaderboards { if (Scope == BeatmapLeaderboardScope.Local) { - Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).ToArray(); + Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).OrderByDescending(s => s.TotalScore).ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index fa5dc4c1d1..c5fa9e2396 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -26,9 +24,6 @@ namespace osu.Game.Screens.Select [Resolved(typeof(Room))] protected Bindable CurrentItem { get; private set; } - [Resolved] - private Bindable> selectedMods { get; set; } - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -46,7 +41,7 @@ namespace osu.Game.Screens.Select RulesetID = Ruleset.Value.ID ?? 0 }; - item.RequiredMods.AddRange(SelectedMods.Value); + item.RequiredMods.AddRange(Mods.Value); Selected?.Invoke(item); @@ -65,11 +60,12 @@ namespace osu.Game.Screens.Select { Ruleset.Value = CurrentItem.Value.Ruleset; Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); - Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); + Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); } Beatmap.Disabled = true; Ruleset.Disabled = true; + Mods.Disabled = true; return false; } @@ -80,6 +76,7 @@ namespace osu.Game.Screens.Select Beatmap.Disabled = false; Ruleset.Disabled = false; + Mods.Disabled = false; } } } diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 758e1c24c3..a8b5bbbd00 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -4,7 +4,9 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,7 +34,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 +142,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..77a8054981 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(); @@ -51,10 +52,11 @@ namespace osu.Game.Screens.Select var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto.GetType(); - var mods = SelectedMods.Value; + var mods = Mods.Value; + if (mods.All(m => m.GetType() != autoType)) { - SelectedMods.Value = mods.Append(auto); + Mods.Value = mods.Append(auto).ToArray(); removeAutoModOnResume = true; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a86d0beb39..d5301116a9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -32,6 +32,7 @@ using osuTK.Input; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { @@ -83,19 +84,18 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + [Cached(Type = typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting protected SongSelect() { const float carousel_width = 640; - 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[] @@ -154,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 = () => @@ -217,20 +217,19 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, Bindable> selectedMods) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins) { - if (selectedMods != null) - SelectedMods.BindTo(selectedMods); + mods.BindTo(Mods); if (Footer != null) { - Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); - Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); - Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(mods), ModSelect); + Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); + Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.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) @@ -269,6 +268,7 @@ namespace osu.Game.Screens.Select protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); dependencies.CacheAs(decoupledRuleset); dependencies.CacheAs>(decoupledRuleset); @@ -300,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(); @@ -373,17 +377,22 @@ 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"}"); - bool preview = false; - if (ruleset?.Equals(decoupledRuleset.Value) == false) { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - Beatmap.Value.Mods.Value = Enumerable.Empty(); + mods.Value = Array.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -402,7 +411,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) @@ -414,16 +422,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() @@ -525,8 +527,8 @@ namespace osu.Game.Screens.Select if (Beatmap.Value.Track != null) Beatmap.Value.Track.Looping = false; - SelectedMods.UnbindAll(); - Beatmap.Value.Mods.Value = new Mod[] { }; + mods.UnbindAll(); + Mods.Value = Array.Empty(); return false; } @@ -553,8 +555,6 @@ namespace osu.Game.Screens.Select /// The working beatmap. protected virtual void UpdateBeatmap(WorkingBeatmap beatmap) { - beatmap.Mods.BindTo(SelectedMods); - Logger.Log($"working beatmap updated to {beatmap}"); if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) @@ -572,17 +572,17 @@ 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(); } } @@ -593,18 +593,7 @@ namespace osu.Game.Screens.Select 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)) @@ -618,6 +607,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/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index f8445a4a7d..8499b56847 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -318,6 +318,7 @@ namespace osu.Game.Screens.Tournament using (StreamReader sr = new StreamReader(stream)) { string line; + while ((line = sr.ReadLine()?.Trim()) != null) { if (string.IsNullOrEmpty(line)) diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs index 0bcf1b1816..02f7f73399 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs @@ -108,12 +108,14 @@ namespace osu.Game.Screens.Tournament speedTo(1000f, 200); tracker.FadeOut(100); break; + case ScrollState.Stopping: speedTo(0f, 2000); tracker.FadeIn(200); delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); break; + case ScrollState.Stopped: // Find closest to center if (!Children.Any()) @@ -155,6 +157,7 @@ namespace osu.Game.Screens.Tournament delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); break; + case ScrollState.Idle: resetSelected(); diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs new file mode 100644 index 0000000000..0e67a1897c --- /dev/null +++ b/osu.Game/Skinning/ISkin.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + /// + /// Provides access to skinnable elements. + /// + public interface ISkin + { + Drawable GetDrawableComponent(string componentName); + + Texture GetTexture(string componentName); + + SampleChannel GetSample(string sampleName); + + TValue GetValue(Func query) where TConfiguration : SkinConfiguration; + } +} diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 6d2b9e6fe2..337d2a87a4 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -2,25 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { /// /// Provides access to skinnable elements. /// - public interface ISkinSource + public interface ISkinSource : ISkin { event Action SourceChanged; - - Drawable GetDrawableComponent(string componentName); - - Texture GetTexture(string componentName); - - SampleChannel GetSample(string sampleName); - - TValue GetValue(Func query) where TConfiguration : SkinConfiguration; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 358b2b222b..ea4a777b47 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -49,15 +49,19 @@ namespace osu.Game.Skinning case "Play/Miss": componentName = "hit0"; break; + case "Play/Meh": componentName = "hit50"; break; + case "Play/Good": componentName = "hit100"; break; + case "Play/Great": componentName = "hit300"; break; + case "Play/osu/number-text": return !hasFont(Configuration.HitCircleFont) ? null @@ -82,6 +86,7 @@ namespace osu.Game.Skinning float ratio = 2; var texture = Textures.Get($"{componentName}@2x"); + if (texture == null) { ratio = 1; @@ -184,6 +189,7 @@ namespace osu.Game.Skinning float ratio = 36; var texture = textures.Get($"{textureName}@2x"); + if (texture == null) { ratio = 18; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 96a9116c51..09f7e09961 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -16,43 +16,41 @@ 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": skin.SkinInfo.Name = pair.Value; break; + case @"Author": skin.SkinInfo.Creator = pair.Value; break; + case @"CursorExpand": skin.CursorExpand = pair.Value != "0"; break; } break; - } - case Section.Fonts: - { - var pair = SplitKeyVal(line); + case Section.Fonts: switch (pair.Key) { case "HitCirclePrefix": skin.HitCircleFont = pair.Value; break; + case "HitCircleOverlap": skin.HitCircleOverlap = int.Parse(pair.Value); break; } break; - } } base.ParseLine(skin, section, line); diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index 955ef7b65b..f1ed14595e 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -22,18 +22,18 @@ namespace osu.Game.Skinning private readonly Bindable beatmapSkins = new Bindable(); private readonly Bindable beatmapHitsounds = new Bindable(); - private readonly ISkinSource source; + private readonly ISkin skin; private ISkinSource fallbackSource; - public LocalSkinOverrideContainer(ISkinSource source) + public LocalSkinOverrideContainer(ISkin skin) { - this.source = source; + this.skin = skin; } public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins.Value && (sourceDrawable = source.GetDrawableComponent(componentName)) != null) + if (beatmapSkins.Value && (sourceDrawable = skin.GetDrawableComponent(componentName)) != null) return sourceDrawable; return fallbackSource?.GetDrawableComponent(componentName); @@ -42,7 +42,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins.Value && (sourceTexture = source.GetTexture(componentName)) != null) + if (beatmapSkins.Value && (sourceTexture = skin.GetTexture(componentName)) != null) return sourceTexture; return fallbackSource.GetTexture(componentName); @@ -51,7 +51,7 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = source.GetSample(sampleName)) != null) + if (beatmapHitsounds.Value && (sourceChannel = skin.GetSample(sampleName)) != null) return sourceChannel; return fallbackSource?.GetSample(sampleName); @@ -60,7 +60,7 @@ namespace osu.Game.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { TValue val; - if ((source as Skin)?.Configuration is TConfiguration conf) + if ((skin as Skin)?.Configuration is TConfiguration conf) if (beatmapSkins.Value && (val = query.Invoke(conf)) != null) return val; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 1d14f9cd6a..09c0d3d0bc 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -8,14 +8,12 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { - public abstract class Skin : IDisposable, ISkinSource + public abstract class Skin : IDisposable, ISkin { public readonly SkinInfo SkinInfo; public virtual SkinConfiguration Configuration { get; protected set; } - public event Action SourceChanged; - public abstract Drawable GetDrawableComponent(string componentName); public abstract SampleChannel GetSample(string sampleName); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index f6bbbc8355..3a4d44f608 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -76,6 +76,7 @@ namespace osu.Game.Skinning base.Populate(model, archive); Skin reference = getSkin(model); + if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { model.Name = reference.Configuration.SkinInfo.Name; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 0b9ebaf3a7..d01fba7d39 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -67,6 +67,7 @@ namespace osu.Game.Storyboards.Drawables private void load(IBindable beatmap, TextureStore textureStore) { var basePath = Animation.Path.ToLowerInvariant(); + for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = basePath.Replace(".", frame + "."); 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/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index b91b05bd04..8f8ec22aae 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -82,6 +82,7 @@ namespace osu.Game.Storyboards where T : struct { var initialized = false; + foreach (var command in commands.OrderBy(l => l)) { if (!initialized) diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 44ac38044d..6a5e17eb38 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int mappingCounter = 0; + while (true) { if (mappingCounter >= ourResult.Mappings.Count && mappingCounter >= expectedResult.Mappings.Count) @@ -61,6 +62,7 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int objectCounter = 0; + while (true) { if (objectCounter >= ourMapping.Objects.Count && objectCounter >= expectedMapping.Objects.Count) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 90b5178169..c558275f62 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -70,7 +70,8 @@ namespace osu.Game.Tests.Beatmaps { offset = MathHelper.Clamp(seek, 0, Length); lastReferenceTime = null; - return true; + + return offset == seek; } public override void Start() 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 index 507848730f..40aa90f2d1 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -3,15 +3,14 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { @@ -26,18 +25,11 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - foreach (var r in rulesets.AvailableRulesets) { Player p = null; AddStep(r.Name, () => p = loadPlayerFor(r)); - AddUntilStep(() => + AddUntilStep("player loaded", () => { if (p?.IsLoaded == true) { @@ -46,10 +38,14 @@ namespace osu.Game.Tests.Visual } return false; - }, "player loaded"); + }); AddCheckSteps(); } + + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } protected abstract void AddCheckSteps(); @@ -68,27 +64,18 @@ namespace osu.Game.Tests.Visual var working = CreateWorkingBeatmap(beatmap, Clock); Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; Player?.Exit(); Player = null; - var player = CreatePlayer(r); + Player = CreatePlayer(r); - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); + LoadScreen(Player); - return player; + return Player; } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); } } diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index 7f36a0e142..c71c2ae857 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual public abstract class EditorClockTestCase : OsuTestCase { protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); - protected readonly EditorClock Clock; + protected new readonly EditorClock Clock; protected EditorClockTestCase() { 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..9b1ccdd6a4 100644 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs @@ -4,18 +4,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing.Input; +using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { public abstract class ManualInputManagerTestCase : OsuTestCase { - protected override Container Content => InputManager; + protected override Container Content => content; + private readonly Container content; + protected readonly ManualInputManager InputManager; protected ManualInputManagerTestCase() { - base.Content.Add(InputManager = new ManualInputManager()); - ReturnUserInput(); + base.Content.Add(InputManager = new ManualInputManager + { + UseParentInput = true, + Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, + }); } /// diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 2fbf6f7e46..1f475209a4 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -10,40 +11,42 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual { public abstract class OsuTestCase : TestCase { + [Cached(typeof(Bindable))] + [Cached(typeof(IBindable))] private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); + protected BindableBeatmap Beatmap => beatmap; + [Cached] + [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); - protected DependencyContainer Dependencies { get; private set; } + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + + protected new DependencyContainer Dependencies { get; private set; } private readonly Lazy localStorage; protected Storage LocalStorage => localStorage.Value; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures - beatmap.Default = new DummyWorkingBeatmap(Dependencies.Get()); + beatmap.Default = new DummyWorkingBeatmap(parent.Get()); - Dependencies.CacheAs>(beatmap); - Dependencies.CacheAs>(beatmap); - - Dependencies.CacheAs(Ruleset); - Dependencies.CacheAs>(Ruleset); - - return Dependencies; + return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); } protected OsuTestCase() { - 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 index ad01d82281..c1960eefeb 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -2,15 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { @@ -23,20 +22,21 @@ namespace osu.Game.Tests.Visual protected PlayerTestCase(Ruleset ruleset) { this.ruleset = ruleset; + } - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); + [BackgroundDependencyLoader] + private void load() + { + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } [SetUpSteps] public void SetUpSteps() { AddStep(ruleset.RulesetInfo.Name, loadPlayer); - AddUntilStep(() => Player.IsLoaded, "player loaded"); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); @@ -50,20 +50,12 @@ namespace osu.Game.Tests.Visual Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); if (!AllowFail) - Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; - LoadComponentAsync(Player = CreatePlayer(ruleset), p => - { - Player = p; - LoadScreen(p); - }); + Player = CreatePlayer(ruleset); + LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); } } 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/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index f2e03208fd..bdad3d278c 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -74,9 +74,11 @@ namespace osu.Game.Tests.Visual case ScrollVisualisationMethod.Constant: implementation = new ConstantScrollAlgorithm(); break; + case ScrollVisualisationMethod.Overlapping: implementation = new OverlappingScrollAlgorithm(ControlPoints); break; + case ScrollVisualisationMethod.Sequential: implementation = new SequentialScrollAlgorithm(ControlPoints); break; diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs deleted file mode 100644 index fed56ba4d1..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("no leaked beatmaps", () => - { - p = null; - - 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 virtual void AddCheckSteps(Func player) - { - AddUntilStep("player loaded", () => player().IsLoaded); - } - - 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/User.cs b/osu.Game/Users/User.cs index 292ac90245..314684069a 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; +using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -59,6 +61,9 @@ namespace osu.Game.Users [JsonProperty(@"is_supporter")] public bool IsSupporter; + [JsonProperty(@"support_level")] + public int SupportLevel; + [JsonProperty(@"is_gmt")] public bool IsGMT; @@ -71,6 +76,9 @@ namespace osu.Game.Users [JsonProperty(@"is_active")] public bool Active; + [JsonProperty(@"pm_friends_only")] + public bool PMFriendsOnly; + [JsonProperty(@"interests")] public string Interests; @@ -104,8 +112,16 @@ namespace osu.Game.Users [JsonProperty(@"post_count")] public int PostCount; - [JsonProperty(@"playstyle")] - public string[] PlayStyle; + [JsonProperty(@"follower_count")] + public int[] FollowerCount; + + [JsonProperty] + private string[] playstyle + { + set { PlayStyles = value?.Select(str => Enum.Parse(typeof(PlayStyle), str, true)).Cast().ToArray(); } + } + + public PlayStyle[] PlayStyles; [JsonProperty(@"playmode")] public string PlayMode; @@ -143,6 +159,18 @@ namespace osu.Game.Users [JsonProperty("badges")] public Badge[] Badges; + [JsonProperty("user_achievements")] + public UserAchievement[] Achievements; + + public class UserAchievement + { + [JsonProperty("achieved_at")] + public DateTimeOffset AchievedAt; + + [JsonProperty("achievement_id")] + public int ID; + } + public override string ToString() => Username; /// @@ -153,5 +181,20 @@ namespace osu.Game.Users Username = "system", Id = 0 }; + + public enum PlayStyle + { + [Description("Keyboard")] + Keyboard, + + [Description("Mouse")] + Mouse, + + [Description("Tablet")] + Tablet, + + [Description("Touch Screen")] + Touch, + } } } diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 4c72762498..dbc132995a 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,30 +1,52 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK.Graphics; namespace osu.Game.Users { - public class UserCoverBackground : Sprite + public class UserCoverBackground : ModelBackedDrawable { - private readonly User user; - - public UserCoverBackground(User user) + public User User { - this.user = user; + get => Model; + set => Model = value; } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - if (textures == null) - throw new ArgumentNullException(nameof(textures)); + [Resolved] + private LargeTextureStore textures { get; set; } - if (!string.IsNullOrEmpty(user.CoverUrl)) - Texture = textures.Get(user.CoverUrl); + protected override Drawable CreateDrawable(User user) + { + if (user == null) + { + return new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) + }; + } + else + { + var sprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(user.CoverUrl), + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + sprite.OnLoadComplete += d => d.FadeInFromZero(400); + return sprite; + } } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 65062dc58e..47571b673d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,8 +16,10 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; namespace osu.Game.Users { @@ -75,12 +77,12 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(coverBackground = new UserCoverBackground(user) + new DelayedLoadWrapper(coverBackground = new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - FillMode = FillMode.Fill, + User = user, }, 300) { RelativeSizeAxes = Axes.Both }, new Box { @@ -165,7 +167,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), }, @@ -188,8 +190,8 @@ namespace osu.Game.Users { infoContainer.Add(new SupporterIcon { - RelativeSizeAxes = Axes.Y, - Width = 20f, + Height = 20f, + SupportLevel = user.SupportLevel }); } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 2de54ed8be..7afbef01c5 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; +using osu.Game.Scoring; namespace osu.Game.Users { @@ -40,6 +42,9 @@ namespace osu.Game.Users [JsonProperty(@"play_count")] public int PlayCount; + [JsonProperty(@"play_time")] + public int? PlayTime; + [JsonProperty(@"total_score")] public long TotalScore; @@ -71,6 +76,33 @@ namespace osu.Game.Users [JsonProperty(@"a")] public int A; + + public int this[ScoreRank rank] + { + get + { + switch (rank) + { + case ScoreRank.XH: + return SSPlus; + + case ScoreRank.X: + return SS; + + case ScoreRank.SH: + return SPlus; + + case ScoreRank.S: + return S; + + case ScoreRank.A: + return A; + + default: + throw new ArgumentException($"API does not return {rank.ToString()}"); + } + } + } } public struct UserRanks diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d8561770fd..8f733a5c55 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -5,19 +5,18 @@ Library AnyCPU true - 0 - - - - - - - + + + + + + + diff --git a/osu.iOS.props b/osu.iOS.props index 3dcb647cd2..e5b4d61615 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 71cbd83e3e..c3e274569d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -230,6 +230,8 @@ True True NEXT_LINE + 1 + 1 NEXT_LINE 1 1 @@ -268,6 +270,7 @@ MD5 NS OS + PM RGB RNG SHA