diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
index 2eff16cc91..6463dd6ea5 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index cae9754560..0b63b2d966 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
similarity index 72%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
rename to .idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 49ec93e1b3..750ece648b 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index d0964c6f68..7b359a1ca0 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml
new file mode 100644
index 0000000000..3722f3dc04
--- /dev/null
+++ b/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml
new file mode 100644
index 0000000000..e2628a1bb4
--- /dev/null
+++ b/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
index 2735f4ceb3..7ac6bb745d 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
similarity index 70%
rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
rename to .idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
index 95cb4c0e62..7fcb7c15ea 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
@@ -1,17 +1,20 @@
-
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index c3306c2db7..57ff3e6b43 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,41 +1,6 @@
{
"version": "0.2.0",
- "configurations": [
- {
- "name": "VisualTests (Debug)",
- "type": "coreclr",
- "request": "launch",
- "program": "dotnet",
- "args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
- ],
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build tests (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
- }
- },
- "console": "internalConsole"
- },
- {
- "name": "VisualTests (Release)",
- "type": "coreclr",
- "request": "launch",
- "program": "dotnet",
- "args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
- ],
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build tests (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
- }
- },
- "console": "internalConsole"
- },
- {
+ "configurations": [{
"name": "osu! (Debug)",
"type": "coreclr",
"request": "launch",
@@ -69,6 +34,111 @@
},
"console": "internalConsole"
},
+ {
+ "name": "osu! (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ }, {
+ "name": "osu! (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tournament tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tournament tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
{
"name": "Cake: Debug Script",
"type": "coreclr",
@@ -84,4 +154,4 @@
"externalConsole": false
}
]
-}
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index de799a7c03..aba590f466 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,8 +2,7 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
- "tasks": [
- {
+ "tasks": [{
"label": "Build osu! (Debug)",
"type": "shell",
"command": "dotnet",
@@ -65,6 +64,36 @@
"group": "build",
"problemMatcher": "$msCompile"
},
+ {
+ "label": "Build tournament tests (Debug)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ }, {
+ "label": "Build tournament tests (Release)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
{
"label": "Restore (netcoreapp2.2)",
"type": "shell",
diff --git a/README.md b/README.md
index 04f133fd56..55f2eebec9 100644
--- a/README.md
+++ b/README.md
@@ -24,15 +24,15 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh

-If you are not interested in developing the game, you can consume our [binary releases](https://github.com/ppy/osu/releases).
+If you are not interested in developing the game, you can still consume our [binary releases](https://github.com/ppy/osu/releases).
-**Latest build:***
+**Latest build:**
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) |
| ------------- | ------------- |
- **Linux** users are recommended to self-compile until we have official deployment in place.
-- **iOS** users can join the [TestFlight beta program](https://t.co/xQJmHkfC18) (note that due to high demand this is regularly full).
+- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full).
- **Android** users can self-compile, and expect a public beta soon.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
@@ -72,7 +72,7 @@ If the build fails, try to restore nuget packages with `dotnet restore`.
On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`.
-`$NETCORE_VERSION` is the version of .NET Core SDK. You can have it with `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`.
+`$NETCORE_VERSION` is the version of the targeted .NET Core SDK. You can check it by running `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`.
For example, you can run osu! with the following command:
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index d2aad99f41..1f1d2cea5f 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -12,7 +13,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
-using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
@@ -61,7 +61,7 @@ namespace osu.Desktop.Overlays
},
new OsuSpriteText
{
- Colour = DebugUtils.IsDebug ? colours.Red : Color4.White,
+ Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
Text = game.Version
},
}
@@ -120,7 +120,7 @@ namespace osu.Desktop.Overlays
Activated = delegate
{
- changelog.ShowBuild("lazer", version);
+ changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
return true;
};
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 29554df64c..cb488fea52 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -11,6 +11,7 @@ using osu.Framework.Development;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.IPC;
+using osu.Game.Tournament;
namespace osu.Desktop
{
@@ -46,6 +47,10 @@ namespace osu.Desktop
default:
host.Run(new OsuGameDesktop(args));
break;
+
+ case "--tournament":
+ host.Run(new TournamentGame());
+ break;
}
}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index aa8848c55f..8c9e1f279f 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -17,6 +17,7 @@
osu.Desktop.Program
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
new file mode 100644
index 0000000000..33f93cdb4a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -0,0 +1,105 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
+using System;
+using System.Collections.Generic;
+using osu.Game.Skinning;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osuTK.Graphics;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneCatcher : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherSprite),
+ };
+
+ private readonly Container container;
+
+ public TestSceneCatcher()
+ {
+ Child = container = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddStep("show default catcher implementation", () => { container.Child = new CatcherSprite(); });
+
+ AddStep("show custom catcher implementation", () =>
+ {
+ container.Child = new CatchCustomSkinSourceContainer
+ {
+ Child = new CatcherSprite()
+ };
+ });
+ }
+
+ private class CatcherCustomSkin : Container
+ {
+ public CatcherCustomSkin()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Blue
+ },
+ new SpriteText
+ {
+ Text = "custom"
+ }
+ };
+ }
+ }
+
+ [Cached(typeof(ISkinSource))]
+ private class CatchCustomSkinSourceContainer : Container, ISkinSource
+ {
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+
+ public Drawable GetDrawableComponent(string componentName)
+ {
+ switch (componentName)
+ {
+ case "Play/Catch/fruit-catcher-idle":
+ return new CatcherCustomSkin();
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(string sampleName) =>
+ throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName) =>
+ throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration =>
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index daa3f61de3..8dd00756f2 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -43,10 +43,13 @@ namespace osu.Game.Rulesets.Catch.Replays
float positionChange = Math.Abs(lastPosition - h.X);
double timeAvailable = h.StartTime - lastTime;
- //So we can either make it there without a dash or not.
- double speedRequired = positionChange / timeAvailable;
+ // So we can either make it there without a dash or not.
+ // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too)
+ // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
+ double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable;
- bool dashRequired = speedRequired > movement_speed && h.StartTime != 0;
+ bool dashRequired = speedRequired > movement_speed;
+ bool impossibleJump = speedRequired > movement_speed * 2;
// todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f;
@@ -59,9 +62,8 @@ namespace osu.Game.Rulesets.Catch.Replays
return;
}
- if (h is Banana)
+ if (impossibleJump)
{
- // auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
}
else if (h.HyperDash)
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 90052d9b11..0b06e958e6 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -6,8 +6,6 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
@@ -141,7 +139,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- Children = new Drawable[]
+ Children = new[]
{
caughtFruit = new Container
{
@@ -212,7 +210,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
- private Sprite createCatcherSprite() => new CatcherSprite();
+ private Drawable createCatcherSprite() => new CatcherSprite();
///
/// Add a caught fruit to the catcher's stack.
@@ -444,23 +442,6 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Expire();
}
-
- private class CatcherSprite : Sprite
- {
- public CatcherSprite()
- {
- Size = new Vector2(CATCHER_SIZE);
-
- // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
- OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE;
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- Texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
- }
- }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
new file mode 100644
index 0000000000..c0c1952064
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -0,0 +1,33 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherSprite : CompositeDrawable
+ {
+ public CatcherSprite()
+ {
+ Size = new Vector2(CatcherArea.CATCHER_SIZE);
+
+ // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
+ OriginPosition = new Vector2(-0.02f, 0.06f) * CatcherArea.CATCHER_SIZE;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle")
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index a2da2bbf53..8072dc09c1 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -2,8 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -11,7 +14,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
- internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
+ internal class OsuModGrow : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
{
public override string Name => "Grow";
@@ -25,9 +28,16 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
+ private Bindable increaseFirstObjectVisibility = new Bindable();
+
+ public void ReadFromConfig(OsuConfigManager config)
+ {
+ increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
+ }
+
public void ApplyToDrawableHitObjects(IEnumerable drawables)
{
- foreach (var drawable in drawables)
+ foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
{
switch (drawable)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
index 8ee065aaea..9981585f9e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
@@ -4,7 +4,6 @@
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.Skinning;
@@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) });
+ Child = new SkinnableSprite("Play/osu/approachcircle");
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index d72c334ed3..893c7875fa 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -73,7 +74,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
- if (--downCount == 0)
+ // Todo: Math.Max() is required as a temporary measure to address https://github.com/ppy/osu-framework/issues/2576
+ downCount = Math.Max(0, downCount - 1);
+
+ if (downCount == 0)
updateExpandedState();
break;
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
new file mode 100644
index 0000000000..d0db193738
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -0,0 +1,68 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoSuddenDeath : PlayerTestScene
+ {
+ public TestSceneTaikoSuddenDeath()
+ : base(new TaikoRuleset())
+ {
+ }
+
+ protected override bool AllowFail => true;
+
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
+ return new ScoreAccessiblePlayer();
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) =>
+ new TaikoBeatmap
+ {
+ HitObjects =
+ {
+ new Swell { StartTime = 1500 },
+ new Hit { StartTime = 100000 },
+ },
+ BeatmapInfo =
+ {
+ Ruleset = new TaikoRuleset().RulesetInfo
+ }
+ };
+
+ [Test]
+ public void TestSpinnerDoesNotFail()
+ {
+ bool judged = false;
+ AddStep("Setup judgements", () =>
+ {
+ judged = false;
+ ((ScoreAccessiblePlayer)Player).ScoreProcessor.NewJudgement += b => judged = true;
+ });
+ AddUntilStep("swell judged", () => judged);
+ AddAssert("not failed", () => !Player.HasFailed);
+ }
+
+ private class ScoreAccessiblePlayer : TestPlayer
+ {
+ public ScoreAccessiblePlayer()
+ : base(false, false)
+ {
+ }
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index f020c2a805..ad0ed00989 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -11,7 +11,9 @@ using NUnit.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
+using osu.Framework.Logging;
using osu.Game.Beatmaps;
+using osu.Game.IO;
using osu.Game.Tests.Resources;
using SharpCompress.Archives.Zip;
@@ -21,14 +23,14 @@ namespace osu.Game.Tests.Beatmaps.IO
public class ImportBeatmapTest
{
[Test]
- public void TestImportWhenClosed()
+ public async Task TestImportWhenClosed()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
{
try
{
- LoadOszIntoOsu(loadOsu(host));
+ await LoadOszIntoOsu(loadOsu(host));
}
finally
{
@@ -38,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenDelete()
+ public async Task TestImportThenDelete()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
@@ -47,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu);
}
@@ -59,7 +61,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenImport()
+ public async Task TestImportThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
@@ -68,17 +70,15 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- var manager = osu.Dependencies.Get();
-
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 1);
+ checkSingleReferencedFileCount(osu, 18);
}
finally
{
@@ -88,30 +88,41 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestRollbackOnFailure()
+ public async Task TestRollbackOnFailure()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
{
try
{
+ int itemAddRemoveFireCount = 0;
+ int loggedExceptionCount = 0;
+
+ Logger.NewEntry += l =>
+ {
+ if (l.Target == LoggingTarget.Database && l.Exception != null)
+ Interlocked.Increment(ref loggedExceptionCount);
+ };
+
var osu = loadOsu(host);
var manager = osu.Dependencies.Get();
- int fireCount = 0;
-
// ReSharper disable once AccessToModifiedClosure
- manager.ItemAdded += (_, __) => fireCount++;
- manager.ItemRemoved += _ => fireCount++;
+ manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
+ manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
- Assert.AreEqual(0, fireCount -= 1);
+ Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
imported.Hash += "-changed";
manager.Update(imported);
- Assert.AreEqual(0, fireCount -= 2);
+ Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
+
+ checkBeatmapSetCount(osu, 1);
+ checkBeatmapCount(osu, 12);
+ checkSingleReferencedFileCount(osu, 18);
var breakTemp = TestResources.GetTestBeatmapForImport();
@@ -127,19 +138,24 @@ namespace osu.Game.Tests.Beatmaps.IO
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
}
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
- Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
-
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
- manager.Import(breakTemp);
+ try
+ {
+ await manager.Import(breakTemp);
+ }
+ catch
+ {
+ }
// no events should be fired in the case of a rollback.
- Assert.AreEqual(0, fireCount);
+ Assert.AreEqual(0, itemAddRemoveFireCount);
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
- Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 1);
+ checkBeatmapCount(osu, 12);
+
+ checkSingleReferencedFileCount(osu, 18);
+
+ Assert.AreEqual(1, loggedExceptionCount);
}
finally
{
@@ -149,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenImportDifferentHash()
+ public async Task TestImportThenImportDifferentHash()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
@@ -159,19 +175,18 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
var manager = osu.Dependencies.Get();
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
imported.Hash += "-changed";
manager.Update(imported);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 1);
}
finally
{
@@ -181,7 +196,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenDeleteThenImport()
+ public async Task TestImportThenDeleteThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
@@ -190,11 +205,11 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -209,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[TestCase(true)]
[TestCase(false)]
- public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
+ public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
@@ -218,7 +233,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
if (set)
imported.OnlineBeatmapSetID = 1234;
@@ -229,7 +244,7 @@ namespace osu.Game.Tests.Beatmaps.IO
deleteBeatmapSet(imported, osu);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert.IsTrue(imported.ID != importedSecondTime.ID);
@@ -243,7 +258,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportWithDuplicateBeatmapIDs()
+ public async Task TestImportWithDuplicateBeatmapIDs()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
@@ -284,7 +299,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- var imported = manager.Import(toImport);
+ var imported = await manager.Import(toImport);
Assert.NotNull(imported);
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
@@ -330,7 +345,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportWhenFileOpen()
+ public async Task TestImportWhenFileOpen()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
{
@@ -339,7 +354,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
- osu.Dependencies.Get().Import(temp);
+ await osu.Dependencies.Get().Import(temp);
ensureLoaded(osu);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
@@ -351,13 +366,13 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
+ public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null)
{
var temp = path ?? TestResources.GetTestBeatmapForImport();
var manager = osu.Dependencies.Get();
- manager.Import(temp);
+ await manager.Import(temp);
var imported = manager.GetAllUsableBeatmapSets();
@@ -373,11 +388,32 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
manager.Delete(imported);
- Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 0);
+ checkBeatmapSetCount(osu, 1, true);
+ checkSingleReferencedFileCount(osu, 0);
+
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
}
+ private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
+ {
+ var manager = osu.Dependencies.Get();
+
+ Assert.AreEqual(expected, includeDeletePending
+ ? manager.QueryBeatmapSets(_ => true).ToList().Count
+ : manager.GetAllUsableBeatmapSets().Count);
+ }
+
+ private void checkBeatmapCount(OsuGameBase osu, int expected)
+ {
+ Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count);
+ }
+
+ private void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
+ {
+ Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count());
+ }
+
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index e39f18c3cd..4babb07213 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -23,13 +23,13 @@ namespace osu.Game.Tests.Scores.IO
public class ImportScoreTest
{
[Test]
- public void TestBasicImport()
+ public async Task TestBasicImport()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
{
try
{
- var osu = loadOsu(host);
+ var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO
OnlineScoreID = 12345,
};
- var imported = loadIntoOsu(osu, toImport);
+ var imported = await loadIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Rank, imported.Rank);
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
@@ -62,20 +62,20 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public void TestImportMods()
+ public async Task TestImportMods()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
{
try
{
- var osu = loadOsu(host);
+ var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
- var imported = loadIntoOsu(osu, toImport);
+ var imported = await loadIntoOsu(osu, toImport);
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
@@ -88,13 +88,13 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public void TestImportStatistics()
+ public async Task TestImportStatistics()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
{
try
{
- var osu = loadOsu(host);
+ var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
- var imported = loadIntoOsu(osu, toImport);
+ var imported = await loadIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Scores.IO
}
}
- private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score)
+ private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score)
{
var beatmapManager = osu.Dependencies.Get();
@@ -125,20 +125,24 @@ namespace osu.Game.Tests.Scores.IO
score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
- scoreManager.Import(score);
+ await scoreManager.Import(score);
return scoreManager.GetAllUsableScores().First();
}
- private OsuGameBase loadOsu(GameHost host)
+ private async Task loadOsu(GameHost host)
{
var osu = new OsuGameBase();
+
+#pragma warning disable 4014
Task.Run(() => host.Run(osu));
+#pragma warning restore 4014
+
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
var beatmapFile = TestResources.GetTestBeatmapForImport();
var beatmapManager = osu.Dependencies.Get();
- beatmapManager.Import(beatmapFile);
+ await beatmapManager.Import(beatmapFile);
return osu;
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs
index 7104a420a3..8b941e4633 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Background
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
- manager.Import(TestResources.GetTestBeatmapForImport());
+ manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}
diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
index e97983dd8b..55aaeed8bf 100644
--- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
+++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
@@ -14,94 +14,132 @@ namespace osu.Game.Tests.Visual.Components
[TestFixture]
public class TestSceneIdleTracker : ManualInputManagerTestScene
{
- private readonly IdleTrackingBox box1;
- private readonly IdleTrackingBox box2;
- private readonly IdleTrackingBox box3;
- private readonly IdleTrackingBox box4;
+ private IdleTrackingBox box1;
+ private IdleTrackingBox box2;
+ private IdleTrackingBox box3;
+ private IdleTrackingBox box4;
- public TestSceneIdleTracker()
+ private IdleTrackingBox[] boxes;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Children = new Drawable[]
+ InputManager.MoveMouseTo(Vector2.Zero);
+
+ Children = boxes = new[]
{
- box1 = new IdleTrackingBox(1000)
+ box1 = new IdleTrackingBox(2000)
{
+ Name = "TopLeft",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
- box2 = new IdleTrackingBox(2000)
+ box2 = new IdleTrackingBox(4000)
{
+ Name = "TopRight",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Green,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
- box3 = new IdleTrackingBox(3000)
+ box3 = new IdleTrackingBox(6000)
{
+ Name = "BottomLeft",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
- box4 = new IdleTrackingBox(4000)
+ box4 = new IdleTrackingBox(8000)
{
+ Name = "BottomRight",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Orange,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
};
- }
+ });
[Test]
public void TestNudge()
{
- AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre));
+ AddStep("move to top left", () => InputManager.MoveMouseTo(box1));
- AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
+ waitForAllIdle();
AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1)));
- AddAssert("check not idle", () => !box1.IsIdle);
- AddAssert("check idle", () => box2.IsIdle);
- AddAssert("check idle", () => box3.IsIdle);
- AddAssert("check idle", () => box4.IsIdle);
+ checkIdleStatus(1, false);
+ checkIdleStatus(2, true);
+ checkIdleStatus(3, true);
+ checkIdleStatus(4, true);
}
[Test]
public void TestMovement()
{
- AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre));
+ AddStep("move to top right", () => InputManager.MoveMouseTo(box2));
- AddAssert("check not idle", () => box1.IsIdle);
- AddAssert("check not idle", () => !box2.IsIdle);
- AddAssert("check idle", () => box3.IsIdle);
- AddAssert("check idle", () => box4.IsIdle);
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, true);
+ checkIdleStatus(4, true);
- AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre));
- AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre));
+ AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3));
+ AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4));
- AddAssert("check not idle", () => box1.IsIdle);
- AddAssert("check not idle", () => !box2.IsIdle);
- AddAssert("check idle", () => !box3.IsIdle);
- AddAssert("check idle", () => !box4.IsIdle);
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
- AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
+ waitForAllIdle();
}
[Test]
public void TestTimings()
{
- AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre));
+ AddStep("move to centre", () => InputManager.MoveMouseTo(Content));
+
+ checkIdleStatus(1, false);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
- AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
AddUntilStep("Wait for idle", () => box1.IsIdle);
- AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
+
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
+
AddUntilStep("Wait for idle", () => box2.IsIdle);
- AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle);
+
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, true);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
+
AddUntilStep("Wait for idle", () => box3.IsIdle);
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, true);
+ checkIdleStatus(3, true);
+ checkIdleStatus(4, false);
+
+ waitForAllIdle();
+ }
+
+ private void checkIdleStatus(int box, bool expectedIdle)
+ {
+ AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle);
+ }
+
+ private void waitForAllIdle()
+ {
AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index f06f72615b..f4e8a68819 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- ScoreProcessor.FailConditions += _ => true;
+ ScoreProcessor.FailConditions += (_, __) => true;
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
new file mode 100644
index 0000000000..237fee1594
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -0,0 +1,109 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneGameplayRewinding : PlayerTestScene
+ {
+ private RulesetExposingPlayer player => (RulesetExposingPlayer)Player;
+
+ [Resolved]
+ private AudioManager audioManager { get; set; }
+
+ public TestSceneGameplayRewinding()
+ : base(new OsuRuleset())
+ {
+ }
+
+ private Track track;
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ {
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ track = working.Track;
+ return working;
+ }
+
+ [Test]
+ public void TestNoJudgementsOnRewind()
+ {
+ AddUntilStep("wait for track to start running", () => track.IsRunning);
+ addSeekStep(3000);
+ AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
+ AddStep("clear results", () => player.AppliedResults.Clear());
+ addSeekStep(0);
+ AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
+ AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
+ }
+
+ private void addSeekStep(double time)
+ {
+ AddStep($"seek to {time}", () => track.Seek(time));
+
+ // Allow a few frames of lenience
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ }
+
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ return new RulesetExposingPlayer();
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { BaseDifficulty = { ApproachRate = 9 } },
+ };
+
+ for (int i = 0; i < 15; i++)
+ {
+ beatmap.HitObjects.Add(new HitCircle
+ {
+ Position = new Vector2(256, 192),
+ StartTime = 1000 + 30 * i
+ });
+ }
+
+ return beatmap;
+ }
+
+ private class RulesetExposingPlayer : Player
+ {
+ public readonly List AppliedResults = new List();
+
+ public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
+
+ public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
+
+ public RulesetExposingPlayer()
+ : base(false, false)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScoreProcessor.NewJudgement += r => AppliedResults.Add(r);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index ac10c77a78..5808a78056 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -137,6 +137,22 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
+ [Test]
+ public void TestExitViaHoldToExit()
+ {
+ AddStep("exit", () =>
+ {
+ InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.First(c => c is HoldToConfirmContainer));
+ InputManager.PressButton(MouseButton.Left);
+ });
+
+ confirmPaused();
+
+ AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
+
+ exitAndConfirm();
+ }
+
[Test]
public void TestExitFromPause()
{
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index 0df6605cdd..f24589ed35 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus
{
typeof(ToolbarButton),
typeof(ToolbarRulesetSelector),
- typeof(ToolbarRulesetButton),
+ typeof(ToolbarRulesetTabButton),
typeof(ToolbarNotificationButton),
};
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 5910da7b88..fb2d4efc68 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -41,6 +41,9 @@ namespace osu.Game.Tests.Visual.Online
typeof(SuccessRate),
};
+ private RulesetInfo maniaRuleset;
+ private RulesetInfo taikoRuleset;
+
public TestSceneBeatmapSetOverlay()
{
Add(overlay = new BeatmapSetOverlay());
@@ -49,13 +52,25 @@ namespace osu.Game.Tests.Visual.Online
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- var mania = rulesets.GetRuleset(3);
- var taiko = rulesets.GetRuleset(1);
+ maniaRuleset = rulesets.GetRuleset(3);
+ taikoRuleset = rulesets.GetRuleset(1);
+ }
+ [Test]
+ public void TestLoading()
+ {
AddStep(@"show loading", () => overlay.ShowBeatmapSet(null));
+ }
+ [Test]
+ public void TestOnline()
+ {
AddStep(@"show online", () => overlay.FetchAndShowBeatmapSet(55));
+ }
+ [Test]
+ public void TestLocalBeatmaps()
+ {
AddStep(@"show first", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
@@ -87,13 +102,14 @@ namespace osu.Game.Tests.Visual.Online
Cover = @"https://assets.ppy.sh/beatmaps/415886/covers/cover.jpg?1465651778",
},
},
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List
{
new BeatmapInfo
{
StarDifficulty = 1.36,
Version = @"BASIC",
- Ruleset = mania,
+ Ruleset = maniaRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
@@ -111,16 +127,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 2.22,
Version = @"NOVICE",
- Ruleset = mania,
+ Ruleset = maniaRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
@@ -138,16 +153,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 3.49,
Version = @"ADVANCED",
- Ruleset = mania,
+ Ruleset = maniaRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
@@ -165,16 +179,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 4.24,
Version = @"EXHAUST",
- Ruleset = mania,
+ Ruleset = maniaRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
@@ -192,16 +205,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 5.26,
Version = @"GRAVITY",
- Ruleset = mania,
+ Ruleset = maniaRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
@@ -219,9 +231,8 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
@@ -258,13 +269,14 @@ namespace osu.Game.Tests.Visual.Online
Cover = @"https://assets.ppy.sh/beatmaps/625493/covers/cover.jpg?1499167472",
},
},
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List
{
new BeatmapInfo
{
StarDifficulty = 1.40,
Version = @"yzrin's Kantan",
- Ruleset = taiko,
+ Ruleset = taikoRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
@@ -282,16 +294,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 2.23,
Version = @"Futsuu",
- Ruleset = taiko,
+ Ruleset = taikoRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
@@ -309,16 +320,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 3.19,
Version = @"Muzukashii",
- Ruleset = taiko,
+ Ruleset = taikoRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
@@ -336,16 +346,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 3.97,
Version = @"Charlotte's Oni",
- Ruleset = taiko,
+ Ruleset = taikoRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
@@ -363,16 +372,15 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
new BeatmapInfo
{
StarDifficulty = 5.08,
Version = @"Labyrinth Oni",
- Ruleset = taiko,
+ Ruleset = taikoRuleset,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
@@ -390,16 +398,24 @@ namespace osu.Game.Tests.Visual.Online
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
});
});
+ }
+ [Test]
+ public void TestHide()
+ {
AddStep(@"hide", overlay.Hide);
+ }
+
+ [Test]
+ public void TestShowWithNoReload()
+ {
AddStep(@"show without reload", overlay.Show);
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
new file mode 100644
index 0000000000..2a45e68c0a
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.BeatmapSet;
+using osu.Game.Screens.Select.Details;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneBeatmapSetOverlayDetails : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Details)
+ };
+
+ private RatingsExposingDetails details;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = details = new RatingsExposingDetails
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ };
+ });
+
+ [Test]
+ public void TestMetrics()
+ {
+ var firstSet = createSet();
+ var secondSet = createSet();
+
+ AddStep("set first set", () => details.BeatmapSet = firstSet);
+ AddAssert("ratings set", () => details.Ratings.Metrics == firstSet.Metrics);
+
+ AddStep("set second set", () => details.BeatmapSet = secondSet);
+ AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
+
+ BeatmapSetInfo createSet() => new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
+ Beatmaps = new List
+ {
+ new BeatmapInfo
+ {
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
+ }
+ }
+ }
+ };
+ }
+
+ private class RatingsExposingDetails : Details
+ {
+ public new UserRatings Ratings => base.Ratings;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
new file mode 100644
index 0000000000..05f5c117e4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
@@ -0,0 +1,82 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.BeatmapSet;
+using osu.Game.Screens.Select.Details;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneBeatmapSetOverlaySuccessRate : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Details)
+ };
+
+ private GraphExposingSuccessRate successRate;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(275, 220),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ successRate = new GraphExposingSuccessRate
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(275, 220),
+ Padding = new MarginPadding(20)
+ }
+ }
+ };
+ });
+
+ [Test]
+ public void TestMetrics()
+ {
+ var firstBeatmap = createBeatmap();
+ var secondBeatmap = createBeatmap();
+
+ AddStep("set first set", () => successRate.Beatmap = firstBeatmap);
+ AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics);
+
+ AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
+ AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics);
+
+ BeatmapInfo createBeatmap() => new BeatmapInfo
+ {
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
+ }
+ };
+ }
+
+ private class GraphExposingSuccessRate : SuccessRate
+ {
+ public new FailRetryGraph Graph => base.Graph;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index d1a7730bee..0655611230 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
{
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
- UpdateStream = new APIUpdateStream { Name = "lazer" },
+ UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME },
ChangelogEntries = new List
{
new APIChangelogEntry
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
similarity index 93%
rename from osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs
rename to osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 2789feef3d..c75348112f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -15,11 +15,10 @@ using osu.Game.Overlays.Chat.Tabs;
namespace osu.Game.Tests.Visual.Online
{
[Description("Testing chat api and overlay")]
- public class TestSceneChatDisplay : OsuTestScene
+ public class TestSceneChatOverlay : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(ChatOverlay),
typeof(ChatLine),
typeof(DrawableChannel),
typeof(ChannelSelectorTabItem),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
index 455807649a..883f0c5e3f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
@@ -5,9 +5,9 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Users;
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
Colour = OsuColour.Gray(0.2f)
});
- Add(new ScrollContainer
+ Add(new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = section = new HistoricalSection(),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
index 2f88a4b01d..06414af865 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -39,8 +40,7 @@ namespace osu.Game.Tests.Visual.Online
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Both,
Width = 0.8f,
Children = new Drawable[]
{
@@ -173,7 +173,9 @@ namespace osu.Game.Tests.Visual.Online
s.Statistics.Add(HitResult.Miss, RNG.Next(2000));
}
- scoresContainer.Scores = scores;
+ AddStep("Load all scores", () => scoresContainer.Scores = scores);
+ AddStep("Load null scores", () => scoresContainer.Scores = null);
+ AddStep("Load only one score", () => scoresContainer.Scores = new[] { scores.First() });
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index fca18a9263..54f06d6ad2 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Users;
@@ -12,10 +13,12 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserPanel : OsuTestScene
{
+ private readonly UserPanel peppy;
+
public TestSceneUserPanel()
{
UserPanel flyte;
- UserPanel peppy;
+
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
@@ -44,13 +47,31 @@ namespace osu.Game.Tests.Visual.Online
});
flyte.Status.Value = new UserStatusOnline();
- peppy.Status.Value = new UserStatusSoloGame();
+ peppy.Status.Value = null;
+ }
- AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
- AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
- AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
- AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
- AddStep(@"null status", () => { flyte.Status.Value = null; });
+ [Test]
+ public void UserStatusesTests()
+ {
+ AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); });
+ AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); });
+ AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); });
+ AddStep(@"null status", () => { peppy.Status.Value = null; });
+ }
+
+ [Test]
+ public void UserActivitiesTests()
+ {
+ Bindable activity = new Bindable();
+
+ peppy.Activity.BindTo(activity);
+
+ AddStep("idle", () => { activity.Value = null; });
+ AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); });
+ AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); });
+ AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); });
+ AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); });
+ AddStep("modding", () => { activity.Value = new UserActivity.Modding(); });
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index d9230090fc..2285c9b799 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -39,13 +39,27 @@ namespace osu.Game.Tests.Visual.Online
header = new ProfileHeader();
Add(header);
- AddStep("Show offline dummy", () => header.User.Value = TestSceneUserProfileOverlay.TEST_USER);
+ AddStep("Show test dummy", () => header.User.Value = TestSceneUserProfileOverlay.TEST_USER);
AddStep("Show null dummy", () => header.User.Value = new User
{
Username = "Null"
});
+ AddStep("Show online dummy", () => header.User.Value = new User
+ {
+ Username = "IAmOnline",
+ LastVisit = DateTimeOffset.Now,
+ IsOnline = true,
+ });
+
+ AddStep("Show offline dummy", () => header.User.Value = new User
+ {
+ Username = "IAmOffline",
+ LastVisit = DateTimeOffset.Now,
+ IsOnline = false,
+ });
+
addOnlineStep("Show ppy", new User
{
Username = @"peppy",
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs
index d60e723102..f022425bf6 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Sections;
@@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
},
- new ScrollContainer
+ new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
index 70118b5ebd..9f0a8c769a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Users;
@@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
},
- new ScrollContainer
+ new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = ranks = new RanksSection(),
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyConfiguration.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
similarity index 50%
rename from osu.Game.Tests/Visual/Settings/TestSceneKeyConfiguration.cs
rename to osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index d06d82ddb5..426ff988c4 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyConfiguration.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -1,17 +1,30 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Overlays;
+using osu.Game.Overlays.KeyBinding;
namespace osu.Game.Tests.Visual.Settings
{
[TestFixture]
- public class TestSceneKeyConfiguration : OsuTestScene
+ public class TestSceneKeyBindingPanel : OsuTestScene
{
private readonly KeyBindingPanel panel;
- public TestSceneKeyConfiguration()
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(KeyBindingRow),
+ typeof(GlobalKeyBindingsSection),
+ typeof(KeyBindingRow),
+ typeof(KeyBindingsSubsection),
+ typeof(RulesetBindingsSection),
+ typeof(VariantBindingsSubsection),
+ };
+
+ public TestSceneKeyBindingPanel()
{
Child = panel = new KeyBindingPanel();
}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
similarity index 71%
rename from osu.Game.Tests/Visual/Settings/TestSceneSettings.cs
rename to osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
index f97ce8c69e..668fdf2c20 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettings.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
@@ -1,20 +1,29 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
+using osu.Game.Overlays.Settings;
namespace osu.Game.Tests.Visual.Settings
{
[TestFixture]
- public class TestSceneSettings : OsuTestScene
+ public class TestSceneSettingsPanel : OsuTestScene
{
private readonly SettingsPanel settings;
private readonly DialogOverlay dialogOverlay;
- public TestSceneSettings()
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SettingsFooter),
+ typeof(SettingsOverlay),
+ };
+
+ public TestSceneSettingsPanel()
{
settings = new SettingsOverlay
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
index 8395ece457..7b97a27732 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
@@ -32,6 +32,10 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
{
+ BeatmapSetInfo =
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
BeatmapInfo =
{
Version = "All Metrics",
@@ -50,9 +54,8 @@ namespace osu.Game.Tests.Visual.SongSelect
StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
}
@@ -60,6 +63,10 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
{
+ BeatmapSetInfo =
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
BeatmapInfo =
{
Version = "All Metrics",
@@ -77,15 +84,18 @@ namespace osu.Game.Tests.Visual.SongSelect
StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
{
+ BeatmapSetInfo =
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
BeatmapInfo =
{
Version = "Only Ratings",
@@ -101,11 +111,7 @@ namespace osu.Game.Tests.Visual.SongSelect
OverallDifficulty = 6,
ApproachRate = 6,
},
- StarDifficulty = 4.8f,
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- },
+ StarDifficulty = 4.8f
}
});
@@ -129,8 +135,8 @@ namespace osu.Game.Tests.Visual.SongSelect
StarDifficulty = 2.91f,
Metrics = new BeatmapMetrics
{
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
index acbbd4e18b..acf037198f 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
@@ -1,28 +1,38 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.ComponentModel;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect
{
- [Description("PlaySongSelect beatmap details")]
+ [System.ComponentModel.Description("PlaySongSelect beatmap details")]
public class TestSceneBeatmapDetails : OsuTestScene
{
- public TestSceneBeatmapDetails()
+ private BeatmapDetails details;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
{
- BeatmapDetails details;
- Add(details = new BeatmapDetails
+ Child = details = new BeatmapDetails
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(150),
- });
+ };
+ });
+ [Test]
+ public void TestAllMetrics()
+ {
AddStep("all metrics", () => details.Beatmap = new BeatmapInfo
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
@@ -39,14 +49,21 @@ namespace osu.Game.Tests.Visual.SongSelect
StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
});
+ }
+ [Test]
+ public void TestAllMetricsExceptSource()
+ {
AddStep("all except source", () => details.Beatmap = new BeatmapInfo
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
@@ -62,14 +79,21 @@ namespace osu.Game.Tests.Visual.SongSelect
StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
});
+ }
+ [Test]
+ public void TestOnlyRatings()
+ {
AddStep("ratings", () => details.Beatmap = new BeatmapInfo
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "Only Ratings",
Metadata = new BeatmapMetadata
{
@@ -84,12 +108,12 @@ namespace osu.Game.Tests.Visual.SongSelect
ApproachRate = 6,
},
StarDifficulty = 4.8f,
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- },
});
+ }
+ [Test]
+ public void TestOnlyFailsAndRetries()
+ {
AddStep("fails retries", () => details.Beatmap = new BeatmapInfo
{
Version = "Only Retries and Fails",
@@ -108,11 +132,15 @@ namespace osu.Game.Tests.Visual.SongSelect
StarDifficulty = 2.91f,
Metrics = new BeatmapMetrics
{
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
});
+ }
+ [Test]
+ public void TestNoMetrics()
+ {
AddStep("no metrics", () => details.Beatmap = new BeatmapInfo
{
Version = "No Metrics",
@@ -129,10 +157,22 @@ namespace osu.Game.Tests.Visual.SongSelect
ApproachRate = 6.5f,
},
StarDifficulty = 1.97f,
- Metrics = new BeatmapMetrics(),
});
+ }
+ [Test]
+ public void TestNullBeatmap()
+ {
AddStep("null beatmap", () => details.Beatmap = null);
}
+
+ [Test]
+ public void TestOnlineMetrics()
+ {
+ AddStep("online ratings/retries/fails", () => details.Beatmap = new BeatmapInfo
+ {
+ OnlineBeatmapID = 162,
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
index 9365e2c5b1..157e572606 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
@@ -270,9 +270,8 @@ namespace osu.Game.Tests.Visual.SongSelect
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
};
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index ebee358730..962e0fb362 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Select;
@@ -100,8 +101,11 @@ namespace osu.Game.Tests.Visual.SongSelect
}
[SetUp]
- public virtual void SetUp() =>
- Schedule(() => { manager?.Delete(manager.GetAllUsableBeatmapSets()); });
+ public virtual void SetUp() => Schedule(() =>
+ {
+ Ruleset.Value = new OsuRuleset().RulesetInfo;
+ manager?.Delete(manager.GetAllUsableBeatmapSets());
+ });
[Test]
public void TestDummy()
@@ -138,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
createSongSelect();
changeRuleset(2);
- importForRuleset(0);
+ addRulesetImportStep(0);
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
@@ -147,8 +151,8 @@ namespace osu.Game.Tests.Visual.SongSelect
{
createSongSelect();
changeRuleset(2);
- importForRuleset(2);
- importForRuleset(1);
+ addRulesetImportStep(2);
+ addRulesetImportStep(1);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
changeRuleset(1);
@@ -185,7 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("empty mods", () => !Mods.Value.Any());
void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++;
- void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--;
+ void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++;
}
[Test]
@@ -210,7 +214,21 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("start not requested", () => !startRequested);
}
- private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())));
+ [Test]
+ public void TestHideSetSelectsCorrectBeatmap()
+ {
+ int? previousID = null;
+ createSongSelect();
+ addRulesetImportStep(0);
+ AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last()));
+ AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID);
+ AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First()));
+ AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID);
+ }
+
+ private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
+
+ private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
private static int importId;
private int getImportId() => ++importId;
@@ -232,7 +250,7 @@ namespace osu.Game.Tests.Visual.SongSelect
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
for (int i = 0; i < 100; i += 10)
- manager.Import(createTestBeatmapSet(i, usableRulesets));
+ manager.Import(createTestBeatmapSet(i, usableRulesets)).Wait();
});
}
diff --git a/osu.Game.Tests/Visual/Tournament/TestSceneDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestSceneDrawings.cs
deleted file mode 100644
index 995819f7ae..0000000000
--- a/osu.Game.Tests/Visual/Tournament/TestSceneDrawings.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using System.ComponentModel;
-using osu.Framework.Allocation;
-using osu.Game.Screens.Tournament;
-using osu.Game.Screens.Tournament.Teams;
-
-namespace osu.Game.Tests.Visual.Tournament
-{
- [Description("for tournament use")]
- public class TestSceneDrawings : ScreenTestScene
- {
- [BackgroundDependencyLoader]
- private void load()
- {
- LoadScreen(new Drawings
- {
- TeamList = new TestTeamList(),
- });
- }
-
- private class TestTeamList : ITeamList
- {
- public IEnumerable Teams { get; } = new[]
- {
- new DrawingsTeam
- {
- FlagName = "GB",
- FullName = "United Kingdom",
- Acronym = "UK"
- },
- new DrawingsTeam
- {
- FlagName = "FR",
- FullName = "France",
- Acronym = "FRA"
- },
- new DrawingsTeam
- {
- FlagName = "CN",
- FullName = "China",
- Acronym = "CHN"
- },
- new DrawingsTeam
- {
- FlagName = "AU",
- FullName = "Australia",
- Acronym = "AUS"
- },
- new DrawingsTeam
- {
- FlagName = "JP",
- FullName = "Japan",
- Acronym = "JPN"
- },
- new DrawingsTeam
- {
- FlagName = "RO",
- FullName = "Romania",
- Acronym = "ROM"
- },
- new DrawingsTeam
- {
- FlagName = "IT",
- FullName = "Italy",
- Acronym = "PIZZA"
- },
- new DrawingsTeam
- {
- FlagName = "VE",
- FullName = "Venezuela",
- Acronym = "VNZ"
- },
- new DrawingsTeam
- {
- FlagName = "US",
- FullName = "United States of America",
- Acronym = "USA"
- },
- };
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs
new file mode 100644
index 0000000000..867b3130c9
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.UserInterface;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneBackButton : OsuTestScene
+ {
+ private readonly BackButton button;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(TwoLayerButton)
+ };
+
+ public TestSceneBackButton()
+ {
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(300),
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ button = new BackButton
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Action = () => button.Hide(),
+ }
+ }
+ };
+
+ AddStep("show button", () => button.Show());
+ AddStep("hide button", () => button.Hide());
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs
index 0c9ce50288..c80b3e6297 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs
@@ -26,8 +26,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
new NamedIconButton("No change", new IconButton()),
new NamedIconButton("Background colours", new ColouredIconButton()),
- new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }),
- new NamedIconButton("Unchanging size", new IconButton(), false),
+ new NamedIconButton("Full-width", new IconButton { Size = new Vector2(200, 30) }),
new NamedIconButton("Icon colours", new IconButton
{
IconColour = Color4.Green,
@@ -48,7 +47,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private class NamedIconButton : Container
{
- public NamedIconButton(string name, IconButton button, bool allowSizeChange = true)
+ public NamedIconButton(string name, IconButton button)
{
AutoSizeAxes = Axes.Y;
Width = 200;
@@ -101,13 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface
}
};
- if (allowSizeChange)
- iconContainer.AutoSizeAxes = Axes.Both;
- else
- {
- iconContainer.RelativeSizeAxes = Axes.X;
- iconContainer.Height = 30;
- }
+ iconContainer.AutoSizeAxes = Axes.Both;
button.Anchor = Anchor.Centre;
button.Origin = Anchor.Centre;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
similarity index 90%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneMods.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 2e36ba39ed..80408ab43b 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneMods.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -24,11 +24,10 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[Description("mod select and icon display")]
- public class TestSceneMods : OsuTestScene
+ public class TestSceneModSelectOverlay : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(ModSelectOverlay),
typeof(ModDisplay),
typeof(ModSection),
typeof(ModIcon),
@@ -77,7 +76,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestOsuMods()
{
var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 0);
- AddStep("change ruleset", () => { Ruleset.Value = ruleset; });
+ changeRuleset(ruleset);
var instance = ruleset.CreateInstance();
@@ -109,7 +108,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestManiaMods()
{
var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 3);
- AddStep("change ruleset", () => { Ruleset.Value = ruleset; });
+ changeRuleset(ruleset);
testRankedText(ruleset.CreateInstance().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom));
}
@@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var rulesetOsu = rulesets.AvailableRulesets.First(r => r.ID == 0);
var rulesetMania = rulesets.AvailableRulesets.First(r => r.ID == 3);
- AddStep("change ruleset to null", () => { Ruleset.Value = null; });
+ changeRuleset(null);
var instance = rulesetOsu.CreateInstance();
var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
@@ -128,15 +127,15 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set mods externally", () => { modDisplay.Current.Value = new[] { noFailMod }; });
- AddStep("change ruleset to osu", () => { Ruleset.Value = rulesetOsu; });
+ changeRuleset(rulesetOsu);
AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null);
- AddStep("change ruleset to mania", () => { Ruleset.Value = rulesetMania; });
+ changeRuleset(rulesetMania);
AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any(m => m is OsuModNoFail));
- AddStep("change ruleset to osu", () => { Ruleset.Value = rulesetOsu; });
+ changeRuleset(rulesetOsu);
AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any());
}
@@ -217,14 +216,11 @@ namespace osu.Game.Tests.Visual.UserInterface
private void testRankedText(Mod mod)
{
- AddWaitStep("wait for fade", 1);
- AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
+ AddUntilStep("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
selectNext(mod);
- AddWaitStep("wait for fade", 1);
- AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0);
+ AddUntilStep("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0);
selectPrevious(mod);
- AddWaitStep("wait for fade", 1);
- AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
+ AddUntilStep("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
}
private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1));
@@ -240,6 +236,15 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
+ private void changeRuleset(RulesetInfo ruleset)
+ {
+ AddStep($"change ruleset to {ruleset}", () => { Ruleset.Value = ruleset; });
+ waitForLoad();
+ }
+
+ private void waitForLoad() =>
+ AddUntilStep("wait for icons to load", () => modSelect.AllLoaded);
+
private void checkNotSelected(Mod mod)
{
AddAssert($"check {mod.Name} is not selected", () =>
@@ -255,6 +260,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public new Bindable> SelectedMods => base.SelectedMods;
+ public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded);
+
public ModButton GetModButton(Mod mod)
{
var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs
index 2f2a40925f..ab2ca47100 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs
@@ -3,7 +3,6 @@
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Overlays;
@@ -23,9 +22,9 @@ namespace osu.Game.Tests.Visual.UserInterface
};
Add(mc);
- AddToggleStep(@"toggle visibility", state => mc.State.Value = state ? Visibility.Visible : Visibility.Hidden);
AddStep(@"show", () => mc.Show());
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
+ AddStep(@"show", () => mc.Hide());
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
index 6b7427cef5..d8a4514df1 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
@@ -18,9 +18,6 @@ namespace osu.Game.Tests.Visual.UserInterface
[TestFixture]
public class TestSceneNotificationOverlay : OsuTestScene
{
- private readonly NotificationOverlay manager;
- private readonly List progressingNotifications = new List();
-
public override IReadOnlyList RequiredTypes => new[]
{
typeof(NotificationSection),
@@ -31,25 +28,33 @@ namespace osu.Game.Tests.Visual.UserInterface
typeof(Notification)
};
- public TestSceneNotificationOverlay()
+ private NotificationOverlay notificationOverlay;
+
+ private readonly List progressingNotifications = new List();
+
+ private SpriteText displayedCount;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
progressingNotifications.Clear();
- Content.Add(manager = new NotificationOverlay
+ Content.Children = new Drawable[]
{
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight
- });
+ notificationOverlay = new NotificationOverlay
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight
+ },
+ displayedCount = new OsuSpriteText()
+ };
- SpriteText displayedCount = new OsuSpriteText();
-
- Content.Add(displayedCount);
-
- void setState(Visibility state) => AddStep(state.ToString(), () => manager.State.Value = state);
- void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
-
- manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
+ notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
+ });
+ [Test]
+ public void TestBasicFlow()
+ {
setState(Visibility.Visible);
AddStep(@"simple #1", sendHelloNotification);
AddStep(@"simple #2", sendAmazingNotification);
@@ -61,6 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface
setState(Visibility.Hidden);
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
+
AddWaitStep("wait some", 5);
checkProgressingCount(0);
@@ -69,18 +75,122 @@ namespace osu.Game.Tests.Visual.UserInterface
checkProgressingCount(1);
- AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33);
+ checkDisplayedCount(33);
AddWaitStep("wait some", 10);
checkProgressingCount(0);
-
- setState(Visibility.Visible);
-
- //AddStep(@"barrage", () => sendBarrage());
}
- private void sendBarrage(int remaining = 10)
+ [Test]
+ public void TestImportantWhileClosed()
+ {
+ AddStep(@"simple #1", sendHelloNotification);
+
+ AddAssert("Is visible", () => notificationOverlay.State.Value == Visibility.Visible);
+
+ checkDisplayedCount(1);
+
+ AddStep(@"progress #1", sendUploadProgress);
+ AddStep(@"progress #2", sendDownloadProgress);
+
+ checkProgressingCount(2);
+ checkDisplayedCount(3);
+ }
+
+ [Test]
+ public void TestUnimportantWhileClosed()
+ {
+ AddStep(@"background #1", sendBackgroundNotification);
+
+ AddAssert("Is not visible", () => notificationOverlay.State.Value == Visibility.Hidden);
+
+ checkDisplayedCount(1);
+
+ AddStep(@"background progress #1", sendBackgroundUploadProgress);
+
+ AddWaitStep("wait some", 5);
+
+ checkProgressingCount(0);
+
+ checkDisplayedCount(2);
+
+ AddStep(@"simple #1", sendHelloNotification);
+
+ checkDisplayedCount(3);
+ }
+
+ [Test]
+ public void TestSpam()
+ {
+ setState(Visibility.Visible);
+ AddRepeatStep("send barrage", sendBarrage, 10);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed);
+
+ if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
+ {
+ var p = progressingNotifications.Find(n => n.State == ProgressNotificationState.Queued);
+
+ if (p != null)
+ p.State = ProgressNotificationState.Active;
+ }
+
+ foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
+ {
+ if (n.Progress < 1)
+ n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle();
+ else
+ n.State = ProgressNotificationState.Completed;
+ }
+ }
+
+ private void checkDisplayedCount(int expected) =>
+ AddAssert($"Displayed count is {expected}", () => notificationOverlay.UnreadCount.Value == expected);
+
+ private void sendDownloadProgress()
+ {
+ var n = new ProgressNotification
+ {
+ Text = @"Downloading Haitai...",
+ CompletionText = "Downloaded Haitai!",
+ };
+ notificationOverlay.Post(n);
+ progressingNotifications.Add(n);
+ }
+
+ private void sendUploadProgress()
+ {
+ var n = new ProgressNotification
+ {
+ Text = @"Uploading to BSS...",
+ CompletionText = "Uploaded to BSS!",
+ };
+ notificationOverlay.Post(n);
+ progressingNotifications.Add(n);
+ }
+
+ private void sendBackgroundUploadProgress()
+ {
+ var n = new BackgroundProgressNotification
+ {
+ Text = @"Uploading to BSS...",
+ CompletionText = "Uploaded to BSS!",
+ };
+ notificationOverlay.Post(n);
+ progressingNotifications.Add(n);
+ }
+
+ private void setState(Visibility state) => AddStep(state.ToString(), () => notificationOverlay.State.Value = state);
+
+ private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
+
+ private void sendBarrage()
{
switch (RNG.Next(0, 4))
{
@@ -100,69 +210,37 @@ namespace osu.Game.Tests.Visual.UserInterface
sendDownloadProgress();
break;
}
-
- if (remaining > 0)
- Scheduler.AddDelayed(() => sendBarrage(remaining - 1), 80);
- }
-
- protected override void Update()
- {
- base.Update();
-
- progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed);
-
- if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
- {
- var p = progressingNotifications.Find(n => n.State == ProgressNotificationState.Queued);
- if (p != null)
- p.State = ProgressNotificationState.Active;
- }
-
- foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
- {
- if (n.Progress < 1)
- n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle();
- else
- n.State = ProgressNotificationState.Completed;
- }
- }
-
- private void sendDownloadProgress()
- {
- var n = new ProgressNotification
- {
- Text = @"Downloading Haitai...",
- CompletionText = "Downloaded Haitai!",
- };
- manager.Post(n);
- progressingNotifications.Add(n);
- }
-
- private void sendUploadProgress()
- {
- var n = new ProgressNotification
- {
- Text = @"Uploading to BSS...",
- CompletionText = "Uploaded to BSS!",
- };
- manager.Post(n);
- progressingNotifications.Add(n);
}
private void sendAmazingNotification()
{
- manager.Post(new SimpleNotification { Text = @"You are amazing" });
+ notificationOverlay.Post(new SimpleNotification { Text = @"You are amazing" });
}
private void sendHelloNotification()
{
- manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
+ notificationOverlay.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
+ }
+
+ private void sendBackgroundNotification()
+ {
+ notificationOverlay.Post(new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
}
private void sendManyNotifications()
{
for (int i = 0; i < 10; i++)
- manager.Post(new SimpleNotification { Text = @"Spam incoming!!" });
+ notificationOverlay.Post(new SimpleNotification { Text = @"Spam incoming!!" });
+ }
+
+ private class BackgroundNotification : SimpleNotification
+ {
+ public override bool IsImportant => false;
+ }
+
+ private class BackgroundProgressNotification : ProgressNotification
+ {
+ public override bool IsImportant => false;
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs
new file mode 100644
index 0000000000..f73450db60
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ [TestFixture]
+ public class TestSceneNumberBox : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuNumberBox),
+ };
+
+ private OsuNumberBox numberBox;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Padding = new MarginPadding { Horizontal = 250 },
+ Child = numberBox = new OsuNumberBox
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ PlaceholderText = "Insert numbers here"
+ }
+ };
+
+ clearInput();
+ AddStep("enter numbers", () => numberBox.Text = "987654321");
+ expectedValue("987654321");
+ clearInput();
+ AddStep("enter text + single number", () => numberBox.Text = "1 hello 2 world 3");
+ expectedValue("123");
+ clearInput();
+ }
+
+ private void clearInput() => AddStep("clear input", () => numberBox.Text = null);
+
+ private void expectedValue(string value) => AddAssert("expect number", () => numberBox.Text == value);
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs
index 2c2a28394c..061039b297 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
@@ -29,7 +30,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Colour = Color4.Teal,
RelativeSizeAxes = Axes.Both,
},
- new ScrollContainer
+ new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = flow = new FillFlowContainer
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs
new file mode 100644
index 0000000000..582303024b
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Containers;
+using osu.Game.Overlays.Toolbar;
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using System.Linq;
+using osu.Framework.MathUtils;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneToolbarRulesetSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ToolbarRulesetSelector),
+ typeof(ToolbarRulesetTabButton),
+ };
+
+ public TestSceneToolbarRulesetSelector()
+ {
+ ToolbarRulesetSelector selector;
+
+ Add(new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.X,
+ Height = Toolbar.HEIGHT,
+ Child = selector = new ToolbarRulesetSelector()
+ });
+
+ AddStep("Select random", () =>
+ {
+ selector.Current.Value = selector.Items.ElementAt(RNG.Next(selector.Items.Count()));
+ });
+ AddStep("Toggle disabled state", () => selector.Current.Disabled = !selector.Current.Disabled);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs
index b9ed1a71cc..849577186d 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs
@@ -1,17 +1,26 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.ComponentModel;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
- [Description("mostly back button")]
public class TestSceneTwoLayerButton : OsuTestScene
{
public TestSceneTwoLayerButton()
{
- Add(new BackButton());
+ Add(new TwoLayerButton
+ {
+ Position = new Vector2(100),
+ Text = "button",
+ Icon = FontAwesome.Solid.Check,
+ BackgroundColour = Color4.SlateGray,
+ HoverColour = Color4.SlateGray.Darken(0.2f)
+ });
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
index f59458ef8d..9cdfcb6cc4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
@@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface
this.api = api;
this.rulesets = rulesets;
- testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu);
+ testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result;
}
[Test]
@@ -92,13 +93,13 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestUnloadAndReload()
{
var backgrounds = new List();
- ScrollContainer scrollContainer = null;
+ OsuScrollContainer scrollContainer = null;
AddStep("create backgrounds hierarchy", () =>
{
FillFlowContainer backgroundFlow;
- Child = scrollContainer = new ScrollContainer
+ Child = scrollContainer = new OsuScrollContainer
{
Size = new Vector2(500),
Child = backgroundFlow = new FillFlowContainer
diff --git a/osu.Game.Tournament.Tests/.vscode/launch.json b/osu.Game.Tournament.Tests/.vscode/launch.json
new file mode 100644
index 0000000000..0204158347
--- /dev/null
+++ b/osu.Game.Tournament.Tests/.vscode/launch.json
@@ -0,0 +1,31 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "VisualTests (Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Tournament.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Debug)",
+ "env": {},
+ "console": "internalConsole"
+ },
+ {
+ "name": "VisualTests (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Tournament.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Release)",
+ "env": {},
+ "console": "internalConsole"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Tournament.Tests/.vscode/tasks.json b/osu.Game.Tournament.Tests/.vscode/tasks.json
new file mode 100644
index 0000000000..37f2f32874
--- /dev/null
+++ b/osu.Game.Tournament.Tests/.vscode/tasks.json
@@ -0,0 +1,47 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Build (Debug)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests.csproj",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Build (Release)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests.csproj",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Restore",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "restore"
+ ],
+ "problemMatcher": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs
new file mode 100644
index 0000000000..f329623703
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs
@@ -0,0 +1,93 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osu.Game.Tournament.Screens.Ladder.Components;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneDrawableTournamentMatch : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(TournamentMatch),
+ typeof(DrawableTournamentTeam),
+ };
+
+ public TestSceneDrawableTournamentMatch()
+ {
+ Container level1;
+ Container level2;
+
+ var match1 = new TournamentMatch(
+ new TournamentTeam { FlagName = { Value = "AU" }, FullName = { Value = "Australia" }, },
+ new TournamentTeam { FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, Acronym = { Value = "JPN" } })
+ {
+ Team1Score = { Value = 4 },
+ Team2Score = { Value = 1 },
+ };
+
+ var match2 = new TournamentMatch(
+ new TournamentTeam
+ {
+ FlagName = { Value = "RO" },
+ FullName = { Value = "Romania" },
+ }
+ );
+
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ level1 = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Children = new[]
+ {
+ new DrawableTournamentMatch(match1),
+ new DrawableTournamentMatch(match2),
+ new DrawableTournamentMatch(new TournamentMatch()),
+ }
+ },
+ level2 = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Margin = new MarginPadding(20),
+ Children = new[]
+ {
+ new DrawableTournamentMatch(new TournamentMatch()),
+ new DrawableTournamentMatch(new TournamentMatch())
+ }
+ }
+ }
+ };
+
+ level1.Children[0].Match.Progression.Value = level2.Children[0].Match;
+ level1.Children[1].Match.Progression.Value = level2.Children[0].Match;
+
+ AddRepeatStep("change scores", () => match1.Team2Score.Value++, 4);
+ AddStep("add new team", () => match2.Team2.Value = new TournamentTeam { FlagName = { Value = "PT" }, FullName = { Value = "Portugal" } });
+ AddStep("Add progression", () => level1.Children[2].Match.Progression.Value = level2.Children[1].Match);
+
+ AddStep("start match", () => match2.StartMatch());
+
+ AddRepeatStep("change scores", () => match2.Team1Score.Value++, 10);
+
+ AddStep("start submatch", () => level2.Children[0].Match.StartMatch());
+
+ AddRepeatStep("change scores", () => level2.Children[0].Match.Team1Score.Value++, 5);
+
+ AddRepeatStep("change scores", () => level2.Children[0].Match.Team2Score.Value++, 4);
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
new file mode 100644
index 0000000000..72d9eb0e07
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.MathUtils;
+using osu.Game.Tournament.IPC;
+using osu.Game.Tournament.Screens.Gameplay.Components;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneMatchScoreDisplay : LadderTestScene
+ {
+ [Cached(Type = typeof(MatchIPCInfo))]
+ private MatchIPCInfo matchInfo = new MatchIPCInfo();
+
+ public TestSceneMatchScoreDisplay()
+ {
+ Add(new MatchScoreDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Scheduler.AddDelayed(() =>
+ {
+ int amount = (int)((RNG.NextDouble() - 0.5) * 10000);
+ if (amount < 0)
+ matchInfo.Score1.Value -= amount;
+ else
+ matchInfo.Score2.Value += amount;
+ }, 100, true);
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
new file mode 100644
index 0000000000..77fa411058
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Rulesets;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Components;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneTournamentBeatmapPanel : OsuTestScene
+ {
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 1091460 });
+ req.Success += success;
+ api.Queue(req);
+ }
+
+ private void success(APIBeatmap apiBeatmap)
+ {
+ var beatmap = apiBeatmap.ToBeatmap(rulesets);
+ Add(new TournamentBeatmapPanel(beatmap)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
new file mode 100644
index 0000000000..41d32d9448
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
@@ -0,0 +1,130 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Online.Chat;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.IPC;
+using osu.Game.Tournament.Models;
+using osu.Game.Users;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneTournamentMatchChatDisplay : OsuTestScene
+ {
+ private readonly Channel testChannel = new Channel();
+ private readonly Channel testChannel2 = new Channel();
+
+ private readonly User admin = new User
+ {
+ Username = "HappyStick",
+ Id = 2,
+ Colour = "f2ca34"
+ };
+
+ private readonly User redUser = new User
+ {
+ Username = "BanchoBot",
+ Id = 3,
+ };
+
+ private readonly User blueUser = new User
+ {
+ Username = "Zallius",
+ Id = 4,
+ };
+
+ [Cached]
+ private LadderInfo ladderInfo = new LadderInfo();
+
+ [Cached]
+ private MatchIPCInfo matchInfo = new MatchIPCInfo(); // hide parent
+
+ private readonly TournamentMatchChatDisplay chatDisplay;
+
+ public TestSceneTournamentMatchChatDisplay()
+ {
+ Add(chatDisplay = new TournamentMatchChatDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+
+ ladderInfo.CurrentMatch.Value = new TournamentMatch
+ {
+ Team1 =
+ {
+ Value = new TournamentTeam { Players = new BindableList { redUser } }
+ },
+ Team2 =
+ {
+ Value = new TournamentTeam { Players = new BindableList { blueUser } }
+ }
+ };
+
+ chatDisplay.Channel.Value = testChannel;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("message from admin", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ {
+ Sender = admin,
+ Content = "I am a wang!"
+ }));
+
+ AddStep("message from team red", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ {
+ Sender = redUser,
+ Content = "I am team red."
+ }));
+
+ AddStep("message from team red", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ {
+ Sender = redUser,
+ Content = "I plan to win!"
+ }));
+
+ AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ {
+ Sender = blueUser,
+ Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand."
+ }));
+
+ AddStep("message from admin", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ {
+ Sender = admin,
+ Content = "Okay okay, calm down guys. Let's do this!"
+ }));
+
+ AddStep("multiple messages", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ {
+ Sender = admin,
+ Content = "I spam you!"
+ },
+ new Message(nextMessageId())
+ {
+ Sender = admin,
+ Content = "I spam you!!!1"
+ },
+ new Message(nextMessageId())
+ {
+ Sender = admin,
+ Content = "I spam you!1!1"
+ }));
+
+ AddStep("change channel to 2", () => chatDisplay.Channel.Value = testChannel2);
+
+ AddStep("change channel to 1", () => chatDisplay.Channel.Value = testChannel);
+ }
+
+ private int messageId;
+
+ private long? nextMessageId() => messageId++;
+ }
+}
diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs
new file mode 100644
index 0000000000..b49341d0d1
--- /dev/null
+++ b/osu.Game.Tournament.Tests/LadderTestScene.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Tests
+{
+ public abstract class LadderTestScene : OsuTestScene
+ {
+ [Resolved]
+ protected LadderInfo Ladder { get; private set; }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs
new file mode 100644
index 0000000000..201736f38a
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Screens.Gameplay;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneGameplayScreen : OsuTestScene
+ {
+ [Cached]
+ private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new GameplayScreen());
+ Add(chat);
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs
new file mode 100644
index 0000000000..a45c5de2bd
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Cursor;
+using osu.Game.Tournament.Screens.Editors;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneLadderEditorScreen : LadderTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new OsuContextMenuContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new LadderEditorScreen()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs
new file mode 100644
index 0000000000..2be0564c82
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Cursor;
+using osu.Game.Tournament.Screens.Ladder;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneLadderScreen : LadderTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new OsuContextMenuContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new LadderScreen()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
new file mode 100644
index 0000000000..a7011c6d3c
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Game.Tournament.Screens.MapPool;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneMapPoolScreen : LadderTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(MapPoolScreen)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new MapPoolScreen { Width = 0.7f });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs
new file mode 100644
index 0000000000..e15ac416b0
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tournament.Screens.Editors;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneRoundEditorScreen : LadderTestScene
+ {
+ public TestSceneRoundEditorScreen()
+ {
+ Add(new RoundEditorScreen
+ {
+ Width = 0.85f // create room for control panel
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs
new file mode 100644
index 0000000000..f3e65919eb
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Screens.Schedule;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneScheduleScreen : OsuTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new ScheduleScreen());
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs
new file mode 100644
index 0000000000..edf1477b06
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Screens.Showcase;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneShowcaseScreen : OsuTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new ShowcaseScreen());
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs
new file mode 100644
index 0000000000..097bad4a02
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tournament.Screens.Editors;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneTeamEditorScreen : LadderTestScene
+ {
+ public TestSceneTeamEditorScreen()
+ {
+ Add(new TeamEditorScreen
+ {
+ Width = 0.85f // create room for control panel
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs
new file mode 100644
index 0000000000..3d340e393c
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Tournament.Models;
+using osu.Game.Tournament.Screens.TeamIntro;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneTeamIntroScreen : LadderTestScene
+ {
+ [Cached]
+ private readonly Bindable currentMatch = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var match = new TournamentMatch();
+ match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA");
+ match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN");
+ match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
+ currentMatch.Value = match;
+
+ Add(new TeamIntroScreen
+ {
+ FillMode = FillMode.Fit,
+ FillAspectRatio = 16 / 9f
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs
new file mode 100644
index 0000000000..6f5e17a36e
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Tournament.Models;
+using osu.Game.Tournament.Screens.TeamWin;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneTeamWinScreen : LadderTestScene
+ {
+ [Cached]
+ private readonly Bindable currentMatch = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var match = new TournamentMatch();
+ match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA");
+ match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN");
+ match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
+ currentMatch.Value = match;
+
+ Add(new TeamWinScreen
+ {
+ FillMode = FillMode.Fit,
+ FillAspectRatio = 16 / 9f
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs
new file mode 100644
index 0000000000..378614343a
--- /dev/null
+++ b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Platform;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tournament.Tests
+{
+ public class TestSceneTournamentSceneManager : OsuTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load(Storage storage)
+ {
+ Add(new TournamentSceneManager());
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs
new file mode 100644
index 0000000000..f7ad757926
--- /dev/null
+++ b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Testing;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+
+namespace osu.Game.Tournament.Tests
+{
+ public class TournamentTestBrowser : TournamentGameBase
+ {
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LoadComponentAsync(new Background("Menu/menu-background-0")
+ {
+ Colour = OsuColour.Gray(0.5f),
+ Depth = 10
+ }, AddInternal);
+
+ // Have to construct this here, rather than in the constructor, because
+ // we depend on some dependencies to be loaded within OsuGameBase.load().
+ Add(new TestBrowser());
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs
new file mode 100644
index 0000000000..1f63f7c545
--- /dev/null
+++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework;
+using osu.Framework.Platform;
+
+namespace osu.Game.Tournament.Tests
+{
+ public static class TournamentTestRunner
+ {
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ {
+ host.Run(new TournamentTestBrowser());
+ return 0;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
new file mode 100644
index 0000000000..1c169184fb
--- /dev/null
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ osu.Game.Tournament.Tests.TournamentTestRunner
+
+
+
+
+
+
+
+
+ WinExe
+ netcoreapp2.2
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tournament/Components/ControlPanel.cs b/osu.Game.Tournament/Components/ControlPanel.cs
new file mode 100644
index 0000000000..a9bb1bf42f
--- /dev/null
+++ b/osu.Game.Tournament/Components/ControlPanel.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ ///
+ /// An element anchored to the right-hand area of a screen that provides streamer level controls.
+ /// Should be off-screen.
+ ///
+ public class ControlPanel : Container
+ {
+ private readonly FillFlowContainer buttons;
+
+ protected override Container Content => buttons;
+
+ public ControlPanel()
+ {
+ RelativeSizeAxes = Axes.Both;
+ AlwaysPresent = true;
+ Width = 0.15f;
+ Anchor = Anchor.TopRight;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = new Color4(54, 54, 54, 255)
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = "Control Panel",
+ Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22)
+ },
+ buttons = new FillFlowContainer
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Width = 0.75f,
+ Position = new Vector2(0, 35f),
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 5f),
+ },
+ };
+ }
+
+ public class Spacer : CompositeDrawable
+ {
+ public Spacer(float height = 20)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = height;
+ AlwaysPresent = true;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs
new file mode 100644
index 0000000000..ee7e350970
--- /dev/null
+++ b/osu.Game.Tournament/Components/DateTextBox.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Bindables;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Settings;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DateTextBox : SettingsTextBox
+ {
+ public new Bindable Bindable
+ {
+ get => bindable;
+ set
+ {
+ bindable = value.GetBoundCopy();
+ bindable.BindValueChanged(dto =>
+ base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
+ }
+ }
+
+ // hold a reference to the provided bindable so we don't have to in every settings section.
+ private Bindable bindable;
+
+ public DateTextBox()
+ {
+ base.Bindable = new Bindable();
+ ((OsuTextBox)Control).OnCommit = (sender, newText) =>
+ {
+ try
+ {
+ bindable.Value = DateTimeOffset.Parse(sender.Text);
+ }
+ catch
+ {
+ // reset textbox content to its last valid state on a parse failure.
+ bindable.TriggerChange();
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
new file mode 100644
index 0000000000..361bd92770
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Components
+{
+ public abstract class DrawableTournamentTeam : CompositeDrawable
+ {
+ public readonly TournamentTeam Team;
+
+ protected readonly Sprite Flag;
+ protected readonly OsuSpriteText AcronymText;
+
+ [UsedImplicitly]
+ private Bindable acronym;
+
+ [UsedImplicitly]
+ private Bindable flag;
+
+ protected DrawableTournamentTeam(TournamentTeam team)
+ {
+ Team = team;
+
+ Flag = new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit
+ };
+
+ AcronymText = new OsuSpriteText
+ {
+ Font = OsuFont.GetFont(weight: FontWeight.Regular),
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ if (Team == null) return;
+
+ (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(acronym => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true);
+ (flag = Team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Flag.Texture = textures.Get($@"Flags/{Team.FlagName}"), true);
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs
new file mode 100644
index 0000000000..c07882ddd0
--- /dev/null
+++ b/osu.Game.Tournament/Components/SongBar.cs
@@ -0,0 +1,251 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Screens.Menu;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ public class SongBar : CompositeDrawable
+ {
+ private BeatmapInfo beatmap;
+
+ public BeatmapInfo Beatmap
+ {
+ get => beatmap;
+ set
+ {
+ if (beatmap == value)
+ return;
+
+ beatmap = value;
+ update();
+ }
+ }
+
+ private LegacyMods mods;
+
+ public LegacyMods Mods
+ {
+ get => mods;
+ set
+ {
+ mods = value;
+ update();
+ }
+ }
+
+ private Container panelContents;
+ private Container innerPanel;
+ private Container outerPanel;
+ private TournamentBeatmapPanel panel;
+
+ private float panelWidth => expanded ? 0.6f : 1;
+
+ private const float main_width = 0.97f;
+ private const float inner_panel_width = 0.7f;
+
+ private bool expanded;
+
+ public bool Expanded
+ {
+ get => expanded;
+ set
+ {
+ expanded = value;
+ panel?.ResizeWidthTo(panelWidth, 800, Easing.OutQuint);
+
+ if (expanded)
+ {
+ innerPanel.ResizeWidthTo(inner_panel_width, 800, Easing.OutQuint);
+ outerPanel.ResizeWidthTo(main_width, 800, Easing.OutQuint);
+ }
+ else
+ {
+ innerPanel.ResizeWidthTo(1, 800, Easing.OutQuint);
+ outerPanel.ResizeWidthTo(0.25f, 800, Easing.OutQuint);
+ }
+ }
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ outerPanel = new Container
+ {
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Colour = Color4.Black.Opacity(0.2f),
+ Type = EdgeEffectType.Shadow,
+ Radius = 5,
+ },
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ RelativePositionAxes = Axes.X,
+ X = -(1 - main_width) / 2,
+ Y = -10,
+ Width = main_width,
+ Height = TournamentBeatmapPanel.HEIGHT,
+ CornerRadius = TournamentBeatmapPanel.HEIGHT / 2,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(0.93f),
+ },
+ new OsuLogo
+ {
+ Triangles = false,
+ Colour = OsuColour.Gray(0.33f),
+ Scale = new Vector2(0.08f),
+ Margin = new MarginPadding(50),
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ },
+ innerPanel = new Container
+ {
+ Masking = true,
+ CornerRadius = TournamentBeatmapPanel.HEIGHT / 2,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Width = inner_panel_width,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(0.86f),
+ },
+ panelContents = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ }
+ }
+ }
+ }
+ };
+
+ Expanded = true;
+ }
+
+ private void update()
+ {
+ if (beatmap == null)
+ {
+ panelContents.Clear();
+ return;
+ }
+
+ var bpm = beatmap.BeatmapSet.OnlineInfo.BPM;
+ var length = beatmap.OnlineInfo.Length;
+ string hardRockExtra = "";
+ string srExtra = "";
+
+ //var ar = beatmap.BaseDifficulty.ApproachRate;
+ if ((mods & LegacyMods.HardRock) > 0)
+ {
+ hardRockExtra = "*";
+ srExtra = "*";
+ }
+
+ if ((mods & LegacyMods.DoubleTime) > 0)
+ {
+ //ar *= 1.5f;
+ bpm *= 1.5f;
+ length /= 1.5f;
+ srExtra = "*";
+ }
+
+ panelContents.Children = new Drawable[]
+ {
+ new DiffPiece(("Length", TimeSpan.FromSeconds(length).ToString(@"mm\:ss")))
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.BottomLeft,
+ },
+ new DiffPiece(("BPM", $"{bpm:0.#}"))
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.TopLeft
+ },
+ new DiffPiece(
+ //("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
+ //("AR", $"{ar:0.#}{srExtra}"),
+ ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
+ ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}")
+ )
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.BottomRight
+ },
+ new DiffPiece(("Star Rating", $"{beatmap.StarDifficulty:0.#}{srExtra}"))
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.TopRight
+ },
+ panel = new TournamentBeatmapPanel(beatmap)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(panelWidth, 1)
+ }
+ };
+ }
+
+ public class DiffPiece : TextFlowContainer
+ {
+ public DiffPiece(params (string heading, string content)[] tuples)
+ {
+ Margin = new MarginPadding { Horizontal = 15, Vertical = 1 };
+ AutoSizeAxes = Axes.Both;
+
+ void cp(SpriteText s, Color4 colour)
+ {
+ s.Colour = colour;
+ s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15);
+ }
+
+ for (var i = 0; i < tuples.Length; i++)
+ {
+ var tuple = tuples[i];
+
+ if (i > 0)
+ {
+ AddText(" / ", s =>
+ {
+ cp(s, OsuColour.Gray(0.33f));
+ s.Spacing = new Vector2(-2, 0);
+ });
+ }
+
+ AddText(new OsuSpriteText { Text = tuple.heading }, s => cp(s, OsuColour.Gray(0.33f)));
+ AddText(" ", s => cp(s, OsuColour.Gray(0.33f)));
+ AddText(new OsuSpriteText { Text = tuple.content }, s => cp(s, OsuColour.Gray(0.5f)));
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
new file mode 100644
index 0000000000..d5e28c1e3e
--- /dev/null
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -0,0 +1,203 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Specialized;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Tournament.Models;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ public class TournamentBeatmapPanel : CompositeDrawable
+ {
+ public readonly BeatmapInfo Beatmap;
+ private readonly string mods;
+
+ private const float horizontal_padding = 10;
+ private const float vertical_padding = 5;
+
+ public const float HEIGHT = 50;
+
+ private readonly Bindable currentMatch = new Bindable();
+ private Box flash;
+
+ public TournamentBeatmapPanel(BeatmapInfo beatmap, string mods = null)
+ {
+ if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
+
+ Beatmap = beatmap;
+ this.mods = mods;
+ Width = 400;
+ Height = HEIGHT;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(LadderInfo ladder, TextureStore textures)
+ {
+ currentMatch.BindValueChanged(matchChanged);
+ currentMatch.BindTo(ladder.CurrentMatch);
+
+ CornerRadius = HEIGHT / 2;
+ Masking = true;
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ new UpdateableBeatmapSetCover
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(0.5f),
+ BeatmapSet = Beatmap.BeatmapSet,
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Padding = new MarginPadding(vertical_padding),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = new LocalisedString((
+ $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}",
+ $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")),
+ Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true),
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Padding = new MarginPadding(vertical_padding),
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = "mapper",
+ Padding = new MarginPadding { Right = 5 },
+ Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14)
+ },
+ new OsuSpriteText
+ {
+ Text = Beatmap.Metadata.AuthorString,
+ Padding = new MarginPadding { Right = 20 },
+ Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14)
+ },
+ new OsuSpriteText
+ {
+ Text = "difficulty",
+ Padding = new MarginPadding { Right = 5 },
+ Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14)
+ },
+ new OsuSpriteText
+ {
+ Text = Beatmap.Version,
+ Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14)
+ },
+ }
+ }
+ },
+ },
+ flash = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ Blending = BlendingMode.Additive,
+ Alpha = 0,
+ },
+ });
+
+ if (!string.IsNullOrEmpty(mods))
+ AddInternal(new Sprite
+ {
+ Texture = textures.Get($"mods/{mods}"),
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Margin = new MarginPadding(20),
+ Scale = new Vector2(0.5f)
+ });
+ }
+
+ private void matchChanged(ValueChangedEvent match)
+ {
+ if (match.OldValue != null)
+ match.OldValue.PicksBans.CollectionChanged -= picksBansOnCollectionChanged;
+ match.NewValue.PicksBans.CollectionChanged += picksBansOnCollectionChanged;
+ updateState();
+ }
+
+ private void picksBansOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ => updateState();
+
+ private BeatmapChoice choice;
+
+ private void updateState()
+ {
+ var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap.OnlineBeatmapID);
+
+ bool doFlash = found != choice;
+ choice = found;
+
+ if (found != null)
+ {
+ if (doFlash)
+ flash?.FadeOutFromOne(500).Loop(0, 10);
+
+ BorderThickness = 6;
+
+ switch (found.Team)
+ {
+ case TeamColour.Red:
+ BorderColour = Color4.Red;
+ break;
+
+ case TeamColour.Blue:
+ BorderColour = Color4.Blue;
+ break;
+ }
+
+ switch (found.Type)
+ {
+ case ChoiceType.Pick:
+ Colour = Color4.White;
+ Alpha = 1;
+ break;
+
+ case ChoiceType.Ban:
+ Colour = Color4.Gray;
+ Alpha = 0.5f;
+ break;
+ }
+ }
+ else
+ {
+ Colour = Color4.White;
+ BorderThickness = 0;
+ Alpha = 1;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
new file mode 100644
index 0000000000..48c5b9bd35
--- /dev/null
+++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
@@ -0,0 +1,93 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Online.Chat;
+using osu.Game.Overlays.Chat;
+using osu.Game.Tournament.IPC;
+using osu.Game.Tournament.Models;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ public class TournamentMatchChatDisplay : StandAloneChatDisplay
+ {
+ private readonly Bindable chatChannel = new Bindable();
+
+ private ChannelManager manager;
+
+ public TournamentMatchChatDisplay()
+ {
+ RelativeSizeAxes = Axes.X;
+ Y = 100;
+ Size = new Vector2(0.45f, 112);
+ Margin = new MarginPadding(10);
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(MatchIPCInfo ipc)
+ {
+ if (ipc != null)
+ {
+ chatChannel.BindTo(ipc.ChatChannel);
+ chatChannel.BindValueChanged(c =>
+ {
+ if (string.IsNullOrWhiteSpace(c.NewValue))
+ return;
+
+ int id = int.Parse(c.NewValue);
+
+ if (id <= 0) return;
+
+ if (manager == null)
+ {
+ AddInternal(manager = new ChannelManager());
+ Channel.BindTo(manager.CurrentChannel);
+ }
+
+ foreach (var ch in manager.JoinedChannels.ToList())
+ manager.LeaveChannel(ch);
+
+ var channel = new Channel
+ {
+ Id = id,
+ Type = ChannelType.Public
+ };
+
+ manager.JoinChannel(channel);
+ manager.CurrentChannel.Value = channel;
+ }, true);
+ }
+ }
+
+ protected override ChatLine CreateMessage(Message message) => new MatchMessage(message);
+
+ protected class MatchMessage : StandAloneMessage
+ {
+ public MatchMessage(Message message)
+ : base(message)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(LadderInfo info)
+ {
+ //if (info.CurrentMatch.Value.Team1.Value.Players.Any(u => u.Id == Message.Sender.Id))
+ // ColourBox.Colour = red;
+ //else if (info.CurrentMatch.Value.Team2.Value.Players.Any(u => u.Id == Message.Sender.Id))
+ // ColourBox.Colour = blue;
+ //else if (Message.Sender.Colour != null)
+ // SenderText.Colour = ColourBox.Colour = OsuColour.FromHex(Message.Sender.Colour);
+ }
+
+ private readonly Color4 red = new Color4(186, 0, 18, 255);
+ private readonly Color4 blue = new Color4(17, 136, 170, 255);
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs
new file mode 100644
index 0000000000..4f4660f645
--- /dev/null
+++ b/osu.Game.Tournament/Components/TourneyVideo.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Video;
+using osu.Game.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ public class TourneyVideo : CompositeDrawable
+ {
+ private readonly VideoSprite video;
+
+ public TourneyVideo(Stream stream)
+ {
+ if (stream == null)
+ {
+ InternalChild = new Box
+ {
+ Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.3f), OsuColour.Gray(0.6f)),
+ RelativeSizeAxes = Axes.Both,
+ };
+ }
+ else
+ InternalChild = video = new VideoSprite(stream)
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ };
+ }
+
+ public bool Loop
+ {
+ set
+ {
+ if (video != null)
+ video.Loop = value;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
new file mode 100644
index 0000000000..4fd858bd12
--- /dev/null
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -0,0 +1,194 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.IO;
+using System.Linq;
+using Microsoft.Win32;
+using osu.Framework.Allocation;
+using osu.Framework.Logging;
+using osu.Framework.Platform;
+using osu.Framework.Platform.Windows;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Rulesets;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.IPC
+{
+ public class FileBasedIPC : MatchIPCInfo
+ {
+ [Resolved]
+ protected IAPIProvider API { get; private set; }
+
+ [Resolved]
+ protected RulesetStore Rulesets { get; private set; }
+
+ private int lastBeatmapId;
+
+ [BackgroundDependencyLoader]
+ private void load(LadderInfo ladder, GameHost host)
+ {
+ StableStorage stable;
+
+ try
+ {
+ stable = new StableStorage(host as DesktopGameHost);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Stable installation could not be found; disabling file based IPC");
+ return;
+ }
+
+ const string file_ipc_filename = "ipc.txt";
+ const string file_ipc_state_filename = "ipc-state.txt";
+ const string file_ipc_scores_filename = "ipc-scores.txt";
+ const string file_ipc_channel_filename = "ipc-channel.txt";
+
+ if (stable.Exists(file_ipc_filename))
+ Scheduler.AddDelayed(delegate
+ {
+ try
+ {
+ using (var stream = stable.GetStream(file_ipc_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ var beatmapId = int.Parse(sr.ReadLine());
+ var mods = int.Parse(sr.ReadLine());
+
+ if (lastBeatmapId != beatmapId)
+ {
+ lastBeatmapId = beatmapId;
+
+ var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null);
+
+ if (existing != null)
+ Beatmap.Value = existing.BeatmapInfo;
+ else
+ {
+ var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
+ req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets);
+ API.Queue(req);
+ }
+ }
+
+ Mods.Value = (LegacyMods)mods;
+ }
+ }
+ catch
+ {
+ // file might be in use.
+ }
+
+ try
+ {
+ using (var stream = stable.GetStream(file_ipc_channel_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ ChatChannel.Value = sr.ReadLine();
+ }
+ }
+ catch (Exception)
+ {
+ // file might be in use.
+ }
+
+ try
+ {
+ using (var stream = stable.GetStream(file_ipc_state_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine());
+ }
+ }
+ catch (Exception)
+ {
+ // file might be in use.
+ }
+
+ try
+ {
+ using (var stream = stable.GetStream(file_ipc_scores_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ Score1.Value = int.Parse(sr.ReadLine());
+ Score2.Value = int.Parse(sr.ReadLine());
+ }
+ }
+ catch (Exception)
+ {
+ // file might be in use.
+ }
+ }, 250, true);
+ }
+
+ ///
+ /// A method of accessing an osu-stable install in a controlled fashion.
+ ///
+ private class StableStorage : WindowsStorage
+ {
+ protected override string LocateBasePath()
+ {
+ bool checkExists(string p)
+ {
+ return File.Exists(Path.Combine(p, "ipc.txt"));
+ }
+
+ string stableInstallPath = string.Empty;
+
+ try
+ {
+ try
+ {
+ stableInstallPath = "G:\\My Drive\\Main\\osu!tourney";
+
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+
+ stableInstallPath = "G:\\My Drive\\Main\\osu!mappool";
+
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
+ stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
+
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+ }
+ catch
+ {
+ }
+
+ stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+
+ stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+
+ return null;
+ }
+ finally
+ {
+ Logger.Log($"Stable path for tourney usage: {stableInstallPath}");
+ }
+ }
+
+ public StableStorage(DesktopGameHost host)
+ : base(string.Empty, host)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/IPC/MatchIPCInfo.cs b/osu.Game.Tournament/IPC/MatchIPCInfo.cs
new file mode 100644
index 0000000000..701258c6c7
--- /dev/null
+++ b/osu.Game.Tournament/IPC/MatchIPCInfo.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Legacy;
+
+namespace osu.Game.Tournament.IPC
+{
+ public class MatchIPCInfo : Component
+ {
+ public Bindable Beatmap { get; } = new Bindable();
+ public Bindable Mods { get; } = new Bindable();
+ public Bindable State { get; } = new Bindable();
+ public Bindable ChatChannel { get; } = new Bindable();
+ public BindableInt Score1 { get; } = new BindableInt();
+ public BindableInt Score2 { get; } = new BindableInt();
+ }
+}
diff --git a/osu.Game.Tournament/IPC/TourneyState.cs b/osu.Game.Tournament/IPC/TourneyState.cs
new file mode 100644
index 0000000000..ef1c612a53
--- /dev/null
+++ b/osu.Game.Tournament/IPC/TourneyState.cs
@@ -0,0 +1,14 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Tournament.IPC
+{
+ public enum TourneyState
+ {
+ Initialising,
+ Idle,
+ WaitingForClients,
+ Playing,
+ Ranking
+ }
+}
diff --git a/osu.Game.Tournament/Models/BeatmapChoice.cs b/osu.Game.Tournament/Models/BeatmapChoice.cs
new file mode 100644
index 0000000000..384b349b24
--- /dev/null
+++ b/osu.Game.Tournament/Models/BeatmapChoice.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// A beatmap choice by a team from a tournament's map pool.
+ ///
+ [Serializable]
+ public class BeatmapChoice
+ {
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ public TeamColour Team;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ public ChoiceType Type;
+
+ public int BeatmapID;
+ }
+
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum TeamColour
+ {
+ Red,
+ Blue
+ }
+
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum ChoiceType
+ {
+ Pick,
+ Ban,
+ }
+}
diff --git a/osu.Game.Tournament/Models/LadderEditorInfo.cs b/osu.Game.Tournament/Models/LadderEditorInfo.cs
new file mode 100644
index 0000000000..70fd115e25
--- /dev/null
+++ b/osu.Game.Tournament/Models/LadderEditorInfo.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+
+namespace osu.Game.Tournament.Models
+{
+ public class LadderEditorInfo
+ {
+ public readonly Bindable Selected = new Bindable();
+ }
+}
diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs
new file mode 100644
index 0000000000..547c4eab08
--- /dev/null
+++ b/osu.Game.Tournament/Models/LadderInfo.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using osu.Framework.Bindables;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// Holds the complete data required to operate the tournament system.
+ ///
+ [Serializable]
+ public class LadderInfo
+ {
+ public BindableList Matches = new BindableList();
+ public BindableList Rounds = new BindableList();
+ public BindableList Teams = new BindableList();
+
+ // only used for serialisation
+ public List Progressions = new List();
+
+ [JsonIgnore]
+ public Bindable CurrentMatch = new Bindable();
+ }
+}
diff --git a/osu.Game.Tournament/Models/RoundBeatmap.cs b/osu.Game.Tournament/Models/RoundBeatmap.cs
new file mode 100644
index 0000000000..5d43d0ca66
--- /dev/null
+++ b/osu.Game.Tournament/Models/RoundBeatmap.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Tournament.Models
+{
+ public class RoundBeatmap
+ {
+ public int ID;
+ public string Mods;
+
+ public BeatmapInfo BeatmapInfo;
+ }
+}
diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs
new file mode 100644
index 0000000000..06cce3d59e
--- /dev/null
+++ b/osu.Game.Tournament/Models/TournamentMatch.cs
@@ -0,0 +1,125 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Newtonsoft.Json;
+using osu.Framework.Bindables;
+using osu.Game.Tournament.Screens.Ladder.Components;
+using SixLabors.Primitives;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// A collection of two teams competing in a head-to-head match.
+ ///
+ [Serializable]
+ public class TournamentMatch
+ {
+ public int ID;
+
+ public List Acronyms
+ {
+ get
+ {
+ List acronyms = new List();
+ if (Team1Acronym != null) acronyms.Add(Team1Acronym);
+ if (Team2Acronym != null) acronyms.Add(Team2Acronym);
+ return acronyms;
+ }
+ }
+
+ [JsonIgnore]
+ public readonly Bindable Team1 = new Bindable();
+
+ public string Team1Acronym;
+
+ public readonly Bindable Team1Score = new Bindable();
+
+ [JsonIgnore]
+ public readonly Bindable Team2 = new Bindable();
+
+ public string Team2Acronym;
+
+ public readonly Bindable Team2Score = new Bindable();
+
+ public readonly Bindable Completed = new Bindable();
+
+ public readonly Bindable Losers = new Bindable();
+
+ public readonly ObservableCollection PicksBans = new ObservableCollection();
+
+ [JsonIgnore]
+ public readonly Bindable Round = new Bindable();
+
+ [JsonIgnore]
+ public readonly Bindable Progression = new Bindable();
+
+ [JsonIgnore]
+ public readonly Bindable LosersProgression = new Bindable();
+
+ ///
+ /// Should not be set directly. Use LadderInfo.CurrentMatch.Value = this instead.
+ ///
+ public readonly Bindable Current = new Bindable();
+
+ public readonly Bindable Date = new Bindable(DateTimeOffset.Now);
+
+ [JsonProperty]
+ public readonly BindableList ConditionalMatches = new BindableList();
+
+ public readonly Bindable Position = new Bindable();
+
+ public TournamentMatch()
+ {
+ Team1.BindValueChanged(t => Team1Acronym = t.NewValue?.Acronym.Value, true);
+ Team2.BindValueChanged(t => Team2Acronym = t.NewValue?.Acronym.Value, true);
+ }
+
+ public TournamentMatch(TournamentTeam team1 = null, TournamentTeam team2 = null)
+ : this()
+ {
+ Team1.Value = team1;
+ Team2.Value = team2;
+ }
+
+ [JsonIgnore]
+ public TournamentTeam Winner => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team1.Value : Team2.Value;
+
+ [JsonIgnore]
+ public TournamentTeam Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value;
+
+ public int PointsToWin => Round.Value?.BestOf.Value / 2 + 1 ?? 0;
+
+ ///
+ /// Remove scores from the match, in case of a false click or false start.
+ ///
+ public void CancelMatchStart()
+ {
+ Team1Score.Value = null;
+ Team2Score.Value = null;
+ }
+
+ ///
+ /// Initialise this match with zeroed scores. Will be a noop if either team is not present.
+ ///
+ public void StartMatch()
+ {
+ if (Team1.Value == null || Team2.Value == null)
+ return;
+
+ Team1Score.Value = 0;
+ Team2Score.Value = 0;
+ }
+
+ public void Reset()
+ {
+ CancelMatchStart();
+ Team1.Value = null;
+ Team2.Value = null;
+ Completed.Value = false;
+ PicksBans.Clear();
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Models/TournamentProgression.cs b/osu.Game.Tournament/Models/TournamentProgression.cs
new file mode 100644
index 0000000000..5575b4c29e
--- /dev/null
+++ b/osu.Game.Tournament/Models/TournamentProgression.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// A mapping between two es.
+ /// Used for serialisation exclusively.
+ ///
+ [Serializable]
+ public class TournamentProgression
+ {
+ public int SourceID;
+ public int TargetID;
+
+ public bool Losers;
+
+ public TournamentProgression(int sourceID, int targetID, bool losers = false)
+ {
+ SourceID = sourceID;
+ TargetID = targetID;
+ Losers = losers;
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Models/TournamentRound.cs b/osu.Game.Tournament/Models/TournamentRound.cs
new file mode 100644
index 0000000000..08b3143be1
--- /dev/null
+++ b/osu.Game.Tournament/Models/TournamentRound.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using osu.Framework.Bindables;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// A tournament round, containing many matches, generally executed in a short time period.
+ ///
+ [Serializable]
+ public class TournamentRound
+ {
+ public readonly Bindable Name = new Bindable();
+ public readonly Bindable Description = new Bindable();
+
+ public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 };
+
+ [JsonProperty]
+ public readonly BindableList Beatmaps = new BindableList();
+
+ public readonly Bindable StartDate = new Bindable { Value = DateTimeOffset.UtcNow };
+
+ // only used for serialisation
+ public List Matches = new List();
+
+ public override string ToString() => Name.Value ?? "None";
+ }
+}
diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs
new file mode 100644
index 0000000000..54b8a35180
--- /dev/null
+++ b/osu.Game.Tournament/Models/TournamentTeam.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using Newtonsoft.Json;
+using osu.Framework.Bindables;
+using osu.Game.Users;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// A team representation. For official tournaments this is generally a country.
+ ///
+ [Serializable]
+ public class TournamentTeam
+ {
+ ///
+ /// The name of this team.
+ ///
+ public Bindable FullName = new Bindable(string.Empty);
+
+ ///
+ /// Name of the file containing the flag.
+ ///
+ public Bindable FlagName = new Bindable(string.Empty);
+
+ ///
+ /// Short acronym which appears in the group boxes post-selection.
+ ///
+ public Bindable Acronym = new Bindable(string.Empty);
+
+ [JsonProperty]
+ public BindableList Players { get; set; } = new BindableList();
+
+ public TournamentTeam()
+ {
+ Acronym.ValueChanged += val =>
+ {
+ // use a sane default flag name based on acronym.
+ if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase))
+ FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpper() : string.Empty;
+ };
+
+ FullName.ValueChanged += val =>
+ {
+ // use a sane acronym based on full name.
+ if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase))
+ Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpper() : string.Empty;
+ };
+ }
+
+ public override string ToString() => FullName.Value ?? Acronym.Value;
+ }
+}
diff --git a/osu.Game.Tournament/Properties/AssemblyInfo.cs b/osu.Game.Tournament/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..70e42bcafb
--- /dev/null
+++ b/osu.Game.Tournament/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Runtime.CompilerServices;
+
+// We publish our internal attributes to other sub-projects of the framework.
+// Note, that we omit visual tests as they are meant to test the framework
+// behavior "in the wild".
+
+[assembly: InternalsVisibleTo("osu.Game.Tournament.Tests")]
+[assembly: InternalsVisibleTo("osu.Game.Tournament.Tests.Dynamic")]
diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin
new file mode 100644
index 0000000000..42cfdf08de
Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin differ
diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png
new file mode 100644
index 0000000000..332d9ca056
Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png differ
diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin
new file mode 100644
index 0000000000..3047c2eb3e
Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin differ
diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png
new file mode 100644
index 0000000000..1252d233d3
Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png differ
diff --git a/osu.Game.Tournament/Resources/countries.json b/osu.Game.Tournament/Resources/countries.json
new file mode 100644
index 0000000000..ec2ca2bf37
--- /dev/null
+++ b/osu.Game.Tournament/Resources/countries.json
@@ -0,0 +1,1252 @@
+[
+ {
+ "FlagName": "BD",
+ "FullName": "Bangladesh",
+ "Acronym": "BGD"
+ },
+ {
+ "FlagName": "BE",
+ "FullName": "Belgium",
+ "Acronym": "BEL"
+ },
+ {
+ "FlagName": "BF",
+ "FullName": "Burkina Faso",
+ "Acronym": "BFA"
+ },
+ {
+ "FlagName": "BG",
+ "FullName": "Bulgaria",
+ "Acronym": "BGR"
+ },
+ {
+ "FlagName": "BA",
+ "FullName": "Bosnia and Herzegovina",
+ "Acronym": "BIH"
+ },
+ {
+ "FlagName": "BB",
+ "FullName": "Barbados",
+ "Acronym": "BRB"
+ },
+ {
+ "FlagName": "WF",
+ "FullName": "Wallis and Futuna",
+ "Acronym": "WLF"
+ },
+ {
+ "FlagName": "BL",
+ "FullName": "Saint Barthelemy",
+ "Acronym": "BLM"
+ },
+ {
+ "FlagName": "BM",
+ "FullName": "Bermuda",
+ "Acronym": "BMU"
+ },
+ {
+ "FlagName": "BN",
+ "FullName": "Brunei",
+ "Acronym": "BRN"
+ },
+ {
+ "FlagName": "BO",
+ "FullName": "Bolivia",
+ "Acronym": "BOL"
+ },
+ {
+ "FlagName": "BH",
+ "FullName": "Bahrain",
+ "Acronym": "BHR"
+ },
+ {
+ "FlagName": "BI",
+ "FullName": "Burundi",
+ "Acronym": "BDI"
+ },
+ {
+ "FlagName": "BJ",
+ "FullName": "Benin",
+ "Acronym": "BEN"
+ },
+ {
+ "FlagName": "BT",
+ "FullName": "Bhutan",
+ "Acronym": "BTN"
+ },
+ {
+ "FlagName": "JM",
+ "FullName": "Jamaica",
+ "Acronym": "JAM"
+ },
+ {
+ "FlagName": "BV",
+ "FullName": "Bouvet Island",
+ "Acronym": "BVT"
+ },
+ {
+ "FlagName": "BW",
+ "FullName": "Botswana",
+ "Acronym": "BWA"
+ },
+ {
+ "FlagName": "WS",
+ "FullName": "Samoa",
+ "Acronym": "WSM"
+ },
+ {
+ "FlagName": "BQ",
+ "FullName": "Bonaire, Saint Eustatius and Saba",
+ "Acronym": "BES"
+ },
+ {
+ "FlagName": "BR",
+ "FullName": "Brazil",
+ "Acronym": "BRA"
+ },
+ {
+ "FlagName": "BS",
+ "FullName": "Bahamas",
+ "Acronym": "BHS"
+ },
+ {
+ "FlagName": "JE",
+ "FullName": "Jersey",
+ "Acronym": "JEY"
+ },
+ {
+ "FlagName": "BY",
+ "FullName": "Belarus",
+ "Acronym": "BLR"
+ },
+ {
+ "FlagName": "BZ",
+ "FullName": "Belize",
+ "Acronym": "BLZ"
+ },
+ {
+ "FlagName": "RU",
+ "FullName": "Russia",
+ "Acronym": "RUS"
+ },
+ {
+ "FlagName": "RW",
+ "FullName": "Rwanda",
+ "Acronym": "RWA"
+ },
+ {
+ "FlagName": "RS",
+ "FullName": "Serbia",
+ "Acronym": "SRB"
+ },
+ {
+ "FlagName": "TL",
+ "FullName": "East Timor",
+ "Acronym": "TLS"
+ },
+ {
+ "FlagName": "RE",
+ "FullName": "Reunion",
+ "Acronym": "REU"
+ },
+ {
+ "FlagName": "TM",
+ "FullName": "Turkmenistan",
+ "Acronym": "TKM"
+ },
+ {
+ "FlagName": "TJ",
+ "FullName": "Tajikistan",
+ "Acronym": "TJK"
+ },
+ {
+ "FlagName": "RO",
+ "FullName": "Romania",
+ "Acronym": "ROU"
+ },
+ {
+ "FlagName": "TK",
+ "FullName": "Tokelau",
+ "Acronym": "TKL"
+ },
+ {
+ "FlagName": "GW",
+ "FullName": "Guinea-Bissau",
+ "Acronym": "GNB"
+ },
+ {
+ "FlagName": "GU",
+ "FullName": "Guam",
+ "Acronym": "GUM"
+ },
+ {
+ "FlagName": "GT",
+ "FullName": "Guatemala",
+ "Acronym": "GTM"
+ },
+ {
+ "FlagName": "GS",
+ "FullName": "South Georgia and the South Sandwich Islands",
+ "Acronym": "SGS"
+ },
+ {
+ "FlagName": "GR",
+ "FullName": "Greece",
+ "Acronym": "GRC"
+ },
+ {
+ "FlagName": "GQ",
+ "FullName": "Equatorial Guinea",
+ "Acronym": "GNQ"
+ },
+ {
+ "FlagName": "GP",
+ "FullName": "Guadeloupe",
+ "Acronym": "GLP"
+ },
+ {
+ "FlagName": "JP",
+ "FullName": "Japan",
+ "Acronym": "JPN"
+ },
+ {
+ "FlagName": "GY",
+ "FullName": "Guyana",
+ "Acronym": "GUY"
+ },
+ {
+ "FlagName": "GG",
+ "FullName": "Guernsey",
+ "Acronym": "GGY"
+ },
+ {
+ "FlagName": "GF",
+ "FullName": "French Guiana",
+ "Acronym": "GUF"
+ },
+ {
+ "FlagName": "GE",
+ "FullName": "Georgia",
+ "Acronym": "GEO"
+ },
+ {
+ "FlagName": "GD",
+ "FullName": "Grenada",
+ "Acronym": "GRD"
+ },
+ {
+ "FlagName": "GB",
+ "FullName": "United Kingdom",
+ "Acronym": "GBR"
+ },
+ {
+ "FlagName": "GA",
+ "FullName": "Gabon",
+ "Acronym": "GAB"
+ },
+ {
+ "FlagName": "SV",
+ "FullName": "El Salvador",
+ "Acronym": "SLV"
+ },
+ {
+ "FlagName": "GN",
+ "FullName": "Guinea",
+ "Acronym": "GIN"
+ },
+ {
+ "FlagName": "GM",
+ "FullName": "Gambia",
+ "Acronym": "GMB"
+ },
+ {
+ "FlagName": "GL",
+ "FullName": "Greenland",
+ "Acronym": "GRL"
+ },
+ {
+ "FlagName": "GI",
+ "FullName": "Gibraltar",
+ "Acronym": "GIB"
+ },
+ {
+ "FlagName": "GH",
+ "FullName": "Ghana",
+ "Acronym": "GHA"
+ },
+ {
+ "FlagName": "OM",
+ "FullName": "Oman",
+ "Acronym": "OMN"
+ },
+ {
+ "FlagName": "TN",
+ "FullName": "Tunisia",
+ "Acronym": "TUN"
+ },
+ {
+ "FlagName": "JO",
+ "FullName": "Jordan",
+ "Acronym": "JOR"
+ },
+ {
+ "FlagName": "HR",
+ "FullName": "Croatia",
+ "Acronym": "HRV"
+ },
+ {
+ "FlagName": "HT",
+ "FullName": "Haiti",
+ "Acronym": "HTI"
+ },
+ {
+ "FlagName": "HU",
+ "FullName": "Hungary",
+ "Acronym": "HUN"
+ },
+ {
+ "FlagName": "HK",
+ "FullName": "Hong Kong",
+ "Acronym": "HKG"
+ },
+ {
+ "FlagName": "HN",
+ "FullName": "Honduras",
+ "Acronym": "HND"
+ },
+ {
+ "FlagName": "HM",
+ "FullName": "Heard Island and McDonald Islands",
+ "Acronym": "HMD"
+ },
+ {
+ "FlagName": "VE",
+ "FullName": "Venezuela",
+ "Acronym": "VEN"
+ },
+ {
+ "FlagName": "PR",
+ "FullName": "Puerto Rico",
+ "Acronym": "PRI"
+ },
+ {
+ "FlagName": "PS",
+ "FullName": "Palestinian Territory",
+ "Acronym": "PSE"
+ },
+ {
+ "FlagName": "PW",
+ "FullName": "Palau",
+ "Acronym": "PLW"
+ },
+ {
+ "FlagName": "PT",
+ "FullName": "Portugal",
+ "Acronym": "PRT"
+ },
+ {
+ "FlagName": "SJ",
+ "FullName": "Svalbard and Jan Mayen",
+ "Acronym": "SJM"
+ },
+ {
+ "FlagName": "PY",
+ "FullName": "Paraguay",
+ "Acronym": "PRY"
+ },
+ {
+ "FlagName": "IQ",
+ "FullName": "Iraq",
+ "Acronym": "IRQ"
+ },
+ {
+ "FlagName": "PA",
+ "FullName": "Panama",
+ "Acronym": "PAN"
+ },
+ {
+ "FlagName": "PF",
+ "FullName": "French Polynesia",
+ "Acronym": "PYF"
+ },
+ {
+ "FlagName": "PG",
+ "FullName": "Papua New Guinea",
+ "Acronym": "PNG"
+ },
+ {
+ "FlagName": "PE",
+ "FullName": "Peru",
+ "Acronym": "PER"
+ },
+ {
+ "FlagName": "PK",
+ "FullName": "Pakistan",
+ "Acronym": "PAK"
+ },
+ {
+ "FlagName": "PH",
+ "FullName": "Philippines",
+ "Acronym": "PHL"
+ },
+ {
+ "FlagName": "PN",
+ "FullName": "Pitcairn",
+ "Acronym": "PCN"
+ },
+ {
+ "FlagName": "PL",
+ "FullName": "Poland",
+ "Acronym": "POL"
+ },
+ {
+ "FlagName": "PM",
+ "FullName": "Saint Pierre and Miquelon",
+ "Acronym": "SPM"
+ },
+ {
+ "FlagName": "ZM",
+ "FullName": "Zambia",
+ "Acronym": "ZMB"
+ },
+ {
+ "FlagName": "EH",
+ "FullName": "Western Sahara",
+ "Acronym": "ESH"
+ },
+ {
+ "FlagName": "EE",
+ "FullName": "Estonia",
+ "Acronym": "EST"
+ },
+ {
+ "FlagName": "EG",
+ "FullName": "Egypt",
+ "Acronym": "EGY"
+ },
+ {
+ "FlagName": "ZA",
+ "FullName": "South Africa",
+ "Acronym": "ZAF"
+ },
+ {
+ "FlagName": "EC",
+ "FullName": "Ecuador",
+ "Acronym": "ECU"
+ },
+ {
+ "FlagName": "IT",
+ "FullName": "Italy",
+ "Acronym": "ITA"
+ },
+ {
+ "FlagName": "VN",
+ "FullName": "Vietnam",
+ "Acronym": "VNM"
+ },
+ {
+ "FlagName": "SB",
+ "FullName": "Solomon Islands",
+ "Acronym": "SLB"
+ },
+ {
+ "FlagName": "ET",
+ "FullName": "Ethiopia",
+ "Acronym": "ETH"
+ },
+ {
+ "FlagName": "SO",
+ "FullName": "Somalia",
+ "Acronym": "SOM"
+ },
+ {
+ "FlagName": "ZW",
+ "FullName": "Zimbabwe",
+ "Acronym": "ZWE"
+ },
+ {
+ "FlagName": "SA",
+ "FullName": "Saudi Arabia",
+ "Acronym": "SAU"
+ },
+ {
+ "FlagName": "ES",
+ "FullName": "Spain",
+ "Acronym": "ESP"
+ },
+ {
+ "FlagName": "ER",
+ "FullName": "Eritrea",
+ "Acronym": "ERI"
+ },
+ {
+ "FlagName": "ME",
+ "FullName": "Montenegro",
+ "Acronym": "MNE"
+ },
+ {
+ "FlagName": "MD",
+ "FullName": "Moldova",
+ "Acronym": "MDA"
+ },
+ {
+ "FlagName": "MG",
+ "FullName": "Madagascar",
+ "Acronym": "MDG"
+ },
+ {
+ "FlagName": "MF",
+ "FullName": "Saint Martin",
+ "Acronym": "MAF"
+ },
+ {
+ "FlagName": "MA",
+ "FullName": "Morocco",
+ "Acronym": "MAR"
+ },
+ {
+ "FlagName": "MC",
+ "FullName": "Monaco",
+ "Acronym": "MCO"
+ },
+ {
+ "FlagName": "UZ",
+ "FullName": "Uzbekistan",
+ "Acronym": "UZB"
+ },
+ {
+ "FlagName": "MM",
+ "FullName": "Myanmar",
+ "Acronym": "MMR"
+ },
+ {
+ "FlagName": "ML",
+ "FullName": "Mali",
+ "Acronym": "MLI"
+ },
+ {
+ "FlagName": "MO",
+ "FullName": "Macao",
+ "Acronym": "MAC"
+ },
+ {
+ "FlagName": "MN",
+ "FullName": "Mongolia",
+ "Acronym": "MNG"
+ },
+ {
+ "FlagName": "MH",
+ "FullName": "Marshall Islands",
+ "Acronym": "MHL"
+ },
+ {
+ "FlagName": "MK",
+ "FullName": "Macedonia",
+ "Acronym": "MKD"
+ },
+ {
+ "FlagName": "MU",
+ "FullName": "Mauritius",
+ "Acronym": "MUS"
+ },
+ {
+ "FlagName": "MT",
+ "FullName": "Malta",
+ "Acronym": "MLT"
+ },
+ {
+ "FlagName": "MW",
+ "FullName": "Malawi",
+ "Acronym": "MWI"
+ },
+ {
+ "FlagName": "MV",
+ "FullName": "Maldives",
+ "Acronym": "MDV"
+ },
+ {
+ "FlagName": "MQ",
+ "FullName": "Martinique",
+ "Acronym": "MTQ"
+ },
+ {
+ "FlagName": "MP",
+ "FullName": "Northern Mariana Islands",
+ "Acronym": "MNP"
+ },
+ {
+ "FlagName": "MS",
+ "FullName": "Montserrat",
+ "Acronym": "MSR"
+ },
+ {
+ "FlagName": "MR",
+ "FullName": "Mauritania",
+ "Acronym": "MRT"
+ },
+ {
+ "FlagName": "IM",
+ "FullName": "Isle of Man",
+ "Acronym": "IMN"
+ },
+ {
+ "FlagName": "UG",
+ "FullName": "Uganda",
+ "Acronym": "UGA"
+ },
+ {
+ "FlagName": "TZ",
+ "FullName": "Tanzania",
+ "Acronym": "TZA"
+ },
+ {
+ "FlagName": "MY",
+ "FullName": "Malaysia",
+ "Acronym": "MYS"
+ },
+ {
+ "FlagName": "MX",
+ "FullName": "Mexico",
+ "Acronym": "MEX"
+ },
+ {
+ "FlagName": "IL",
+ "FullName": "Israel",
+ "Acronym": "ISR"
+ },
+ {
+ "FlagName": "FR",
+ "FullName": "France",
+ "Acronym": "FRA"
+ },
+ {
+ "FlagName": "IO",
+ "FullName": "British Indian Ocean Territory",
+ "Acronym": "IOT"
+ },
+ {
+ "FlagName": "SH",
+ "FullName": "Saint Helena",
+ "Acronym": "SHN"
+ },
+ {
+ "FlagName": "FI",
+ "FullName": "Finland",
+ "Acronym": "FIN"
+ },
+ {
+ "FlagName": "FJ",
+ "FullName": "Fiji",
+ "Acronym": "FJI"
+ },
+ {
+ "FlagName": "FK",
+ "FullName": "Falkland Islands",
+ "Acronym": "FLK"
+ },
+ {
+ "FlagName": "FM",
+ "FullName": "Micronesia",
+ "Acronym": "FSM"
+ },
+ {
+ "FlagName": "FO",
+ "FullName": "Faroe Islands",
+ "Acronym": "FRO"
+ },
+ {
+ "FlagName": "NI",
+ "FullName": "Nicaragua",
+ "Acronym": "NIC"
+ },
+ {
+ "FlagName": "NL",
+ "FullName": "Netherlands",
+ "Acronym": "NLD"
+ },
+ {
+ "FlagName": "NO",
+ "FullName": "Norway",
+ "Acronym": "NOR"
+ },
+ {
+ "FlagName": "NA",
+ "FullName": "Namibia",
+ "Acronym": "NAM"
+ },
+ {
+ "FlagName": "VU",
+ "FullName": "Vanuatu",
+ "Acronym": "VUT"
+ },
+ {
+ "FlagName": "NC",
+ "FullName": "New Caledonia",
+ "Acronym": "NCL"
+ },
+ {
+ "FlagName": "NE",
+ "FullName": "Niger",
+ "Acronym": "NER"
+ },
+ {
+ "FlagName": "NF",
+ "FullName": "Norfolk Island",
+ "Acronym": "NFK"
+ },
+ {
+ "FlagName": "NG",
+ "FullName": "Nigeria",
+ "Acronym": "NGA"
+ },
+ {
+ "FlagName": "NZ",
+ "FullName": "New Zealand",
+ "Acronym": "NZL"
+ },
+ {
+ "FlagName": "NP",
+ "FullName": "Nepal",
+ "Acronym": "NPL"
+ },
+ {
+ "FlagName": "NR",
+ "FullName": "Nauru",
+ "Acronym": "NRU"
+ },
+ {
+ "FlagName": "NU",
+ "FullName": "Niue",
+ "Acronym": "NIU"
+ },
+ {
+ "FlagName": "CK",
+ "FullName": "Cook Islands",
+ "Acronym": "COK"
+ },
+ {
+ "FlagName": "XK",
+ "FullName": "Kosovo",
+ "Acronym": "XKX"
+ },
+ {
+ "FlagName": "CI",
+ "FullName": "Ivory Coast",
+ "Acronym": "CIV"
+ },
+ {
+ "FlagName": "CH",
+ "FullName": "Switzerland",
+ "Acronym": "CHE"
+ },
+ {
+ "FlagName": "CO",
+ "FullName": "Colombia",
+ "Acronym": "COL"
+ },
+ {
+ "FlagName": "CN",
+ "FullName": "China",
+ "Acronym": "CHN"
+ },
+ {
+ "FlagName": "CM",
+ "FullName": "Cameroon",
+ "Acronym": "CMR"
+ },
+ {
+ "FlagName": "CL",
+ "FullName": "Chile",
+ "Acronym": "CHL"
+ },
+ {
+ "FlagName": "CC",
+ "FullName": "Cocos Islands",
+ "Acronym": "CCK"
+ },
+ {
+ "FlagName": "CA",
+ "FullName": "Canada",
+ "Acronym": "CAN"
+ },
+ {
+ "FlagName": "CG",
+ "FullName": "Republic of the Congo",
+ "Acronym": "COG"
+ },
+ {
+ "FlagName": "CF",
+ "FullName": "Central African Republic",
+ "Acronym": "CAF"
+ },
+ {
+ "FlagName": "CD",
+ "FullName": "Democratic Republic of the Congo",
+ "Acronym": "COD"
+ },
+ {
+ "FlagName": "CZ",
+ "FullName": "Czech Republic",
+ "Acronym": "CZE"
+ },
+ {
+ "FlagName": "CY",
+ "FullName": "Cyprus",
+ "Acronym": "CYP"
+ },
+ {
+ "FlagName": "CX",
+ "FullName": "Christmas Island",
+ "Acronym": "CXR"
+ },
+ {
+ "FlagName": "CR",
+ "FullName": "Costa Rica",
+ "Acronym": "CRI"
+ },
+ {
+ "FlagName": "CW",
+ "FullName": "Curacao",
+ "Acronym": "CUW"
+ },
+ {
+ "FlagName": "CV",
+ "FullName": "Cape Verde",
+ "Acronym": "CPV"
+ },
+ {
+ "FlagName": "CU",
+ "FullName": "Cuba",
+ "Acronym": "CUB"
+ },
+ {
+ "FlagName": "SZ",
+ "FullName": "Swaziland",
+ "Acronym": "SWZ"
+ },
+ {
+ "FlagName": "SY",
+ "FullName": "Syria",
+ "Acronym": "SYR"
+ },
+ {
+ "FlagName": "SX",
+ "FullName": "Sint Maarten",
+ "Acronym": "SXM"
+ },
+ {
+ "FlagName": "KG",
+ "FullName": "Kyrgyzstan",
+ "Acronym": "KGZ"
+ },
+ {
+ "FlagName": "KE",
+ "FullName": "Kenya",
+ "Acronym": "KEN"
+ },
+ {
+ "FlagName": "SS",
+ "FullName": "South Sudan",
+ "Acronym": "SSD"
+ },
+ {
+ "FlagName": "SR",
+ "FullName": "Suriname",
+ "Acronym": "SUR"
+ },
+ {
+ "FlagName": "KI",
+ "FullName": "Kiribati",
+ "Acronym": "KIR"
+ },
+ {
+ "FlagName": "KH",
+ "FullName": "Cambodia",
+ "Acronym": "KHM"
+ },
+ {
+ "FlagName": "KN",
+ "FullName": "Saint Kitts and Nevis",
+ "Acronym": "KNA"
+ },
+ {
+ "FlagName": "KM",
+ "FullName": "Comoros",
+ "Acronym": "COM"
+ },
+ {
+ "FlagName": "ST",
+ "FullName": "Sao Tome and Principe",
+ "Acronym": "STP"
+ },
+ {
+ "FlagName": "SK",
+ "FullName": "Slovakia",
+ "Acronym": "SVK"
+ },
+ {
+ "FlagName": "KR",
+ "FullName": "South Korea",
+ "Acronym": "KOR"
+ },
+ {
+ "FlagName": "SI",
+ "FullName": "Slovenia",
+ "Acronym": "SVN"
+ },
+ {
+ "FlagName": "KP",
+ "FullName": "North Korea",
+ "Acronym": "PRK"
+ },
+ {
+ "FlagName": "KW",
+ "FullName": "Kuwait",
+ "Acronym": "KWT"
+ },
+ {
+ "FlagName": "SN",
+ "FullName": "Senegal",
+ "Acronym": "SEN"
+ },
+ {
+ "FlagName": "SM",
+ "FullName": "San Marino",
+ "Acronym": "SMR"
+ },
+ {
+ "FlagName": "SL",
+ "FullName": "Sierra Leone",
+ "Acronym": "SLE"
+ },
+ {
+ "FlagName": "SC",
+ "FullName": "Seychelles",
+ "Acronym": "SYC"
+ },
+ {
+ "FlagName": "KZ",
+ "FullName": "Kazakhstan",
+ "Acronym": "KAZ"
+ },
+ {
+ "FlagName": "KY",
+ "FullName": "Cayman Islands",
+ "Acronym": "CYM"
+ },
+ {
+ "FlagName": "SG",
+ "FullName": "Singapore",
+ "Acronym": "SGP"
+ },
+ {
+ "FlagName": "SE",
+ "FullName": "Sweden",
+ "Acronym": "SWE"
+ },
+ {
+ "FlagName": "SD",
+ "FullName": "Sudan",
+ "Acronym": "SDN"
+ },
+ {
+ "FlagName": "DO",
+ "FullName": "Dominican Republic",
+ "Acronym": "DOM"
+ },
+ {
+ "FlagName": "DM",
+ "FullName": "Dominica",
+ "Acronym": "DMA"
+ },
+ {
+ "FlagName": "DJ",
+ "FullName": "Djibouti",
+ "Acronym": "DJI"
+ },
+ {
+ "FlagName": "DK",
+ "FullName": "Denmark",
+ "Acronym": "DNK"
+ },
+ {
+ "FlagName": "VG",
+ "FullName": "British Virgin Islands",
+ "Acronym": "VGB"
+ },
+ {
+ "FlagName": "DE",
+ "FullName": "Germany",
+ "Acronym": "DEU"
+ },
+ {
+ "FlagName": "YE",
+ "FullName": "Yemen",
+ "Acronym": "YEM"
+ },
+ {
+ "FlagName": "DZ",
+ "FullName": "Algeria",
+ "Acronym": "DZA"
+ },
+ {
+ "FlagName": "US",
+ "FullName": "United States",
+ "Acronym": "USA"
+ },
+ {
+ "FlagName": "UY",
+ "FullName": "Uruguay",
+ "Acronym": "URY"
+ },
+ {
+ "FlagName": "YT",
+ "FullName": "Mayotte",
+ "Acronym": "MYT"
+ },
+ {
+ "FlagName": "UM",
+ "FullName": "United States Minor Outlying Islands",
+ "Acronym": "UMI"
+ },
+ {
+ "FlagName": "LB",
+ "FullName": "Lebanon",
+ "Acronym": "LBN"
+ },
+ {
+ "FlagName": "LC",
+ "FullName": "Saint Lucia",
+ "Acronym": "LCA"
+ },
+ {
+ "FlagName": "LA",
+ "FullName": "Laos",
+ "Acronym": "LAO"
+ },
+ {
+ "FlagName": "TV",
+ "FullName": "Tuvalu",
+ "Acronym": "TUV"
+ },
+ {
+ "FlagName": "TW",
+ "FullName": "Taiwan",
+ "Acronym": "TWN"
+ },
+ {
+ "FlagName": "TT",
+ "FullName": "Trinidad and Tobago",
+ "Acronym": "TTO"
+ },
+ {
+ "FlagName": "TR",
+ "FullName": "Turkey",
+ "Acronym": "TUR"
+ },
+ {
+ "FlagName": "LK",
+ "FullName": "Sri Lanka",
+ "Acronym": "LKA"
+ },
+ {
+ "FlagName": "LI",
+ "FullName": "Liechtenstein",
+ "Acronym": "LIE"
+ },
+ {
+ "FlagName": "LV",
+ "FullName": "Latvia",
+ "Acronym": "LVA"
+ },
+ {
+ "FlagName": "TO",
+ "FullName": "Tonga",
+ "Acronym": "TON"
+ },
+ {
+ "FlagName": "LT",
+ "FullName": "Lithuania",
+ "Acronym": "LTU"
+ },
+ {
+ "FlagName": "LU",
+ "FullName": "Luxembourg",
+ "Acronym": "LUX"
+ },
+ {
+ "FlagName": "LR",
+ "FullName": "Liberia",
+ "Acronym": "LBR"
+ },
+ {
+ "FlagName": "LS",
+ "FullName": "Lesotho",
+ "Acronym": "LSO"
+ },
+ {
+ "FlagName": "TH",
+ "FullName": "Thailand",
+ "Acronym": "THA"
+ },
+ {
+ "FlagName": "TF",
+ "FullName": "French Southern Territories",
+ "Acronym": "ATF"
+ },
+ {
+ "FlagName": "TG",
+ "FullName": "Togo",
+ "Acronym": "TGO"
+ },
+ {
+ "FlagName": "TD",
+ "FullName": "Chad",
+ "Acronym": "TCD"
+ },
+ {
+ "FlagName": "TC",
+ "FullName": "Turks and Caicos Islands",
+ "Acronym": "TCA"
+ },
+ {
+ "FlagName": "LY",
+ "FullName": "Libya",
+ "Acronym": "LBY"
+ },
+ {
+ "FlagName": "VA",
+ "FullName": "Vatican",
+ "Acronym": "VAT"
+ },
+ {
+ "FlagName": "VC",
+ "FullName": "Saint Vincent and the Grenadines",
+ "Acronym": "VCT"
+ },
+ {
+ "FlagName": "AE",
+ "FullName": "United Arab Emirates",
+ "Acronym": "ARE"
+ },
+ {
+ "FlagName": "AD",
+ "FullName": "Andorra",
+ "Acronym": "AND"
+ },
+ {
+ "FlagName": "AG",
+ "FullName": "Antigua and Barbuda",
+ "Acronym": "ATG"
+ },
+ {
+ "FlagName": "AF",
+ "FullName": "Afghanistan",
+ "Acronym": "AFG"
+ },
+ {
+ "FlagName": "AI",
+ "FullName": "Anguilla",
+ "Acronym": "AIA"
+ },
+ {
+ "FlagName": "VI",
+ "FullName": "U.S. Virgin Islands",
+ "Acronym": "VIR"
+ },
+ {
+ "FlagName": "IS",
+ "FullName": "Iceland",
+ "Acronym": "ISL"
+ },
+ {
+ "FlagName": "IR",
+ "FullName": "Iran",
+ "Acronym": "IRN"
+ },
+ {
+ "FlagName": "AM",
+ "FullName": "Armenia",
+ "Acronym": "ARM"
+ },
+ {
+ "FlagName": "AL",
+ "FullName": "Albania",
+ "Acronym": "ALB"
+ },
+ {
+ "FlagName": "AO",
+ "FullName": "Angola",
+ "Acronym": "AGO"
+ },
+ {
+ "FlagName": "AQ",
+ "FullName": "Antarctica",
+ "Acronym": "ATA"
+ },
+ {
+ "FlagName": "AS",
+ "FullName": "American Samoa",
+ "Acronym": "ASM"
+ },
+ {
+ "FlagName": "AR",
+ "FullName": "Argentina",
+ "Acronym": "ARG"
+ },
+ {
+ "FlagName": "AU",
+ "FullName": "Australia",
+ "Acronym": "AUS"
+ },
+ {
+ "FlagName": "AT",
+ "FullName": "Austria",
+ "Acronym": "AUT"
+ },
+ {
+ "FlagName": "AW",
+ "FullName": "Aruba",
+ "Acronym": "ABW"
+ },
+ {
+ "FlagName": "IN",
+ "FullName": "India",
+ "Acronym": "IND"
+ },
+ {
+ "FlagName": "AX",
+ "FullName": "Aland Islands",
+ "Acronym": "ALA"
+ },
+ {
+ "FlagName": "AZ",
+ "FullName": "Azerbaijan",
+ "Acronym": "AZE"
+ },
+ {
+ "FlagName": "IE",
+ "FullName": "Ireland",
+ "Acronym": "IRL"
+ },
+ {
+ "FlagName": "ID",
+ "FullName": "Indonesia",
+ "Acronym": "IDN"
+ },
+ {
+ "FlagName": "UA",
+ "FullName": "Ukraine",
+ "Acronym": "UKR"
+ },
+ {
+ "FlagName": "QA",
+ "FullName": "Qatar",
+ "Acronym": "QAT"
+ },
+ {
+ "FlagName": "MZ",
+ "FullName": "Mozambique",
+ "Acronym": "MOZ"
+ }
+]
\ No newline at end of file
diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
new file mode 100644
index 0000000000..fccd35ca9e
--- /dev/null
+++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.IPC;
+
+namespace osu.Game.Tournament.Screens
+{
+ public abstract class BeatmapInfoScreen : TournamentScreen
+ {
+ protected readonly SongBar SongBar;
+
+ protected BeatmapInfoScreen()
+ {
+ AddInternal(SongBar = new SongBar
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(MatchIPCInfo ipc)
+ {
+ ipc.Beatmap.BindValueChanged(beatmapChanged, true);
+ ipc.Mods.BindValueChanged(modsChanged, true);
+ }
+
+ private void modsChanged(ValueChangedEvent mods)
+ {
+ SongBar.Mods = mods.NewValue;
+ }
+
+ private void beatmapChanged(ValueChangedEvent beatmap)
+ {
+ SongBar.FadeInFromZero(300, Easing.OutQuint);
+ SongBar.Beatmap = beatmap.NewValue;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs
similarity index 92%
rename from osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs
index 36af1389ff..d197c0f5d9 100644
--- a/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs
@@ -4,7 +4,7 @@
using osu.Framework.Configuration;
using osu.Framework.Platform;
-namespace osu.Game.Screens.Tournament.Components
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public class DrawingsConfigManager : IniConfigManager
{
diff --git a/osu.Game/Screens/Tournament/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs
similarity index 70%
rename from osu.Game/Screens/Tournament/Group.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/Group.cs
index 4fed10b94d..549ff26018 100644
--- a/osu.Game/Screens/Tournament/Group.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs
@@ -4,19 +4,17 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Graphics.Sprites;
-using osuTK;
-using osuTK.Graphics;
-using osu.Game.Screens.Tournament.Teams;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osuTK;
+using osuTK.Graphics;
-namespace osu.Game.Screens.Tournament
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public class Group : Container
{
@@ -73,7 +71,7 @@ namespace osu.Game.Screens.Tournament
};
}
- public void AddTeam(DrawingsTeam team)
+ public void AddTeam(TournamentTeam team)
{
GroupTeam gt = new GroupTeam(team);
@@ -88,10 +86,10 @@ namespace osu.Game.Screens.Tournament
public bool ContainsTeam(string fullName)
{
- return allTeams.Any(t => t.Team.FullName == fullName);
+ return allTeams.Any(t => t.Team.FullName.Value == fullName);
}
- public bool RemoveTeam(DrawingsTeam team)
+ public bool RemoveTeam(TournamentTeam team)
{
allTeams.RemoveAll(gt => gt.Team == team);
@@ -116,25 +114,29 @@ namespace osu.Game.Screens.Tournament
{
StringBuilder sb = new StringBuilder();
foreach (GroupTeam gt in allTeams)
- sb.AppendLine(gt.Team.FullName);
+ sb.AppendLine(gt.Team.FullName.Value);
return sb.ToString();
}
- private class GroupTeam : Container
+ private class GroupTeam : DrawableTournamentTeam
{
- public readonly DrawingsTeam Team;
-
private readonly FillFlowContainer innerContainer;
- private readonly Sprite flagSprite;
- public GroupTeam(DrawingsTeam team)
+ public GroupTeam(TournamentTeam team)
+ : base(team)
{
- Team = team;
-
Width = 36;
AutoSizeAxes = Axes.Y;
- Children = new Drawable[]
+ Flag.Anchor = Anchor.TopCentre;
+ Flag.Origin = Anchor.TopCentre;
+
+ AcronymText.Anchor = Anchor.TopCentre;
+ AcronymText.Origin = Anchor.TopCentre;
+ AcronymText.Text = team.Acronym.Value.ToUpperInvariant();
+ AcronymText.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 10);
+
+ InternalChildren = new Drawable[]
{
innerContainer = new FillFlowContainer
{
@@ -149,21 +151,8 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[]
{
- flagSprite = new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- FillMode = FillMode.Fit
- },
- new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
-
- Text = team.Acronym.ToUpperInvariant(),
- Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 10),
- }
+ Flag,
+ AcronymText
}
}
};
@@ -175,12 +164,6 @@ namespace osu.Game.Screens.Tournament
innerContainer.ScaleTo(1.5f);
innerContainer.ScaleTo(1f, 200);
}
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- flagSprite.Texture = textures.Get($@"Flags/{Team.FlagName}");
- }
}
}
}
diff --git a/osu.Game/Screens/Tournament/GroupContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs
similarity index 95%
rename from osu.Game/Screens/Tournament/GroupContainer.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs
index 211a28ca38..8a66ca7bf6 100644
--- a/osu.Game/Screens/Tournament/GroupContainer.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs
@@ -7,10 +7,10 @@ using System.Linq;
using System.Text;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Tournament.Models;
using osuTK;
-using osu.Game.Screens.Tournament.Teams;
-namespace osu.Game.Screens.Tournament
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public class GroupContainer : Container
{
@@ -64,7 +64,7 @@ namespace osu.Game.Screens.Tournament
}
}
- public void AddTeam(DrawingsTeam team)
+ public void AddTeam(TournamentTeam team)
{
if (groups[currentGroup].TeamsCount == maxTeams)
return;
diff --git a/osu.Game/Screens/Tournament/Teams/ITeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/ITeamList.cs
similarity index 62%
rename from osu.Game/Screens/Tournament/Teams/ITeamList.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/ITeamList.cs
index e998c5dce4..09208818a9 100644
--- a/osu.Game/Screens/Tournament/Teams/ITeamList.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/ITeamList.cs
@@ -2,11 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using osu.Game.Tournament.Models;
-namespace osu.Game.Screens.Tournament.Teams
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public interface ITeamList
{
- IEnumerable Teams { get; }
+ IEnumerable Teams { get; }
}
}
diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
similarity index 88%
rename from osu.Game/Screens/Tournament/ScrollingTeamContainer.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
index 02f7f73399..b147d680f0 100644
--- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
@@ -5,26 +5,25 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Threading;
-using osu.Game.Screens.Tournament.Teams;
+using osu.Game.Graphics;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Screens.Tournament
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public class ScrollingTeamContainer : Container
{
public event Action OnScrollStarted;
- public event Action OnSelected;
+ public event Action OnSelected;
- private readonly List availableTeams = new List();
+ private readonly List availableTeams = new List();
private readonly Container tracker;
@@ -55,6 +54,7 @@ namespace osu.Game.Screens.Tournament
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(0.33f),
Masking = true,
CornerRadius = 10f,
@@ -170,7 +170,7 @@ namespace osu.Game.Screens.Tournament
}
}
- public void AddTeam(DrawingsTeam team)
+ public void AddTeam(TournamentTeam team)
{
if (availableTeams.Contains(team))
return;
@@ -181,12 +181,12 @@ namespace osu.Game.Screens.Tournament
scrollState = ScrollState.Idle;
}
- public void AddTeams(IEnumerable teams)
+ public void AddTeams(IEnumerable teams)
{
if (teams == null)
return;
- foreach (DrawingsTeam t in teams)
+ foreach (TournamentTeam t in teams)
AddTeam(t);
}
@@ -197,7 +197,7 @@ namespace osu.Game.Screens.Tournament
scrollState = ScrollState.Idle;
}
- public void RemoveTeam(DrawingsTeam team)
+ public void RemoveTeam(TournamentTeam team)
{
availableTeams.Remove(team);
@@ -282,7 +282,7 @@ namespace osu.Game.Screens.Tournament
private void addFlags()
{
- foreach (DrawingsTeam t in availableTeams)
+ foreach (TournamentTeam t in availableTeams)
{
Add(new ScrollingTeam(t)
{
@@ -319,14 +319,11 @@ namespace osu.Game.Screens.Tournament
Scrolling
}
- public class ScrollingTeam : Container
+ public class ScrollingTeam : DrawableTournamentTeam
{
public const float WIDTH = 58;
public const float HEIGHT = 41;
- public DrawingsTeam Team;
-
- private readonly Sprite flagSprite;
private readonly Box outline;
private bool selected;
@@ -346,10 +343,9 @@ namespace osu.Game.Screens.Tournament
}
}
- public ScrollingTeam(DrawingsTeam team)
+ public ScrollingTeam(TournamentTeam team)
+ : base(team)
{
- Team = team;
-
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
@@ -359,28 +355,21 @@ namespace osu.Game.Screens.Tournament
Alpha = 0;
- Children = new Drawable[]
+ Flag.Anchor = Anchor.Centre;
+ Flag.Origin = Anchor.Centre;
+ Flag.Scale = new Vector2(0.9f);
+
+ InternalChildren = new Drawable[]
{
outline = new Box
{
RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(0.33f),
Alpha = 0
},
- flagSprite = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
-
- Size = new Vector2(WIDTH, HEIGHT) - new Vector2(8)
- }
+ Flag
};
}
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- flagSprite.Texture = textures.Get($@"Flags/{Team.FlagName}");
- }
}
}
}
diff --git a/osu.Game/Screens/Tournament/Teams/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs
similarity index 73%
rename from osu.Game/Screens/Tournament/Teams/StorageBackedTeamList.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs
index fad47c0922..f96ec01cbb 100644
--- a/osu.Game/Screens/Tournament/Teams/StorageBackedTeamList.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs
@@ -6,8 +6,9 @@ using System.Collections.Generic;
using System.IO;
using osu.Framework.Logging;
using osu.Framework.Platform;
+using osu.Game.Tournament.Models;
-namespace osu.Game.Screens.Tournament.Teams
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public class StorageBackedTeamList : ITeamList
{
@@ -20,11 +21,11 @@ namespace osu.Game.Screens.Tournament.Teams
this.storage = storage;
}
- public IEnumerable Teams
+ public IEnumerable Teams
{
get
{
- var teams = new List();
+ var teams = new List();
try
{
@@ -47,17 +48,11 @@ namespace osu.Game.Screens.Tournament.Teams
continue;
}
- string flagName = split[0].Trim();
- string teamName = split[1].Trim();
-
- string acronym = split.Length >= 3 ? split[2].Trim() : teamName;
- acronym = acronym.Substring(0, Math.Min(3, acronym.Length));
-
- teams.Add(new DrawingsTeam
+ teams.Add(new TournamentTeam
{
- FlagName = flagName,
- FullName = teamName,
- Acronym = acronym
+ FullName = { Value = split[1].Trim(), },
+ Acronym = { Value = split.Length >= 3 ? split[2].Trim() : null, },
+ FlagName = { Value = split[0].Trim() }
});
}
}
diff --git a/osu.Game/Screens/Tournament/Components/VisualiserContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs
similarity index 98%
rename from osu.Game/Screens/Tournament/Components/VisualiserContainer.cs
rename to osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs
index 6b0888313b..1cd942b987 100644
--- a/osu.Game/Screens/Tournament/Components/VisualiserContainer.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs
@@ -1,16 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.MathUtils;
-using System.Collections.Generic;
-using System.Linq;
-namespace osu.Game.Screens.Tournament.Components
+namespace osu.Game.Tournament.Screens.Drawings.Components
{
public class VisualiserContainer : Container
{
diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
new file mode 100644
index 0000000000..3a14b6d9c2
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
@@ -0,0 +1,265 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Logging;
+using osu.Framework.Platform;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osu.Game.Tournament.Screens.Drawings.Components;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Screens.Drawings
+{
+ public class DrawingsScreen : CompositeDrawable
+ {
+ private const string results_filename = "drawings_results.txt";
+
+ private ScrollingTeamContainer teamsContainer;
+ private GroupContainer groupsContainer;
+ private OsuSpriteText fullTeamNameText;
+
+ private readonly List allTeams = new List();
+
+ private DrawingsConfigManager drawingsConfig;
+
+ private Task writeOp;
+
+ private Storage storage;
+
+ public ITeamList TeamList;
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures, Storage storage)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ this.storage = storage;
+
+ if (TeamList == null)
+ TeamList = new StorageBackedTeamList(storage);
+
+ if (!TeamList.Teams.Any())
+ {
+ return;
+ }
+
+ drawingsConfig = new DrawingsConfigManager(storage);
+
+ InternalChildren = new Drawable[]
+ {
+ // Main container
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fill,
+ Texture = textures.Get(@"Backgrounds/Drawings/background.png")
+ },
+ // Visualiser
+ new VisualiserContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+
+ RelativeSizeAxes = Axes.X,
+ Size = new Vector2(1, 10),
+
+ Colour = new Color4(255, 204, 34, 255),
+
+ Lines = 6
+ },
+ // Groups
+ groupsContainer = new GroupContainer(drawingsConfig.Get(DrawingsConfig.Groups), drawingsConfig.Get(DrawingsConfig.TeamsPerGroup))
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+
+ Padding = new MarginPadding
+ {
+ Top = 35f,
+ Bottom = 35f
+ }
+ },
+ // Scrolling teams
+ teamsContainer = new ScrollingTeamContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+
+ RelativeSizeAxes = Axes.X,
+ },
+ // Scrolling team name
+ fullTeamNameText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+
+ Position = new Vector2(0, 45f),
+
+ Colour = OsuColour.Gray(0.33f),
+
+ Alpha = 0,
+
+ Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42),
+ }
+ }
+ },
+ // Control panel container
+ new ControlPanel
+ {
+ new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+
+ Text = "Begin random",
+ Action = teamsContainer.StartScrolling,
+ },
+ new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+
+ Text = "Stop random",
+ Action = teamsContainer.StopScrolling,
+ },
+ new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+
+ Text = "Reload",
+ Action = reloadTeams
+ },
+ new ControlPanel.Spacer(),
+ new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+
+ Text = "Reset",
+ Action = () => reset()
+ }
+ }
+ };
+
+ teamsContainer.OnSelected += onTeamSelected;
+ teamsContainer.OnScrollStarted += () => fullTeamNameText.FadeOut(200);
+
+ reset(true);
+ }
+
+ private void onTeamSelected(TournamentTeam team)
+ {
+ groupsContainer.AddTeam(team);
+
+ fullTeamNameText.Text = team.FullName.Value;
+ fullTeamNameText.FadeIn(200);
+
+ writeResults(groupsContainer.GetStringRepresentation());
+ }
+
+ private void writeResults(string text)
+ {
+ void writeAction()
+ {
+ try
+ {
+ // Write to drawings_results
+ using (Stream stream = storage.GetStream(results_filename, FileAccess.Write, FileMode.Create))
+ using (StreamWriter sw = new StreamWriter(stream))
+ {
+ sw.Write(text);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to write results.");
+ }
+ }
+
+ writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run((Action)writeAction);
+ }
+
+ private void reloadTeams()
+ {
+ teamsContainer.ClearTeams();
+ allTeams.Clear();
+
+ foreach (TournamentTeam t in TeamList.Teams)
+ {
+ if (groupsContainer.ContainsTeam(t.FullName.Value))
+ continue;
+
+ allTeams.Add(t);
+ teamsContainer.AddTeam(t);
+ }
+ }
+
+ private void reset(bool loadLastResults = false)
+ {
+ groupsContainer.ClearTeams();
+
+ reloadTeams();
+
+ if (!storage.Exists(results_filename))
+ return;
+
+ if (loadLastResults)
+ {
+ try
+ {
+ // Read from drawings_results
+ using (Stream stream = storage.GetStream(results_filename, FileAccess.Read, FileMode.Open))
+ using (StreamReader sr = new StreamReader(stream))
+ {
+ string line;
+
+ while ((line = sr.ReadLine()?.Trim()) != null)
+ {
+ if (string.IsNullOrEmpty(line))
+ continue;
+
+ if (line.ToUpperInvariant().StartsWith("GROUP"))
+ continue;
+
+ // ReSharper disable once AccessToModifiedClosure
+ TournamentTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName.Value == line);
+
+ if (teamToAdd == null)
+ continue;
+
+ groupsContainer.AddTeam(teamToAdd);
+ teamsContainer.RemoveTeam(teamToAdd);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to read last drawings results.");
+ }
+ }
+ else
+ {
+ writeResults(string.Empty);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Editors/IModelBacked.cs b/osu.Game.Tournament/Screens/Editors/IModelBacked.cs
new file mode 100644
index 0000000000..ed94bd7f45
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Editors/IModelBacked.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Tournament.Screens.Editors
+{
+ ///
+ /// Provides a mechanism to access a related model from a representing class.
+ ///
+ /// The type of model.
+ public interface IModelBacked
+ {
+ ///
+ /// The model.
+ ///
+ TModel Model { get; }
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
new file mode 100644
index 0000000000..ba63013886
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
@@ -0,0 +1,152 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Tournament.Models;
+using osu.Game.Tournament.Screens.Ladder;
+using osu.Game.Tournament.Screens.Ladder.Components;
+using osuTK;
+using osuTK.Graphics;
+using SixLabors.Primitives;
+
+namespace osu.Game.Tournament.Screens.Editors
+{
+ [Cached]
+ public class LadderEditorScreen : LadderScreen, IHasContextMenu
+ {
+ [Cached]
+ private LadderEditorInfo editorInfo = new LadderEditorInfo();
+
+ protected override bool DrawLoserPaths => true;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Content.Add(new LadderEditorSettings
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Margin = new MarginPadding(5)
+ });
+ }
+
+ public void BeginJoin(TournamentMatch match, bool losers)
+ {
+ ScrollContent.Add(new JoinVisualiser(MatchesContainer, match, losers, UpdateLayout));
+ }
+
+ public MenuItem[] ContextMenuItems
+ {
+ get
+ {
+ if (editorInfo == null)
+ return new MenuItem[0];
+
+ return new MenuItem[]
+ {
+ new OsuMenuItem("Create new match", MenuItemType.Highlighted, () =>
+ {
+ var pos = MatchesContainer.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position);
+ LadderInfo.Matches.Add(new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } });
+ }),
+ new OsuMenuItem("Reset teams", MenuItemType.Destructive, () =>
+ {
+ foreach (var p in MatchesContainer)
+ p.Match.Reset();
+ })
+ };
+ }
+ }
+
+ public void Remove(TournamentMatch match)
+ {
+ MatchesContainer.FirstOrDefault(p => p.Match == match)?.Remove();
+ }
+
+ private class JoinVisualiser : CompositeDrawable
+ {
+ private readonly Container matchesContainer;
+ public readonly TournamentMatch Source;
+ private readonly bool losers;
+ private readonly Action complete;
+
+ private ProgressionPath path;
+
+ public JoinVisualiser(Container matchesContainer, TournamentMatch source, bool losers, Action complete)
+ {
+ this.matchesContainer = matchesContainer;
+ RelativeSizeAxes = Axes.Both;
+
+ Source = source;
+ this.losers = losers;
+ this.complete = complete;
+ if (losers)
+ Source.LosersProgression.Value = null;
+ else
+ Source.Progression.Value = null;
+ }
+
+ private DrawableTournamentMatch findTarget(InputState state)
+ {
+ return matchesContainer.FirstOrDefault(d => d.ReceivePositionalInputAt(state.Mouse.Position));
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
+ {
+ return true;
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ var found = findTarget(e.CurrentState);
+
+ if (found == path?.Destination)
+ return false;
+
+ path?.Expire();
+ path = null;
+
+ if (found == null)
+ return false;
+
+ AddInternal(path = new ProgressionPath(matchesContainer.First(c => c.Match == Source), found)
+ {
+ Colour = Color4.Yellow,
+ });
+
+ return base.OnMouseMove(e);
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ var found = findTarget(e.CurrentState);
+
+ if (found != null)
+ {
+ if (found.Match != Source)
+ {
+ if (losers)
+ Source.LosersProgression.Value = found.Match;
+ else
+ Source.Progression.Value = found.Match;
+ }
+
+ complete?.Invoke();
+ Expire();
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
new file mode 100644
index 0000000000..b036350879
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
@@ -0,0 +1,282 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osuTK;
+
+namespace osu.Game.Tournament.Screens.Editors
+{
+ public class RoundEditorScreen : TournamentEditorScreen
+ {
+ protected override BindableList Storage => LadderInfo.Rounds;
+
+ public class RoundRow : CompositeDrawable, IModelBacked
+ {
+ public TournamentRound Model { get; }
+
+ [Resolved]
+ private LadderInfo ladderInfo { get; set; }
+
+ public RoundRow(TournamentRound round)
+ {
+ Model = round;
+
+ Masking = true;
+ CornerRadius = 10;
+
+ RoundBeatmapEditor beatmapEditor = new RoundBeatmapEditor(round)
+ {
+ Width = 0.95f
+ };
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Colour = OsuColour.Gray(0.1f),
+ RelativeSizeAxes = Axes.Both,
+ },
+ new FillFlowContainer
+ {
+ Margin = new MarginPadding(5),
+ Padding = new MarginPadding { Right = 160 },
+ Spacing = new Vector2(5),
+ Direction = FillDirection.Full,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ new SettingsTextBox
+ {
+ LabelText = "Name",
+ Width = 0.33f,
+ Bindable = Model.Name
+ },
+ new SettingsTextBox
+ {
+ LabelText = "Description",
+ Width = 0.33f,
+ Bindable = Model.Description
+ },
+ new DateTextBox
+ {
+ LabelText = "Start Time",
+ Width = 0.33f,
+ Bindable = Model.StartDate
+ },
+ new SettingsSlider
+ {
+ LabelText = "Best of",
+ Width = 0.33f,
+ Bindable = Model.BestOf
+ },
+ new SettingsButton
+ {
+ Width = 0.2f,
+ Margin = new MarginPadding(10),
+ Text = "Add beatmap",
+ Action = () => beatmapEditor.CreateNew()
+ },
+ beatmapEditor
+ }
+ },
+ new DangerousSettingsButton
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.None,
+ Width = 150,
+ Text = "Delete Round",
+ Action = () =>
+ {
+ Expire();
+ ladderInfo.Rounds.Remove(Model);
+ },
+ }
+ };
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ }
+
+ public class RoundBeatmapEditor : CompositeDrawable
+ {
+ private readonly TournamentRound round;
+ private readonly FillFlowContainer flow;
+
+ public RoundBeatmapEditor(TournamentRound round)
+ {
+ this.round = round;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ InternalChild = flow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ LayoutDuration = 200,
+ LayoutEasing = Easing.OutQuint,
+ ChildrenEnumerable = round.Beatmaps.Select(p => new RoundBeatmapRow(round, p))
+ };
+ }
+
+ public void CreateNew()
+ {
+ var user = new RoundBeatmap();
+ round.Beatmaps.Add(user);
+ flow.Add(new RoundBeatmapRow(round, user));
+ }
+
+ public class RoundBeatmapRow : CompositeDrawable
+ {
+ public RoundBeatmap Model { get; }
+
+ [Resolved]
+ protected IAPIProvider API { get; private set; }
+
+ private readonly Bindable beatmapId = new Bindable();
+
+ private readonly Bindable mods = new Bindable();
+
+ private readonly Container drawableContainer;
+
+ public RoundBeatmapRow(TournamentRound team, RoundBeatmap beatmap)
+ {
+ Model = beatmap;
+
+ Margin = new MarginPadding(10);
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Masking = true;
+ CornerRadius = 5;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Colour = OsuColour.Gray(0.2f),
+ RelativeSizeAxes = Axes.Both,
+ },
+ new FillFlowContainer
+ {
+ Margin = new MarginPadding(5),
+ Padding = new MarginPadding { Right = 160 },
+ Spacing = new Vector2(5),
+ Direction = FillDirection.Horizontal,
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new SettingsNumberBox
+ {
+ LabelText = "Beatmap ID",
+ RelativeSizeAxes = Axes.None,
+ Width = 200,
+ Bindable = beatmapId,
+ },
+ new SettingsTextBox
+ {
+ LabelText = "Mods",
+ RelativeSizeAxes = Axes.None,
+ Width = 200,
+ Bindable = mods,
+ },
+ drawableContainer = new Container
+ {
+ Size = new Vector2(100, 70),
+ },
+ }
+ },
+ new DangerousSettingsButton
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.None,
+ Width = 150,
+ Text = "Delete Beatmap",
+ Action = () =>
+ {
+ Expire();
+ team.Beatmaps.Remove(beatmap);
+ },
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ beatmapId.Value = Model.ID.ToString();
+ beatmapId.BindValueChanged(idString =>
+ {
+ int parsed;
+
+ int.TryParse(idString.NewValue, out parsed);
+
+ Model.ID = parsed;
+
+ if (idString.NewValue != idString.OldValue)
+ Model.BeatmapInfo = null;
+
+ if (Model.BeatmapInfo != null)
+ {
+ updatePanel();
+ return;
+ }
+
+ var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID });
+
+ req.Success += res =>
+ {
+ Model.BeatmapInfo = res.ToBeatmap(rulesets);
+ updatePanel();
+ };
+
+ req.Failure += _ =>
+ {
+ Model.BeatmapInfo = null;
+ updatePanel();
+ };
+
+ API.Queue(req);
+ }, true);
+
+ mods.Value = Model.Mods;
+ mods.BindValueChanged(modString => Model.Mods = modString.NewValue);
+ }
+
+ private void updatePanel()
+ {
+ drawableContainer.Clear();
+
+ if (Model.BeatmapInfo != null)
+ drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = 300
+ };
+ }
+ }
+ }
+ }
+
+ protected override RoundRow CreateDrawable(TournamentRound model) => new RoundRow(model);
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
new file mode 100644
index 0000000000..a4479f3cfd
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
@@ -0,0 +1,314 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Settings;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tournament.Screens.Editors
+{
+ public class TeamEditorScreen : TournamentEditorScreen
+ {
+ [Resolved]
+ private Framework.Game game { get; set; }
+
+ protected override BindableList Storage => LadderInfo.Teams;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ControlPanel.Add(new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+ Text = "Add all countries",
+ Action = addAllCountries
+ });
+ }
+
+ protected override TeamRow CreateDrawable(TournamentTeam model) => new TeamRow(model);
+
+ private void addAllCountries()
+ {
+ List countries;
+ using (Stream stream = game.Resources.GetStream("Resources/countries.json"))
+ using (var sr = new StreamReader(stream))
+ countries = JsonConvert.DeserializeObject>(sr.ReadToEnd());
+
+ foreach (var c in countries)
+ Storage.Add(c);
+ }
+
+ public class TeamRow : CompositeDrawable, IModelBacked
+ {
+ public TournamentTeam Model { get; }
+
+ private readonly Container drawableContainer;
+
+ [Resolved]
+ private LadderInfo ladderInfo { get; set; }
+
+ public TeamRow(TournamentTeam team)
+ {
+ Model = team;
+
+ Masking = true;
+ CornerRadius = 10;
+
+ PlayerEditor playerEditor = new PlayerEditor(Model)
+ {
+ Width = 0.95f
+ };
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Colour = OsuColour.Gray(0.1f),
+ RelativeSizeAxes = Axes.Both,
+ },
+ drawableContainer = new Container
+ {
+ Size = new Vector2(100, 50),
+ Margin = new MarginPadding(10),
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ },
+ new FillFlowContainer
+ {
+ Margin = new MarginPadding(5),
+ Spacing = new Vector2(5),
+ Direction = FillDirection.Full,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ new SettingsTextBox
+ {
+ LabelText = "Name",
+ Width = 0.2f,
+ Bindable = Model.FullName
+ },
+ new SettingsTextBox
+ {
+ LabelText = "Acronym",
+ Width = 0.2f,
+ Bindable = Model.Acronym
+ },
+ new SettingsTextBox
+ {
+ LabelText = "Flag",
+ Width = 0.2f,
+ Bindable = Model.FlagName
+ },
+ new SettingsButton
+ {
+ Width = 0.11f,
+ Margin = new MarginPadding(10),
+ Text = "Add player",
+ Action = () => playerEditor.CreateNew()
+ },
+ new DangerousSettingsButton
+ {
+ Width = 0.11f,
+ Text = "Delete Team",
+ Margin = new MarginPadding(10),
+ Action = () =>
+ {
+ Expire();
+ ladderInfo.Teams.Remove(Model);
+ },
+ },
+ playerEditor
+ }
+ },
+ };
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Model.FlagName.BindValueChanged(updateDrawable, true);
+ }
+
+ private void updateDrawable(ValueChangedEvent flag)
+ {
+ drawableContainer.Child = new DrawableTeamFlag(Model);
+ }
+
+ private class DrawableTeamFlag : DrawableTournamentTeam
+ {
+ public DrawableTeamFlag(TournamentTeam team)
+ : base(team)
+ {
+ InternalChild = Flag;
+ RelativeSizeAxes = Axes.Both;
+
+ Flag.Anchor = Anchor.Centre;
+ Flag.Origin = Anchor.Centre;
+ }
+ }
+
+ public class PlayerEditor : CompositeDrawable
+ {
+ private readonly TournamentTeam team;
+ private readonly FillFlowContainer flow;
+
+ public PlayerEditor(TournamentTeam team)
+ {
+ this.team = team;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ InternalChild = flow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ LayoutDuration = 200,
+ LayoutEasing = Easing.OutQuint,
+ ChildrenEnumerable = team.Players.Select(p => new PlayerRow(team, p))
+ };
+ }
+
+ public void CreateNew()
+ {
+ var user = new User();
+ team.Players.Add(user);
+ flow.Add(new PlayerRow(team, user));
+ }
+
+ public class PlayerRow : CompositeDrawable
+ {
+ private readonly User user;
+
+ [Resolved]
+ protected IAPIProvider API { get; private set; }
+
+ private readonly Bindable userId = new Bindable();
+
+ private readonly Container drawableContainer;
+
+ public PlayerRow(TournamentTeam team, User user)
+ {
+ this.user = user;
+
+ Margin = new MarginPadding(10);
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Masking = true;
+ CornerRadius = 5;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Colour = OsuColour.Gray(0.2f),
+ RelativeSizeAxes = Axes.Both,
+ },
+ new FillFlowContainer
+ {
+ Margin = new MarginPadding(5),
+ Padding = new MarginPadding { Right = 160 },
+ Spacing = new Vector2(5),
+ Direction = FillDirection.Horizontal,
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new SettingsNumberBox
+ {
+ LabelText = "User ID",
+ RelativeSizeAxes = Axes.None,
+ Width = 200,
+ Bindable = userId,
+ },
+ drawableContainer = new Container
+ {
+ Size = new Vector2(100, 70),
+ },
+ }
+ },
+ new DangerousSettingsButton
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.None,
+ Width = 150,
+ Text = "Delete Player",
+ Action = () =>
+ {
+ Expire();
+ team.Players.Remove(user);
+ },
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ userId.Value = user.Id.ToString();
+ userId.BindValueChanged(idString =>
+ {
+ long parsed;
+
+ long.TryParse(idString.NewValue, out parsed);
+
+ user.Id = parsed;
+
+ if (idString.NewValue != idString.OldValue)
+ user.Username = string.Empty;
+
+ if (!string.IsNullOrEmpty(user.Username))
+ {
+ updatePanel();
+ return;
+ }
+
+ var req = new GetUserRequest(user.Id);
+
+ req.Success += res =>
+ {
+ // TODO: this should be done in a better way.
+ user.Username = res.Username;
+ user.Country = res.Country;
+ user.Cover = res.Cover;
+
+ updatePanel();
+ };
+
+ req.Failure += _ =>
+ {
+ user.Id = 1;
+ updatePanel();
+ };
+
+ API.Queue(req);
+ }, true);
+ }
+
+ private void updatePanel()
+ {
+ drawableContainer.Child = new UserPanel(user) { Width = 300 };
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
new file mode 100644
index 0000000000..50d3207345
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Settings;
+using osu.Game.Tournament.Components;
+using osuTK;
+
+namespace osu.Game.Tournament.Screens.Editors
+{
+ public abstract class TournamentEditorScreen : TournamentScreen, IProvideVideo
+ where TDrawable : Drawable, IModelBacked
+ where TModel : class, new()
+ {
+ protected abstract BindableList