diff --git a/.vscode/launch.json b/.vscode/launch.json
index ed67fa92cc..e9b8d6f397 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,11 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
},
{
@@ -24,7 +28,11 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
},
{
@@ -33,11 +41,15 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
},
{
@@ -46,12 +58,16 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
- "env": {},
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
"console": "internalConsole"
}
]
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index 9d19f16ebd..fd0ba838cc 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,32 @@
# osu! [](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
-Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era!
+Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
# Status
-This is still heavily under development and is not intended for end-user use. This repository is intended for developer collaboration. You're welcome to try and use it but please do not submit bug reports without a patch. Please do not ask for help building or using this software.
+This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
+
+We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
# Requirements
-- A desktop platform that can compile .NET 4.7.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here.
+- A desktop platform with the [.NET Core SDK 2.1](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).
-# Getting Started
-- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
-- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`)
+# Building and running
+
+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).
+
+Clone the repository including submodules
+
+`git clone --recurse-submodules https://github.com/ppy/osu`
+
+Build and run
+
+- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
+- From command line using `dotnet run --project osu.Desktop --framework netcoreapp2.1`
+
+The above methods should automatically do so, but if you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`).
# Contributing
diff --git a/appveyor.yml b/appveyor.yml
index ac6d6ebff8..7c08eb9e9c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,6 +2,9 @@ clone_depth: 1
version: '{branch}-{build}'
image: Visual Studio 2017
configuration: Debug
+cache:
+ - C:\ProgramData\chocolatey\bin -> appveyor.yml
+ - C:\ProgramData\chocolatey\lib -> appveyor.yml
install:
- cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y
@@ -18,4 +21,4 @@ build:
verbosity: minimal
after_build:
- cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
- - cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
\ No newline at end of file
+ - cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 64adcecba4..a4270f22b4 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game;
using OpenTK.Input;
using Microsoft.Win32;
+using osu.Desktop.Updater;
using osu.Framework.Platform.Windows;
namespace osu.Desktop
@@ -38,6 +39,52 @@ namespace osu.Desktop
}
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (!noVersionOverlay)
+ {
+ LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
+ {
+ Add(v);
+ v.State = Visibility.Visible;
+ });
+
+#if NET_FRAMEWORK
+ Add(new SquirrelUpdateManager());
+#else
+ Add(new SimpleUpdateManager());
+#endif
+ }
+ }
+
+ public override void SetHost(GameHost host)
+ {
+ base.SetHost(host);
+ var desktopWindow = host.Window as DesktopGameWindow;
+ if (desktopWindow != null)
+ {
+ desktopWindow.CursorState |= CursorState.Hidden;
+
+ desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
+ desktopWindow.Title = Name;
+
+ desktopWindow.FileDrop += fileDrop;
+ }
+ }
+
+ private void fileDrop(object sender, FileDropEventArgs e)
+ {
+ var filePaths = new[] { e.FileName };
+
+ var firstExtension = Path.GetExtension(filePaths.First());
+
+ if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
+
+ Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
+ }
+
///
/// A method of accessing an osu-stable install in a controlled fashion.
///
@@ -77,45 +124,5 @@ namespace osu.Desktop
{
}
}
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- if (!noVersionOverlay)
- {
- LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
- {
- Add(v);
- v.State = Visibility.Visible;
- });
- }
- }
-
- public override void SetHost(GameHost host)
- {
- base.SetHost(host);
- var desktopWindow = host.Window as DesktopGameWindow;
- if (desktopWindow != null)
- {
- desktopWindow.CursorState |= CursorState.Hidden;
-
- desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
- desktopWindow.Title = Name;
-
- desktopWindow.FileDrop += fileDrop;
- }
- }
-
- private void fileDrop(object sender, FileDropEventArgs e)
- {
- var filePaths = new[] { e.FileName };
-
- var firstExtension = Path.GetExtension(filePaths.First());
-
- if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
-
- Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
- }
}
}
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index bc1faec822..1129969694 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -91,10 +91,6 @@ namespace osu.Desktop.Overlays
}
}
};
-
-#if NET_FRAMEWORK
- Add(new SquirrelUpdateManager());
-#endif
}
protected override void LoadComplete()
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 61d2006315..7e6a07c89f 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
+
#if NET_FRAMEWORK
using System.Runtime;
#endif
@@ -18,10 +19,7 @@ namespace osu.Desktop
[STAThread]
public static int Main(string[] args)
{
- // required to initialise native SQLite libraries on some platforms.
-
- if (!RuntimeInfo.IsMono)
- useMulticoreJit();
+ useMultiCoreJit();
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
@@ -54,7 +52,7 @@ namespace osu.Desktop
}
}
- private static void useMulticoreJit()
+ private static void useMultiCoreJit()
{
#if NET_FRAMEWORK
var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"));
diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs
new file mode 100644
index 0000000000..6c363422f7
--- /dev/null
+++ b/osu.Desktop/Updater/SimpleUpdateManager.cs
@@ -0,0 +1,103 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using osu.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.IO.Network;
+using osu.Framework.Platform;
+using osu.Game;
+using osu.Game.Graphics;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Notifications;
+
+namespace osu.Desktop.Updater
+{
+ ///
+ /// An update manager that shows notifications if a newer release is detected.
+ /// Installation is left up to the user.
+ ///
+ internal class SimpleUpdateManager : CompositeDrawable
+ {
+ private NotificationOverlay notificationOverlay;
+ private string version;
+ private GameHost host;
+
+ [BackgroundDependencyLoader]
+ private void load(NotificationOverlay notification, OsuGameBase game, GameHost host)
+ {
+ notificationOverlay = notification;
+
+ this.host = host;
+ version = game.Version;
+
+ if (game.IsDeployedBuild)
+ Schedule(() => Task.Run(() => checkForUpdateAsync()));
+ }
+
+ private async void checkForUpdateAsync()
+ {
+ var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest");
+ await releases.PerformAsync();
+
+ var latest = releases.ResponseObject;
+
+ if (latest.TagName != version)
+ {
+ notificationOverlay.Post(new SimpleNotification
+ {
+ Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n"
+ + "Click here to download the new version, which can be installed over the top of your existing installation",
+ Icon = FontAwesome.fa_upload,
+ Activated = () =>
+ {
+ host.OpenUrlExternally(getBestUrl(latest));
+ return true;
+ }
+ });
+ }
+ }
+
+ private string getBestUrl(GitHubRelease release)
+ {
+ GitHubAsset bestAsset = null;
+
+ switch (RuntimeInfo.OS)
+ {
+ case RuntimeInfo.Platform.Windows:
+ bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".exe"));
+ break;
+ case RuntimeInfo.Platform.MacOsx:
+ bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".app.zip"));
+ break;
+ }
+
+ return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl;
+ }
+
+ public class GitHubRelease
+ {
+ [JsonProperty("html_url")]
+ public string HtmlUrl { get; set; }
+
+ [JsonProperty("tag_name")]
+ public string TagName { get; set; }
+
+ [JsonProperty("assets")]
+ public List Assets { get; set; }
+ }
+
+ public class GitHubAsset
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("browser_download_url")]
+ public string BrowserDownloadUrl { get; set; }
+ }
+ }
+}
diff --git a/osu.Desktop/Overlays/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
similarity index 97%
rename from osu.Desktop/Overlays/SquirrelUpdateManager.cs
rename to osu.Desktop/Updater/SquirrelUpdateManager.cs
index ea86d2f028..81da26cff2 100644
--- a/osu.Desktop/Overlays/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -3,6 +3,7 @@
#if NET_FRAMEWORK
using System;
+using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -16,7 +17,7 @@ using OpenTK;
using OpenTK.Graphics;
using Squirrel;
-namespace osu.Desktop.Overlays
+namespace osu.Desktop.Updater
{
public class SquirrelUpdateManager : Component
{
@@ -35,7 +36,7 @@ namespace osu.Desktop.Overlays
notificationOverlay = notification;
if (game.IsDeployedBuild)
- Schedule(() => checkForUpdateAsync());
+ Schedule(() => Task.Run(() => checkForUpdateAsync()));
}
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
index 0ba6398ced..25f7ca108d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperdashState(status ? 2 : 1);
+ public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
index e77dd76353..5c41e4136c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index)
{
Fruit fruit = index == 5
- ? new BananaShower.Banana
+ ? new Banana
{
StartTime = 1000000000000,
IndexInBeatmap = index,
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
similarity index 88%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
rename to osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
index 896582bf0a..14487b2c7f 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
@@ -8,9 +8,9 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer
+ public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer
{
- public TestCaseHyperdash()
+ public TestCaseHyperDash()
: base(new CatchRuleset())
{
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 8473f5a36c..ab0afb08d7 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapProcessor : BeatmapProcessor
{
+ public const int RNG_SEED = 1337;
+
public CatchBeatmapProcessor(IBeatmap beatmap)
: base(beatmap)
{
@@ -22,12 +24,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public override void PostProcess()
{
+ base.PostProcess();
+
applyPositionOffsets();
initialiseHyperDash((List)Beatmap.HitObjects);
- base.PostProcess();
-
int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType())
{
@@ -37,8 +39,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
- public const int RNG_SEED = 1337;
-
private void applyPositionOffsets()
{
var rng = new FastRandom(RNG_SEED);
@@ -49,9 +49,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
switch (obj)
{
case BananaShower bananaShower:
- foreach (var nested in bananaShower.NestedHitObjects)
+ foreach (var banana in bananaShower.NestedHitObjects.OfType())
{
- ((BananaShower.Banana)nested).X = (float)rng.NextDouble();
+ banana.X = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 3d1013aad3..31c56c37c4 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new CatchDifficultyAttributes(mods, 0);
// this is the same as osu!, so there's potential to share the implementation... maybe
- double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
+ double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
return new CatchDifficultyAttributes(mods, starRating)
{
- ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
+ ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = difficultyHitObjects.Count
};
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
new file mode 100644
index 0000000000..c39e663d75
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -0,0 +1,36 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchBananaJudgement : CatchJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ public override bool ShouldExplode => true;
+
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 1100;
+ }
+ }
+
+ protected override float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 8;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
new file mode 100644
index 0000000000..0df2305339
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -0,0 +1,32 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchDropletJudgement : CatchJudgement
+ {
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 30;
+ }
+ }
+
+ protected override float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 7;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index bb2786f14f..51d7d3b5cd 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -2,11 +2,51 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchJudgement : Judgement
{
- // todo: wangs
+ public override HitResult MaxResult => HitResult.Perfect;
+
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 300;
+ }
+ }
+
+ ///
+ /// The base health increase for the result achieved.
+ ///
+ public float HealthIncrease => HealthIncreaseFor(Result);
+
+ ///
+ /// Whether fruit on the platter should explode or drop.
+ /// Note that this is only checked if the owning object is also
+ ///
+ public virtual bool ShouldExplode => IsHit;
+
+ ///
+ /// Convert a to a base health increase.
+ ///
+ /// The value to convert.
+ /// The base health increase.
+ protected virtual float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 10.2f;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
new file mode 100644
index 0000000000..8b77351027
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchTinyDropletJudgement : CatchJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override int NumericResultFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 10;
+ }
+ }
+
+ protected override float HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case HitResult.Perfect:
+ return 4;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs
new file mode 100644
index 0000000000..f7c60a7a47
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Catch.Objects
+{
+ public class Banana : Fruit
+ {
+ public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 4dd491966c..25af7e4bdf 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -37,10 +37,5 @@ namespace osu.Game.Rulesets.Catch.Objects
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
-
- public class Banana : Fruit
- {
- public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
new file mode 100644
index 0000000000..dd027abbe0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Catch.Judgements;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public class DrawableBanana : DrawableFruit
+ {
+ public DrawableBanana(Banana h)
+ : base(h)
+ {
+ }
+
+ protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
index 739cc6a59b..f039504600 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
@@ -5,9 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
- foreach (var b in s.NestedHitObjects.Cast())
+ foreach (var b in s.NestedHitObjects.Cast())
AddNested(getVisualRepresentation?.Invoke(b));
}
- protected override void CheckForJudgements(bool userTriggered, double timeOffset)
- {
- if (timeOffset >= 0)
- AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss });
- }
+ protected override bool ProvidesJudgement => false;
protected override void AddNested(DrawableHitObject h)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index e3564b5967..6ce2e6a2ae 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -2,14 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using OpenTK;
+using OpenTK.Graphics;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
-using OpenTK;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
-using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -58,9 +58,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
if (CheckPosition == null) return;
if (timeOffset >= 0)
- AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
+ {
+ var judgement = CreateJudgement();
+ judgement.Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss;
+ AddJudgement(judgement);
+ }
}
+ protected virtual CatchJudgement CreateJudgement() => new CatchJudgement();
+
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
index 5c8a7c4a7c..11d5ed1f92 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
using OpenTK.Graphics;
+using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Masking = false;
}
+ protected override CatchJudgement CreateJudgement() => new CatchDropletJudgement();
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
new file mode 100644
index 0000000000..2232bb81a7
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using osu.Game.Rulesets.Catch.Judgements;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public class DrawableTinyDroplet : DrawableDroplet
+ {
+ public DrawableTinyDroplet(Droplet h)
+ : base(h)
+ {
+ Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
+ }
+
+ protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 936ab6a9d3..23b620248f 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
return;
}
- if (h is BananaShower.Banana)
+ if (h is Banana)
{
// auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
@@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{
switch (nestedObj)
{
- case BananaShower.Banana _:
+ case Banana _:
case TinyDroplet _:
case Droplet _:
case Fruit _:
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index ce1aee5c34..5b69d836a3 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,10 +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.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@@ -17,28 +19,57 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
}
+ private float hpDrainRate;
+
protected override void SimulateAutoplay(Beatmap beatmap)
{
+ hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
+
foreach (var obj in beatmap.HitObjects)
{
switch (obj)
{
case JuiceStream stream:
- foreach (var _ in stream.NestedHitObjects.Cast())
- AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ foreach (var nestedObject in stream.NestedHitObjects)
+ switch (nestedObject)
+ {
+ case TinyDroplet _:
+ AddJudgement(new CatchTinyDropletJudgement { Result = HitResult.Perfect });
+ break;
+ case Droplet _:
+ AddJudgement(new CatchDropletJudgement { Result = HitResult.Perfect });
+ break;
+ case Fruit _:
+ AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ break;
+ }
break;
case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast())
- AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
- AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break;
}
}
+ }
- base.SimulateAutoplay(beatmap);
+ private const double harshness = 0.01;
+
+ protected override void OnNewJudgement(Judgement judgement)
+ {
+ base.OnNewJudgement(judgement);
+
+ if (judgement.Result == HitResult.Miss)
+ {
+ if (!judgement.IsBonus)
+ Health.Value -= hpDrainRate * (harshness * 2);
+ return;
+ }
+
+ if (judgement is CatchJudgement catchJudgement)
+ Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 52763e09af..1ac052de4d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -38,14 +38,16 @@ namespace osu.Game.Rulesets.Catch.UI
{
switch (h)
{
+ case Banana banana:
+ return new DrawableBanana(banana);
case Fruit fruit:
return new DrawableFruit(fruit);
case JuiceStream stream:
return new DrawableJuiceStream(stream, GetVisualRepresentation);
- case BananaShower banana:
- return new DrawableBananaShower(banana, GetVisualRepresentation);
+ case BananaShower shower:
+ return new DrawableBananaShower(shower, GetVisualRepresentation);
case TinyDroplet tiny:
- return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) };
+ return new DrawableTinyDroplet(tiny);
case Droplet droplet:
return new DrawableDroplet(droplet);
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index ceb05d349f..2f42902fd0 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
@@ -78,12 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
-
}
if (fruit.HitObject.LastInCombo)
{
- if (judgement.IsHit)
+ if (((CatchJudgement)judgement).ShouldExplode)
runAfterLoaded(() => MovableCatcher.Explode());
else
MovableCatcher.Drop();
@@ -255,11 +255,11 @@ namespace osu.Game.Rulesets.Catch.UI
double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
- SetHyperdashState(Math.Abs(velocity), target.X);
+ SetHyperDashState(Math.Abs(velocity), target.X);
}
else
{
- SetHyperdashState();
+ SetHyperDashState();
}
return validCatch;
@@ -270,18 +270,18 @@ namespace osu.Game.Rulesets.Catch.UI
private float hyperDashTargetPosition;
///
- /// Whether we are hypderdashing or not.
+ /// Whether we are hyper-dashing or not.
///
public bool HyperDashing => hyperDashModifier != 1;
///
- /// Set hyperdash state.
+ /// Set hyper-dash state.
///
- /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyperdashing state.
- /// When this catcher crosses this position, this catcher ends hyperdashing.
- public void SetHyperdashState(double modifier = 1, float targetPosition = -1)
+ /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.
+ /// When this catcher crosses this position, this catcher ends hyper-dashing.
+ public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
{
- const float hyperdash_transition_length = 180;
+ const float hyper_dash_transition_length = 180;
bool previouslyHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition)
@@ -291,8 +291,8 @@ namespace osu.Game.Rulesets.Catch.UI
if (previouslyHyperDashing)
{
- this.FadeColour(Color4.White, hyperdash_transition_length, Easing.OutQuint);
- this.FadeTo(1, hyperdash_transition_length, Easing.OutQuint);
+ this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
+ this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
}
}
else
@@ -303,8 +303,8 @@ namespace osu.Game.Rulesets.Catch.UI
if (!previouslyHyperDashing)
{
- this.FadeColour(Color4.OrangeRed, hyperdash_transition_length, Easing.OutQuint);
- this.FadeTo(0.2f, hyperdash_transition_length, Easing.OutQuint);
+ this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
+ this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
Trail = true;
}
}
@@ -370,7 +370,7 @@ namespace osu.Game.Rulesets.Catch.UI
hyperDashDirection < 0 && hyperDashTargetPosition > X)
{
X = hyperDashTargetPosition;
- SetHyperdashState();
+ SetHyperDashState();
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 73555dcecb..58f2ab7747 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -13,11 +15,10 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- public class ManiaBeatmapConversionTest : BeatmapConversionTest
+ public class ManiaBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
- [NonParallelizable]
[TestCase("basic")]
public new void Test(string name)
{
@@ -34,9 +35,35 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
+ protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
+
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
+ public class ManiaConvertMapping : ConvertMapping, IEquatable
+ {
+ public uint RandomW;
+ public uint RandomX;
+ public uint RandomY;
+ public uint RandomZ;
+
+ public ManiaConvertMapping()
+ {
+ }
+
+ public ManiaConvertMapping(IBeatmapConverter converter)
+ {
+ var maniaConverter = (ManiaBeatmapConverter)converter;
+ RandomW = maniaConverter.Random.W;
+ RandomX = maniaConverter.Random.X;
+ RandomY = maniaConverter.Random.Y;
+ RandomZ = maniaConverter.Random.Z;
+ }
+
+ public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
+ public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
+ }
+
public struct ConvertValue : IEquatable
{
///
diff --git a/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
index 78a98e83e8..5a93efb0dc 100644
--- a/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
@@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.Mania.Tests
this.direction = direction;
}
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(new ScrollingInfo { Direction = { Value = direction }});
return dependencies;
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
index 72f0b046b6..de2bfaed9c 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- columns[i].Add(new DrawableNote(obj, columns[i].Action));
+ columns[i].Add(new DrawableNote(obj));
}
}
@@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- columns[i].Add(new DrawableHoldNote(obj, columns[i].Action));
+ columns[i].Add(new DrawableHoldNote(obj));
}
}
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.Centre,
Height = 0.85f,
AccentColour = Color4.OrangeRed,
- Action = action,
+ Action = { Value = action },
VisibleTimeRange = { Value = 2000 }
};
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
index 4fdfac93b7..64ed08373a 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -63,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}")
{
- Child = new DrawableNote(note, ManiaAction.Key1) { AccentColour = Color4.OrangeRed }
+ Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
}
};
}
@@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}")
{
- Child = new DrawableHoldNote(note, ManiaAction.Key1)
+ Child = new DrawableHoldNote(note)
{
RelativeSizeAxes = Axes.Both,
AccentColour = Color4.OrangeRed,
@@ -136,6 +137,13 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs>(new Bindable());
+ return dependencies;
+ }
+
protected override void Update()
{
base.Update();
@@ -145,9 +153,6 @@ namespace osu.Game.Rulesets.Mania.Tests
if (!(obj.HitObject is IHasEndTime endTime))
continue;
- if (!obj.HasNestedHitObjects)
- continue;
-
foreach (var nested in obj.NestedHitObjects)
{
double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
index 9aff853ffd..8046c46fc1 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- stage.Add(new DrawableNote(obj, stage.Columns[i].Action));
+ stage.Add(new DrawableNote(obj));
}
}
}
@@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- stage.Add(new DrawableHoldNote(obj, stage.Columns[i].Action));
+ stage.Add(new DrawableHoldNote(obj));
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 19fef9eb54..c15b303048 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -5,6 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
using System;
using System.Linq;
using System.Collections.Generic;
+using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -28,8 +29,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public int TargetColumns;
public readonly bool IsForCurrentRuleset;
+ // Internal for testing purposes
+ internal FastRandom Random { get; private set; }
+
private Pattern lastPattern = new Pattern();
- private FastRandom random;
private ManiaBeatmap beatmap;
@@ -62,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
- random = new FastRandom(seed);
+ Random = new FastRandom(seed);
return base.ConvertBeatmap(original);
}
@@ -100,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime;
private Vector2 lastPosition;
- private PatternType lastStair;
+ private PatternType lastStair = PatternType.Stair;
private void recordNote(double time, Vector2 position)
{
lastTime = time;
@@ -115,12 +118,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The hit objects generated.
private IEnumerable generateSpecific(HitObject original, IBeatmap originalBeatmap)
{
- var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
+ var generator = new SpecificBeatmapPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
- Pattern newPattern = generator.Generate();
- lastPattern = newPattern;
+ foreach (var newPattern in generator.Generate())
+ {
+ lastPattern = newPattern;
- return newPattern.HitObjects;
+ foreach (var obj in newPattern.HitObjects)
+ yield return obj;
+ }
}
///
@@ -138,27 +144,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null;
if (distanceData != null)
- conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
+ {
+ var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
+ conversion = generator;
+
+ for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
+ {
+ recordNote(time, positionData?.Position ?? Vector2.Zero);
+ computeDensity(time);
+ }
+ }
else if (endTimeData != null)
- conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap);
+ {
+ conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
+
+ recordNote(endTimeData.EndTime, new Vector2(256, 192));
+ computeDensity(endTimeData.EndTime);
+ }
else if (positionData != null)
{
computeDensity(original.StartTime);
- conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
+ conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position);
}
if (conversion == null)
- return null;
+ yield break;
- Pattern newPattern = conversion.Generate();
+ foreach (var newPattern in conversion.Generate())
+ {
+ lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
+ lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
- lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
- lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
+ foreach (var obj in newPattern.HitObjects)
+ yield return obj;
- return newPattern.HitObjects;
+ }
}
///
@@ -171,7 +194,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
}
- public override Pattern Generate()
+ public override IEnumerable Generate()
+ {
+ yield return generate();
+ }
+
+ private Pattern generate()
{
var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index c6fa465a0f..280c2f45d4 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.MathUtils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
@@ -24,8 +25,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
private const float osu_base_scoring_distance = 100;
- private readonly double endTime;
- private readonly double segmentDuration;
+ public readonly double EndTime;
+ public readonly double SegmentDuration;
+
private readonly int spanCount;
private PatternType convertType;
@@ -52,53 +54,81 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
- endTime = hitObject.StartTime + osuDuration;
- segmentDuration = (endTime - HitObject.StartTime) / spanCount;
+ EndTime = hitObject.StartTime + osuDuration;
+ SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
}
- public override Pattern Generate()
+ public override IEnumerable Generate()
+ {
+ var originalPattern = generate();
+
+ if (originalPattern.HitObjects.Count() == 1)
+ {
+ yield return originalPattern;
+ yield break;
+ }
+
+ // We need to split the intermediate pattern into two new patterns:
+ // 1. A pattern containing all objects that do not end at our EndTime.
+ // 2. A pattern containing all objects that end at our EndTime. This will be used for further pattern generation.
+ var intermediatePattern = new Pattern();
+ var endTimePattern = new Pattern();
+
+ foreach (var obj in originalPattern.HitObjects)
+ {
+ if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime))
+ intermediatePattern.Add(obj);
+ else
+ endTimePattern.Add(obj);
+ }
+
+ yield return intermediatePattern;
+ yield return endTimePattern;
+ }
+
+ private Pattern generate()
{
if (TotalColumns == 1)
{
var pattern = new Pattern();
- addToPattern(pattern, 0, HitObject.StartTime, endTime);
+ addToPattern(pattern, 0, HitObject.StartTime, EndTime);
return pattern;
}
if (spanCount > 1)
{
- if (segmentDuration <= 90)
+ if (SegmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1);
- if (segmentDuration <= 120)
+ if (SegmentDuration <= 120)
{
convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, spanCount + 1);
}
- if (segmentDuration <= 160)
+ if (SegmentDuration <= 160)
return generateStair(HitObject.StartTime);
- if (segmentDuration <= 200 && ConversionDifficulty > 3)
+ if (SegmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime);
- double duration = endTime - HitObject.StartTime;
+ double duration = EndTime - HitObject.StartTime;
if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
- if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
+ if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime);
}
- if (segmentDuration <= 110)
+ if (SegmentDuration <= 110)
{
if (PreviousPattern.ColumnWithObjects < TotalColumns)
convertType |= PatternType.ForceNotStack;
else
convertType &= ~PatternType.ForceNotStack;
- return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2);
+ return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2);
}
if (ConversionDifficulty > 6.5)
@@ -148,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
nextColumn = Random.Next(RandomStart, TotalColumns);
- addToPattern(pattern, nextColumn, startTime, endTime);
+ addToPattern(pattern, nextColumn, startTime, EndTime);
}
// This is can't be combined with the above loop due to RNG
@@ -156,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns);
- addToPattern(pattern, nextColumn, startTime, endTime);
+ addToPattern(pattern, nextColumn, startTime, EndTime);
}
return pattern;
@@ -193,7 +223,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
nextColumn = Random.Next(RandomStart, TotalColumns);
lastColumn = nextColumn;
- startTime += segmentDuration;
+ startTime += SegmentDuration;
}
return pattern;
@@ -223,7 +253,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
for (int i = 0; i <= spanCount; i++)
{
addToPattern(pattern, column, startTime, startTime);
- startTime += segmentDuration;
+ startTime += SegmentDuration;
// Check if we're at the borders of the stage, and invert the pattern if so
if (increasing)
@@ -284,7 +314,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, TotalColumns);
- startTime += segmentDuration;
+ startTime += SegmentDuration;
}
return pattern;
@@ -372,8 +402,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns);
- addToPattern(pattern, nextColumn, startTime, endTime);
- startTime += segmentDuration;
+ addToPattern(pattern, nextColumn, startTime, EndTime);
+ startTime += SegmentDuration;
}
return pattern;
@@ -402,7 +432,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
// Create the hold note
- addToPattern(pattern, holdColumn, startTime, endTime);
+ addToPattern(pattern, holdColumn, startTime, EndTime);
int nextColumn = Random.Next(RandomStart, TotalColumns);
int noteCount;
@@ -434,7 +464,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
pattern.Add(rowPattern);
rowPattern.Clear();
- startTime += segmentDuration;
+ startTime += SegmentDuration;
}
return pattern;
@@ -452,7 +482,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (curveData == null)
return HitObject.Samples;
- double segmentTime = (endTime - HitObject.StartTime) / spanCount;
+ double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 3f34afee85..b902ee63c6 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -19,12 +19,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{
- var endtimeData = HitObject as IHasEndTime;
-
- endTime = endtimeData?.EndTime ?? 0;
+ endTime = (HitObject as IHasEndTime)?.EndTime ?? 0;
}
- public override Pattern Generate()
+ public override IEnumerable Generate()
+ {
+ yield return generate();
+ }
+
+ private Pattern generate()
{
var pattern = new Pattern();
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index b4160dc98b..0e839d87a2 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.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 System.Linq;
using OpenTK;
using osu.Game.Audio;
@@ -82,127 +83,133 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror;
- else
+ else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
convertType |= PatternType.Gathered;
}
}
- public override Pattern Generate()
+ public override IEnumerable Generate()
{
- if (TotalColumns == 1)
+ yield return generate();
+ }
+
+ private Pattern generate()
+ {
+ var pattern = new Pattern();
+
+ try
{
- var pattern = new Pattern();
- addToPattern(pattern, 0);
- return pattern;
- }
-
- int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
-
- if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
- {
- // Generate a new pattern by copying the last hit objects in reverse-column order
- var pattern = new Pattern();
-
- for (int i = RandomStart; i < TotalColumns; i++)
- if (PreviousPattern.ColumnHasObject(i))
- addToPattern(pattern, RandomStart + TotalColumns - i - 1);
-
- return pattern;
- }
-
- if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
- // If we convert to 7K + 1, let's not overload the special key
- && (TotalColumns != 8 || lastColumn != 0)
- // Make sure the last column was not the centre column
- && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
- {
- // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
- var pattern = new Pattern();
-
- int column = RandomStart + TotalColumns - lastColumn - 1;
- addToPattern(pattern, column);
-
- return pattern;
- }
-
- if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
- {
- // Generate a new pattern by placing on the already filled columns
- var pattern = new Pattern();
-
- for (int i = RandomStart; i < TotalColumns; i++)
- if (PreviousPattern.ColumnHasObject(i))
- addToPattern(pattern, i);
-
- return pattern;
- }
-
- if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1)
- {
- // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
- var pattern = new Pattern();
-
- int targetColumn = lastColumn + 1;
- if (targetColumn == TotalColumns)
+ if (TotalColumns == 1)
{
- targetColumn = RandomStart;
- StairType = PatternType.ReverseStair;
+ addToPattern(pattern, 0);
+ return pattern;
}
- addToPattern(pattern, targetColumn);
- return pattern;
- }
+ int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
- if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1)
- {
- // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
- var pattern = new Pattern();
-
- int targetColumn = lastColumn - 1;
- if (targetColumn == RandomStart - 1)
+ if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{
- targetColumn = TotalColumns - 1;
- StairType = PatternType.Stair;
+ // Generate a new pattern by copying the last hit objects in reverse-column order
+ for (int i = RandomStart; i < TotalColumns; i++)
+ if (PreviousPattern.ColumnHasObject(i))
+ addToPattern(pattern, RandomStart + TotalColumns - i - 1);
+
+ return pattern;
}
- addToPattern(pattern, targetColumn);
- return pattern;
- }
+ if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
+ // If we convert to 7K + 1, let's not overload the special key
+ && (TotalColumns != 8 || lastColumn != 0)
+ // Make sure the last column was not the centre column
+ && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
+ {
+ // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
+ int column = RandomStart + TotalColumns - lastColumn - 1;
+ addToPattern(pattern, column);
- if ((convertType & PatternType.KeepSingle) > 0)
- return generateRandomNotes(1);
+ return pattern;
+ }
+
+ if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
+ {
+ // Generate a new pattern by placing on the already filled columns
+ for (int i = RandomStart; i < TotalColumns; i++)
+ if (PreviousPattern.ColumnHasObject(i))
+ addToPattern(pattern, i);
+
+ return pattern;
+ }
+
+ if (PreviousPattern.HitObjects.Count() == 1)
+ {
+ if ((convertType & PatternType.Stair) > 0)
+ {
+ // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
+ int targetColumn = lastColumn + 1;
+ if (targetColumn == TotalColumns)
+ targetColumn = RandomStart;
+
+ addToPattern(pattern, targetColumn);
+ return pattern;
+ }
+
+ if ((convertType & PatternType.ReverseStair) > 0)
+ {
+ // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
+ int targetColumn = lastColumn - 1;
+ if (targetColumn == RandomStart - 1)
+ targetColumn = TotalColumns - 1;
+
+ addToPattern(pattern, targetColumn);
+ return pattern;
+ }
+ }
+
+ if ((convertType & PatternType.KeepSingle) > 0)
+ return pattern = generateRandomNotes(1);
+
+ if ((convertType & PatternType.Mirror) > 0)
+ {
+ if (ConversionDifficulty > 6.5)
+ return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
+ if (ConversionDifficulty > 4)
+ return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
+ return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
+ }
- if ((convertType & PatternType.Mirror) > 0)
- {
if (ConversionDifficulty > 6.5)
- return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
+ {
+ if ((convertType & PatternType.LowProbability) > 0)
+ return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
+ return pattern = generateRandomPattern(1, 0.62, 0, 0);
+ }
+
if (ConversionDifficulty > 4)
- return generateRandomPatternWithMirrored(0.12, 0.17, 0);
- return generateRandomPatternWithMirrored(0.12, 0, 0);
- }
+ {
+ if ((convertType & PatternType.LowProbability) > 0)
+ return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
+ return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
+ }
- if (ConversionDifficulty > 6.5)
+ if (ConversionDifficulty > 2)
+ {
+ if ((convertType & PatternType.LowProbability) > 0)
+ return pattern = generateRandomPattern(0.18, 0, 0, 0);
+ return pattern = generateRandomPattern(0.45, 0, 0, 0);
+ }
+
+ return pattern = generateRandomPattern(0, 0, 0, 0);
+ }
+ finally
{
- if ((convertType & PatternType.LowProbability) > 0)
- return generateRandomPattern(0.78, 0.42, 0, 0);
- return generateRandomPattern(1, 0.62, 0, 0);
+ foreach (var obj in pattern.HitObjects)
+ {
+ if ((convertType & PatternType.Stair) > 0 && obj.Column == TotalColumns - 1)
+ StairType = PatternType.ReverseStair;
+ if ((convertType & PatternType.ReverseStair) > 0 && obj.Column == RandomStart)
+ StairType = PatternType.Stair;
+ }
}
-
- if (ConversionDifficulty > 4)
- {
- if ((convertType & PatternType.LowProbability) > 0)
- return generateRandomPattern(0.35, 0.08, 0, 0);
- return generateRandomPattern(0.52, 0.15, 0, 0);
- }
-
- if (ConversionDifficulty > 2)
- {
- if ((convertType & PatternType.LowProbability) > 0)
- return generateRandomPattern(0.18, 0, 0, 0);
- return generateRandomPattern(0.45, 0, 0, 0);
- }
-
- return generateRandomPattern(0, 0, 0, 0);
}
///
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 930597c1ad..55081e5822 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -103,17 +103,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
- double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
- drainTime -= OriginalBeatmap.TotalBreakTime;
+ // Drain time in seconds
+ int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - OriginalBeatmap.TotalBreakTime) / 1000);
if (drainTime == 0)
- drainTime = 10000000;
-
- // We need this in seconds
- drainTime /= 1000;
+ drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
- conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
+ conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
index 2bfcd52b6a..a42d57cdd1 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.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.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
@@ -42,9 +43,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
}
///
- /// Generates the pattern for , filled with hit objects.
+ /// Generates the patterns for , each filled with hit objects.
///
- /// The containing the hit objects.
- public abstract Pattern Generate();
+ /// The s containing the hit objects.
+ public abstract IEnumerable Generate();
}
}
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
index 9630ba9273..9055e48a4c 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
public class HoldNoteJudgement : ManiaJudgement
{
public override bool AffectsCombo => false;
+
protected override int NumericResultFor(HitResult result) => 0;
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
index 54a7bf954d..783142fadc 100644
--- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
@@ -19,14 +19,14 @@ namespace osu.Game.Rulesets.Mania
}
[BackgroundDependencyLoader]
- private void load(ManiaConfigManager config)
+ private void load()
{
Children = new Drawable[]
{
new SettingsEnumDropdown
{
LabelText = "Scrolling direction",
- Bindable = config.GetBindable(ManiaSetting.ScrollDirection)
+ Bindable = ((ManiaConfigManager)Config).GetBindable(ManiaSetting.ScrollDirection)
}
};
}
diff --git a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
index a3efd5c2bd..785cd5ab06 100644
--- a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
@@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Mania.MathUtils
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
- private uint _x, _y = y, _z = z, _w = w;
+
+ internal uint X { get; private set; }
+ internal uint Y { get; private set; } = y;
+ internal uint Z { get; private set; } = z;
+ internal uint W { get; private set; } = w;
public FastRandom(int seed)
{
- _x = (uint)seed;
+ X = (uint)seed;
}
public FastRandom()
@@ -33,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// The random value.
public uint NextUInt()
{
- uint t = _x ^ _x << 11;
- _x = _y;
- _y = _z;
- _z = _w;
- return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
+ uint t = X ^ X << 11;
+ X = Y;
+ Y = Z;
+ Z = W;
+ return W = W ^ W >> 19 ^ t ^ t >> 8;
}
///
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index ce0276f759..597450f223 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -38,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container tickContainer;
- public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
- : base(hitObject, action)
+ public DrawableHoldNote(HoldNote hitObject)
+ : base(hitObject)
{
RelativeSizeAxes = Axes.X;
@@ -57,12 +57,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = () => holdStartTime
})
},
- head = new DrawableHeadNote(this, action)
+ head = new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
- tail = new DrawableTailNote(this, action)
+ tail = new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
@@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false;
- if (action != Action)
+ if (action != Action.Value)
return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
@@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdStartTime.HasValue)
return false;
- if (action != Action)
+ if (action != Action.Value)
return false;
holdStartTime = null;
@@ -154,8 +154,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly DrawableHoldNote holdNote;
- public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action)
- : base(holdNote.HitObject.Head, action)
+ public DrawableHeadNote(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Head)
{
this.holdNote = holdNote;
}
@@ -191,8 +191,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly DrawableHoldNote holdNote;
- public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
- : base(holdNote.HitObject.Tail, action)
+ public DrawableTailNote(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Tail)
{
this.holdNote = holdNote;
}
@@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdNote.holdStartTime.HasValue)
return false;
- if (action != Action)
+ if (action != Action.Value)
return false;
UpdateJudgement(true);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index 1271fae0c1..cb6196a890 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
@@ -10,30 +11,26 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
- public abstract class DrawableManiaHitObject : DrawableHitObject
- where TObject : ManiaHitObject
+ public abstract class DrawableManiaHitObject : DrawableHitObject
{
///
- /// The key that will trigger input for this hit object.
+ /// The which causes this to be hit.
///
- protected ManiaAction Action { get; }
-
- public new TObject HitObject;
+ protected readonly IBindable Action = new Bindable();
protected readonly IBindable Direction = new Bindable();
- protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
+ protected DrawableManiaHitObject(ManiaHitObject hitObject)
: base(hitObject)
{
- HitObject = hitObject;
-
- if (action != null)
- Action = action.Value;
}
- [BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] IBindable action, [NotNull] IScrollingInfo scrollingInfo)
{
+ if (action != null)
+ Action.BindTo(action);
+
Direction.BindTo(scrollingInfo.Direction);
Direction.BindValueChanged(OnDirectionChanged, true);
}
@@ -42,6 +39,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
+ }
+
+ public abstract class DrawableManiaHitObject : DrawableManiaHitObject
+ where TObject : ManiaHitObject
+ {
+ public new readonly TObject HitObject;
+
+ protected DrawableManiaHitObject(TObject hitObject)
+ : base(hitObject)
+ {
+ HitObject = hitObject;
+ }
protected override void UpdateState(ArmedState state)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index fb4aa74ad1..18084c4c08 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly NotePiece headPiece;
- public DrawableNote(Note hitObject, ManiaAction action)
- : base(hitObject, action)
+ public DrawableNote(Note hitObject)
+ : base(hitObject)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public virtual bool OnPressed(ManiaAction action)
{
- if (action != Action)
+ if (action != Action.Value)
return false;
return UpdateJudgement(true);
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
index d593b2b052..753db99856 100644
--- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -1,103 +1,132 @@
{
"Mappings": [{
- "StartTime": 500,
- "Objects": [{
- "StartTime": 500,
- "EndTime": 2500,
- "Column": 0
- },
- {
- "StartTime": 1500,
- "EndTime": 2500,
- "Column": 1
- }
- ]
- },
- {
- "StartTime": 3000,
- "Objects": [{
- "StartTime": 3000,
- "EndTime": 4000,
- "Column": 2
- }]
- },
- {
- "StartTime": 4500,
- "Objects": [{
- "StartTime": 4500,
- "EndTime": 5500,
- "Column": 4
- }]
- },
- {
- "StartTime": 6000,
- "Objects": [{
- "StartTime": 6000,
- "EndTime": 6500,
- "Column": 2
- }]
- },
- {
- "StartTime": 7000,
- "Objects": [{
- "StartTime": 7000,
- "EndTime": 8000,
- "Column": 2
- }]
- },
- {
- "StartTime": 8500,
- "Objects": [{
- "StartTime": 8500,
- "EndTime": 11000,
- "Column": 0
- }]
- },
- {
- "StartTime": 11500,
- "Objects": [{
- "StartTime": 11500,
- "EndTime": 12000,
- "Column": 1
- }]
- },
- {
- "StartTime": 12500,
- "Objects": [{
- "StartTime": 12500,
- "EndTime": 16500,
- "Column": 4
- }]
- },
- {
- "StartTime": 17000,
- "Objects": [{
- "StartTime": 17000,
- "EndTime": 18000,
- "Column": 2
- }]
- },
- {
- "StartTime": 18500,
- "Objects": [{
- "StartTime": 18500,
- "EndTime": 19450,
- "Column": 0
- }]
- },
- {
- "StartTime": 19875,
- "Objects": [{
- "StartTime": 19875,
- "EndTime": 23875,
- "Column": 1
- },
- {
- "StartTime": 19875,
- "EndTime": 23875,
- "Column": 0
- }
- ]
- }
- ]
+ "RandomW": 2659373485,
+ "RandomX": 3579807591,
+ "RandomY": 273326509,
+ "RandomZ": 272969173,
+ "StartTime": 500.0,
+ "Objects": [{
+ "StartTime": 500.0,
+ "EndTime": 2500.0,
+ "Column": 0
+ }, {
+ "StartTime": 1500.0,
+ "EndTime": 2500.0,
+ "Column": 1
+ }]
+ }, {
+ "RandomW": 3083803045,
+ "RandomX": 273326509,
+ "RandomY": 272969173,
+ "RandomZ": 2659373485,
+ "StartTime": 3000.0,
+ "Objects": [{
+ "StartTime": 3000.0,
+ "EndTime": 4000.0,
+ "Column": 2
+ }]
+ }, {
+ "RandomW": 4073554232,
+ "RandomX": 272969173,
+ "RandomY": 2659373485,
+ "RandomZ": 3083803045,
+ "StartTime": 4500.0,
+ "Objects": [{
+ "StartTime": 4500.0,
+ "EndTime": 5500.0,
+ "Column": 4
+ }]
+ }, {
+ "RandomW": 3420401969,
+ "RandomX": 2659373485,
+ "RandomY": 3083803045,
+ "RandomZ": 4073554232,
+ "StartTime": 6000.0,
+ "Objects": [{
+ "StartTime": 6000.0,
+ "EndTime": 6500.0,
+ "Column": 2
+ }]
+ }, {
+ "RandomW": 1129881182,
+ "RandomX": 3083803045,
+ "RandomY": 4073554232,
+ "RandomZ": 3420401969,
+ "StartTime": 7000.0,
+ "Objects": [{
+ "StartTime": 7000.0,
+ "EndTime": 8000.0,
+ "Column": 2
+ }]
+ }, {
+ "RandomW": 315568458,
+ "RandomX": 3420401969,
+ "RandomY": 1129881182,
+ "RandomZ": 2358617505,
+ "StartTime": 8500.0,
+ "Objects": [{
+ "StartTime": 8500.0,
+ "EndTime": 11000.0,
+ "Column": 0
+ }]
+ }, {
+ "RandomW": 548134043,
+ "RandomX": 1129881182,
+ "RandomY": 2358617505,
+ "RandomZ": 315568458,
+ "StartTime": 11500.0,
+ "Objects": [{
+ "StartTime": 11500.0,
+ "EndTime": 12000.0,
+ "Column": 1
+ }]
+ }, {
+ "RandomW": 3979422122,
+ "RandomX": 548134043,
+ "RandomY": 2810584254,
+ "RandomZ": 2250186050,
+ "StartTime": 12500.0,
+ "Objects": [{
+ "StartTime": 12500.0,
+ "EndTime": 16500.0,
+ "Column": 4
+ }]
+ }, {
+ "RandomW": 2466283411,
+ "RandomX": 2810584254,
+ "RandomY": 2250186050,
+ "RandomZ": 3979422122,
+ "StartTime": 17000.0,
+ "Objects": [{
+ "StartTime": 17000.0,
+ "EndTime": 18000.0,
+ "Column": 2
+ }]
+ }, {
+ "RandomW": 83157665,
+ "RandomX": 2250186050,
+ "RandomY": 3979422122,
+ "RandomZ": 2466283411,
+ "StartTime": 18500.0,
+ "Objects": [{
+ "StartTime": 18500.0,
+ "EndTime": 19450.0,
+ "Column": 0
+ }]
+ }, {
+ "RandomW": 2383087700,
+ "RandomX": 83157665,
+ "RandomY": 2055150192,
+ "RandomZ": 510071020,
+ "StartTime": 19875.0,
+ "Objects": [{
+ "StartTime": 19875.0,
+ "EndTime": 23875.0,
+ "Column": 1
+ }, {
+ "StartTime": 19875.0,
+ "EndTime": 23875.0,
+ "Column": 0
+ }]
+ }]
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index e731ce9195..8465258055 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components;
@@ -19,21 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45;
private const float special_column_width = 70;
- private ManiaAction action;
-
- public ManiaAction Action
- {
- get => action;
- set
- {
- if (action == value)
- return;
- action = value;
-
- background.Action = value;
- keyArea.Action = value;
- }
- }
+ public readonly Bindable Action = new Bindable();
private readonly ColumnBackground background;
private readonly ColumnKeyArea keyArea;
@@ -130,6 +118,13 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs>(Action);
+ return dependencies;
+ }
+
///
/// Adds a DrawableHitObject to this Playfield.
///
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index 9b744bd254..19cc8fffef 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnBackground : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
{
- public ManiaAction Action;
+ private readonly IBindable action = new Bindable();
private Box background;
private Box backgroundOverlay;
@@ -25,8 +25,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private readonly IBindable direction = new Bindable();
[BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
+ private void load(IBindable action, IScrollingInfo scrollingInfo)
{
+ this.action.BindTo(action);
+
InternalChildren = new[]
{
background = new Box
@@ -91,14 +93,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components
public bool OnPressed(ManiaAction action)
{
- if (action == Action)
+ if (action == this.action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
public bool OnReleased(ManiaAction action)
{
- if (action == Action)
+ if (action == this.action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
return false;
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
index 4ce1614310..e30a033831 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
@@ -21,15 +21,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
- public ManiaAction Action;
-
+ private readonly IBindable action = new Bindable();
private readonly IBindable direction = new Bindable();
private Container keyIcon;
[BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
+ private void load(IBindable action, IScrollingInfo scrollingInfo)
{
+ this.action.BindTo(action);
+
Drawable gradient;
InternalChildren = new[]
@@ -107,14 +108,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components
public bool OnPressed(ManiaAction action)
{
- if (action == Action)
+ if (action == this.action.Value)
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false;
}
public bool OnReleased(ManiaAction action)
{
- if (action == Action)
+ if (action == this.action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
return false;
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index fa6fba0cd8..abc9705119 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -70,19 +70,19 @@ namespace osu.Game.Rulesets.Mania.UI
}
[BackgroundDependencyLoader]
- private void load(ManiaConfigManager config)
+ private void load()
{
BarLines.ForEach(Playfield.Add);
- config.BindWith(ManiaSetting.ScrollDirection, configDirection);
+ ((ManiaConfigManager)Config).BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
}
private DependencyContainer dependencies;
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(scrollingInfo = new ScrollingInfo());
return dependencies;
}
@@ -101,17 +101,15 @@ namespace osu.Game.Rulesets.Mania.UI
protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h)
{
- ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
-
- var holdNote = h as HoldNote;
- if (holdNote != null)
- return new DrawableHoldNote(holdNote, action);
-
- var note = h as Note;
- if (note != null)
- return new DrawableNote(note, action);
-
- return null;
+ switch (h)
+ {
+ case HoldNote holdNote:
+ return new DrawableHoldNote(holdNote);
+ case Note note:
+ return new DrawableNote(note);
+ default:
+ return null;
+ }
}
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index 7b68582944..4c7deb4567 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI
var column = new Column(direction)
{
IsSpecial = isSpecial,
- Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++
+ Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
};
AddColumn(column);
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 3fa039d946..3a551bbbcf 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public struct ConvertValue : IEquatable
{
///
- /// A sane value to account for osu!stable using ints everwhere.
+ /// A sane value to account for osu!stable using s everywhere.
///
private const double conversion_lenience = 2;
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index c7c9f4a01a..bbe2d67baa 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -15,10 +15,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
}
- public override void PostProcess()
+ public override void PreProcess()
{
+ base.PreProcess();
applyStacking((Beatmap)Beatmap);
- base.PostProcess();
}
private void applyStacking(Beatmap beatmap)
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue;
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
- double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
+ double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object.
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
- double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
+ double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
/* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 62fafd8196..5e91ed7a97 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -61,19 +61,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
- // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
- double preEmpt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
+ double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
int maxCombo = beatmap.HitObjects.Count();
- // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
+ // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
return new OsuDifficultyAttributes(mods, starRating)
{
AimStrain = aimRating,
SpeedStrain = speedRating,
- ApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5,
+ ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo
};
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
index b7c4470592..26becfdec9 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -12,11 +11,6 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public override HitResult MaxResult => HitResult.Great;
- ///
- /// The positional hit offset.
- ///
- public Vector2 PositionOffset;
-
protected override int NumericResultFor(HitResult result)
{
switch (result)
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
index c4e265aac9..d52de9f971 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Judgements
public class OsuSliderTailJudgement : OsuJudgement
{
public override bool AffectsCombo => false;
+
protected override int NumericResultFor(HitResult result) => 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 4220b72b16..4eff2a55c8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToDrawableHitObjects(IEnumerable drawables)
{
- void adjustFadeIn(OsuHitObject h) => h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier;
+ void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
foreach (var d in drawables.OfType())
{
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
var h = d.HitObject;
- var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadein;
+ var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn;
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index 4653f45149..4ac3b0c983 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
using (fp.BeginAbsoluteSequence(fadeInTime))
{
- fp.FadeIn(currHitObject.TimeFadein);
- fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out);
+ fp.FadeIn(currHitObject.TimeFadeIn);
+ fp.ScaleTo(1, currHitObject.TimeFadeIn, Easing.Out);
- fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out);
+ fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
- fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein);
+ fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn);
}
fp.Expire(true);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 9fe6dcd46c..c525b4bd97 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement
{
Result = result,
- PositionOffset = Vector2.Zero //todo: set to correct value
});
}
@@ -101,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdatePreemptState();
- ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt));
+ ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 7c9503dfe2..02def2189f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
- protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
+ protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn);
protected virtual void UpdateCurrentState(ArmedState state)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index dfab123038..f1907a92a8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -93,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.AccentColour = value;
Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour;
+
+ foreach (var drawableHitObject in NestedHitObjects)
+ drawableHitObject.AccentColour = AccentColour;
}
}
@@ -133,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
if (!userTriggered && Time.Current >= slider.EndTime)
{
- var judgementsCount = NestedHitObjects.Count;
+ var judgementsCount = NestedHitObjects.Count();
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 10539f85a2..1d3df69fb8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && Disc.Tracking)
- spmCounter.FadeIn(HitObject.TimeFadein);
+ spmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update();
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 94a61e7904..283d6b91f6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
- double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
+ double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 54126b934f..48a6365c00 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public event Action PositionChanged;
public double TimePreempt = 600;
- public double TimeFadein = 400;
+ public double TimeFadeIn = 400;
private Vector2 position;
@@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public virtual bool NewCombo { get; set; }
- public int IndexInCurrentCombo { get; set; }
+ public virtual int IndexInCurrentCombo { get; set; }
- public int ComboIndex { get; set; }
+ public virtual int ComboIndex { get; set; }
public bool LastInCombo { get; set; }
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
- TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
+ TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 2ebe5efd0f..698f9de787 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -26,6 +26,28 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
+ public override int ComboIndex
+ {
+ get => base.ComboIndex;
+ set
+ {
+ base.ComboIndex = value;
+ foreach (var n in NestedHitObjects.OfType())
+ n.ComboIndex = value;
+ }
+ }
+
+ public override int IndexInCurrentCombo
+ {
+ get => base.IndexInCurrentCombo;
+ set
+ {
+ base.IndexInCurrentCombo = value;
+ foreach (var n in NestedHitObjects.OfType())
+ n.IndexInCurrentCombo = value;
+ }
+ }
+
public SliderCurve Curve { get; } = new SliderCurve();
public List ControlPoints
@@ -147,7 +169,8 @@ namespace osu.Game.Rulesets.Osu.Objects
var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
- var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
+ var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL)
+ ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List();
if (firstSample != null)
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 4db6eb9883..54337a12be 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// This is so on repeats ticks don't appear too late to be visually processed by the player.
offset = 200;
else
- offset = TimeFadein * 0.66f;
+ offset = TimeFadeIn * 0.66f;
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index f2d5631e93..04724931ae 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI;
using System.Linq;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.UI
{
@@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject)
{
Origin = Anchor.Centre,
- Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset
+ Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition
};
judgementLayer.Add(explosion);
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 999e33e51a..e04b4d45f6 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private void loadBarLines()
{
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
- double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
+ double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 1628423fe8..2f5b4a13d9 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats
@@ -211,5 +212,41 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
}
}
+
+ [Test]
+ public void TestDecodeCustomSamples()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+ using (var resStream = Resource.OpenResource("custom-samples.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var hitObjects = decoder.Decode(stream).HitObjects;
+
+ Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
+ Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
+ Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
+ Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
+ }
+
+ SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
+ }
+
+ [Test]
+ public void TestDecodeCustomHitObjectSamples()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+ using (var resStream = Resource.OpenResource("custom-hitobject-samples.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var hitObjects = decoder.Decode(stream).HitObjects;
+
+ Assert.AreEqual("hit_1.wav", hitObjects[0].Samples[0].LookupNames.First());
+ Assert.AreEqual("hit_2.wav", hitObjects[1].Samples[0].LookupNames.First());
+ Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
+ Assert.AreEqual("hit_1.wav", hitObjects[3].Samples[0].LookupNames.First());
+ }
+
+ SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index b834be71f1..64bd563897 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -118,7 +118,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestParity(string beatmap)
{
var legacy = decode(beatmap, out Beatmap json);
- json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert();
+ json.WithDeepEqual(legacy)
+ .IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)
+ // Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism
+ || r.Name == nameof(LegacyDecoder.LegacySampleControlPoint.CustomSampleBank))
+ .Assert();
}
///
diff --git a/osu.Game.Tests/Resources/custom-hitobject-samples.osu b/osu.Game.Tests/Resources/custom-hitobject-samples.osu
new file mode 100644
index 0000000000..588672e2d9
--- /dev/null
+++ b/osu.Game.Tests/Resources/custom-hitobject-samples.osu
@@ -0,0 +1,16 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+
+[TimingPoints]
+2170,468.75,4,1,0,40,1,0
+2638,-100,4,1,1,40,0,0
+3107,-100,4,1,2,40,0,0
+3576,-100,4,1,0,40,0,0
+
+[HitObjects]
+255,193,2170,1,0,0:0:0:0:hit_1.wav
+256,191,2638,5,0,0:0:0:0:hit_2.wav
+255,193,3107,1,0,0:0:0:0:
+256,191,3576,1,0,0:0:0:0:hit_1.wav
diff --git a/osu.Game.Tests/Resources/custom-samples.osu b/osu.Game.Tests/Resources/custom-samples.osu
new file mode 100644
index 0000000000..1e0e6f558e
--- /dev/null
+++ b/osu.Game.Tests/Resources/custom-samples.osu
@@ -0,0 +1,16 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+
+[TimingPoints]
+2170,468.75,4,1,0,40,1,0
+2638,-100,4,1,1,40,0,0
+3107,-100,4,1,2,40,0,0
+3576,-100,4,1,0,40,0,0
+
+[HitObjects]
+255,193,2170,1,0,0:0:0:0:
+256,191,2638,5,0,0:0:0:0:
+255,193,3107,1,0,0:0:0:0:
+256,191,3576,1,0,0:0:0:0:
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index f52fecfd02..b232180eba 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -65,17 +65,19 @@ namespace osu.Game.Tests.Visual
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
- var ruleset = rulesetInfo.CreateInstance();
+ var instance = rulesetInfo.CreateInstance();
var testBeatmap = createTestBeatmap(rulesetInfo);
beatmaps.Add(testBeatmap);
+ AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
+
selectBeatmap(testBeatmap);
- testBeatmapLabels(ruleset);
+ testBeatmapLabels(instance);
// TODO: adjust cases once more info is shown for other gamemodes
- switch (ruleset)
+ switch (instance)
{
case OsuRuleset _:
testInfoLabels(5);
@@ -108,8 +110,8 @@ namespace osu.Game.Tests.Visual
private void testInfoLabels(int expectedCount)
{
- AddAssert("check infolabels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any());
- AddAssert("check infolabels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount);
+ AddAssert("check info labels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any());
+ AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount);
}
private void testNullBeatmap()
@@ -119,7 +121,7 @@ namespace osu.Game.Tests.Visual
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title);
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
- AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
+ AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
}
private void selectBeatmap(IBeatmap b)
diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
index 5eb81cdf9f..7f8133d638 100644
--- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
+++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
@@ -1,6 +1,9 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -13,6 +16,13 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseButtonSystem : OsuTestCase
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ButtonSystem),
+ typeof(ButtonArea),
+ typeof(Button)
+ };
+
public TestCaseButtonSystem()
{
OsuLogo logo;
@@ -30,6 +40,9 @@ namespace osu.Game.Tests.Visual
};
buttons.SetOsuLogo(logo);
+
+ foreach (var s in Enum.GetValues(typeof(ButtonSystemState)).OfType().Skip(1))
+ AddStep($"State to {s}", () => buttons.State = s);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs
new file mode 100644
index 0000000000..a8253a991a
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Screens.Menu;
+using OpenTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseDisclaimer : OsuTestCase
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ new Disclaimer()
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
index 7f5bce3b84..e8ac19e7fc 100644
--- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
@@ -1,6 +1,8 @@
// 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.ComponentModel;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
@@ -17,6 +19,12 @@ namespace osu.Game.Tests.Visual
[Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase
{
+ public override IReadOnlyList RequiredTypes => new[] {
+ typeof(Placeholder),
+ typeof(MessagePlaceholder),
+ typeof(RetrievalFailurePlaceholder),
+ };
+
private RulesetStore rulesets;
private readonly FailableLeaderboard leaderboard;
diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index 3255478bea..1a28442e38 100644
--- a/osu.Game.Tests/Visual/TestCaseMods.cs
+++ b/osu.Game.Tests/Visual/TestCaseMods.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
Ruleset ruleset = rulesetInfo.CreateInstance();
- AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo);
+ AddStep($"switch to {ruleset.Description}", () => Ruleset.Value = rulesetInfo);
switch (ruleset)
{
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
- testUnimplmentedMod(autoPilotMod);
+ testUnimplementedMod(autoPilotMod);
}
private void testManiaMods(ManiaRuleset ruleset)
@@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual
checkNotSelected(mod);
}
- private void testUnimplmentedMod(Mod mod)
+ private void testUnimplementedMod(Mod mod)
{
selectNext(mod);
checkNotSelected(mod);
diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
index 123c1fe055..bc232d814d 100644
--- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
@@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual
private class TestOnScreenDisplay : OnScreenDisplay
{
- protected override void Display(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
+ protected override void DisplayTemporarily(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs
index f1a21a58d5..7a4e4c1210 100644
--- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Timing;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using OpenTK.Graphics;
@@ -23,19 +22,15 @@ namespace osu.Game.Tests.Visual
public TestCaseOsuGame()
{
- var rateAdjustClock = new StopwatchClock(true);
- var framedClock = new FramedClock(rateAdjustClock);
- framedClock.ProcessFrame();
-
- Add(new Box
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- });
-
- Add(new Loader());
-
- AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v);
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ new Loader()
+ };
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
index d711d501fe..e4cb848d90 100644
--- a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
+++ b/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
@@ -14,9 +14,9 @@ namespace osu.Game.Tests.Visual
{
private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager();
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(trackManager);
dependencies.CacheAs(this);
return dependencies;
@@ -101,9 +101,9 @@ namespace osu.Game.Tests.Visual
AddInternal(track);
}
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(this);
return dependencies;
}
diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/TestCaseToolbar.cs
index fd218af054..96f14e6b32 100644
--- a/osu.Game.Tests/Visual/TestCaseToolbar.cs
+++ b/osu.Game.Tests/Visual/TestCaseToolbar.cs
@@ -16,8 +16,8 @@ namespace osu.Game.Tests.Visual
public override IReadOnlyList RequiredTypes => new[]
{
typeof(ToolbarButton),
- typeof(ToolbarModeSelector),
- typeof(ToolbarModeButton),
+ typeof(ToolbarRulesetSelector),
+ typeof(ToolbarRulesetButton),
typeof(ToolbarNotificationButton),
};
diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs
index aca832110a..cb281d045b 100644
--- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs
+++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs
@@ -53,7 +53,6 @@ namespace osu.Game.Tests.Visual
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
JoinDate = DateTimeOffset.Now.AddDays(-1),
LastVisit = DateTimeOffset.Now,
- Age = 1,
ProfileOrder = new[] { "me" },
Statistics = new UserStatistics
{
diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs
index f635b74030..4345d09e05 100644
--- a/osu.Game/Audio/SampleInfo.cs
+++ b/osu.Game/Audio/SampleInfo.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
namespace osu.Game.Audio
{
@@ -28,9 +29,37 @@ namespace osu.Game.Audio
///
public string Name;
+ ///
+ /// An optional suffix to provide priority lookup. Falls back to non-suffixed .
+ ///
+ public string Suffix;
+
///
/// The sample volume.
///
public int Volume;
+
+ ///
+ /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
+ ///
+ public virtual IEnumerable LookupNames
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Namespace))
+ {
+ if (!string.IsNullOrEmpty(Suffix))
+ yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
+ yield return $"{Namespace}/{Bank}-{Name}";
+ }
+
+ // check non-namespace as a fallback even when we have a namespace
+ if (!string.IsNullOrEmpty(Suffix))
+ yield return $"{Bank}-{Name}{Suffix}";
+ yield return $"{Bank}-{Name}";
+ }
+ }
+
+ public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 3afc3c4d32..303a19aab3 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -66,8 +66,8 @@ namespace osu.Game.Beatmaps
// General
public int AudioLeadIn { get; set; }
- public bool Countdown { get; set; }
- public float StackLeniency { get; set; }
+ public bool Countdown { get; set; } = true;
+ public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; }
public int RulesetID { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 50428cc5e6..fc4d43080e 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps
// by setting the model here, we can update the noline set id below.
b.BeatmapSet = model;
- fetchAndPopulateOnlineIDs(b);
+ fetchAndPopulateOnlineIDs(b, model.Beatmaps);
}
// check if a set already exists with the same online id, delete if it does.
@@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps
PostNotification?.Invoke(new SimpleNotification
{
Icon = FontAwesome.fa_superpowers,
- Text = "You gotta be a supporter to download for now 'yo"
+ Text = "You gotta be an osu!supporter to download for now 'yo"
});
return;
}
@@ -339,6 +339,8 @@ namespace osu.Game.Beatmaps
{
var beatmapInfos = new List();
+ bool invalidateOnlineIDs = false;
+
foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu")))
{
using (var raw = reader.GetStream(name))
@@ -355,9 +357,18 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
- // check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
- if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
- beatmap.BeatmapInfo.OnlineBeatmapID = null;
+ if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue)
+ {
+ var ourId = beatmap.BeatmapInfo.OnlineBeatmapID;
+
+ // check that no existing beatmap in database exists that is imported with the same online beatmap ID. if so, give it precedence.
+ if (QueryBeatmap(b => b.OnlineBeatmapID.Value == ourId) != null)
+ beatmap.BeatmapInfo.OnlineBeatmapID = null;
+
+ // check that no other beatmap in this imported set has a conflicting online beatmap ID. If so, presume *all* are incorrect.
+ if (beatmapInfos.Any(b => b.OnlineBeatmapID == ourId))
+ invalidateOnlineIDs = true;
+ }
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
@@ -375,6 +386,9 @@ namespace osu.Game.Beatmaps
}
}
+ if (invalidateOnlineIDs)
+ beatmapInfos.ForEach(b => b.OnlineBeatmapID = null);
+
return beatmapInfos;
}
@@ -382,9 +396,10 @@ namespace osu.Game.Beatmaps
/// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties.
///
/// The beatmap to populate.
+ /// The other beatmaps contained within this set.
/// Whether to re-query if the provided beatmap already has populated values.
/// True if population was successful.
- private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, bool force = false)
+ private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false)
{
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
return true;
@@ -404,6 +419,12 @@ namespace osu.Game.Beatmaps
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
+ if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID))
+ {
+ Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database);
+ return false;
+ }
+
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
return true;
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 71406c6034..43ae30f780 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -69,11 +69,22 @@ namespace osu.Game.Beatmaps
}
catch
{
- return new TrackVirtual();
+ return null;
}
}
- protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
+ protected override Waveform GetWaveform()
+ {
+ try
+ {
+ var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
+ return trackData == null ? null : new Waveform(trackData);
+ }
+ catch
+ {
+ return null;
+ }
+ }
protected override Storyboard GetStoryboard()
{
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 6c1bcd0531..57983ec568 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -14,7 +14,6 @@ namespace osu.Game.Beatmaps
public class BeatmapMetadata : IEquatable
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- [JsonIgnore]
public int ID { get; set; }
public string Title { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index bf1cd7d4ee..0173125e8b 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -6,23 +6,9 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
- public interface IBeatmapProcessor
- {
- IBeatmap Beatmap { get; }
-
- ///
- /// Post-processes to add mode-specific components that aren't added during conversion.
- ///
- /// An example of such a usage is for combo colours.
- ///
- ///
- void PostProcess();
- }
-
///
- /// Processes a post-converted Beatmap.
+ /// Provides functionality to alter a after it has been converted.
///
- /// The type of HitObject contained in the Beatmap.
public class BeatmapProcessor : IBeatmapProcessor
{
public IBeatmap Beatmap { get; }
@@ -32,13 +18,7 @@ namespace osu.Game.Beatmaps
Beatmap = beatmap;
}
- ///
- /// Post-processes a Beatmap to add mode-specific components that aren't added during conversion.
- ///
- /// An example of such a usage is for combo colours.
- ///
- ///
- public virtual void PostProcess()
+ public virtual void PreProcess()
{
IHasComboInformation lastObj = null;
@@ -62,5 +42,9 @@ namespace osu.Game.Beatmaps
lastObj = obj;
}
}
+
+ public virtual void PostProcess()
+ {
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index ed8fbdbb26..ebebe42097 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -13,7 +13,13 @@ namespace osu.Game.Beatmaps
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- public int? OnlineBeatmapSetID { get; set; }
+ private int? onlineBeatmapSetID;
+
+ public int? OnlineBeatmapSetID
+ {
+ get { return onlineBeatmapSetID; }
+ set { onlineBeatmapSetID = value > 0 ? value : null; }
+ }
public BeatmapMetadata Metadata { get; set; }
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index db9e712d86..9ed476d97c 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -14,6 +14,15 @@ namespace osu.Game.Beatmaps.ControlPoints
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
- public bool Equals(ControlPoint other) => Time.Equals(other?.Time);
+ ///
+ /// Whether this provides the same parametric changes as another .
+ /// Basically an equality check without considering the .
+ ///
+ /// The to compare to.
+ /// Whether this is equivalent to .
+ public virtual bool EquivalentTo(ControlPoint other) => true;
+
+ public bool Equals(ControlPoint other)
+ => EquivalentTo(other) && Time.Equals(other?.Time);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 9f717d21e3..526bddf51a 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -17,5 +17,10 @@ namespace osu.Game.Beatmaps.ControlPoints
}
private double speedMultiplier = 1;
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is DifficultyControlPoint difficulty
+ && SpeedMultiplier.Equals(difficulty.SpeedMultiplier);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 73d5232f44..dd9d568133 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -14,5 +14,11 @@ namespace osu.Game.Beatmaps.ControlPoints
/// Whether the first bar line of this control point is ignored.
///
public bool OmitFirstBarLine;
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is EffectControlPoint effect
+ && KiaiMode.Equals(effect.KiaiMode)
+ && OmitFirstBarLine.Equals(effect.OmitFirstBarLine);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index 5d801a1163..acccbcde46 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -30,5 +30,25 @@ namespace osu.Game.Beatmaps.ControlPoints
Name = sampleName,
Volume = SampleVolume,
};
+
+ ///
+ /// Applies and to a if necessary, returning the modified .
+ ///
+ /// The . This will not be modified.
+ /// The modified . This does not share a reference with .
+ public virtual SampleInfo ApplyTo(SampleInfo sampleInfo)
+ {
+ var newSampleInfo = sampleInfo.Clone();
+ newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank;
+ newSampleInfo.Name = sampleInfo.Name;
+ newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume;
+ return newSampleInfo;
+ }
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is SampleControlPoint sample
+ && SampleBank.Equals(sample.SampleBank)
+ && SampleVolume.Equals(sample.SampleVolume);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index d20b1b87a6..eb60133fed 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -23,5 +23,11 @@ namespace osu.Game.Beatmaps.ControlPoints
}
private double beatLength = 1000;
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is TimingControlPoint timing
+ && TimeSignature.Equals(timing.TimeSignature)
+ && BeatLength.Equals(timing.BeatLength);
}
}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 265c6832b2..25a76b52a7 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4");
- protected override Track GetTrack() => new TrackVirtual();
+ protected override Track GetTrack() => new TrackVirtual { Length = 1000 };
private class DummyRulesetInfo : RulesetInfo
{
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 581207607a..c79938e613 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -289,9 +289,9 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 4)
sampleSet = (LegacySampleBank)int.Parse(split[3]);
- //SampleBank sampleBank = SampleBank.Default;
- //if (split.Length >= 5)
- // sampleBank = (SampleBank)int.Parse(split[4]);
+ int customSampleBank = 0;
+ if (split.Length >= 5)
+ customSampleBank = int.Parse(split[4]);
int sampleVolume = defaultSampleVolume;
if (split.Length >= 6)
@@ -314,13 +314,9 @@ namespace osu.Game.Beatmaps.Formats
if (stringSampleSet == @"none")
stringSampleSet = @"normal";
- DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
- SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
- EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
-
if (timingChange)
{
- beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
+ handleTimingControlPoint(new TimingControlPoint
{
Time = time,
BeatLength = beatLength,
@@ -328,41 +324,68 @@ namespace osu.Game.Beatmaps.Formats
});
}
- if (speedMultiplier != difficultyPoint.SpeedMultiplier)
+ handleDifficultyControlPoint(new DifficultyControlPoint
{
- beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
- beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
- {
- Time = time,
- SpeedMultiplier = speedMultiplier
- });
- }
+ Time = time,
+ SpeedMultiplier = speedMultiplier
+ });
- if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume)
+ handleEffectControlPoint(new EffectControlPoint
{
- beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint
- {
- Time = time,
- SampleBank = stringSampleSet,
- SampleVolume = sampleVolume
- });
- }
+ Time = time,
+ KiaiMode = kiaiMode,
+ OmitFirstBarLine = omitFirstBarSignature
+ });
- if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
+ handleSampleControlPoint(new LegacySampleControlPoint
{
- beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
- {
- Time = time,
- KiaiMode = kiaiMode,
- OmitFirstBarLine = omitFirstBarSignature
- });
- }
+ Time = time,
+ SampleBank = stringSampleSet,
+ SampleVolume = sampleVolume,
+ CustomSampleBank = customSampleBank
+ });
}
catch (FormatException e)
{
}
}
+ private void handleTimingControlPoint(TimingControlPoint newPoint)
+ {
+ beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
+ }
+
+ private void handleDifficultyControlPoint(DifficultyControlPoint newPoint)
+ {
+ var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
+
+ if (newPoint.EquivalentTo(existing))
+ return;
+
+ beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == newPoint.Time);
+ beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
+ }
+
+ private void handleEffectControlPoint(EffectControlPoint newPoint)
+ {
+ var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
+
+ if (newPoint.EquivalentTo(existing))
+ return;
+
+ beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
+ }
+
+ private void handleSampleControlPoint(SampleControlPoint newPoint)
+ {
+ var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
+
+ if (newPoint.EquivalentTo(existing))
+ return;
+
+ beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
+ }
+
private void handleHitObject(string line)
{
// If the ruleset wasn't specified, assume the osu!standard ruleset.
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index e77efd8508..c8874c3bc7 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using osu.Framework.Logging;
+using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Formats
@@ -167,5 +169,25 @@ namespace osu.Game.Beatmaps.Formats
Pass = 2,
Foreground = 3
}
+
+ internal class LegacySampleControlPoint : SampleControlPoint
+ {
+ public int CustomSampleBank;
+
+ public override SampleInfo ApplyTo(SampleInfo sampleInfo)
+ {
+ var baseInfo = base.ApplyTo(sampleInfo);
+
+ if (CustomSampleBank > 1)
+ baseInfo.Suffix = CustomSampleBank.ToString();
+
+ return baseInfo;
+ }
+
+ public override bool EquivalentTo(ControlPoint other)
+ => base.EquivalentTo(other)
+ && other is LegacySampleControlPoint legacy
+ && CustomSampleBank == legacy.CustomSampleBank;
+ }
}
}
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index 00566093b8..cbf9d184ac 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -7,6 +7,9 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps
{
+ ///
+ /// Provides functionality to convert a for a .
+ ///
public interface IBeatmapConverter
{
///
diff --git a/osu.Game/Beatmaps/IBeatmapProcessor.cs b/osu.Game/Beatmaps/IBeatmapProcessor.cs
new file mode 100644
index 0000000000..282662373a
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapProcessor.cs
@@ -0,0 +1,40 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// Provides functionality to alter a after it has been converted.
+ ///
+ public interface IBeatmapProcessor
+ {
+ ///
+ /// The to process. This should already be converted to the applicable .
+ ///
+ IBeatmap Beatmap { get; }
+
+ ///
+ /// Processes the converted prior to being invoked.
+ ///
+ /// Nested s generated during will not be present by this point,
+ /// and no mods will have been applied to the s.
+ ///
+ ///
+ ///
+ /// This can only be used to add alterations to s generated directly through the conversion process.
+ ///
+ void PreProcess();
+
+ ///
+ /// Processes the converted after has been invoked.
+ ///
+ /// Nested s generated during will be present by this point,
+ /// and mods will have been applied to all s.
+ ///
+ ///
+ ///
+ /// This should be used to add alterations to s while they are in their most playable state.
+ ///
+ void PostProcess();
+ }
+}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 9b50aed077..74da978d9c 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -19,7 +19,7 @@ using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
- public abstract class WorkingBeatmap : IDisposable
+ public abstract partial class WorkingBeatmap : IDisposable
{
public readonly BeatmapInfo BeatmapInfo;
@@ -116,6 +116,10 @@ namespace osu.Game.Beatmaps
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
}
+ IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted);
+
+ processor?.PreProcess();
+
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
foreach (var obj in converted.HitObjects)
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
@@ -124,8 +128,7 @@ namespace osu.Game.Beatmaps
foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj);
- // Post-process
- rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
+ processor?.PostProcess();
return converted;
}
@@ -145,7 +148,7 @@ namespace osu.Game.Beatmaps
private Track populateTrack()
{
// we want to ensure that we always have a track, even if it's a fake one.
- var t = GetTrack() ?? new TrackVirtual();
+ var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap);
applyRateAdjustments(t);
return t;
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs
new file mode 100644
index 0000000000..0e0a9a3bb9
--- /dev/null
+++ b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs
@@ -0,0 +1,38 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Framework.Audio.Track;
+using osu.Game.Rulesets.Objects.Types;
+
+namespace osu.Game.Beatmaps
+{
+ public partial class WorkingBeatmap
+ {
+ ///
+ /// A type of which provides a valid length based on the s of an .
+ ///
+ protected class VirtualBeatmapTrack : TrackVirtual
+ {
+ private const double excess_length = 1000;
+
+ public VirtualBeatmapTrack(IBeatmap beatmap)
+ {
+ var lastObject = beatmap.HitObjects.LastOrDefault();
+
+ switch (lastObject)
+ {
+ case null:
+ Length = excess_length;
+ break;
+ case IHasEndTime endTime:
+ Length = endTime.EndTime + excess_length;
+ break;
+ default:
+ Length = lastObject.StartTime + excess_length;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index d6b6595b69..1d832d1c54 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -8,23 +8,27 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Configuration;
+using osu.Framework.Input.Bindings;
using osu.Game.Audio;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers
{
- public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner
+ public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler
{
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
+ protected virtual bool PlaySamplesOnStateChange => true;
+
private PreviewTrackManager previewTrackManager;
protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All);
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(this);
return dependencies;
}
@@ -63,18 +67,33 @@ namespace osu.Game.Graphics.Containers
return base.OnClick(state);
}
+ public virtual bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ State = Visibility.Hidden;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => false;
+
private void onStateChanged(Visibility visibility)
{
switch (visibility)
{
case Visibility.Visible:
if (OverlayActivationMode != OverlayActivation.Disabled)
- samplePopIn?.Play();
+ {
+ if (PlaySamplesOnStateChange) samplePopIn?.Play();
+ }
else
State = Visibility.Hidden;
break;
case Visibility.Hidden:
- samplePopOut?.Play();
+ if (PlaySamplesOnStateChange) samplePopOut?.Play();
break;
}
}
diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
index 119af4d762..e77e075fe2 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
@@ -16,6 +17,8 @@ namespace osu.Game.Graphics.Containers
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
+ public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable);
+
public void AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters);
}
}
diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs
index 5f57fb76b0..517be4c055 100644
--- a/osu.Game/Graphics/Cursor/MenuCursor.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursor.cs
@@ -11,9 +11,9 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Configuration;
using System;
-using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Graphics.Textures;
+using OpenTK.Input;
namespace osu.Game.Graphics.Cursor
{
@@ -22,12 +22,13 @@ namespace osu.Game.Graphics.Cursor
private readonly IBindable screenshotCursorVisibility = new Bindable(true);
public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent;
- protected override Drawable CreateCursor() => new Cursor();
+ protected override Drawable CreateCursor() => activeCursor = new Cursor();
+
+ private Cursor activeCursor;
private Bindable cursorRotate;
- private bool dragging;
-
- private bool startRotation;
+ private DragRotationState dragRotationState;
+ private Vector2 positionMouseDown;
[BackgroundDependencyLoader(true)]
private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager)
@@ -40,46 +41,50 @@ namespace osu.Game.Graphics.Cursor
protected override bool OnMouseMove(InputState state)
{
- if (cursorRotate && dragging)
+ if (dragRotationState != DragRotationState.NotDragging)
{
- Debug.Assert(state.Mouse.PositionMouseDown != null);
-
+ var position = state.Mouse.Position;
+ var distance = Vector2Extensions.Distance(position, positionMouseDown);
// don't start rotating until we're moved a minimum distance away from the mouse down location,
// else it can have an annoying effect.
- // ReSharper disable once PossibleInvalidOperationException
- startRotation |= Vector2Extensions.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30;
-
- if (startRotation)
+ if (dragRotationState == DragRotationState.DragStarted && distance > 30)
+ dragRotationState = DragRotationState.Rotating;
+ // don't rotate when distance is zero to avoid NaN
+ if (dragRotationState == DragRotationState.Rotating && distance > 0)
{
- Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
+ Vector2 offset = state.Mouse.Position - positionMouseDown;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance
- float diff = (degrees - ActiveCursor.Rotation) % 360;
+ float diff = (degrees - activeCursor.Rotation) % 360;
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
- degrees = ActiveCursor.Rotation + diff;
+ degrees = activeCursor.Rotation + diff;
- ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint);
+ activeCursor.RotateTo(degrees, 600, Easing.OutQuint);
}
}
return base.OnMouseMove(state);
}
- protected override bool OnDragStart(InputState state)
- {
- dragging = true;
- return base.OnDragStart(state);
- }
-
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
- ActiveCursor.Scale = new Vector2(1);
- ActiveCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
+ // only trigger animation for main mouse buttons
+ if (args.Button <= MouseButton.Right)
+ {
+ activeCursor.Scale = new Vector2(1);
+ activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
- ((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0;
- ((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
+ activeCursor.AdditiveLayer.Alpha = 0;
+ activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
+ }
+
+ if (args.Button == MouseButton.Left && cursorRotate)
+ {
+ dragRotationState = DragRotationState.DragStarted;
+ positionMouseDown = state.Mouse.Position;
+ }
return base.OnMouseDown(state, args);
}
@@ -87,34 +92,29 @@ namespace osu.Game.Graphics.Cursor
{
if (!state.Mouse.HasMainButtonPressed)
{
- dragging = false;
- startRotation = false;
-
- ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint);
- ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf);
- ActiveCursor.ScaleTo(1, 500, Easing.OutElastic);
+ activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint);
+ activeCursor.ScaleTo(1, 500, Easing.OutElastic);
}
+ if (args.Button == MouseButton.Left)
+ {
+ if (dragRotationState == DragRotationState.Rotating)
+ activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf);
+ dragRotationState = DragRotationState.NotDragging;
+ }
return base.OnMouseUp(state, args);
}
- protected override bool OnClick(InputState state)
- {
- ((Cursor)ActiveCursor).AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint);
-
- return base.OnClick(state);
- }
-
protected override void PopIn()
{
- ActiveCursor.FadeTo(1, 250, Easing.OutQuint);
- ActiveCursor.ScaleTo(1, 400, Easing.OutQuint);
+ activeCursor.FadeTo(1, 250, Easing.OutQuint);
+ activeCursor.ScaleTo(1, 400, Easing.OutQuint);
}
protected override void PopOut()
{
- ActiveCursor.FadeTo(0, 250, Easing.OutQuint);
- ActiveCursor.ScaleTo(0.6f, 250, Easing.In);
+ activeCursor.FadeTo(0, 250, Easing.OutQuint);
+ activeCursor.ScaleTo(0.6f, 250, Easing.In);
}
public class Cursor : Container
@@ -160,5 +160,12 @@ namespace osu.Game.Graphics.Cursor
cursorScale.TriggerChange();
}
}
+
+ private enum DragRotationState
+ {
+ NotDragging,
+ DragStarted,
+ Rotating,
+ }
}
}
diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs
index c0e331148d..44156f6e83 100644
--- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs
+++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Graphics.Cursor
{
}
+ protected override double AppearDelay => (1 - CurrentTooltip.Alpha) * base.AppearDelay; // reduce appear delay if the tooltip is already partly visible.
+
public class OsuTooltip : Tooltip
{
private readonly Box background;
diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs
index 763e57e397..406fc2ffd2 100644
--- a/osu.Game/Graphics/DrawableDate.cs
+++ b/osu.Game/Graphics/DrawableDate.cs
@@ -12,14 +12,14 @@ namespace osu.Game.Graphics
{
public class DrawableDate : OsuSpriteText, IHasTooltip
{
- private readonly DateTimeOffset date;
+ protected readonly DateTimeOffset Date;
public DrawableDate(DateTimeOffset date)
{
AutoSizeAxes = Axes.Both;
Font = "Exo2.0-RegularItalic";
- this.date = date.ToLocalTime();
+ Date = date.ToLocalTime();
}
[BackgroundDependencyLoader]
@@ -38,7 +38,7 @@ namespace osu.Game.Graphics
{
updateTime();
- var diffToNow = DateTimeOffset.Now.Subtract(date);
+ var diffToNow = DateTimeOffset.Now.Subtract(Date);
double timeUntilNextUpdate = 1000;
if (diffToNow.TotalSeconds > 60)
@@ -58,8 +58,10 @@ namespace osu.Game.Graphics
public override bool HandleMouseInput => true;
- private void updateTime() => Text = date.Humanize();
+ protected virtual string Format() => Date.Humanize();
- public string TooltipText => date.ToString();
+ private void updateTime() => Text = Format();
+
+ public virtual string TooltipText => string.Format($"{Date:MMMM d, yyyy h:mm tt \"UTC\"z}");
}
}
diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
index 6500578de3..28d04c9a82 100644
--- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
@@ -2,9 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
-using OpenTK.Input;
using osu.Framework.Input;
using System;
+using osu.Game.Input.Bindings;
+using OpenTK.Input;
namespace osu.Game.Graphics.UserInterface
{
@@ -19,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface
public Action Exit;
private bool focus;
+
public bool HoldFocus
{
get { return focus; }
@@ -26,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
{
focus = value;
if (!focus && HasFocus)
- GetContainingInputManager().ChangeFocus(null);
+ base.KillFocus();
}
}
@@ -41,18 +43,34 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
- if (!args.Repeat && args.Key == Key.Escape)
- {
- if (Text.Length > 0)
- Text = string.Empty;
- else
- Exit?.Invoke();
- return true;
- }
+ if (!HasFocus) return false;
+
+ if (args.Key == Key.Escape)
+ return false; // disable the framework-level handling of escape key for confority (we use GlobalAction.Back).
return base.OnKeyDown(state, args);
}
+ public override bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ if (Text.Length > 0)
+ {
+ Text = string.Empty;
+ return true;
+ }
+ }
+
+ return base.OnPressed(action);
+ }
+
+ protected override void KillFocus()
+ {
+ base.KillFocus();
+ Exit?.Invoke();
+ }
+
public override bool RequestsFocus => HoldFocus;
}
}
diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
index d34d2b2a7c..07920865c0 100644
--- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface
{
protected override Drawable GetDrawableCharacter(char c) => new PasswordMaskChar(CalculatedTextSize);
- public override bool AllowClipboardExport => false;
+ protected override bool AllowClipboardExport => false;
private readonly CapsWarning warning;
diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index 6021af2028..88b0543de0 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -9,10 +9,12 @@ using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface
{
- public class OsuTextBox : TextBox
+ public class OsuTextBox : TextBox, IKeyBindingHandler
{
protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f);
protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f);
@@ -33,10 +35,7 @@ namespace osu.Game.Graphics.UserInterface
TextContainer.Height = 0.5f;
CornerRadius = 5;
- Current.DisabledChanged += disabled =>
- {
- Alpha = disabled ? 0.3f : 1;
- };
+ Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
}
[BackgroundDependencyLoader]
@@ -59,5 +58,18 @@ namespace osu.Game.Graphics.UserInterface
}
protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), TextSize = CalculatedTextSize };
+
+ public virtual bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Back)
+ {
+ KillFocus();
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(GlobalAction action) => false;
}
}
diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
index e50539e120..7d53c9e9d9 100644
--- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
@@ -34,8 +34,6 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
- if (HandlePendingText(state)) return true;
-
if (!state.Keyboard.ControlPressed && !state.Keyboard.ShiftPressed)
{
switch (args.Key)
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index fced2e5d95..b21deff509 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -39,13 +39,18 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
new KeyBinding(InputKey.Escape, GlobalAction.Back),
- new KeyBinding(InputKey.MouseButton1, GlobalAction.Back)
+ new KeyBinding(InputKey.MouseButton1, GlobalAction.Back),
+
+ new KeyBinding(InputKey.Space, GlobalAction.Select),
+ new KeyBinding(InputKey.Enter, GlobalAction.Select),
};
public IEnumerable InGameKeyBindings => new[]
{
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
- new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry)
+ new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry),
+ new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed),
+ new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
};
protected override IEnumerable KeyBindingInputQueue =>
@@ -84,7 +89,16 @@ namespace osu.Game.Input.Bindings
[Description("Toggle gameplay mouse buttons")]
ToggleGameplayMouseButtons,
- [Description("Go back")]
- Back
+ [Description("Back")]
+ Back,
+
+ [Description("Increase scroll speed")]
+ IncreaseScrollSpeed,
+
+ [Description("Decrease scroll speed")]
+ DecreaseScrollSpeed,
+
+ [Description("Select")]
+ Select,
}
}
diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs
new file mode 100644
index 0000000000..7eeacd56d7
--- /dev/null
+++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs
@@ -0,0 +1,376 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20180628011956_RemoveNegativeSetIDs")]
+ partial class RemoveNegativeSetIDs
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntKey")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs
new file mode 100644
index 0000000000..c38cd19b42
--- /dev/null
+++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs
@@ -0,0 +1,19 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class RemoveNegativeSetIDs : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ // There was a change that baetmaps were being loaded with "-1" online IDs, which is completely incorrect.
+ // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps.
+ migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
index 3b6bb565b0..bbaaa0c756 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Online.API.Requests.Responses
private DateTimeOffset submitted { get; set; }
[JsonProperty(@"ranked_date")]
- private DateTimeOffset ranked { get; set; }
+ private DateTimeOffset? ranked { get; set; }
[JsonProperty(@"last_updated")]
private DateTimeOffset lastUpdated { get; set; }
diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs
index a398bf46ee..25eb32a79f 100644
--- a/osu.Game/Online/API/Requests/Responses/APIScore.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs
@@ -63,7 +63,14 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"beatmapset")]
private BeatmapMetadata metadata
{
- set => Beatmap.Metadata = value;
+ set
+ {
+ // extract the set ID to its correct place.
+ Beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID };
+ value.ID = 0;
+
+ Beatmap.Metadata = value;
+ }
}
[JsonProperty(@"statistics")]
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index ba8685b5b2..b654e5d53f 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -85,7 +85,7 @@ namespace osu.Game
private OnScreenDisplay onscreenDisplay;
private Bindable configRuleset;
- public Bindable Ruleset = new Bindable();
+ private readonly Bindable ruleset = new Bindable();
private Bindable configSkin;
@@ -122,8 +122,8 @@ namespace osu.Game
private DependencyContainer dependencies;
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
- dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig)
@@ -147,10 +147,13 @@ namespace osu.Game
dependencies.CacheAs(this);
+ dependencies.CacheAs(ruleset);
+ dependencies.CacheAs>(ruleset);
+
// bind config int to database RulesetInfo
configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset);
- Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
- Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0;
+ ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
+ ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0;
// bind config int to database SkinInfo
configSkin = LocalConfig.GetBindable(OsuSetting.Skin);
@@ -216,7 +219,7 @@ namespace osu.Game
return;
}
- Ruleset.Value = s.Ruleset;
+ ruleset.Value = s.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap);
Beatmap.Value.Mods.Value = s.Mods;
@@ -247,7 +250,8 @@ namespace osu.Game
new VolumeControlReceptor
{
RelativeSizeAxes = Axes.Both,
- ActionRequested = action => volume.Adjust(action)
+ ActionRequested = action => volume.Adjust(action),
+ ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
},
mainContent = new Container { RelativeSizeAxes = Axes.Both },
overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue },
@@ -550,7 +554,7 @@ namespace osu.Game
// the use case for not applying is in visual/unit tests.
bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false;
- Ruleset.Disabled = applyRestrictions;
+ ruleset.Disabled = applyRestrictions;
Beatmap.Disabled = applyRestrictions;
mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 246229a794..a9b74d6740 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -20,6 +20,7 @@ using osu.Game.Graphics.Cursor;
using osu.Game.Online.API;
using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Database;
@@ -30,6 +31,7 @@ using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
+using OpenTK.Input;
using DebugUtils = osu.Game.Utils.DebugUtils;
namespace osu.Game
@@ -93,11 +95,13 @@ namespace osu.Game
private DependencyContainer dependencies;
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
- dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
private DatabaseContextFactory contextFactory;
+ protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
+
[BackgroundDependencyLoader]
private void load()
{
@@ -267,5 +271,31 @@ namespace osu.Game
return copy;
}
}
+
+ private class OsuUserInputManager : UserInputManager
+ {
+ protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button)
+ {
+ switch (button)
+ {
+ case MouseButton.Right:
+ return new RightMouseManager(button);
+ }
+
+ return base.CreateButtonManagerFor(button);
+ }
+
+ private class RightMouseManager : MouseButtonEventManager
+ {
+ public RightMouseManager(MouseButton button)
+ : base(button)
+ {
+ }
+
+ public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers.
+ public override bool EnableClick => false;
+ public override bool ChangeFocusOnClick => false;
+ }
+ }
}
}
diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs
index 5f90ec67ad..e32d7fb036 100644
--- a/osu.Game/Overlays/Dialog/PopupDialog.cs
+++ b/osu.Game/Overlays/Dialog/PopupDialog.cs
@@ -6,16 +6,17 @@ using System.Linq;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
+using osu.Game.Input.Bindings;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Dialog
{
@@ -23,6 +24,9 @@ namespace osu.Game.Overlays.Dialog
{
public static readonly float ENTER_DURATION = 500;
public static readonly float EXIT_DURATION = 200;
+
+ protected override bool BlockPassThroughMouse => false;
+
private readonly Vector2 ringSize = new Vector2(100f);
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
@@ -34,26 +38,28 @@ namespace osu.Game.Overlays.Dialog
private readonly SpriteText header;
private readonly TextFlowContainer body;
+ private bool actionInvoked;
+
public FontAwesome Icon
{
- get { return icon.Icon; }
- set { icon.Icon = value; }
+ get => icon.Icon;
+ set => icon.Icon = value;
}
public string HeaderText
{
- get { return header.Text; }
- set { header.Text = value; }
+ get => header.Text;
+ set => header.Text = value;
}
public string BodyText
{
- set { body.Text = value; }
+ set => body.Text = value;
}
public IEnumerable Buttons
{
- get { return buttonsContainer.Children; }
+ get => buttonsContainer.Children;
set
{
buttonsContainer.ChildrenEnumerable = value;
@@ -62,71 +68,17 @@ namespace osu.Game.Overlays.Dialog
var action = b.Action;
b.Action = () =>
{
- Hide();
+ if (actionInvoked) return;
+
+ actionInvoked = true;
action?.Invoke();
+
+ Hide();
};
}
}
}
- private void pressButtonAtIndex(int index)
- {
- if (index < Buttons.Count())
- Buttons.Skip(index).First().TriggerOnClick();
- }
-
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
- {
- if (args.Repeat) return false;
-
- if (args.Key == Key.Enter || args.Key == Key.KeypadEnter)
- {
- Buttons.OfType().FirstOrDefault()?.TriggerOnClick();
- return true;
- }
-
- // press button at number if 1-9 on number row or keypad are pressed
- var k = args.Key;
- if (k >= Key.Number1 && k <= Key.Number9)
- {
- pressButtonAtIndex(k - Key.Number1);
- return true;
- }
-
- if (k >= Key.Keypad1 && k <= Key.Keypad9)
- {
- pressButtonAtIndex(k - Key.Keypad1);
- return true;
- }
-
- return base.OnKeyDown(state, args);
- }
-
- protected override void PopIn()
- {
- base.PopIn();
-
- // Reset various animations but only if the dialog animation fully completed
- if (content.Alpha == 0)
- {
- buttonsContainer.TransformSpacingTo(buttonsEnterSpacing);
- buttonsContainer.MoveToY(buttonsEnterSpacing.Y);
- ring.ResizeTo(ringMinifiedSize);
- }
-
- content.FadeIn(ENTER_DURATION, Easing.OutQuint);
- ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint);
- buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint);
- buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint);
- }
-
- protected override void PopOut()
- {
- base.PopOut();
-
- content.FadeOut(EXIT_DURATION, Easing.InSine);
- }
-
public PopupDialog()
{
RelativeSizeAxes = Axes.Both;
@@ -136,9 +88,6 @@ namespace osu.Game.Overlays.Dialog
content = new Container
{
RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- Width = 0.4f,
Alpha = 0f,
Children = new Drawable[]
{
@@ -243,5 +192,75 @@ namespace osu.Game.Overlays.Dialog
},
};
}
+
+ public override bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Select:
+ Buttons.OfType().FirstOrDefault()?.TriggerOnClick();
+ return true;
+ }
+
+ return base.OnPressed(action);
+ }
+
+ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ {
+ if (args.Repeat) return false;
+
+ // press button at number if 1-9 on number row or keypad are pressed
+ var k = args.Key;
+ if (k >= Key.Number1 && k <= Key.Number9)
+ {
+ pressButtonAtIndex(k - Key.Number1);
+ return true;
+ }
+
+ if (k >= Key.Keypad1 && k <= Key.Keypad9)
+ {
+ pressButtonAtIndex(k - Key.Keypad1);
+ return true;
+ }
+
+ return base.OnKeyDown(state, args);
+ }
+
+ protected override void PopIn()
+ {
+ base.PopIn();
+
+ actionInvoked = false;
+
+ // Reset various animations but only if the dialog animation fully completed
+ if (content.Alpha == 0)
+ {
+ buttonsContainer.TransformSpacingTo(buttonsEnterSpacing);
+ buttonsContainer.MoveToY(buttonsEnterSpacing.Y);
+ ring.ResizeTo(ringMinifiedSize);
+ }
+
+ content.FadeIn(ENTER_DURATION, Easing.OutQuint);
+ ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint);
+ buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint);
+ buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint);
+ }
+
+ protected override void PopOut()
+ {
+ if (!actionInvoked)
+ // In the case a user did not choose an action before a hide was triggered, press the last button.
+ // This is presumed to always be a sane default "cancel" action.
+ buttonsContainer.Last().TriggerOnClick();
+
+ base.PopOut();
+ content.FadeOut(EXIT_DURATION, Easing.InSine);
+ }
+
+ private void pressButtonAtIndex(int index)
+ {
+ if (index < Buttons.Count())
+ Buttons.Skip(index).First().TriggerOnClick();
+ }
}
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index 7caab0dd6c..c40f517023 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -1,12 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Dialog;
-using OpenTK.Graphics;
-using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays
@@ -16,6 +13,20 @@ namespace osu.Game.Overlays
private readonly Container dialogContainer;
private PopupDialog currentDialog;
+ public DialogOverlay()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Child = dialogContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+
+ Width = 0.4f;
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ }
+
public void Push(PopupDialog dialog)
{
if (dialog == currentDialog) return;
@@ -30,6 +41,10 @@ namespace osu.Game.Overlays
State = Visibility.Visible;
}
+ protected override bool PlaySamplesOnStateChange => false;
+
+ protected override bool BlockPassThroughKeyboard => true;
+
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
{
if (v != Visibility.Hidden) return;
@@ -50,32 +65,14 @@ namespace osu.Game.Overlays
protected override void PopOut()
{
base.PopOut();
- this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
- }
- public DialogOverlay()
- {
- RelativeSizeAxes = Axes.Both;
-
- Children = new Drawable[]
+ if (currentDialog?.State == Visibility.Visible)
{
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black.Opacity(0.5f),
- },
- },
- },
- dialogContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- };
+ currentDialog.Hide();
+ return;
+ }
+
+ this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
}
}
}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 4f4348c131..df98cc3c32 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -35,15 +35,13 @@ namespace osu.Game.Overlays.Direct
}
[BackgroundDependencyLoader(true)]
- private void load(OsuGame game, RulesetStore rulesets, OsuColour colours)
+ private void load(RulesetStore rulesets, OsuColour colours, Bindable ruleset)
{
DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
- Ruleset.Value = game?.Ruleset.Value ?? rulesets.GetRuleset(0);
+ Ruleset.Value = ruleset ?? rulesets.GetRuleset(0);
foreach (var r in rulesets.AvailableRulesets)
- {
modeButtons.Add(new RulesetToggleButton(Ruleset, r));
- }
}
private class RulesetToggleButton : OsuClickableContainer
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index a12f9dee7e..d407dc9cf9 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -102,6 +102,7 @@ namespace osu.Game.Overlays.KeyBinding
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding(padding),
+ Padding = new MarginPadding { Top = height },
Alpha = 0,
Colour = colours.YellowDark
}
@@ -186,7 +187,7 @@ namespace osu.Game.Overlays.KeyBinding
{
if (bindTarget.IsHovered)
{
- bindTarget.UpdateKeyCombination(new KeyCombination(KeyCombination.FromInputState(state).Keys.Append(state.Mouse.ScrollDelta.Y > 0 ? InputKey.MouseWheelUp : InputKey.MouseWheelDown)));
+ bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state, state.Mouse.ScrollDelta));
finalise();
return true;
}
@@ -202,9 +203,6 @@ namespace osu.Game.Overlays.KeyBinding
switch (args.Key)
{
- case Key.Escape:
- finalise();
- return true;
case Key.Delete:
{
if (state.Keyboard.ShiftPressed)
@@ -270,7 +268,7 @@ namespace osu.Game.Overlays.KeyBinding
GetContainingInputManager().ChangeFocus(null);
pressAKey.FadeOut(300, Easing.OutQuint);
- pressAKey.Padding = new MarginPadding { Top = height, Bottom = -pressAKey.DrawHeight };
+ pressAKey.BypassAutoSizeAxes |= Axes.Y;
}
protected override void OnFocus(InputState state)
@@ -279,7 +277,7 @@ namespace osu.Game.Overlays.KeyBinding
AutoSizeEasing = Easing.OutQuint;
pressAKey.FadeIn(300, Easing.OutQuint);
- pressAKey.Padding = new MarginPadding { Top = height };
+ pressAKey.BypassAutoSizeAxes &= ~Axes.Y;
updateBindTarget();
base.OnFocus(state);
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index f1624721da..c584a32a82 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Mods
public readonly Bindable> SelectedMods = new Bindable>();
- public readonly Bindable Ruleset = new Bindable();
+ public readonly IBindable Ruleset = new Bindable();
private void rulesetChanged(RulesetInfo newRuleset)
{
@@ -51,8 +51,8 @@ namespace osu.Game.Overlays.Mods
refreshSelectedMods();
}
- [BackgroundDependencyLoader(permitNulls: true)]
- private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets, AudioManager audio)
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, IBindable ruleset, AudioManager audio)
{
SelectedMods.ValueChanged += selectedModsChanged;
@@ -60,13 +60,8 @@ namespace osu.Game.Overlays.Mods
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
- if (osu != null)
- Ruleset.BindTo(osu.Ruleset);
- else
- Ruleset.Value = rulesets.AvailableRulesets.First();
-
- Ruleset.ValueChanged += rulesetChanged;
- Ruleset.TriggerChange();
+ Ruleset.BindTo(ruleset);
+ Ruleset.BindValueChanged(rulesetChanged, true);
sampleOn = audio.Sample.Get(@"UI/check-on");
sampleOff = audio.Sample.Get(@"UI/check-off");
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index 1c80f2e626..753cd33cc6 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -14,6 +14,8 @@ using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics.Transforms;
+using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
@@ -135,7 +137,7 @@ namespace osu.Game.Overlays
/// If is already being tracked from the same .
public void BeginTracking(object source, ITrackableConfigManager configManager)
{
- if (configManager == null) throw new ArgumentNullException(nameof(configManager));
+ if (configManager == null) throw new ArgumentNullException(nameof(configManager));
if (trackedConfigManagers.ContainsKey((source, configManager)))
throw new InvalidOperationException($"{nameof(configManager)} is already registered.");
@@ -159,7 +161,7 @@ namespace osu.Game.Overlays
/// If is not being tracked from the same .
public void StopTracking(object source, ITrackableConfigManager configManager)
{
- if (configManager == null) throw new ArgumentNullException(nameof(configManager));
+ if (configManager == null) throw new ArgumentNullException(nameof(configManager));
if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing))
throw new InvalidOperationException($"{nameof(configManager)} is not registered.");
@@ -181,7 +183,7 @@ namespace osu.Game.Overlays
if (string.IsNullOrEmpty(textLine3.Text))
textLine3.Text = "NO KEY BOUND";
- Display(box);
+ DisplayTemporarily(box);
int optionCount = 0;
int selectedOption = -1;
@@ -213,15 +215,29 @@ namespace osu.Game.Overlays
});
}
- protected virtual void Display(Drawable toDisplay)
+ private TransformSequence fadeIn;
+ private ScheduledDelegate fadeOut;
+
+ protected virtual void DisplayTemporarily(Drawable toDisplay)
{
- toDisplay.Animate(
- b => b.FadeIn(500, Easing.OutQuint),
- b => b.ResizeHeightTo(height, 500, Easing.OutQuint)
- ).Then(
- b => b.FadeOutFromOne(1500, Easing.InQuint),
- b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint)
- );
+ // avoid starting a new fade-in if one is already active.
+ if (fadeIn == null)
+ {
+ fadeIn = toDisplay.Animate(
+ b => b.FadeIn(500, Easing.OutQuint),
+ b => b.ResizeHeightTo(height, 500, Easing.OutQuint)
+ );
+
+ fadeIn.Finally(_ => fadeIn = null);
+ }
+
+ fadeOut?.Cancel();
+ fadeOut = Scheduler.AddDelayed(() =>
+ {
+ toDisplay.Animate(
+ b => b.FadeOutFromOne(1500, Easing.InQuint),
+ b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint));
+ }, 500);
}
private class OptionLight : Container
diff --git a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs
new file mode 100644
index 0000000000..11ee329f33
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs
@@ -0,0 +1,20 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Game.Graphics;
+
+namespace osu.Game.Overlays.Profile.Components
+{
+ public class DrawableJoinDate : DrawableDate
+ {
+ public DrawableJoinDate(DateTimeOffset date)
+ : base(date)
+ {
+ }
+
+ protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}";
+
+ public override string TooltipText => $"{Date:MMMM d, yyyy}";
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Components/GradeBadge.cs b/osu.Game/Overlays/Profile/Components/GradeBadge.cs
new file mode 100644
index 0000000000..14a47e8d03
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Components/GradeBadge.cs
@@ -0,0 +1,50 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Profile.Components
+{
+ public class GradeBadge : Container
+ {
+ private const float width = 50;
+ private readonly string grade;
+ private readonly Sprite badge;
+ private readonly SpriteText numberText;
+
+ public int DisplayCount
+ {
+ set => numberText.Text = value.ToString(@"#,0");
+ }
+
+ public GradeBadge(string grade)
+ {
+ this.grade = grade;
+ Width = width;
+ Height = 41;
+ Add(badge = new Sprite
+ {
+ Width = width,
+ Height = 26
+ });
+ Add(numberText = new OsuSpriteText
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ TextSize = 14,
+ Font = @"Exo2.0-Bold"
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ badge.Texture = textures.Get($"Grades/{grade}");
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 067144c26e..9d09836d25 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Profile.Components;
using osu.Game.Overlays.Profile.Header;
using osu.Game.Users;
@@ -157,6 +158,13 @@ namespace osu.Game.Overlays.Profile
}
}
},
+ new Box // this is a temporary workaround for incorrect masking behaviour of FillMode.Fill used in UserCoverBackground (see https://github.com/ppy/osu-framework/issues/1675)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 1,
+ Y = cover_height,
+ Colour = OsuColour.Gray(34),
+ },
infoTextLeft = new LinkFlowContainer(t => t.TextSize = 14)
{
X = UserProfileOverlay.CONTENT_X_MARGIN,
@@ -359,11 +367,6 @@ namespace osu.Game.Overlays.Profile
Text = text
};
- if (user.Age != null)
- {
- infoTextLeft.AddText($"{user.Age} years old ", boldItalic);
- }
-
if (user.Country != null)
{
infoTextLeft.AddText("From ", lightText);
@@ -375,12 +378,12 @@ namespace osu.Game.Overlays.Profile
if (user.JoinDate.ToUniversalTime().Year < 2008)
{
- infoTextLeft.AddText("Here since the beginning", boldItalic);
+ infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText);
}
else
{
infoTextLeft.AddText("Joined ", lightText);
- infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic);
+ infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic);
}
infoTextLeft.NewLine();
@@ -470,43 +473,5 @@ namespace osu.Game.Overlays.Profile
infoTextRight.NewLine();
}
-
- private class GradeBadge : Container
- {
- private const float width = 50;
- private readonly string grade;
- private readonly Sprite badge;
- private readonly SpriteText numberText;
-
- public int DisplayCount
- {
- set { numberText.Text = value.ToString(@"#,0"); }
- }
-
- public GradeBadge(string grade)
- {
- this.grade = grade;
- Width = width;
- Height = 41;
- Add(badge = new Sprite
- {
- Width = width,
- Height = 26
- });
- Add(numberText = new OsuSpriteText
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- TextSize = 14,
- Font = @"Exo2.0-Bold"
- });
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- badge.Texture = textures.Get($"Grades/{grade}");
- }
- }
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
index 359bfc7564..1a1f13933d 100644
--- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
+
namespace osu.Game.Overlays.Profile.Sections
{
///
@@ -32,7 +33,10 @@ namespace osu.Game.Overlays.Profile.Sections
{
Action = () =>
{
- if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value);
+ if (beatmap.OnlineBeatmapID != null)
+ beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value);
+ else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null)
+ beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value);
};
Child = new FillFlowContainer
diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
index 046c1b1a33..fefb289d17 100644
--- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
+++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
@@ -141,11 +141,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
break;
case RecentActivityType.UserSupportFirst:
- message = $"{userLinkTemplate()} has become an osu! supporter - thanks for your generosity!";
+ message = $"{userLinkTemplate()} has become an osu!supporter - thanks for your generosity!";
break;
case RecentActivityType.UserSupportGift:
- message = $"{userLinkTemplate()} has received the gift of osu! supporter!";
+ message = $"{userLinkTemplate()} has received the gift of osu!supporter!";
break;
case RecentActivityType.UsernameChange:
diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs
index 05104018cd..60a1c7c125 100644
--- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs
@@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Configuration;
namespace osu.Game.Overlays.Settings
{
@@ -14,6 +15,8 @@ namespace osu.Game.Overlays.Settings
{
private readonly Ruleset ruleset;
+ protected IRulesetConfigManager Config;
+
protected RulesetSettingsSubsection(Ruleset ruleset)
{
this.ruleset = ruleset;
@@ -21,13 +24,13 @@ namespace osu.Game.Overlays.Settings
private DependencyContainer dependencies;
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- var config = dependencies.Get().GetConfigFor(ruleset);
- if (config != null)
- dependencies.Cache(config);
+ Config = dependencies.Get().GetConfigFor(ruleset);
+ if (Config != null)
+ dependencies.Cache(Config);
return dependencies;
}
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 930b3c1eaa..18a371e904 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -56,7 +56,13 @@ namespace osu.Game.Overlays.Settings.Sections
reloadSkins();
- skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin);
+ var skinBindable = config.GetBindable(OsuSetting.Skin);
+
+ // Todo: This should not be necessary when OsuConfigManager is databased
+ if (skinDropdown.Items.All(s => s.Value != skinBindable.Value))
+ skinBindable.Value = 0;
+
+ skinDropdown.Bindable = skinBindable;
}
private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID));
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 48d0674b3d..eeace2f11c 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Toolbar
{
Action = () => OnHome?.Invoke()
},
- new ToolbarModeSelector()
+ new ToolbarRulesetSelector()
}
},
new FillFlowContainer
diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs
similarity index 96%
rename from osu.Game/Overlays/Toolbar/ToolbarModeButton.cs
rename to osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs
index 90b9abb2e4..bbdf796e7a 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs
@@ -7,7 +7,7 @@ using OpenTK.Graphics;
namespace osu.Game.Overlays.Toolbar
{
- public class ToolbarModeButton : ToolbarButton
+ public class ToolbarRulesetButton : ToolbarButton
{
private RulesetInfo ruleset;
public RulesetInfo Ruleset
diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs
similarity index 88%
rename from osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
rename to osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs
index 3078c44844..b1af3f0d62 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs
@@ -16,18 +16,18 @@ using osu.Game.Rulesets;
namespace osu.Game.Overlays.Toolbar
{
- public class ToolbarModeSelector : Container
+ public class ToolbarRulesetSelector : Container
{
private const float padding = 10;
private readonly FillFlowContainer modeButtons;
private readonly Drawable modeButtonLine;
- private ToolbarModeButton activeButton;
+ private ToolbarRulesetButton activeButton;
private RulesetStore rulesets;
private readonly Bindable ruleset = new Bindable();
- public ToolbarModeSelector()
+ public ToolbarRulesetSelector()
{
RelativeSizeAxes = Axes.Y;
@@ -67,13 +67,13 @@ namespace osu.Game.Overlays.Toolbar
};
}
- [BackgroundDependencyLoader(true)]
- private void load(RulesetStore rulesets, OsuGame game)
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets, Bindable parentRuleset)
{
this.rulesets = rulesets;
foreach (var r in rulesets.AvailableRulesets)
{
- modeButtons.Add(new ToolbarModeButton
+ modeButtons.Add(new ToolbarRulesetButton
{
Ruleset = r,
Action = delegate { ruleset.Value = r; }
@@ -82,11 +82,7 @@ namespace osu.Game.Overlays.Toolbar
ruleset.ValueChanged += rulesetChanged;
ruleset.DisabledChanged += disabledChanged;
-
- if (game != null)
- ruleset.BindTo(game.Ruleset);
- else
- ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault();
+ ruleset.BindTo(parentRuleset);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@@ -119,7 +115,7 @@ namespace osu.Game.Overlays.Toolbar
private void rulesetChanged(RulesetInfo ruleset)
{
- foreach (ToolbarModeButton m in modeButtons.Children.Cast())
+ foreach (ToolbarRulesetButton m in modeButtons.Children.Cast())
{
bool isActive = m.Ruleset.ID == ruleset.ID;
m.Active = isActive;
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index 572b3f0c27..3a64e12b27 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -9,11 +9,13 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Volume
{
- public class VolumeControlReceptor : Container, IKeyBindingHandler, IHandleGlobalInput
+ public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalInput
{
public Func ActionRequested;
+ public Func ScrollActionRequested;
public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
+ public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
public bool OnReleased(GlobalAction action) => false;
}
}
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 1d392e6ee8..9aeca1f35f 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -12,17 +12,15 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Input.Bindings;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Volume
{
- public class VolumeMeter : Container, IKeyBindingHandler
+ public class VolumeMeter : Container
{
private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow;
@@ -226,59 +224,27 @@ namespace osu.Game.Overlays.Volume
private const float adjust_step = 0.05f;
- public void Increase() => adjust(1);
- public void Decrease() => adjust(-1);
-
- private void adjust(int direction)
- {
- float amount = adjust_step * direction;
-
- // handle the case where the OnPressed action was actually a mouse wheel.
- // this allows for precise wheel handling.
- var state = GetContainingInputManager().CurrentState;
- if (state.Mouse?.ScrollDelta.Y != 0)
- {
- OnScroll(state);
- return;
- }
-
- Volume += amount;
- }
-
- public bool OnPressed(GlobalAction action)
- {
- if (!IsHovered) return false;
-
- switch (action)
- {
- case GlobalAction.DecreaseVolume:
- Decrease();
- return true;
- case GlobalAction.IncreaseVolume:
- Increase();
- return true;
- }
-
- return false;
- }
+ public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
+ public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
- private double scrollAmount;
+ private double adjustAccumulator;
+
+ private void adjust(double delta, bool isPrecise)
+ {
+ adjustAccumulator += delta * adjust_step * (isPrecise ? 0.1 : 1);
+ if (Math.Abs(adjustAccumulator) < Bindable.Precision)
+ return;
+ Volume += adjustAccumulator;
+ adjustAccumulator = 0;
+ }
protected override bool OnScroll(InputState state)
{
- scrollAmount += adjust_step * state.Mouse.ScrollDelta.Y * (state.Mouse.HasPreciseScroll ? 0.1f : 1);
-
- if (Math.Abs(scrollAmount) < Bindable.Precision)
- return true;
-
- Volume += scrollAmount;
- scrollAmount = 0;
+ adjust(state.Mouse.ScrollDelta.Y, state.Mouse.HasPreciseScroll);
return true;
}
- public bool OnReleased(GlobalAction action) => false;
-
private const float transition_length = 500;
protected override bool OnHover(InputState state)
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index 1c9c615bbb..e40597b2d4 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -93,7 +93,7 @@ namespace osu.Game.Overlays
muteButton.Current.ValueChanged += _ => Show();
}
- public bool Adjust(GlobalAction action)
+ public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
{
if (!IsLoaded) return false;
@@ -103,13 +103,13 @@ namespace osu.Game.Overlays
if (State == Visibility.Hidden)
Show();
else
- volumeMeterMaster.Decrease();
+ volumeMeterMaster.Decrease(amount, isPrecise);
return true;
case GlobalAction.IncreaseVolume:
if (State == Visibility.Hidden)
Show();
else
- volumeMeterMaster.Increase();
+ volumeMeterMaster.Increase(amount, isPrecise);
return true;
case GlobalAction.ToggleMute:
Show();
diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs
index 587f2c8d15..129dd07c3e 100644
--- a/osu.Game/Rulesets/Judgements/Judgement.cs
+++ b/osu.Game/Rulesets/Judgements/Judgement.cs
@@ -45,11 +45,15 @@ namespace osu.Game.Rulesets.Judgements
public double TimeOffset { get; set; }
///
- /// Whether the should affect the combo portion of the score.
- /// If false, the will be considered for the bonus portion of the score.
+ /// Whether the should affect the current combo.
///
public virtual bool AffectsCombo => true;
+ ///
+ /// Whether the should be counted as base (combo) or bonus score.
+ ///
+ public virtual bool IsBonus => !AffectsCombo;
+
///
/// The numeric representation for the result achieved.
///
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 88990d435c..a22aaa784f 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected virtual IEnumerable GetSamples() => HitObject.Samples;
private readonly Lazy> nestedHitObjects = new Lazy>();
- public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated;
- public IReadOnlyList NestedHitObjects => nestedHitObjects.Value;
+ public IEnumerable NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty();
public event Action OnJudgement;
public event Action OnJudgementRemoved;
@@ -50,12 +49,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Whether this and all of its nested s have been hit.
///
- public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit));
+ public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && NestedHitObjects.All(n => n.IsHit);
///
/// Whether this and all of its nested s have been judged.
///
- public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged));
+ public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && NestedHitObjects.All(h => h.AllJudged);
///
/// Whether this can be judged.
@@ -90,13 +89,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (HitObject.SampleControlPoint == null)
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
- AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo
- {
- Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank,
- Name = s.Name,
- Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume,
- Namespace = SampleNamespace
- }).ToArray()));
+
+ samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
+ foreach (var s in samples)
+ s.Namespace = SampleNamespace;
+
+ AddInternal(Samples = new SkinnableSound(samples));
}
}
@@ -206,9 +204,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (AllJudged)
return false;
- if (HasNestedHitObjects)
- foreach (var d in NestedHitObjects)
- judgementOccurred |= d.UpdateJudgement(userTriggered);
+ foreach (var d in NestedHitObjects)
+ judgementOccurred |= d.UpdateJudgement(userTriggered);
if (!ProvidesJudgement || judgementFinalized || judgementOccurred)
return judgementOccurred;
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 9edd0f1f34..95589d8953 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
@@ -196,9 +197,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
- // Let's not implement this for now, because this doesn't fit nicely into the bank structure
- //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
-
string stringBank = bank.ToString().ToLower();
if (stringBank == @"none")
stringBank = null;
@@ -211,6 +209,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 3)
bankInfo.Volume = int.Parse(split[3]);
+
+ bankInfo.Filename = split.Length > 4 ? split[4] : null;
}
///
@@ -252,6 +252,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{
+ // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
+ if (!string.IsNullOrEmpty(bankInfo.Filename))
+ return new List { new FileSampleInfo { Filename = bankInfo.Filename } };
+
var soundTypes = new List
{
new SampleInfo
@@ -297,14 +301,24 @@ namespace osu.Game.Rulesets.Objects.Legacy
private class SampleBankInfo
{
+ public string Filename;
+
public string Normal;
public string Add;
public int Volume;
- public SampleBankInfo Clone()
+ public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
+ }
+
+ private class FileSampleInfo : SampleInfo
+ {
+ public string Filename;
+
+ public override IEnumerable LookupNames => new[]
{
- return (SampleBankInfo)MemberwiseClone();
- }
+ Filename,
+ Path.ChangeExtension(Filename, null)
+ };
}
[Flags]
diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs b/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs
deleted file mode 100644
index c5d0152ae7..0000000000
--- a/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Rulesets.Objects.Types
-{
- ///
- /// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
- ///
- public interface IHasComboIndex : IHasCombo
- {
- ///
- /// The offset of this hitobject in the current combo.
- ///
- int IndexInCurrentCombo { get; set; }
-
- ///
- /// The offset of this hitobject in the current combo.
- ///
- int ComboIndex { get; set; }
-
- ///
- /// Whether this is the last object in the current combo.
- ///
- bool LastInCombo { get; set; }
- }
-}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index f818523a3d..82d9945ef7 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -57,8 +57,18 @@ namespace osu.Game.Rulesets
///
public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap);
+ ///
+ /// Creates a to convert a to one that is applicable for this .
+ ///
+ /// The to be converted.
+ /// The .
public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap);
+ ///
+ /// Optionally creates a to alter a after it has been converted.
+ ///
+ /// The to be processed.
+ /// The .
public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 8d267f48e9..513173ef2f 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -84,10 +84,17 @@ namespace osu.Game.Rulesets
{
try
{
- var instance = r.CreateInstance();
+ var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo, asm =>
+ {
+ // for the time being, let's ignore the version being loaded.
+ // this allows for debug builds to successfully load rulesets (even though debug rulesets have a 0.0.0 version).
+ asm.Version = null;
+ return Assembly.Load(asm);
+ }, null), (RulesetInfo)null)).RulesetInfo;
- r.Name = instance.Description;
- r.ShortName = instance.ShortName;
+ r.Name = instanceInfo.Name;
+ r.ShortName = instanceInfo.ShortName;
+ r.InstantiationInfo = instanceInfo.InstantiationInfo;
r.Available = true;
}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index dd4120f2fb..9e8ea0f7c2 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -261,13 +261,19 @@ namespace osu.Game.Rulesets.Scoring
break;
}
- baseScore += judgement.NumericResult;
- rollingMaxBaseScore += judgement.MaxNumericResult;
-
JudgedHits++;
}
- else if (judgement.IsHit)
- bonusScore += judgement.NumericResult;
+
+ if (judgement.IsBonus)
+ {
+ if (judgement.IsHit)
+ bonusScore += judgement.NumericResult;
+ }
+ else
+ {
+ baseScore += judgement.NumericResult;
+ rollingMaxBaseScore += judgement.MaxNumericResult;
+ }
}
///
@@ -280,14 +286,18 @@ namespace osu.Game.Rulesets.Scoring
HighestCombo.Value = judgement.HighestComboAtJudgement;
if (judgement.AffectsCombo)
+ JudgedHits--;
+
+ if (judgement.IsBonus)
+ {
+ if (judgement.IsHit)
+ bonusScore -= judgement.NumericResult;
+ }
+ else
{
baseScore -= judgement.NumericResult;
rollingMaxBaseScore -= judgement.MaxNumericResult;
-
- JudgedHits--;
}
- else if (judgement.IsHit)
- bonusScore -= judgement.NumericResult;
}
private void updateScore()
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index fedb6abed3..0bfde148e7 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -70,7 +70,8 @@ namespace osu.Game.Rulesets.UI
protected readonly Ruleset Ruleset;
- private IRulesetConfigManager rulesetConfig;
+ protected IRulesetConfigManager Config { get; private set; }
+
private OnScreenDisplay onScreenDisplay;
///
@@ -85,17 +86,17 @@ namespace osu.Game.Rulesets.UI
Cursor = CreateCursor();
}
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
onScreenDisplay = dependencies.Get();
- rulesetConfig = dependencies.Get().GetConfigFor(Ruleset);
- if (rulesetConfig != null)
+ Config = dependencies.Get().GetConfigFor(Ruleset);
+ if (Config != null)
{
- dependencies.Cache(rulesetConfig);
- onScreenDisplay?.BeginTracking(this, rulesetConfig);
+ dependencies.Cache(Config);
+ onScreenDisplay?.BeginTracking(this, Config);
}
return dependencies;
@@ -143,10 +144,10 @@ namespace osu.Game.Rulesets.UI
{
base.Dispose(isDisposing);
- if (rulesetConfig != null)
+ if (Config != null)
{
- onScreenDisplay?.StopTracking(this, rulesetConfig);
- rulesetConfig = null;
+ onScreenDisplay?.StopTracking(this, Config);
+ Config = null;
}
}
}
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 8046e9f9b4..a3120179a4 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -222,23 +222,16 @@ namespace osu.Game.Rulesets.UI
mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons);
}
- protected override void TransformState(InputState state)
+ protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
- base.TransformState(state);
+ if (mouseDisabled.Value && (args.Button == MouseButton.Left || args.Button == MouseButton.Right)) return false;
+ return base.OnMouseDown(state, args);
+ }
- // we don't want to transform the state if a replay is present (for now, at least).
- if (replayInputHandler != null) return;
-
- var mouse = state.Mouse as Framework.Input.MouseState;
-
- if (mouse != null)
- {
- if (mouseDisabled.Value)
- {
- mouse.SetPressed(MouseButton.Left, false);
- mouse.SetPressed(MouseButton.Right, false);
- }
- }
+ protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+ {
+ if (!CurrentState.Mouse.IsPressed(args.Button)) return false;
+ return base.OnMouseUp(state, args);
}
#endregion
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
index 172b866505..cfcfca157b 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
@@ -4,30 +4,33 @@
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
-using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
-using OpenTK.Input;
namespace osu.Game.Rulesets.UI.Scrolling
{
///
/// A type of specialized towards scrolling s.
///
- public abstract class ScrollingPlayfield : Playfield
+ public abstract class ScrollingPlayfield : Playfield, IKeyBindingHandler
{
///
/// The default span of time visible by the length of the scrolling axes.
/// This is clamped between and .
///
private const double time_span_default = 1500;
+
///
/// The minimum span of time that may be visible by the length of the scrolling axes.
///
private const double time_span_min = 50;
+
///
/// The maximum span of time that may be visible by the length of the scrolling axes.
///
private const double time_span_max = 10000;
+
///
/// The step increase/decrease of the span of time visible by the length of the scrolling axes.
///
@@ -78,27 +81,26 @@ namespace osu.Game.Rulesets.UI.Scrolling
HitObjects.TimeRange.BindTo(VisibleTimeRange);
}
- protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ public bool OnPressed(GlobalAction action)
{
if (!UserScrollSpeedAdjustment)
return false;
- if (state.Keyboard.ControlPressed)
+ switch (action)
{
- switch (args.Key)
- {
- case Key.Minus:
- this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 600, Easing.OutQuint);
- break;
- case Key.Plus:
- this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 600, Easing.OutQuint);
- break;
- }
+ case GlobalAction.IncreaseScrollSpeed:
+ this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
+ return true;
+ case GlobalAction.DecreaseScrollSpeed:
+ this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
+ return true;
}
return false;
}
+ public bool OnReleased(GlobalAction action) => false;
+
protected sealed override HitObjectContainer CreateHitObjectContainer()
{
var container = new ScrollingHitObjectContainer();
diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs
index 35a91275a7..d2b79e2fa7 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs
@@ -47,13 +47,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
}
}
- if (obj.HasNestedHitObjects)
- {
- ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
+ ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
- // Nested hitobjects don't need to scroll, but they do need accurate positions
- UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
- }
+ // Nested hitobjects don't need to scroll, but they do need accurate positions
+ UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs
index 745352a4d3..25dea8dfbf 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs
@@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
}
}
- if (obj.HasNestedHitObjects)
- {
- ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
+ ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
- // Nested hitobjects don't need to scroll, but they do need accurate positions
- UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
- }
+ // Nested hitobjects don't need to scroll, but they do need accurate positions
+ UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
}
diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs
index 5e9863f642..9d9432b2ce 100644
--- a/osu.Game/Screens/BackgroundScreen.cs
+++ b/osu.Game/Screens/BackgroundScreen.cs
@@ -40,7 +40,14 @@ namespace osu.Game.Screens
while (screen.LoadState < LoadState.Ready)
Thread.Sleep(1);
- base.Push(screen);
+ try
+ {
+ base.Push(screen);
+ }
+ catch (ScreenAlreadyExitedException)
+ {
+ // screen may have exited before the push was successful.
+ }
}
protected override void Update()
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs
index d1fc8be005..0dc110951b 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs
@@ -52,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
if (Beatmap.Value == null)
return;
- if (Beatmap.Value.Track.Length == double.PositiveInfinity) return;
-
float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length);
}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
index 07d9398d38..5628630d0e 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
@@ -47,8 +47,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return;
}
- // Todo: This should be handled more gracefully
- timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
+ timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
}
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 7eeabd3e5e..a52fa3b462 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -42,8 +42,8 @@ namespace osu.Game.Screens.Edit
private DependencyContainer dependencies;
private GameHost host;
- protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
- => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
[BackgroundDependencyLoader]
private void load(OsuColour colours, GameHost host)
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
index e6a04bf1e7..8cb0fdd908 100644
--- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
@@ -52,13 +52,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
Beatmap.BindTo(beatmap);
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform);
- waveform.Waveform = Beatmap.Value.Waveform;
+ Beatmap.BindValueChanged(b =>
+ {
+ waveform.Waveform = b.Waveform;
+ track = b.Track;
+ }, true);
}
///
@@ -81,6 +79,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
///
private bool trackWasPlaying;
+ private Track track;
+
protected override void Update()
{
base.Update();
@@ -118,21 +118,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
private void seekTrackToCurrent()
{
- var track = Beatmap.Value.Track;
- if (track is TrackVirtual || !track.IsLoaded)
+ if (!track.IsLoaded)
return;
- if (!(Beatmap.Value.Track is TrackVirtual))
- adjustableClock.Seek(Current / Content.DrawWidth * Beatmap.Value.Track.Length);
+ adjustableClock.Seek(Current / Content.DrawWidth * track.Length);
}
private void scrollToTrackTime()
{
- var track = Beatmap.Value.Track;
- if (track is TrackVirtual || !track.IsLoaded)
+ if (!track.IsLoaded)
return;
- ScrollTo((float)(adjustableClock.CurrentTime / Beatmap.Value.Track.Length) * Content.DrawWidth, false);
+ ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs
index 542ddd2c92..29820f234d 100644
--- a/osu.Game/Screens/Menu/Button.cs
+++ b/osu.Game/Screens/Menu/Button.cs
@@ -35,6 +35,12 @@ namespace osu.Game.Screens.Menu
private readonly Box boxHoverLayer;
private readonly SpriteIcon icon;
private readonly string sampleName;
+
+ ///
+ /// The menu state for which we are visible for.
+ ///
+ public ButtonSystemState VisibleState = ButtonSystemState.TopLevel;
+
private readonly Action clickAction;
private readonly Key triggerKey;
private SampleChannel sampleClick;
@@ -51,7 +57,7 @@ namespace osu.Game.Screens.Menu
AutoSizeAxes = Axes.Both;
Alpha = 0;
- Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonSystem.BUTTON_AREA_HEIGHT);
+ Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonArea.BUTTON_AREA_HEIGHT);
Children = new Drawable[]
{
@@ -260,6 +266,7 @@ namespace osu.Game.Screens.Menu
this.FadeOut(800);
break;
}
+
break;
case ButtonState.Expanded:
const int expand_duration = 500;
@@ -276,6 +283,33 @@ namespace osu.Game.Screens.Menu
StateChanged?.Invoke(State);
}
}
+
+ public ButtonSystemState ButtonSystemState
+ {
+ set
+ {
+ ContractStyle = 0;
+
+ switch (value)
+ {
+ case ButtonSystemState.Initial:
+ State = ButtonState.Contracted;
+ break;
+ case ButtonSystemState.EnteringMode:
+ ContractStyle = 1;
+ State = ButtonState.Contracted;
+ break;
+ default:
+ if (value == VisibleState)
+ State = ButtonState.Expanded;
+ else if (value < VisibleState)
+ State = ButtonState.Contracted;
+ else
+ State = ButtonState.Exploded;
+ break;
+ }
+ }
+ }
}
public enum ButtonState
diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs
new file mode 100644
index 0000000000..06004405b6
--- /dev/null
+++ b/osu.Game/Screens/Menu/ButtonArea.cs
@@ -0,0 +1,148 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using OpenTK;
+
+namespace osu.Game.Screens.Menu
+{
+ public class ButtonArea : Container, IStateful
+ {
+ public FlowContainerWithOrigin Flow;
+
+ protected override Container Content => Flow;
+
+ private readonly ButtonAreaBackground buttonAreaBackground;
+ private Visibility state;
+
+ public const float BUTTON_AREA_HEIGHT = 100;
+
+ public ButtonArea()
+ {
+ RelativeSizeAxes = Axes.Both;
+ InternalChild = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Size = new Vector2(1, BUTTON_AREA_HEIGHT),
+ Alpha = 0,
+ Children = new Drawable[]
+ {
+ buttonAreaBackground = new ButtonAreaBackground(),
+ Flow = new FlowContainerWithOrigin
+ {
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(-ButtonSystem.WEDGE_WIDTH, 0),
+ Anchor = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ }
+ }
+ };
+ }
+
+ public ButtonSystemState ButtonSystemState
+ {
+ set
+ {
+ switch (value)
+ {
+ case ButtonSystemState.Exit:
+ case ButtonSystemState.Initial:
+ case ButtonSystemState.EnteringMode:
+ State = Visibility.Hidden;
+ break;
+ case ButtonSystemState.TopLevel:
+ case ButtonSystemState.Play:
+ State = Visibility.Visible;
+ break;
+ }
+
+ buttonAreaBackground.ButtonSystemState = value;
+ }
+ }
+
+ public Visibility State
+ {
+ get => state;
+ set
+ {
+ if (value == state) return;
+
+ state = value;
+ InternalChild.FadeTo(state == Visibility.Hidden ? 0 : 1, 300);
+ StateChanged?.Invoke(state);
+ }
+ }
+
+ public event Action StateChanged;
+
+ private class ButtonAreaBackground : Box, IStateful
+ {
+ private ButtonAreaBackgroundState state;
+
+ public ButtonAreaBackground()
+ {
+ RelativeSizeAxes = Axes.Both;
+ Size = new Vector2(2, 1);
+ Colour = OsuColour.Gray(50);
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ public ButtonAreaBackgroundState State
+ {
+ get => state;
+ set
+ {
+ if (value == state) return;
+
+ state = value;
+
+ switch (state)
+ {
+ case ButtonAreaBackgroundState.Flat:
+ this.ScaleTo(new Vector2(2, 0), 300, Easing.InSine);
+ break;
+ case ButtonAreaBackgroundState.Normal:
+ this.ScaleTo(Vector2.One, 400, Easing.OutQuint);
+ break;
+ }
+
+ StateChanged?.Invoke(state);
+ }
+ }
+
+ public ButtonSystemState ButtonSystemState
+ {
+ set
+ {
+ switch (value)
+ {
+ default:
+ State = ButtonAreaBackgroundState.Normal;
+ break;
+ case ButtonSystemState.Initial:
+ case ButtonSystemState.Exit:
+ case ButtonSystemState.EnteringMode:
+ State = ButtonAreaBackgroundState.Flat;
+ break;
+ }
+ }
+ }
+
+ public event Action StateChanged;
+ }
+
+ public enum ButtonAreaBackgroundState
+ {
+ Normal,
+ Flat
+ }
+ }
+}
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 81abc4cd3d..ce00686c02 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -10,9 +10,8 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Input.Bindings;
@@ -23,9 +22,9 @@ using OpenTK.Input;
namespace osu.Game.Screens.Menu
{
- public class ButtonSystem : Container, IStateful, IKeyBindingHandler
+ public class ButtonSystem : Container, IStateful, IKeyBindingHandler
{
- public event Action StateChanged;
+ public event Action StateChanged;
public Action OnEdit;
public Action OnExit;
@@ -34,12 +33,6 @@ namespace osu.Game.Screens.Menu
public Action OnSettings;
public Action OnMulti;
public Action OnChart;
- public Action OnTest;
-
- private readonly FlowContainerWithOrigin buttonFlow;
-
- //todo: make these non-internal somehow.
- public const float BUTTON_AREA_HEIGHT = 100;
public const float BUTTON_WIDTH = 140f;
public const float WEDGE_WIDTH = 20;
@@ -55,18 +48,16 @@ namespace osu.Game.Screens.Menu
this.logo.Action = onOsuLogo;
// osuLogo.SizeForFlow relies on loading to be complete.
- buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0);
+ buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0);
updateLogoState();
}
}
private readonly Drawable iconFacade;
- private readonly Container buttonArea;
- private readonly Box buttonAreaBackground;
+ private readonly ButtonArea buttonArea;
private readonly Button backButton;
- private readonly Button settingsButton;
private readonly List