diff --git a/README.md b/README.md index a2f6472371..a54b28b74a 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,71 @@ We are accepting bug reports (please report with as much detail as possible). Fe # Requirements - A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed. -- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial). +- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date. -# Building and running +# Running osu! -If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled (download and run the install executable for your platform). +## Releases -Clone the repository including submodules +If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled. -`git clone --recurse-submodules https://github.com/ppy/osu` +- Windows (x64) users should download and run `install.exe`. +- macOS users (10.12 "Sierra" and higher) should download and run `osu.app.zip`. +- iOS users can join the [TestFlight beta program](https://t.co/xQJmHkfC18). -Build and run +If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. -- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included) -- From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance. -- To run with code analysis, instead use `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice. +## Downloading the source code -Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example +Clone the repository **including submodules**: -If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build. +```shell +git clone --recurse-submodules https://github.com/ppy/osu +cd osu +``` + +> If you forgot the `--recurse-submodules` option, run this command inside the `osu` directory: +> +> `git submodule update --init --recursive` + +To update the source code to the latest commit, run the following command inside the `osu` directory: + +```shell +git pull --recurse-submodules +``` + +## Building + +Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided below. + +> Visual Studio Code users must run the `Restore` task before any build attempt. + +You can also build and run osu! from the command-line with a single command: + +```shell +dotnet run --project osu.Desktop +``` + +If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document. + +If the build fails, try to restore nuget packages with `dotnet restore`. + +### A note for Linux users + +On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`. + +`$NETCORE_VERSION` is the version of .NET Core SDK. You can have it with `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`. + +For example, you can run osu! with the following command: + +```shell +LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop +``` + +## Code analysis + +Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice. # Contributing diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 2eeb112450..0b50db1f72 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -97,7 +97,7 @@ namespace osu.Desktop private void fileDrop(object sender, FileDropEventArgs e) { - var filePaths = new[] { e.FileName }; + var filePaths = e.FileNames; var firstExtension = Path.GetExtension(filePaths.First()); diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index ad08f57c3a..4f0ae3d65d 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs index 8a90b48180..baa317c759 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs @@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Catch.Tests } }; - for (int i = 0; i < 512; i++) beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs index 14487b2c7f..3e322d485f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs @@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Tests { var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; - for (int i = 0; i < 512; i++) if (i % 5 < 3) beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); 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 e875af5a30..feab3ed81c 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 @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index a763989750..b082497e5e 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyCalculator : DifficultyCalculator { - /// /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 2db252ebc6..4c65dbc9e1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : Color4.White); } private const float preempt = 1000; 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 0c6fbfa7d3..e26d2433f9 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 @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 2770a6ff5b..1a0cfa7fbb 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -180,7 +180,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps foreach (var obj in newPattern.HitObjects) yield return obj; - } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 7b4d4b12ed..133c96b16e 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty if (!calculateStrainValues(difficultyHitObjects, timeRate)) return new DifficultyAttributes(mods, 0); - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; return new ManiaDifficultyAttributes(mods, starRating) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 68325b40bf..34e65e8b02 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -3,14 +3,14 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModMirror : Mod, IApplicableToRulesetContainer + public class ManiaModMirror : Mod, IApplicableToBeatmap { public override string Name => "Mirror"; public override string Acronym => "MR"; @@ -18,11 +18,11 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override bool Ranked => true; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToBeatmap(Beatmap beatmap) { - var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; - rulesetContainer.Objects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); + beatmap.HitObjects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index b3a3d4280b..4454012d01 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -4,15 +4,15 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.MathUtils; +using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModRandom : Mod, IApplicableToRulesetContainer + public class ManiaModRandom : Mod, IApplicableToBeatmap { public override string Name => "Random"; public override string Acronym => "RD"; @@ -21,12 +21,12 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Description => @"Shuffle around the keys!"; public override double ScoreMultiplier => 1; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToBeatmap(Beatmap beatmap) { - var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList(); - rulesetContainer.Objects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); + beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs index 390d2b0218..e191a6b1c4 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Mania.Objects private readonly int beatmapColumnCount; - private readonly double endTime; private readonly double[] heldUntil; 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 35f137572d..273d29c3de 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 @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 8fc2b69267..4f01dbe2f3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -57,6 +57,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty s.Process(h); } + // The peak strain will not be saved for the last section in the above loop + foreach (Skill s in skills) + s.SaveCurrentPeak(); + double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 16f0af9875..efa23f1a26 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -105,13 +105,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateFactor = 1.0f; if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f); + approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); else if (Attributes.ApproachRate < 8.0f) { - // HD is worth more with lower ar! - if (mods.Any(h => h is OsuModHidden)) - approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate); - else approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate); } @@ -119,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11. + aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); if (mods.Any(h => h is OsuModFlashlight)) { @@ -152,13 +148,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (beatmapMaxCombo > 0) speedValue *= 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); + + speedValue *= approachRateFactor; + if (mods.Any(m => m is OsuModHidden)) - speedValue *= 1.18f; + speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); // Scale the speed value with accuracy _slightly_ - speedValue *= 0.5f + accuracy / 2.0f; + speedValue *= 0.02f + accuracy; // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; return speedValue; } @@ -186,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); if (mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.02f; + accuracyValue *= 1.08f; if (mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02f; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d8e3b340c9..1752caf74b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing float approxFollowCircleRadius = (float)(slider.Radius * 3); var computeVertex = new Action(t => { - double progress = ((int)t - (int)slider.StartTime) / (float)(int)slider.SpanDuration; - if (progress % 2 > 1) + double progress = (t - slider.StartTime) / slider.SpanDuration; + if (progress % 2 >= 1) progress = 1 - progress % 1; else progress = progress % 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 56c4ea639b..8293d56620 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : Color4.White); } protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 8c9252a2da..542b8cc3bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -101,7 +101,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - float aimRotation = MathHelper.RadiansToDegrees((float)Math.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); while (Math.Abs(aimRotation - Rotation) > 180) aimRotation += aimRotation < Rotation ? 360 : -360; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index eed9a53ad7..60377e373a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -14,6 +14,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -151,6 +152,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : Body.AccentColour); + Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : Body.BorderColour); + Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : Ball.AccentColour); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 44185fb83a..69da1bef6f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Objects if (tickDistance == 0) return; - var minDistanceFromEnd = Velocity * 0.01; + var minDistanceFromEnd = Velocity * 10; var spanCount = this.SpanCount(); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 4aa30777e9..80beb62d6c 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -39,10 +39,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private int downCount; - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; - - private float targetScale => downCount > 0 ? pressed_scale : released_scale; + private void updateExpandedState() + { + if (downCount > 0) + (ActiveCursor as OsuCursor)?.Expand(); + else + (ActiveCursor as OsuCursor)?.Contract(); + } public bool OnPressed(OsuAction action) { @@ -51,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor case OsuAction.LeftButton: case OsuAction.RightButton: downCount++; - ActiveCursor.ScaleTo(released_scale).ScaleTo(targetScale, 100, Easing.OutQuad); + updateExpandedState(); break; } @@ -65,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor case OsuAction.LeftButton: case OsuAction.RightButton: if (--downCount == 0) - ActiveCursor.ScaleTo(targetScale, 200, Easing.OutQuad); + updateExpandedState(); break; } @@ -77,92 +80,106 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(targetScale, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(targetScale * 0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); } - public class OsuCursor : Container + public class OsuCursor : SkinReloadableDrawable { - private Drawable cursorContainer; + private bool cursorExpand; private Bindable cursorScale; private Bindable autoCursorScale; private readonly IBindable beatmap = new Bindable(); + private Container expandTarget; + private Drawable scaleTarget; + public OsuCursor() { Origin = Anchor.Centre; Size = new Vector2(42); } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config, IBindableBeatmap beatmap) { - Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer + InternalChild = expandTarget = new Container { 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, + 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); @@ -187,8 +204,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); } - cursorContainer.Scale = new Vector2(scale); + 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.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 0fc01deed6..fade054382 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 @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index f7e1653cdd..5d45072fd2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -149,7 +149,6 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource(filename)) 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)) diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index 4abfec4371..d81dcb3799 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -23,11 +24,17 @@ namespace osu.Game.Tests.Visual }; } - protected override bool ContinueCondition(Player player) => base.ContinueCondition(player) && ((ScoreAccessiblePlayer)player).ScoreProcessor.TotalScore > 0; + protected override void AddCheckSteps(Func player) + { + base.AddCheckSteps(player); + AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore > 0, "score above zero"); + AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + } private class ScoreAccessiblePlayer : Player { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new HUDOverlay HUDOverlay => base.HUDOverlay; } } } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index f156728981..bfd24f4f34 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual typeof(DrawableCarouselBeatmapSet), }; - private readonly Stack selectedSets = new Stack(); private readonly HashSet eagerSelectedIDs = new HashSet(); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index ec85b33919..02d3acb7f4 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -55,7 +55,6 @@ namespace osu.Game.Tests.Visual AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo }); - scores = new[] { new APIScoreInfo diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs index 73a97c6269..678acc6130 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs @@ -16,7 +16,6 @@ namespace osu.Game.Tests.Visual public TestCaseBreadcrumbs() { - Add(breadcrumbs = new BreadcrumbControl { Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index 465b943651..bccc27418c 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -39,7 +39,6 @@ namespace osu.Game.Tests.Visual }, }; - AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs index cf475de1f0..821bf84047 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs @@ -17,12 +17,13 @@ namespace osu.Game.Tests.Visual { public TestCaseMatchLeaderboard() { - Add(new MatchLeaderboard(new Room { RoomID = { Value = 3 } }) + Add(new MatchLeaderboard { Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(550f, 450f), Scope = MatchLeaderboardScope.Overall, + Room = new Room { RoomID = { Value = 3 } } }); } diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 29060ceb12..369d28fc91 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -57,11 +57,19 @@ namespace osu.Game.Tests.Visual private class TestSongSelect : PlaySongSelect { + public Action StartRequested; + public new Bindable Ruleset => base.Ruleset; public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; + + protected override bool OnStart() + { + StartRequested?.Invoke(); + return base.OnStart(); + } } private TestSongSelect songSelect; @@ -182,6 +190,27 @@ namespace osu.Game.Tests.Visual void onRulesetChange(RulesetInfo ruleset) => rulesetChangeIndex = actionIndex--; } + [Test] + public void TestStartAfterUnMatchingFilterDoesNotStart() + { + addManyTestMaps(); + AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection"); + + bool startRequested = false; + + AddStep("set filter and finalize", () => + { + songSelect.StartRequested = () => startRequested = true; + + songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); + songSelect.FinaliseSelection(); + + songSelect.StartRequested = null; + }); + + AddAssert("start not requested", () => !startRequested); + } + private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray()))); private static int importId; diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index 6ec3bd108b..2240af39be 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -57,5 +57,4 @@ namespace osu.Game.Tests.Visual } } } - } diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipOverlay.cs similarity index 89% rename from osu.Game.Tests/Visual/TestCaseSkipButton.cs rename to osu.Game.Tests/Visual/TestCaseSkipOverlay.cs index 4f381fd7a8..62fb78b4ea 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseSkipButton : OsuTestCase + public class TestCaseSkipOverlay : OsuTestCase { protected override void LoadComplete() { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e6786dfd15..b22c1aed99 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 73fd5da22c..bcf6a95e70 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -17,7 +17,6 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps.Formats; using osu.Game.Database; -using osu.Game.Graphics; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -147,16 +146,6 @@ namespace osu.Game.Beatmaps if (existing != null || api == null) return false; - if (!api.LocalUser.Value.IsSupporter) - { - PostNotification?.Invoke(new SimpleNotification - { - Icon = FontAwesome.fa_superpowers, - Text = "You gotta be an osu!supporter to download for now 'yo" - }); - return false; - } - var downloadNotification = new DownloadNotification { CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8df286ffb2..1b279eee44 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -105,6 +105,8 @@ namespace osu.Game.Configuration Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); + + Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); } public OsuConfigManager(Storage storage) @@ -167,6 +169,7 @@ namespace osu.Game.Configuration ScalingPositionX, ScalingPositionY, ScalingSizeX, - ScalingSizeY + ScalingSizeY, + UIScale } } diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 74315d2522..f794dedcab 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -61,24 +61,25 @@ namespace osu.Game.Graphics.Containers AddText(text.Substring(previousLinkEnd)); } - public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) + public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText); - public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action); - public void AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) AddArbitraryDrawable(t); - createLink(text, null, url, linkType, linkArgument, tooltipText); + return createLink(text, null, url, linkType, linkArgument, tooltipText); } - private void createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) + 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()) { + RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), Action = action ?? (() => { @@ -121,6 +122,13 @@ namespace osu.Game.Graphics.Containers } }), }); + + return drawables; } + + // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. + // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. + // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. + public override IEnumerable FlowingChildren => base.FlowingChildren.Where(c => !(c is DrawableLinkCompiler)); } } diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index e77e075fe2..004c49adb8 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -19,6 +20,6 @@ namespace osu.Game.Graphics.Containers public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable); - public void AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); + public IEnumerable AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); } } diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index ff7a1cdacf..62760b39ea 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -28,6 +28,8 @@ namespace osu.Game.Graphics.Containers private readonly Container content; protected override Container Content => content; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + private readonly Container sizableContainer; private Drawable backgroundLayer; @@ -41,15 +43,44 @@ namespace osu.Game.Graphics.Containers this.targetMode = targetMode; RelativeSizeAxes = Axes.Both; - InternalChild = sizableContainer = new Container + InternalChild = sizableContainer = new AlwaysInputContainer { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, CornerRadius = 10, - Child = content = new DrawSizePreservingFillContainer() + Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay) }; } + private class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer + { + private readonly bool applyUIScale; + private Bindable uiScale; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + public ScalingDrawSizePreservingFillContainer(bool applyUIScale) + { + this.applyUIScale = applyUIScale; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + if (applyUIScale) + { + uiScale = osuConfig.GetBindable(OsuSetting.UIScale); + uiScale.BindValueChanged(scaleChanged, true); + } + } + + private void scaleChanged(float value) + { + this.ScaleTo(new Vector2(value), 500, Easing.Out); + this.ResizeTo(new Vector2(1 / value), 500, Easing.Out); + } + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -116,5 +147,15 @@ namespace osu.Game.Graphics.Containers sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } + + private class AlwaysInputContainer : Container + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + public AlwaysInputContainer() + { + RelativeSizeAxes = Axes.Both; + } + } } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 2bd84ab2b4..26a5c11d5d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -92,6 +92,12 @@ namespace osu.Game.Graphics.UserInterface AccentColour = colours.Pink; } + protected override void LoadComplete() + { + base.LoadComplete(); + CurrentNumber.BindValueChanged(updateTooltipText, true); + } + protected override bool OnHover(HoverEvent e) { Nub.Glowing = true; diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 488e16b6fb..989528e5cd 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -160,7 +160,6 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.BottomLeft, Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetDescription() ?? value.ToString(), TextSize = 14, - Font = @"Exo2.0-Bold", // Font should only turn bold when active? }, Bar = new Box { @@ -173,6 +172,8 @@ namespace osu.Game.Graphics.UserInterface }, new HoverClickSounds() }; + + Active.BindValueChanged(val => Text.Font = val ? @"Exo2.0-Bold" : @"Exo2.0", true); } protected override void OnActivated() => fadeActive(); diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index 50e4743028..15a27b1f6f 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -46,7 +46,6 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.BottomLeft, Text = (value as Enum)?.GetDescription() ?? value.ToString(), TextSize = 14, - Font = @"Exo2.0-Bold", }, box = new Box { @@ -59,6 +58,8 @@ namespace osu.Game.Graphics.UserInterface }, new HoverClickSounds() }; + + Active.BindValueChanged(val => Text.Font = val ? @"Exo2.0-Bold" : @"Exo2.0", true); } [BackgroundDependencyLoader] diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 10b4e73419..db273dd00a 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -101,6 +101,9 @@ namespace osu.Game.Online.API //todo: replace this with a ping request. log.Add(@"In a failing state, waiting a bit before we try again..."); Thread.Sleep(5000); + + if (!IsLoggedIn) goto case APIState.Connecting; + if (queue.Count == 0) { log.Add(@"Queueing a ping request"); diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9dc357c403..982009ed88 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -41,7 +41,6 @@ namespace osu.Game.Online.Chat /// private readonly List pendingMessages = new List(); - /// /// An event that fires when new messages arrived. /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bb356ce7f0..3dde3d2c60 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -221,13 +221,18 @@ namespace osu.Game return; } - var databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); + var databasedSet = beatmap.OnlineBeatmapSetID != null ? + BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) : + BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); - // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); + if (databasedSet != null) + { + // Use first beatmap available for current ruleset, else switch ruleset. + var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); - ruleset.Value = first.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); + ruleset.Value = first.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); + } } switch (currentScreen) @@ -359,8 +364,11 @@ namespace osu.Game { RelativeSizeAxes = Axes.Both, }, - mainContent = new DrawSizePreservingFillContainer(), - overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + overlayContent = new Container + { + RelativeSizeAxes = Axes.Both, + }, + floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, idleTracker = new IdleTracker(6000) }); @@ -379,32 +387,32 @@ namespace osu.Game CloseAllOverlays(false); intro?.ChildScreen?.MakeCurrent(); }, - }, overlayContent.Add); + }, floatingOverlayContent.Add); - loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add); + loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); loadComponentSingleFile(screenshotManager, Add); //overlay elements - loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add); + 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 }, mainContent.Add); + loadComponentSingleFile(chatOverlay = new ChatOverlay { Depth = -1 }, overlayContent.Add); loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset, Depth = -1 - }, overlayContent.Add); - loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); - loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); + }, 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, - }, overlayContent.Add); + }, floatingOverlayContent.Add); loadComponentSingleFile(notifications = new NotificationOverlay { @@ -412,22 +420,22 @@ namespace osu.Game Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, overlayContent.Add); + }, floatingOverlayContent.Add); loadComponentSingleFile(accountCreation = new AccountCreationOverlay { Depth = -6, - }, overlayContent.Add); + }, floatingOverlayContent.Add); loadComponentSingleFile(dialogOverlay = new DialogOverlay { Depth = -7, - }, overlayContent.Add); + }, floatingOverlayContent.Add); loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener { Depth = -8, - }, overlayContent.Add); + }, floatingOverlayContent.Add); dependencies.Cache(idleTracker); dependencies.Cache(settings); @@ -646,10 +654,10 @@ namespace osu.Game public bool OnReleased(GlobalAction action) => false; - private Container mainContent; - private Container overlayContent; + private Container floatingOverlayContent; + private OsuScreen currentScreen; private FrameworkConfigManager frameworkConfig; private ScalingContainer screenContainer; @@ -693,6 +701,7 @@ namespace osu.Game Beatmap.Disabled = applyBeatmapRulesetRestrictions; screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; + overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 8406dada44..658287eb71 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class DownloadButton : HeaderButton, IHasTooltip { - public string TooltipText => Enabled ? null : "You gotta be an osu!supporter to download for now 'yo"; + public string TooltipText => "Download this beatmap"; private readonly IBindable localUser = new Bindable(); @@ -101,12 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private void load(APIAccess api) { localUser.BindTo(api.LocalUser); - localUser.BindValueChanged(userChanged, true); Enabled.BindValueChanged(enabledChanged, true); } - private void userChanged(User user) => Enabled.Value = user.IsSupporter; - private void enabledChanged(bool enabled) => this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 804ddeabb4..d4f3e1b0ac 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -77,7 +77,6 @@ namespace osu.Game.Overlays.Chat.Tabs CloseButton.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); } - protected override void FadeInactive() { base.FadeInactive(); diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index b939483cd8..a645dd86bd 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -18,7 +18,6 @@ namespace osu.Game.Overlays.KeyBinding Add(new InGameKeyBindingsSubsection(manager)); } - private class DefaultBindingsSubsection : KeyBindingsSubsection { protected override string Header => string.Empty; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 742a3830b4..915e96c0fa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -176,7 +176,6 @@ namespace osu.Game.Overlays.Mods section.DeselectTypes(modTypes, immediate); } - private SampleChannel sampleOn, sampleOff; private void modButtonPressed(Mod selectedMod) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index ad886c363b..41a4977a5a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -49,7 +49,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Api.Queue(request); } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 3fa4276616..d59e2e033e 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -63,9 +63,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, + new SettingsSlider + { + LabelText = "UI Scaling", + TransferValueOnCommit = true, + Bindable = osuConfig.GetBindable(OsuSetting.UIScale), + KeyboardStep = 0.01f + }, new SettingsEnumDropdown { - LabelText = "Scaling", + LabelText = "Screen Scaling", Bindable = osuConfig.GetBindable(OsuSetting.Scaling), }, scalingSettings = new FillFlowContainer> @@ -202,6 +209,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics } } + private class UIScaleSlider : OsuSliderBar + { + public override string TooltipText => base.TooltipText + "x"; + } + private class ResolutionSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl { Items = Items }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index c4d180790c..e5cde37254 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; -using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -78,66 +77,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySetting : SettingsSlider { - public override Bindable Bindable - { - get { return ((SensitivitySlider)Control).Sensitivity; } - - set - { - BindableDouble doubleValue = (BindableDouble)value; - - // create a second layer of bindable so we can only handle state changes when not being dragged. - ((SensitivitySlider)Control).Sensitivity = doubleValue; - - // this bindable will still act as the "interactive" bindable displayed during a drag. - base.Bindable = new BindableDouble(doubleValue.Value) - { - Default = doubleValue.Default, - MinValue = doubleValue.MinValue, - MaxValue = doubleValue.MaxValue - }; - - // one-way binding to update the sliderbar with changes from external actions. - doubleValue.DisabledChanged += disabled => base.Bindable.Disabled = disabled; - doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue; - } - } - public SensitivitySetting() { KeyboardStep = 0.01f; + TransferValueOnCommit = true; } } private class SensitivitySlider : OsuSliderBar { - public Bindable Sensitivity; - - public SensitivitySlider() - { - Current.ValueChanged += newValue => - { - if (!isDragging && Sensitivity != null) - Sensitivity.Value = newValue; - }; - } - - private bool isDragging; - - protected override bool OnDragStart(DragStartEvent e) - { - isDragging = true; - return base.OnDragStart(e); - } - - protected override bool OnDragEnd(DragEndEvent e) - { - isDragging = false; - Current.TriggerChange(); - - return base.OnDragEnd(e); - } - public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index e259996b7f..2cce47b593 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -52,6 +52,16 @@ namespace osu.Game.Overlays.Settings.Sections LabelText = "Adjust gameplay cursor size based on current beatmap", Bindable = config.GetBindable(OsuSetting.AutoCursorSize) }, + new SettingsCheckbox + { + LabelText = "Beatmap skins", + Bindable = config.GetBindable(OsuSetting.BeatmapSkins) + }, + new SettingsCheckbox + { + LabelText = "Beatmap hitsounds", + Bindable = config.GetBindable(OsuSetting.BeatmapHitsounds) + }, }; skins.ItemAdded += itemAdded; diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 1c3eb6c245..864147c21d 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Settings } } + public override IEnumerable FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString())); + protected sealed override Drawable CreateControl() => CreateDropdown(); protected virtual OsuDropdown CreateDropdown() => new DropdownControl { Items = Items }; diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 1d7e6350ae..cf25e80820 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Settings } } - public IEnumerable FilterTerms => new[] { LabelText }; + public virtual IEnumerable FilterTerms => new[] { LabelText }; public bool MatchingFilter { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 85eb29033e..7b6c89f0f5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -124,7 +124,6 @@ namespace osu.Game.Rulesets.Objects.Legacy // osu-stable treated the first span of the slider as a repeat, but no repeats are happening repeatCount = Math.Max(0, repeatCount - 1); - if (split.Length > 7) length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index c8c7f3154b..0ea3377952 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -250,14 +250,14 @@ namespace osu.Game.Rulesets.UI [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - KeyBindingInputManager.Children = new Drawable[] + KeyBindingInputManager.AddRange(new Drawable[] { content = new Container { RelativeSizeAxes = Axes.Both, }, Playfield - }; + }); if (Cursor != null) KeyBindingInputManager.Add(Cursor); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index a76942e3fa..e85a048c34 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -27,14 +27,6 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { - public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer - { - public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - } - protected override InputState CreateInitialState() { var state = base.CreateInitialState(); @@ -251,6 +243,14 @@ namespace osu.Game.Rulesets.UI protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new RulesetKeyBindingContainer(ruleset, variant, unique); + + public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer + { + public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + } } /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 3184f776a7..87411a95c8 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -109,12 +109,12 @@ namespace osu.Game.Scoring.Legacy } } - calculateAccuracy(score.ScoreInfo); + CalculateAccuracy(score.ScoreInfo); return score; } - private void calculateAccuracy(ScoreInfo score) + protected void CalculateAccuracy(ScoreInfo score) { int countMiss = score.Statistics[HitResult.Miss]; int count50 = score.Statistics[HitResult.Meh]; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 989883c8b3..f924cf9805 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -2,10 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Threading; using osu.Game.Graphics.Backgrounds; +using osu.Game.Online.API; +using osu.Game.Skinning; +using osu.Game.Users; namespace osu.Game.Screens.Backgrounds { @@ -16,11 +20,21 @@ namespace osu.Game.Screens.Backgrounds private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; + private Bindable user; + private Bindable skin; + [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api, SkinManager skinManager) { + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => Next(); + skin.ValueChanged += _ => Next(); + currentDisplay = RNG.Next(0, background_count); - display(new Background(backgroundName)); + + Next(); } private void display(Background newBackground) @@ -39,8 +53,33 @@ namespace osu.Game.Screens.Backgrounds nextTask?.Cancel(); nextTask = Scheduler.AddDelayed(() => { - LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); + Background background; + + if (user.Value?.IsSupporter ?? false) + background = new SkinnedBackground(skin.Value, backgroundName); + else + background = new Background(backgroundName); + + background.Depth = currentDisplay; + + LoadComponentAsync(background, display); }, 100); } + + private class SkinnedBackground : Background + { + private readonly Skin skin; + + public SkinnedBackground(Skin skin, string fallbackTextureName) : base(fallbackTextureName) + { + this.skin = skin; + } + + [BackgroundDependencyLoader] + private void load() + { + Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture; + } + } } } diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 129ea2bf7d..48ef27add3 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -17,7 +17,6 @@ namespace osu.Game.Screens.Edit.Components public TimeInfoContainer() { - Children = new Drawable[] { trackTimer = new OsuSpriteText diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index c9ad519897..22261f328a 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -1,8 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -24,7 +28,10 @@ namespace osu.Game.Screens.Menu public override bool CursorVisible => false; - private const float icon_y = -0.09f; + private readonly List supporterDrawables = new List(); + private Drawable heart; + + private const float icon_y = -85; public Disclaimer() { @@ -42,7 +49,6 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Icon = FontAwesome.fa_warning, Size = new Vector2(30), - RelativePositionAxes = Axes.Both, Y = icon_y, }, textFlow = new LinkFlowContainer @@ -51,8 +57,9 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Y, Padding = new MarginPadding(50), TextAnchor = Anchor.TopCentre, + Y = -110, Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.TopCentre, Spacing = new Vector2(0, 2), } }; @@ -68,14 +75,37 @@ namespace osu.Game.Screens.Menu t.Font = @"Exo2.0-SemiBold"; }); - textFlow.AddParagraph("Don't expect everything to work perfectly."); - textFlow.AddParagraph(""); - textFlow.AddParagraph("Detailed bug reports are welcomed via github issues."); - textFlow.AddParagraph(""); + textFlow.AddParagraph("Things may not work as expected", t => t.TextSize = 20); + textFlow.NewParagraph(); - textFlow.AddText("Visit "); - textFlow.AddLink("discord.gg/ppy", "https://discord.gg/ppy"); - textFlow.AddText(" if you want to help out or follow progress!"); + Action format = t => + { + t.TextSize = 15; + t.Font = @"Exo2.0-SemiBold"; + }; + + textFlow.AddParagraph("Detailed bug reports are welcomed via github issues.", format); + textFlow.NewParagraph(); + + textFlow.AddText("Visit ", format); + textFlow.AddLink("discord.gg/ppy", "https://discord.gg/ppy", creationParameters:format); + textFlow.AddText(" to help out or follow progress!", format); + + textFlow.NewParagraph(); + textFlow.NewParagraph(); + textFlow.NewParagraph(); + + supporterDrawables.AddRange(textFlow.AddText("Consider becoming an ", format)); + supporterDrawables.AddRange(textFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format)); + supporterDrawables.AddRange(textFlow.AddText(" to help support the game", format)); + + supporterDrawables.Add(heart = textFlow.AddIcon(FontAwesome.fa_heart, t => + { + t.Padding = new MarginPadding { Left = 5 }; + t.TextSize = 12; + t.Colour = colours.Pink; + t.Origin = Anchor.Centre; + }).First()); iconColour = colours.Yellow; } @@ -90,8 +120,15 @@ namespace osu.Game.Screens.Menu { base.OnEntering(last); - icon.Delay(1500).FadeColour(iconColour, 200, Easing.OutQuint); - icon.Delay(1500).MoveToY(icon_y * 1.1f, 100, Easing.OutCirc).Then().MoveToY(icon_y, 100, Easing.InCirc); + icon.Delay(1000).FadeColour(iconColour, 200, Easing.OutQuint); + icon.Delay(1000) + .MoveToY(icon_y * 1.1f, 160, Easing.OutCirc) + .RotateTo(-10, 160, Easing.OutCirc) + .Then() + .MoveToY(icon_y, 160, Easing.InCirc) + .RotateTo(0, 160, Easing.InCirc); + + supporterDrawables.ForEach(d => d.FadeOut().Delay(2000).FadeIn(500)); Content .FadeInFromZero(500) @@ -99,6 +136,8 @@ namespace osu.Game.Screens.Menu .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) .Finally(d => Push(intro)); + + heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); } } } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 70a01a5c9f..8bc9a1f59a 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -3,7 +3,6 @@ using osuTK; using osuTK.Graphics; -using osuTK.Graphics.ES30; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; @@ -150,7 +149,7 @@ namespace osu.Game.Screens.Menu private class VisualiserSharedData { - public readonly LinearBatch VertexBatch = new LinearBatch(100 * 4, 10, PrimitiveType.Quads); + public readonly QuadBatch VertexBatch = new QuadBatch(100, 10); } private class VisualisationDrawNode : DrawNode diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs index 34fc7fe886..228bacf3f3 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs @@ -93,10 +93,14 @@ namespace osu.Game.Screens.Multi.Lounge.Components Host.BindValueChanged(v => { hostText.Clear(); - hostText.AddText("hosted by "); - hostText.AddLink(v.Username, null, LinkAction.OpenUserProfile, v.Id.ToString(), "Open profile", s => s.Font = "Exo2.0-BoldItalic"); + flagContainer.Clear(); - flagContainer.Child = new DrawableFlag(v.Country) { RelativeSizeAxes = Axes.Both }; + if (v != null) + { + hostText.AddText("hosted by "); + hostText.AddLink(v.Username, null, LinkAction.OpenUserProfile, v.Id.ToString(), "Open profile", s => s.Font = "Exo2.0-BoldItalic"); + flagContainer.Child = new DrawableFlag(v.Country) { RelativeSizeAxes = Axes.Both }; + } }); ParticipantCount.BindValueChanged(v => summary.Text = $"{v:#,0}{" participant".Pluralize(v == 1)}"); diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 47f5182c39..665481934e 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Linq; +using System; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; @@ -13,8 +13,9 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; using osu.Game.Users; @@ -37,11 +38,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components private Box statusStrip; private UpdateableBeatmapBackgroundSprite background; private ParticipantCountDisplay participantCount; - private FillFlowContainer topFlow, participantsFlow; private OsuSpriteText name, status; private BeatmapTypeInfo beatmapTypeInfo; - private ScrollContainer participantsScroll; private ParticipantInfo participantInfo; + private MatchParticipants participants; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -58,141 +58,141 @@ namespace osu.Game.Screens.Multi.Lounge.Components RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"343138"), }, - topFlow = new FillFlowContainer + new GridContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new Container + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 200, - Masking = true, - Children = new Drawable[] + new FillFlowContainer { - background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }, - new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Children = new Drawable[] + new Container { - participantCount = new ParticipantCountDisplay + RelativeSizeAxes = Axes.X, + Height = 200, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Children = new Drawable[] + { + participantCount = new ParticipantCountDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + name = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + TextSize = 30, + }, + }, + }, }, - name = new OsuSpriteText + }, + statusStrip = new Box + { + RelativeSizeAxes = Axes.X, + Height = 5, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - TextSize = 30, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = transition_duration, + Padding = contentPadding, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + status = new OsuSpriteText + { + TextSize = 14, + Font = @"Exo2.0-Bold", + }, + beatmapTypeInfo = new BeatmapTypeInfo(), + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = contentPadding, + Children = new Drawable[] + { + participantInfo = new ParticipantInfo(), }, }, }, }, }, - statusStrip = new Box + new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 5, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + participants = new MatchParticipants { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, - Padding = contentPadding, - Spacing = new Vector2(0f, 5f), - Children = new Drawable[] - { - status = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - beatmapTypeInfo = new BeatmapTypeInfo(), - }, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = contentPadding, - Children = new Drawable[] - { - participantInfo = new ParticipantInfo(), - }, - }, - }, - }, - participantsScroll = new OsuScrollContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Top = contentPadding.Top, Left = 38, Right = 37 }, - Children = new[] - { - participantsFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - LayoutDuration = transition_duration, - Spacing = new Vector2(5f), - }, - }, - }, + RelativeSizeAxes = Axes.Both, + } + } + } + } }; participantInfo.Host.BindTo(bindings.Host); participantInfo.ParticipantCount.BindTo(bindings.ParticipantCount); participantInfo.Participants.BindTo(bindings.Participants); - participantCount.Participants.BindTo(bindings.Participants); participantCount.ParticipantCount.BindTo(bindings.ParticipantCount); participantCount.MaxParticipants.BindTo(bindings.MaxParticipants); - beatmapTypeInfo.Beatmap.BindTo(bindings.CurrentBeatmap); beatmapTypeInfo.Ruleset.BindTo(bindings.CurrentRuleset); beatmapTypeInfo.Type.BindTo(bindings.Type); background.Beatmap.BindTo(bindings.CurrentBeatmap); - bindings.Status.BindValueChanged(displayStatus); - bindings.Participants.BindValueChanged(p => participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u))); bindings.Name.BindValueChanged(n => name.Text = n); - Room.BindValueChanged(updateRoom, true); } private void updateRoom(Room room) { bindings.Room = room; + participants.Room = room; if (room != null) { - participantsFlow.FadeIn(transition_duration); participantCount.FadeIn(transition_duration); beatmapTypeInfo.FadeIn(transition_duration); name.FadeIn(transition_duration); @@ -200,7 +200,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components } else { - participantsFlow.FadeOut(transition_duration); participantCount.FadeOut(transition_duration); beatmapTypeInfo.FadeOut(transition_duration); name.FadeOut(transition_duration); @@ -210,13 +209,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - participantsScroll.Height = DrawHeight - topFlow.DrawHeight; - } - private void displayStatus(RoomStatus s) { status.Text = s.Message; @@ -226,39 +218,119 @@ namespace osu.Game.Screens.Multi.Lounge.Components status.FadeColour(c, transition_duration); } - private class UserTile : Container, IHasTooltip - { - private readonly User user; - - public string TooltipText => user.Username; - - public UserTile(User user) - { - this.user = user; - Size = new Vector2(70f); - CornerRadius = 5f; - Masking = true; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"27252d"), - }, - new UpdateableAvatar - { - RelativeSizeAxes = Axes.Both, - User = user, - }, - }; - } - } - private class RoomStatusNoneSelected : RoomStatus { public override string Message => @"No Room Selected"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8; } + + private class MatchParticipants : CompositeDrawable + { + private Room room; + private readonly FillFlowContainer fill; + + public Room Room + { + get { return room; } + set + { + if (room == value) + return; + + room = value; + updateParticipants(); + } + } + + public MatchParticipants() + { + Padding = new MarginPadding { Horizontal = 10 }; + + InternalChild = new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + Spacing = new Vector2(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + } + }; + } + + [Resolved] + private APIAccess api { get; set; } + + private GetRoomScoresRequest request; + + private void updateParticipants() + { + var roomId = room?.RoomID.Value ?? 0; + + request?.Cancel(); + + // nice little progressive fade + int time = 500; + foreach (var c in fill.Children) + { + c.Delay(500 - time).FadeOut(time, Easing.Out); + time = Math.Max(20, time - 20); + c.Expire(); + } + + if (roomId == 0) return; + + request = new GetRoomScoresRequest(roomId); + request.Success += scores => + { + if (roomId != room.RoomID.Value) + return; + + fill.Clear(); + foreach (var s in scores) + fill.Add(new UserTile(s.User)); + + fill.FadeInFromZero(1000, Easing.OutQuint); + }; + + api.Queue(request); + } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + base.Dispose(isDisposing); + } + + private class UserTile : CompositeDrawable, IHasTooltip + { + private readonly User user; + + public string TooltipText => user.Username; + + public UserTile(User user) + { + this.user = user; + Size = new Vector2(70f); + CornerRadius = 5f; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"27252d"), + }, + new UpdateableAvatar + { + RelativeSizeAxes = Axes.Both, + User = user, + }, + }; + } + } + } } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 864191105f..5ac0453373 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -16,17 +16,18 @@ namespace osu.Game.Screens.Multi.Match.Components { public Action> ScoresLoaded; - private readonly Room room; - - public MatchLeaderboard(Room room) + public Room Room { - this.room = room; + get => bindings.Room; + set => bindings.Room = value; } + private readonly RoomBindings bindings = new RoomBindings(); + [BackgroundDependencyLoader] private void load() { - room.RoomID.BindValueChanged(id => + bindings.RoomID.BindValueChanged(id => { if (id == null) return; @@ -38,10 +39,10 @@ namespace osu.Game.Screens.Multi.Match.Components protected override APIRequest FetchScores(Action> scoresCallback) { - if (room.RoomID == null) + if (bindings.RoomID.Value == null) return null; - var req = new GetRoomScoresRequest(room.RoomID.Value ?? 0); + var req = new GetRoomScoresRequest(bindings.RoomID.Value ?? 0); req.Success += r => { diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 55a5a2c85e..a7932e1131 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Multi.Match MatchChatDisplay chat; Components.Header header; + Info info; + GridContainer bottomRow; MatchSettingsOverlay settings; Children = new Drawable[] @@ -61,20 +63,21 @@ namespace osu.Game.Screens.Multi.Match Content = new[] { new Drawable[] { header = new Components.Header(room) { Depth = -1 } }, - new Drawable[] { new Info(room) { OnStart = onStart } }, + new Drawable[] { info = new Info(room) { OnStart = onStart } }, new Drawable[] { - new GridContainer + bottomRow = new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] { - leaderboard = new MatchLeaderboard(room) + leaderboard = new MatchLeaderboard { Padding = new MarginPadding(10), - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Room = room }, new Container { @@ -108,10 +111,19 @@ namespace osu.Game.Screens.Multi.Match header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect { Selected = addPlaylistItem }); header.Tabs.Current.ValueChanged += t => { + const float fade_duration = 500; if (t is SettingsMatchPage) + { settings.Show(); + info.FadeOut(fade_duration, Easing.OutQuint); + bottomRow.FadeOut(fade_duration, Easing.OutQuint); + } else + { settings.Hide(); + info.FadeIn(fade_duration, Easing.OutQuint); + bottomRow.FadeIn(fade_duration, Easing.OutQuint); + } }; chat.Exit += Exit; diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs index 54528e5503..44f5f11c93 100644 --- a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs +++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Multi.Ranking.Pages public class ResultsMatchLeaderboard : MatchLeaderboard { public ResultsMatchLeaderboard(Room room) - : base(room) { + Room = room; } protected override bool FadeTop => true; diff --git a/osu.Game/Screens/Multi/RoomBindings.cs b/osu.Game/Screens/Multi/RoomBindings.cs index cdbb6dbea6..e7f9e323bc 100644 --- a/osu.Game/Screens/Multi/RoomBindings.cs +++ b/osu.Game/Screens/Multi/RoomBindings.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Multi if (room != null) { + RoomID.UnbindFrom(room.RoomID); Name.UnbindFrom(room.Name); Host.UnbindFrom(room.Host); Status.UnbindFrom(room.Status); @@ -52,22 +53,20 @@ namespace osu.Game.Screens.Multi Duration.UnbindFrom(room.Duration); } - room = value; + room = value ?? new Room(); - if (room != null) - { - Name.BindTo(room.Name); - Host.BindTo(room.Host); - Status.BindTo(room.Status); - Type.BindTo(room.Type); - Playlist.BindTo(room.Playlist); - Participants.BindTo(room.Participants); - ParticipantCount.BindTo(room.ParticipantCount); - MaxParticipants.BindTo(room.MaxParticipants); - EndDate.BindTo(room.EndDate); - Availability.BindTo(room.Availability); - Duration.BindTo(room.Duration); - } + RoomID.BindTo(room.RoomID); + Name.BindTo(room.Name); + Host.BindTo(room.Host); + Status.BindTo(room.Status); + Type.BindTo(room.Type); + Playlist.BindTo(room.Playlist); + Participants.BindTo(room.Participants); + ParticipantCount.BindTo(room.ParticipantCount); + MaxParticipants.BindTo(room.MaxParticipants); + EndDate.BindTo(room.EndDate); + Availability.BindTo(room.Availability); + Duration.BindTo(room.Duration); } } @@ -82,6 +81,7 @@ namespace osu.Game.Screens.Multi currentRuleset.Value = playlistItem?.Ruleset; } + public readonly Bindable RoomID = new Bindable(); public readonly Bindable Name = new Bindable(); public readonly Bindable Host = new Bindable(); public readonly Bindable Status = new Bindable(); diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index 2249e743e6..478a99a2eb 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD protected override void PopOut() => this.FadeOut(fade_duration); //We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible - public override bool HandleNonPositionalInput => true; + public override bool PropagateNonPositionalInputSubTree => true; protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 11cee98bdf..2fef8dc4f4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Play { private const int duration = 100; - private readonly Container content; - public readonly KeyCounterCollection KeyCounter; public readonly RollingCounter ComboCounter; public readonly ScoreCounter ScoreCounter; @@ -37,6 +35,7 @@ namespace osu.Game.Screens.Play public readonly PlayerSettingsOverlay PlayerSettingsOverlay; private Bindable showHud; + private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); private static bool hasShownNotificationOnce; @@ -45,34 +44,48 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both; - Add(content = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, // The hud may be hidden but certain elements may need to still be updated - Children = new Drawable[] + visibilityContainer = new Container { - ComboCounter = CreateComboCounter(), - ScoreCounter = CreateScoreCounter(), - AccuracyCounter = CreateAccuracyCounter(), - HealthDisplay = CreateHealthDisplay(), - Progress = CreateProgress(), - ModDisplay = CreateModsContainer(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), - new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Container { - KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), - HoldToQuit = CreateHoldForMenuButton(), - } + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 30, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.Out, + Children = new Drawable[] + { + AccuracyCounter = CreateAccuracyCounter(), + ScoreCounter = CreateScoreCounter(), + ComboCounter = CreateComboCounter(), + }, + }, + HealthDisplay = CreateHealthDisplay(), + Progress = CreateProgress(), + ModDisplay = CreateModsContainer(), + } + }, + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), + HoldToQuit = CreateHoldForMenuButton(), } } - }); + }; BindProcessor(scoreProcessor); BindRulesetContainer(rulesetContainer); @@ -91,7 +104,7 @@ namespace osu.Game.Screens.Play private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) { showHud = config.GetBindable(OsuSetting.ShowInterface); - showHud.ValueChanged += hudVisibility => content.FadeTo(hudVisibility ? 1 : 0, duration); + showHud.ValueChanged += hudVisibility => visibilityContainer.FadeTo(hudVisibility ? 1 : 0, duration); showHud.TriggerChange(); if (!showHud && !hasShownNotificationOnce) @@ -159,20 +172,27 @@ namespace osu.Game.Screens.Play protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - Position = new Vector2(0, 35), TextSize = 20, - Margin = new MarginPadding { Right = 140 }, + BypassAutoSizeAxes = Axes.X, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Top = 5, Right = 20 }, + }; + + protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) + { + TextSize = 40, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, }; protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopLeft, - Position = new Vector2(0, 35), - Margin = new MarginPadding { Left = 140 }, TextSize = 20, + BypassAutoSizeAxes = Axes.X, + Anchor = Anchor.TopRight, + Origin = Anchor.TopLeft, + Margin = new MarginPadding { Top = 5, Left = 20 }, }; protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay @@ -191,14 +211,6 @@ namespace osu.Game.Screens.Play AudioClock = offsetClock }; - protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextSize = 40, - Position = new Vector2(0, 30), - }; - protected virtual SongProgress CreateProgress() => new SongProgress { Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d5c99f5729..a3f46a285e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -32,6 +32,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; +using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -82,7 +83,7 @@ namespace osu.Game.Screens.Play protected ScoreProcessor ScoreProcessor; protected RulesetContainer RulesetContainer; - private HUDOverlay hudOverlay; + protected HUDOverlay HUDOverlay; private FailOverlay failOverlay; private DrawableStoryboard storyboard; @@ -199,7 +200,7 @@ namespace osu.Game.Screens.Play { Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(), }, - hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) + HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { Clock = Clock, // hud overlay doesn't want to use the audio clock directly ProcessCustomClock = false, @@ -232,8 +233,8 @@ namespace osu.Game.Screens.Play } }; - hudOverlay.HoldToQuit.Action = performUserRequestedExit; - hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); + HUDOverlay.HoldToQuit.Action = performUserRequestedExit; + HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused); @@ -401,6 +402,7 @@ namespace osu.Game.Screens.Play { float fadeOutDuration = instant ? 0 : 250; Content.FadeOut(fadeOutDuration); + Background?.FadeColour(Color4.White, fadeOutDuration, Easing.OutQuint); } protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !pauseContainer.IsPaused; @@ -438,7 +440,7 @@ namespace osu.Game.Screens.Play .FadeTo(storyboardVisible && BackgroundOpacity > 0 ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); if (storyboardVisible && beatmap.Storyboard.ReplacesBackground) - Background?.FadeTo(0, BACKGROUND_FADE_DURATION, Easing.OutQuint); + Background?.FadeColour(Color4.Black, BACKGROUND_FADE_DURATION, Easing.OutQuint); } protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ded25f013a..44866846d2 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play protected override void InitializeBackgroundElements() { - Background?.FadeTo(1, BACKGROUND_FADE_DURATION, Easing.OutQuint); + Background?.FadeColour(Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); Background?.BlurTo(background_blur, BACKGROUND_FADE_DURATION, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index f762597e81..439e344020 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.PlayerSettings }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hit sounds" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; } diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 3846b45d2f..61dc70c4ae 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -6,6 +6,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Screens.Backgrounds; using osuTK; @@ -60,14 +61,14 @@ namespace osu.Game.Screens.Play protected virtual void InitializeBackgroundElements() => UpdateBackgroundElements(); /// - /// Called wen background elements require updates, usually due to a user changing a setting. + /// Called when background elements require updates, usually due to a user changing a setting. /// /// protected virtual void UpdateBackgroundElements() { if (!IsCurrentScreen) return; - Background?.FadeTo(BackgroundOpacity, BACKGROUND_FADE_DURATION, Easing.OutQuint); + Background?.FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint); Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint); } } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index e5a6dd2db1..577a1325a9 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -46,10 +46,10 @@ namespace osu.Game.Screens.Play State = Visibility.Visible; RelativePositionAxes = Axes.Both; - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; Position = new Vector2(0.5f, 0.7f); - Size = new Vector2(1, 0.14f); + Size = new Vector2(1, 100); Origin = Anchor.Centre; } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 348394c1f5..63c97f9bd5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -101,7 +101,6 @@ namespace osu.Game.Screens.Select private readonly Container scrollableContent; - public Bindable RightClickScrollingEnabled = new Bindable(); public Bindable RandomAlgorithm = new Bindable(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f65cc0e49d..2f212a2564 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -297,14 +297,14 @@ namespace osu.Game.Screens.Select /// Whether to trigger . public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) { - // avoid attempting to continue before a selection has been obtained. - // this could happen via a user interaction while the carousel is still in a loading state. - if (Carousel.SelectedBeatmap == null) 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(); + // avoid attempting to continue before a selection has been obtained. + // this could happen via a user interaction while the carousel is still in a loading state. + if (Carousel.SelectedBeatmap == null) return; + if (beatmap != null) Carousel.SelectBeatmap(beatmap); diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 609f8e4ac4..53ac4c3454 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -21,8 +21,6 @@ namespace osu.Game.Skinning SampleChannel GetSample(string sampleName); - TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class; - - TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct; + TValue GetValue(Func query) where TConfiguration : SkinConfiguration; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 23367c58c9..1ed6c4b42a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -101,9 +101,10 @@ namespace osu.Game.Skinning bool hasExtension = filename.Contains('.'); string lastPiece = filename.Split('/').Last(); + var legacyName = filename.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; var file = source.Files.Find(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), lastPiece, StringComparison.InvariantCultureIgnoreCase)); + string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), legacyName, StringComparison.InvariantCultureIgnoreCase)); return file?.FileInfo.StoragePath; } diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 67a031fb50..9fbd88596d 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -30,6 +30,9 @@ namespace osu.Game.Skinning case @"Author": skin.SkinInfo.Creator = pair.Value; break; + case @"CursorExpand": + skin.CursorExpand = pair.Value != "0"; + break; } break; diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index d7d2737d35..2dab671936 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -43,22 +43,14 @@ namespace osu.Game.Skinning return fallbackSource?.GetSample(sampleName); } - public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { - TValue? val = null; + TValue val; if ((source as Skin)?.Configuration is TConfiguration conf) - val = query?.Invoke(conf); + if (beatmapSkins && (val = query.Invoke(conf)) != null) + return val; - return val ?? fallbackSource?.GetValue(query); - } - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class - { - TValue val = null; - if ((source as Skin)?.Configuration is TConfiguration conf) - val = query?.Invoke(conf); - - return val ?? fallbackSource?.GetValue(query); + return fallbackSource == null ? default : fallbackSource.GetValue(query); } private readonly ISkinSource source; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 29130f45df..21027ff4ab 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -22,11 +22,8 @@ namespace osu.Game.Skinning public abstract Texture GetTexture(string componentName); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class - => Configuration is TConfiguration conf ? query?.Invoke(conf) : null; - - public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct - => Configuration is TConfiguration conf ? query?.Invoke(conf) : null; + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + => Configuration is TConfiguration conf ? query.Invoke(conf) : default; protected Skin(SkinInfo skin) { diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 7d354d108c..047be591e2 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -16,5 +16,7 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); public string HitCircleFont { get; set; } = "default"; + + public bool? CursorExpand { get; set; } = true; } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ce179d43ef..454e80d8c6 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -116,8 +116,6 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class => CurrentSkin.Value.GetValue(query); - - public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct => CurrentSkin.Value.GetValue(query); + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query); } } diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 47bf787bb5..d52a406915 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual { Player p = null; AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset)); - AddUntilStep(() => ContinueCondition(p)); + AddCheckSteps(() => p); } else { @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual { Player p = null; AddStep(r.Name, () => p = loadPlayerFor(r)); - AddUntilStep(() => ContinueCondition(p)); + AddCheckSteps(() => p); AddUntilStep(() => { @@ -79,7 +79,10 @@ namespace osu.Game.Tests.Visual } } - protected virtual bool ContinueCondition(Player player) => player.IsLoaded; + protected virtual void AddCheckSteps(Func player) + { + AddUntilStep(() => player().IsLoaded, "player loaded"); + } protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index f04bfb62bb..8af270454c 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -78,6 +78,5 @@ namespace osu.Game.Users [JsonProperty(@"country")] public int? Country; } - } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ce6e035b1c..1abd17e056 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,10 +15,10 @@ - - + + - +