1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 17:07:38 +08:00

Merge branch 'master' into generic-download-model-manager

This commit is contained in:
Dean Herbert 2019-06-24 18:35:58 +09:00 committed by GitHub
commit 88b182ab3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
185 changed files with 9501 additions and 1557 deletions

View File

@ -1,18 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (catch)" type="DotNetProject" factoryName=".NET Project">
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<browser url="http://localhost:5000" />
<method />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,18 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (mania)" type="DotNetProject" factoryName=".NET Project">
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<browser url="http://localhost:5000" />
<method />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,18 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (osu!)" type="DotNetProject" factoryName=".NET Project">
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<browser url="http://localhost:5000" />
<method />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,18 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (taiko)" type="DotNetProject" factoryName=".NET Project">
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<browser url="http://localhost:5000" />
<method />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--tournament" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tournament.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />

View File

@ -1,17 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="VisualTests" type="DotNetProject" factoryName=".NET Project">
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<method />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

144
.vscode/launch.json vendored
View File

@ -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
}
]
}
}

33
.vscode/tasks.json vendored
View File

@ -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",

View File

@ -24,9 +24,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
![](https://puu.sh/DCmvA/f6a74f5fbb.png)
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) |
| ------------- | ------------- |

View File

@ -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
},
}

View File

@ -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;
}
}

View File

@ -17,6 +17,7 @@
<StartupObject>osu.Desktop.Program</StartupObject>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Tournament\osu.Game.Tournament.csproj" />
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -0,0 +1,105 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<Type> 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<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration =>
throw new NotImplementedException();
}
}
}

View File

@ -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<DrawableHitObject>
{
@ -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();
/// <summary>
/// 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");
}
}
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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,
};
}
}
}

View File

@ -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");
}
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
}
}
}

View File

@ -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);
}

View File

@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += _ => true;
ScoreProcessor.FailConditions += (_, __) => true;
}
}
}

View File

@ -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<Type> RequiredTypes => new[]
{
typeof(ChatOverlay),
typeof(ChatLine),
typeof(DrawableChannel),
typeof(ChannelSelectorTabItem),

View File

@ -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]

View File

@ -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",

View File

@ -1,17 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<Type> RequiredTypes => new[]
{
typeof(KeyBindingRow),
typeof(GlobalKeyBindingsSection),
typeof(KeyBindingRow),
typeof(KeyBindingsSubsection),
typeof(RulesetBindingsSection),
typeof(VariantBindingsSubsection),
};
public TestSceneKeyBindingPanel()
{
Child = panel = new KeyBindingPanel();
}

View File

@ -1,85 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<DrawingsTeam> 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"
},
};
}
}
}

View File

@ -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<Type> 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<IReadOnlyList<Mod>> 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);

View File

@ -18,9 +18,6 @@ namespace osu.Game.Tests.Visual.UserInterface
[TestFixture]
public class TestSceneNotificationOverlay : OsuTestScene
{
private readonly NotificationOverlay manager;
private readonly List<ProgressNotification> progressingNotifications = new List<ProgressNotification>();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(NotificationSection),
@ -31,25 +28,33 @@ namespace osu.Game.Tests.Visual.UserInterface
typeof(Notification)
};
public TestSceneNotificationOverlay()
private NotificationOverlay notificationOverlay;
private readonly List<ProgressNotification> progressingNotifications = new List<ProgressNotification>();
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;
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<Type> 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);
}
}

View File

@ -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"
}
]
}

View File

@ -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": []
}
]
}

View File

@ -0,0 +1,93 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<Type> RequiredTypes => new[]
{
typeof(TournamentMatch),
typeof(DrawableTournamentTeam),
};
public TestSceneDrawableTournamentMatch()
{
Container<DrawableTournamentMatch> level1;
Container<DrawableTournamentMatch> 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<DrawableTournamentMatch>
{
AutoSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Children = new[]
{
new DrawableTournamentMatch(match1),
new DrawableTournamentMatch(match2),
new DrawableTournamentMatch(new TournamentMatch()),
}
},
level2 = new FillFlowContainer<DrawableTournamentMatch>
{
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);
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
});
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<User> { redUser } }
},
Team2 =
{
Value = new TournamentTeam { Players = new BindableList<User> { 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++;
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; }
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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()
});
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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()
});
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<Type> RequiredTypes => new[]
{
typeof(MapPoolScreen)
};
[BackgroundDependencyLoader]
private void load()
{
Add(new MapPoolScreen { Width = 0.7f });
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
});
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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());
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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());
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
});
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
[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
});
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
[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
});
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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());
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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());
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
}
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" />
<PropertyGroup>
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\osu.Game.Tournament\osu.Game.Tournament.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,69 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// An element anchored to the right-hand area of a screen that provides streamer level controls.
/// Should be off-screen.
/// </summary>
public class ControlPanel : Container
{
private readonly FillFlowContainer buttons;
protected override Container<Drawable> 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;
}
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<DateTimeOffset> 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<DateTimeOffset> bindable;
public DateTextBox()
{
base.Bindable = new Bindable<string>();
((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();
}
};
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<string> acronym;
[UsedImplicitly]
private Bindable<string> 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);
}
}
}

View File

@ -0,0 +1,251 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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)));
}
}
}
}
}

View File

@ -0,0 +1,203 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
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<TournamentMatch> 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;
}
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<string> chatChannel = new Bindable<string>();
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);
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
}
}
}
}

View File

@ -0,0 +1,194 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
}
/// <summary>
/// A method of accessing an osu-stable install in a controlled fashion.
/// </summary>
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)
{
}
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<BeatmapInfo> Beatmap { get; } = new Bindable<BeatmapInfo>();
public Bindable<LegacyMods> Mods { get; } = new Bindable<LegacyMods>();
public Bindable<TourneyState> State { get; } = new Bindable<TourneyState>();
public Bindable<string> ChatChannel { get; } = new Bindable<string>();
public BindableInt Score1 { get; } = new BindableInt();
public BindableInt Score2 { get; } = new BindableInt();
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// A beatmap choice by a team from a tournament's map pool.
/// </summary>
[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,
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TournamentMatch> Selected = new Bindable<TournamentMatch>();
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// Holds the complete data required to operate the tournament system.
/// </summary>
[Serializable]
public class LadderInfo
{
public BindableList<TournamentMatch> Matches = new BindableList<TournamentMatch>();
public BindableList<TournamentRound> Rounds = new BindableList<TournamentRound>();
public BindableList<TournamentTeam> Teams = new BindableList<TournamentTeam>();
// only used for serialisation
public List<TournamentProgression> Progressions = new List<TournamentProgression>();
[JsonIgnore]
public Bindable<TournamentMatch> CurrentMatch = new Bindable<TournamentMatch>();
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
}
}

View File

@ -0,0 +1,125 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// A collection of two teams competing in a head-to-head match.
/// </summary>
[Serializable]
public class TournamentMatch
{
public int ID;
public List<string> Acronyms
{
get
{
List<string> acronyms = new List<string>();
if (Team1Acronym != null) acronyms.Add(Team1Acronym);
if (Team2Acronym != null) acronyms.Add(Team2Acronym);
return acronyms;
}
}
[JsonIgnore]
public readonly Bindable<TournamentTeam> Team1 = new Bindable<TournamentTeam>();
public string Team1Acronym;
public readonly Bindable<int?> Team1Score = new Bindable<int?>();
[JsonIgnore]
public readonly Bindable<TournamentTeam> Team2 = new Bindable<TournamentTeam>();
public string Team2Acronym;
public readonly Bindable<int?> Team2Score = new Bindable<int?>();
public readonly Bindable<bool> Completed = new Bindable<bool>();
public readonly Bindable<bool> Losers = new Bindable<bool>();
public readonly ObservableCollection<BeatmapChoice> PicksBans = new ObservableCollection<BeatmapChoice>();
[JsonIgnore]
public readonly Bindable<TournamentRound> Round = new Bindable<TournamentRound>();
[JsonIgnore]
public readonly Bindable<TournamentMatch> Progression = new Bindable<TournamentMatch>();
[JsonIgnore]
public readonly Bindable<TournamentMatch> LosersProgression = new Bindable<TournamentMatch>();
/// <summary>
/// Should not be set directly. Use LadderInfo.CurrentMatch.Value = this instead.
/// </summary>
public readonly Bindable<bool> Current = new Bindable<bool>();
public readonly Bindable<DateTimeOffset> Date = new Bindable<DateTimeOffset>(DateTimeOffset.Now);
[JsonProperty]
public readonly BindableList<ConditionalTournamentMatch> ConditionalMatches = new BindableList<ConditionalTournamentMatch>();
public readonly Bindable<Point> Position = new Bindable<Point>();
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;
/// <summary>
/// Remove scores from the match, in case of a false click or false start.
/// </summary>
public void CancelMatchStart()
{
Team1Score.Value = null;
Team2Score.Value = null;
}
/// <summary>
/// Initialise this match with zeroed scores. Will be a noop if either team is not present.
/// </summary>
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();
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
namespace osu.Game.Tournament.Models
{
/// <summary>
/// A mapping between two <see cref="TournamentMatch"/>es.
/// Used for serialisation exclusively.
/// </summary>
[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;
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// A tournament round, containing many matches, generally executed in a short time period.
/// </summary>
[Serializable]
public class TournamentRound
{
public readonly Bindable<string> Name = new Bindable<string>();
public readonly Bindable<string> Description = new Bindable<string>();
public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 };
[JsonProperty]
public readonly BindableList<RoundBeatmap> Beatmaps = new BindableList<RoundBeatmap>();
public readonly Bindable<DateTimeOffset> StartDate = new Bindable<DateTimeOffset> { Value = DateTimeOffset.UtcNow };
// only used for serialisation
public List<int> Matches = new List<int>();
public override string ToString() => Name.Value ?? "None";
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// A team representation. For official tournaments this is generally a country.
/// </summary>
[Serializable]
public class TournamentTeam
{
/// <summary>
/// The name of this team.
/// </summary>
public Bindable<string> FullName = new Bindable<string>(string.Empty);
/// <summary>
/// Name of the file containing the flag.
/// </summary>
public Bindable<string> FlagName = new Bindable<string>(string.Empty);
/// <summary>
/// Short acronym which appears in the group boxes post-selection.
/// </summary>
public Bindable<string> Acronym = new Bindable<string>(string.Empty);
[JsonProperty]
public BindableList<User> Players { get; set; } = new BindableList<User>();
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;
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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")]

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<LegacyMods> mods)
{
SongBar.Mods = mods.NewValue;
}
private void beatmapChanged(ValueChangedEvent<BeatmapInfo> beatmap)
{
SongBar.FadeInFromZero(300, Easing.OutQuint);
SongBar.Beatmap = beatmap.NewValue;
}
}
}

View File

@ -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<DrawingsConfig>
{

View File

@ -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}");
}
}
}
}

View File

@ -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;

View File

@ -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<DrawingsTeam> Teams { get; }
IEnumerable<TournamentTeam> Teams { get; }
}
}

View File

@ -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<DrawingsTeam> OnSelected;
public event Action<TournamentTeam> OnSelected;
private readonly List<DrawingsTeam> availableTeams = new List<DrawingsTeam>();
private readonly List<TournamentTeam> availableTeams = new List<TournamentTeam>();
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<DrawingsTeam> teams)
public void AddTeams(IEnumerable<TournamentTeam> 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}");
}
}
}
}

View File

@ -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<DrawingsTeam> Teams
public IEnumerable<TournamentTeam> Teams
{
get
{
var teams = new List<DrawingsTeam>();
var teams = new List<TournamentTeam>();
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() }
});
}
}

View File

@ -1,16 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{

View File

@ -0,0 +1,265 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TournamentTeam> allTeams = new List<TournamentTeam>();
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<int>(DrawingsConfig.Groups), drawingsConfig.Get<int>(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);
}
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Tournament.Screens.Editors
{
/// <summary>
/// Provides a mechanism to access a related model from a representing class.
/// </summary>
/// <typeparam name="TModel">The type of model.</typeparam>
public interface IModelBacked<out TModel>
{
/// <summary>
/// The model.
/// </summary>
TModel Model { get; }
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<DrawableTournamentMatch> matchesContainer;
public readonly TournamentMatch Source;
private readonly bool losers;
private readonly Action complete;
private ProgressionPath path;
public JoinVisualiser(Container<DrawableTournamentMatch> 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;
}
}
}
}

View File

@ -0,0 +1,282 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<RoundEditorScreen.RoundRow, TournamentRound>
{
protected override BindableList<TournamentRound> Storage => LadderInfo.Rounds;
public class RoundRow : CompositeDrawable, IModelBacked<TournamentRound>
{
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<int>
{
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<string> beatmapId = new Bindable<string>();
private readonly Bindable<string> mods = new Bindable<string>();
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);
}
}

View File

@ -0,0 +1,314 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TeamEditorScreen.TeamRow, TournamentTeam>
{
[Resolved]
private Framework.Game game { get; set; }
protected override BindableList<TournamentTeam> 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<TournamentTeam> countries;
using (Stream stream = game.Resources.GetStream("Resources/countries.json"))
using (var sr = new StreamReader(stream))
countries = JsonConvert.DeserializeObject<List<TournamentTeam>>(sr.ReadToEnd());
foreach (var c in countries)
Storage.Add(c);
}
public class TeamRow : CompositeDrawable, IModelBacked<TournamentTeam>
{
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<string> 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<string> userId = new Bindable<string>();
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 };
}
}
}
}
}
}

View File

@ -0,0 +1,84 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TDrawable, TModel> : TournamentScreen, IProvideVideo
where TDrawable : Drawable, IModelBacked<TModel>
where TModel : class, new()
{
protected abstract BindableList<TModel> Storage { get; }
private FillFlowContainer<TDrawable> flow;
protected ControlPanel ControlPanel;
[BackgroundDependencyLoader]
private void load()
{
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f),
},
new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Width = 0.9f,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Child = flow = new FillFlowContainer<TDrawable>
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint,
Spacing = new Vector2(20)
},
},
ControlPanel = new ControlPanel
{
Children = new Drawable[]
{
new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = "Add new",
Action = () => Storage.Add(new TModel())
},
new DangerousSettingsButton
{
RelativeSizeAxes = Axes.X,
Text = "Clear all",
Action = Storage.Clear
},
}
}
});
Storage.ItemsAdded += items => items.ForEach(i => flow.Add(CreateDrawable(i)));
Storage.ItemsRemoved += items => items.ForEach(i => flow.RemoveAll(d => d.Model == i));
foreach (var model in Storage)
flow.Add(CreateDrawable(model));
}
protected abstract TDrawable CreateDrawable(TModel model);
}
}

View File

@ -0,0 +1,220 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
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.Showcase;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
public class MatchHeader : Container
{
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
Height = 95;
Children = new Drawable[]
{
new TournamentLogo(),
new RoundDisplay
{
Y = 10,
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
},
new TeamScoreDisplay(TeamColour.Red)
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
new TeamScoreDisplay(TeamColour.Blue)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
};
}
private class TeamScoreDisplay : CompositeDrawable
{
private readonly TeamColour teamColour;
private readonly Color4 red = new Color4(129, 68, 65, 255);
private readonly Color4 blue = new Color4(41, 91, 97, 255);
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
private readonly Bindable<TournamentTeam> currentTeam = new Bindable<TournamentTeam>();
private readonly Bindable<int?> currentTeamScore = new Bindable<int?>();
public TeamScoreDisplay(TeamColour teamColour)
{
this.teamColour = teamColour;
RelativeSizeAxes = Axes.Y;
Width = 300;
}
[BackgroundDependencyLoader]
private void load(LadderInfo ladder)
{
currentMatch.BindValueChanged(matchChanged);
currentMatch.BindTo(ladder.CurrentMatch);
}
private void matchChanged(ValueChangedEvent<TournamentMatch> match)
{
currentTeamScore.UnbindBindings();
currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1Score : match.NewValue.Team2Score);
currentTeam.UnbindBindings();
currentTeam.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1 : match.NewValue.Team2);
// team may change to same team, which means score is not in a good state.
// thus we handle this manually.
teamChanged(currentTeam.Value);
}
protected override bool OnMouseDown(MouseDownEvent e)
{
switch (e.Button)
{
case MouseButton.Left:
if (currentTeamScore.Value < currentMatch.Value.PointsToWin)
currentTeamScore.Value++;
return true;
case MouseButton.Right:
if (currentTeamScore.Value > 0)
currentTeamScore.Value--;
return true;
}
return base.OnMouseDown(e);
}
private void teamChanged(TournamentTeam team)
{
var colour = teamColour == TeamColour.Red ? red : blue;
var flip = teamColour != TeamColour.Red;
InternalChildren = new Drawable[]
{
new TeamDisplay(team, colour, flip),
new TeamScore(currentTeamScore, flip, currentMatch.Value.PointsToWin)
{
Colour = colour
}
};
}
}
private class TeamScore : CompositeDrawable
{
private readonly Bindable<int?> currentTeamScore = new Bindable<int?>();
private readonly StarCounter counter;
public TeamScore(Bindable<int?> score, bool flip, int count)
{
var anchor = flip ? Anchor.CentreRight : Anchor.CentreLeft;
Anchor = anchor;
Origin = anchor;
InternalChild = counter = new StarCounter(count)
{
Anchor = anchor,
X = (flip ? -1 : 1) * 90,
Y = 5,
Scale = flip ? new Vector2(-1, 1) : Vector2.One,
};
currentTeamScore.BindValueChanged(scoreChanged);
currentTeamScore.BindTo(score);
}
private void scoreChanged(ValueChangedEvent<int?> score) => counter.CountStars = score.NewValue ?? 0;
}
private class TeamDisplay : DrawableTournamentTeam
{
public TeamDisplay(TournamentTeam team, Color4 colour, bool flip)
: base(team)
{
RelativeSizeAxes = Axes.Both;
var anchor = flip ? Anchor.CentreRight : Anchor.CentreLeft;
Anchor = Origin = anchor;
Flag.Anchor = Flag.Origin = anchor;
Flag.RelativeSizeAxes = Axes.None;
Flag.Size = new Vector2(60, 40);
Flag.Margin = new MarginPadding(20);
InternalChild = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
Flag,
new OsuSpriteText
{
Text = team?.FullName.Value.ToUpper() ?? "???",
X = (flip ? -1 : 1) * 90,
Y = -10,
Colour = colour,
Font = TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 20),
Origin = anchor,
Anchor = anchor,
},
}
};
}
}
private class RoundDisplay : CompositeDrawable
{
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
public RoundDisplay()
{
Width = 200;
Height = 20;
}
[BackgroundDependencyLoader]
private void load(LadderInfo ladder)
{
currentMatch.BindValueChanged(matchChanged);
currentMatch.BindTo(ladder.CurrentMatch);
}
private void matchChanged(ValueChangedEvent<TournamentMatch> match)
{
InternalChildren = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round",
Font = TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 18),
},
};
}
}
}
}

View File

@ -0,0 +1,131 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osuTK.Graphics;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
public class MatchScoreDisplay : CompositeDrawable
{
private readonly Color4 red = new Color4(186, 0, 18, 255);
private readonly Color4 blue = new Color4(17, 136, 170, 255);
private const float bar_height = 20;
private readonly BindableInt score1 = new BindableInt();
private readonly BindableInt score2 = new BindableInt();
private readonly MatchScoreCounter score1Text;
private readonly MatchScoreCounter score2Text;
private readonly Circle score1Bar;
private readonly Circle score2Bar;
public MatchScoreDisplay()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
score1Bar = new Circle
{
Name = "top bar red",
RelativeSizeAxes = Axes.X,
Height = bar_height,
Width = 0,
Colour = red,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopRight
},
score1Text = new MatchScoreCounter
{
Colour = red,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
score2Bar = new Circle
{
Name = "top bar blue",
RelativeSizeAxes = Axes.X,
Height = bar_height,
Width = 0,
Colour = blue,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopLeft
},
score2Text = new MatchScoreCounter
{
Colour = blue,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
};
}
[BackgroundDependencyLoader]
private void load(LadderInfo ladder, MatchIPCInfo ipc)
{
score1.BindValueChanged(_ => updateScores());
score1.BindTo(ipc.Score1);
score2.BindValueChanged(_ => updateScores());
score2.BindTo(ipc.Score2);
}
private void updateScores()
{
score1Text.Current.Value = score1.Value;
score2Text.Current.Value = score2.Value;
var winningText = score1.Value > score2.Value ? score1Text : score2Text;
var losingText = score1.Value <= score2.Value ? score1Text : score2Text;
winningText.Winning = true;
losingText.Winning = false;
var winningBar = score1.Value > score2.Value ? score1Bar : score2Bar;
var losingBar = score1.Value <= score2.Value ? score1Bar : score2Bar;
var diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value);
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
winningBar.ResizeWidthTo(Math.Min(0.4f, (float)Math.Pow(diff / 1500000f, 0.5) / 2), 400, Easing.OutQuint);
}
protected override void Update()
{
base.Update();
score1Text.X = -Math.Max(5 + score1Text.DrawWidth / 2, score1Bar.DrawWidth);
score2Text.X = Math.Max(5 + score2Text.DrawWidth / 2, score2Bar.DrawWidth);
}
private class MatchScoreCounter : ScoreCounter
{
public MatchScoreCounter()
{
Margin = new MarginPadding { Top = bar_height + 5, Horizontal = 10 };
Winning = false;
}
public bool Winning
{
set => DisplayedCountSpriteText.Font = value
? TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 60)
: TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Light, size: 40);
}
}
}
}

View File

@ -0,0 +1,211 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Gameplay.Components;
using osu.Game.Tournament.Screens.MapPool;
using osu.Game.Tournament.Screens.TeamWin;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tournament.Screens.Gameplay
{
public class GameplayScreen : BeatmapInfoScreen
{
private readonly BindableBool warmup = new BindableBool();
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
public readonly Bindable<TourneyState> State = new Bindable<TourneyState>();
private OsuButton warmupButton;
private MatchIPCInfo ipc;
private readonly Color4 red = new Color4(186, 0, 18, 255);
private readonly Color4 blue = new Color4(17, 136, 170, 255);
[Resolved(canBeNull: true)]
private TournamentSceneManager sceneManager { get; set; }
[Resolved]
private TournamentMatchChatDisplay chat { get; set; }
[BackgroundDependencyLoader]
private void load(LadderInfo ladder, MatchIPCInfo ipc)
{
this.ipc = ipc;
AddRangeInternal(new Drawable[]
{
new MatchHeader(),
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Y = 5,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
// chroma key area for stable gameplay
Name = "chroma",
RelativeSizeAxes = Axes.X,
Height = 512,
Colour = new Color4(0, 255, 0, 255),
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Y = -4,
Children = new Drawable[]
{
new Circle
{
Name = "top bar red",
RelativeSizeAxes = Axes.X,
Height = 8,
Width = 0.5f,
Colour = red,
},
new Circle
{
Name = "top bar blue",
RelativeSizeAxes = Axes.X,
Height = 8,
Width = 0.5f,
Colour = blue,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
}
},
}
},
scoreDisplay = new MatchScoreDisplay
{
Y = -60,
Scale = new Vector2(0.8f),
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
},
new ControlPanel
{
Children = new Drawable[]
{
warmupButton = new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = "Toggle warmup",
Action = () => warmup.Toggle()
},
new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = "Toggle chat",
Action = () => { State.Value = State.Value == TourneyState.Idle ? TourneyState.Playing : TourneyState.Idle; }
}
}
}
});
State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true);
currentMatch.BindValueChanged(m =>
{
warmup.Value = m.NewValue.Team1Score.Value + m.NewValue.Team2Score.Value == 0;
scheduledOperation?.Cancel();
});
currentMatch.BindTo(ladder.CurrentMatch);
warmup.BindValueChanged(w => warmupButton.Alpha = !w.NewValue ? 0.5f : 1, true);
}
private ScheduledDelegate scheduledOperation;
private MatchScoreDisplay scoreDisplay;
private TourneyState lastState;
private void stateChanged(ValueChangedEvent<TourneyState> state)
{
try
{
if (state.NewValue == TourneyState.Ranking)
{
if (warmup.Value) return;
if (ipc.Score1.Value > ipc.Score2.Value)
currentMatch.Value.Team1Score.Value++;
else
currentMatch.Value.Team2Score.Value++;
}
scheduledOperation?.Cancel();
void expand()
{
chat?.Expand();
using (BeginDelayedSequence(300, true))
{
scoreDisplay.FadeIn(100);
SongBar.Expanded = true;
}
}
void contract()
{
SongBar.Expanded = false;
scoreDisplay.FadeOut(100);
using (chat?.BeginDelayedSequence(500))
chat?.Contract();
}
switch (state.NewValue)
{
case TourneyState.Idle:
contract();
const float delay_before_progression = 4000;
// if we've returned to idle and the last screen was ranking
// we should automatically proceed after a short delay
if (lastState == TourneyState.Ranking && !warmup.Value)
{
if (currentMatch.Value?.Completed.Value == true)
scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression);
else if (currentMatch.Value?.Completed.Value == false)
scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression);
}
break;
case TourneyState.Ranking:
scheduledOperation = Scheduler.AddDelayed(contract, 10000);
break;
default:
chat.Expand();
expand();
break;
}
}
finally
{
lastState = state.NewValue;
}
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Tournament.Screens
{
/// <summary>
/// Marker interface for a screen which provides its own local video background.
/// </summary>
public interface IProvideVideo
{
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens.Ladder.Components
{
/// <summary>
/// A match that may not necessarily occur.
/// </summary>
public class ConditionalTournamentMatch : TournamentMatch
{
}
}

View File

@ -0,0 +1,207 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Editors;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tournament.Screens.Ladder.Components
{
public class DrawableMatchTeam : DrawableTournamentTeam, IHasContextMenu
{
private readonly TournamentMatch match;
private readonly bool losers;
private OsuSpriteText scoreText;
private Box background;
private readonly Bindable<int?> score = new Bindable<int?>();
private readonly BindableBool completed = new BindableBool();
private Color4 colourWinner;
private Color4 colourNormal;
private readonly Func<bool> isWinner;
private LadderEditorScreen ladderEditor;
[Resolved]
private LadderInfo ladderInfo { get; set; }
private void setCurrent()
{
//todo: tournamentgamebase?
if (ladderInfo.CurrentMatch.Value != null)
ladderInfo.CurrentMatch.Value.Current.Value = false;
ladderInfo.CurrentMatch.Value = match;
ladderInfo.CurrentMatch.Value.Current.Value = true;
}
[Resolved(CanBeNull = true)]
private LadderEditorInfo editorInfo { get; set; }
public DrawableMatchTeam(TournamentTeam team, TournamentMatch match, bool losers)
: base(team)
{
this.match = match;
this.losers = losers;
Size = new Vector2(150, 40);
Masking = true;
CornerRadius = 5;
Flag.Scale = new Vector2(0.9f);
Flag.Anchor = Flag.Origin = Anchor.CentreLeft;
AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft;
AcronymText.Padding = new MarginPadding { Left = 50 };
AcronymText.Font = OsuFont.GetFont(size: 24);
if (match != null)
{
isWinner = () => match.Winner == Team;
completed.BindTo(match.Completed);
if (team != null)
score.BindTo(team == match.Team1.Value ? match.Team1Score : match.Team2Score);
}
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, LadderEditorScreen ladderEditor)
{
this.ladderEditor = ladderEditor;
colourWinner = losers ? colours.YellowDarker : colours.BlueDarker;
colourNormal = OsuColour.Gray(0.2f);
InternalChildren = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
Padding = new MarginPadding(5),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
AcronymText,
Flag,
new Container
{
Masking = true,
CornerRadius = 5,
Width = 0.3f,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = OsuColour.Gray(0.1f),
Alpha = 0.8f,
RelativeSizeAxes = Axes.Both,
},
scoreText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(size: 20),
}
}
}
}
}
};
completed.BindValueChanged(_ => updateWinStyle());
score.BindValueChanged(val =>
{
scoreText.Text = val.NewValue?.ToString() ?? string.Empty;
updateWinStyle();
}, true);
}
protected override bool OnClick(ClickEvent e)
{
if (Team == null || editorInfo != null) return false;
if (!match.Current.Value)
{
setCurrent();
return true;
}
if (e.Button == MouseButton.Left)
{
if (score.Value == null)
{
match.StartMatch();
}
else if (!match.Completed.Value)
score.Value++;
}
else
{
if (match.Progression.Value?.Completed.Value == true)
// don't allow changing scores if the match has a progression. can cause large data loss
return false;
if (match.Completed.Value && match.Winner != Team)
// don't allow changing scores from the non-winner
return false;
if (score.Value > 0)
score.Value--;
else
match.CancelMatchStart();
}
return false;
}
private void updateWinStyle()
{
bool winner = completed.Value && isWinner?.Invoke() == true;
background.FadeColour(winner ? colourWinner : colourNormal, winner ? 500 : 0, Easing.OutQuint);
scoreText.Font = AcronymText.Font = OsuFont.GetFont(weight: winner ? FontWeight.Bold : FontWeight.Regular);
}
public MenuItem[] ContextMenuItems
{
get
{
if (editorInfo == null)
return new MenuItem[0];
return new MenuItem[]
{
new OsuMenuItem("Set as current", MenuItemType.Standard, setCurrent),
new OsuMenuItem("Join with", MenuItemType.Standard, () => ladderEditor.BeginJoin(match, false)),
new OsuMenuItem("Join with (loser)", MenuItemType.Standard, () => ladderEditor.BeginJoin(match, true)),
new OsuMenuItem("Remove", MenuItemType.Destructive, () => ladderEditor.Remove(match)),
};
}
}
}
}

View File

@ -0,0 +1,313 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Tournament.Models;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
using SixLabors.Primitives;
namespace osu.Game.Tournament.Screens.Ladder.Components
{
public class DrawableTournamentMatch : CompositeDrawable
{
public readonly TournamentMatch Match;
private readonly bool editor;
protected readonly FillFlowContainer<DrawableMatchTeam> Flow;
private readonly Drawable selectionBox;
private readonly Drawable currentMatchSelectionBox;
private Bindable<TournamentMatch> globalSelection;
[Resolved(CanBeNull = true)]
private LadderEditorInfo editorInfo { get; set; }
[Resolved(CanBeNull = true)]
private LadderInfo ladderInfo { get; set; }
public DrawableTournamentMatch(TournamentMatch match, bool editor = false)
{
Match = match;
this.editor = editor;
AutoSizeAxes = Axes.Both;
Margin = new MarginPadding(5);
InternalChildren = new[]
{
selectionBox = new Container
{
CornerRadius = 5,
Masking = true,
Scale = new Vector2(1.05f),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Colour = Color4.YellowGreen,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
currentMatchSelectionBox = new Container
{
CornerRadius = 5,
Masking = true,
Scale = new Vector2(1.05f),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Colour = Color4.OrangeRed,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
Flow = new FillFlowContainer<DrawableMatchTeam>
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(2)
}
};
boundReference(match.Team1).BindValueChanged(_ => updateTeams());
boundReference(match.Team2).BindValueChanged(_ => updateTeams());
boundReference(match.Team1Score).BindValueChanged(_ => updateWinConditions());
boundReference(match.Team2Score).BindValueChanged(_ => updateWinConditions());
boundReference(match.Round).BindValueChanged(_ =>
{
updateWinConditions();
Changed?.Invoke();
});
boundReference(match.Completed).BindValueChanged(_ => updateProgression());
boundReference(match.Progression).BindValueChanged(_ => updateProgression());
boundReference(match.LosersProgression).BindValueChanged(_ => updateProgression());
boundReference(match.Losers).BindValueChanged(_ =>
{
updateTeams();
Changed?.Invoke();
});
boundReference(match.Current).BindValueChanged(_ => updateCurrentMatch(), true);
boundReference(match.Position).BindValueChanged(pos =>
{
if (!IsDragged)
Position = new Vector2(pos.NewValue.X, pos.NewValue.Y);
Changed?.Invoke();
}, true);
updateTeams();
}
/// <summary>
/// Fired when somethign changed that requires a ladder redraw.
/// </summary>
public Action Changed;
private readonly List<IUnbindable> refBindables = new List<IUnbindable>();
private T boundReference<T>(T obj)
where T : IBindable
{
obj = (T)obj.GetBoundCopy();
refBindables.Add(obj);
return obj;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
foreach (var b in refBindables)
b.UnbindAll();
}
private void updateCurrentMatch()
{
if (Match.Current.Value)
currentMatchSelectionBox.Show();
else
currentMatchSelectionBox.Hide();
}
private bool selected;
public bool Selected
{
get => selected;
set
{
if (value == selected) return;
selected = value;
if (selected)
{
selectionBox.Show();
if (editor)
editorInfo.Selected.Value = Match;
else
ladderInfo.CurrentMatch.Value = Match;
}
else
selectionBox.Hide();
}
}
private void updateProgression()
{
if (!Match.Completed.Value)
{
// ensure we clear any of our teams from our progression.
// this is not pretty logic but should suffice for now.
if (Match.Progression.Value != null && Match.Progression.Value.Team1.Value == Match.Team1.Value)
Match.Progression.Value.Team1.Value = null;
if (Match.Progression.Value != null && Match.Progression.Value.Team2.Value == Match.Team2.Value)
Match.Progression.Value.Team2.Value = null;
if (Match.LosersProgression.Value != null && Match.LosersProgression.Value.Team1.Value == Match.Team1.Value)
Match.LosersProgression.Value.Team1.Value = null;
if (Match.LosersProgression.Value != null && Match.LosersProgression.Value.Team2.Value == Match.Team2.Value)
Match.LosersProgression.Value.Team2.Value = null;
}
else
{
transferProgression(Match.Progression?.Value, Match.Winner);
transferProgression(Match.LosersProgression?.Value, Match.Loser);
}
Changed?.Invoke();
}
private void transferProgression(TournamentMatch destination, TournamentTeam team)
{
if (destination == null) return;
bool progressionAbove = destination.ID < Match.ID;
Bindable<TournamentTeam> destinationTeam;
// check for the case where we have already transferred out value
if (destination.Team1.Value == team)
destinationTeam = destination.Team1;
else if (destination.Team2.Value == team)
destinationTeam = destination.Team2;
else
{
destinationTeam = progressionAbove ? destination.Team2 : destination.Team1;
if (destinationTeam.Value != null)
destinationTeam = progressionAbove ? destination.Team1 : destination.Team2;
}
destinationTeam.Value = team;
}
private void updateWinConditions()
{
if (Match.Round.Value == null) return;
var instaWinAmount = Match.Round.Value.BestOf.Value / 2;
Match.Completed.Value = Match.Round.Value.BestOf.Value > 0
&& (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instaWinAmount || Match.Team2Score.Value > instaWinAmount);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateTeams();
if (editorInfo != null)
{
globalSelection = editorInfo.Selected.GetBoundCopy();
globalSelection.BindValueChanged(s =>
{
if (s.NewValue != Match) Selected = false;
});
}
}
private void updateTeams()
{
if (LoadState != LoadState.Loaded)
return;
// todo: teams may need to be bindable for transitions at a later point.
if (Match.Team1.Value == null || Match.Team2.Value == null)
Match.CancelMatchStart();
if (Match.ConditionalMatches.Count > 0)
{
foreach (var conditional in Match.ConditionalMatches)
{
var team1Match = conditional.Acronyms.Contains(Match.Team1Acronym);
var team2Match = conditional.Acronyms.Contains(Match.Team2Acronym);
if (team1Match && team2Match)
Match.Date.Value = conditional.Date.Value;
}
}
Flow.Children = new[]
{
new DrawableMatchTeam(Match.Team1.Value, Match, Match.Losers.Value),
new DrawableMatchTeam(Match.Team2.Value, Match, Match.Losers.Value)
};
SchedulerAfterChildren.Add(() => Scheduler.Add(updateProgression));
updateWinConditions();
}
protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left && editorInfo != null;
protected override bool OnDragStart(DragStartEvent e) => editorInfo != null;
protected override bool OnKeyDown(KeyDownEvent e)
{
if (Selected && editorInfo != null && e.Key == Key.Delete)
{
Remove();
return true;
}
return base.OnKeyDown(e);
}
protected override bool OnClick(ClickEvent e)
{
if (editorInfo == null || Match is ConditionalTournamentMatch)
return false;
Selected = true;
return true;
}
protected override bool OnDrag(DragEvent e)
{
if (base.OnDrag(e)) return true;
Selected = true;
this.MoveToOffset(e.Delta);
var pos = Position;
Match.Position.Value = new Point((int)pos.X, (int)pos.Y);
return true;
}
public void Remove()
{
Selected = false;
Match.Progression.Value = null;
Match.LosersProgression.Value = null;
ladderInfo.Matches.Remove(Match);
}
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Tournament.Models;
using osuTK.Graphics;
namespace osu.Game.Tournament.Screens.Ladder.Components
{
public class DrawableTournamentRound : CompositeDrawable
{
[UsedImplicitly]
private readonly Bindable<string> name;
[UsedImplicitly]
private readonly Bindable<string> description;
public DrawableTournamentRound(TournamentRound round, bool losers = false)
{
OsuSpriteText textName;
OsuSpriteText textDescription;
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
textDescription = new OsuSpriteText
{
Colour = Color4.Black,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre
},
textName = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Colour = Color4.Black,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre
},
}
};
name = round.Name.GetBoundCopy();
name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpper(), true);
description = round.Description.GetBoundCopy();
description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpper(), true);
}
}
}

View File

@ -0,0 +1,168 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens.Ladder.Components
{
public class LadderEditorSettings : PlayerSettingsGroup
{
private const int padding = 10;
protected override string Title => @"ladder";
private SettingsDropdown<TournamentRound> roundDropdown;
private PlayerCheckbox losersCheckbox;
private DateTextBox dateTimeBox;
private SettingsTeamDropdown team1Dropdown;
private SettingsTeamDropdown team2Dropdown;
[Resolved]
private LadderEditorInfo editorInfo { get; set; }
[Resolved]
private LadderInfo ladderInfo { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
team1Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 1" },
team2Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 2" },
roundDropdown = new SettingsRoundDropdown(ladderInfo.Rounds) { LabelText = "Round" },
losersCheckbox = new PlayerCheckbox { LabelText = "Losers Bracket" },
dateTimeBox = new DateTextBox { LabelText = "Match Time" },
};
editorInfo.Selected.ValueChanged += selection =>
{
roundDropdown.Bindable = selection.NewValue?.Round;
losersCheckbox.Current = selection.NewValue?.Losers;
dateTimeBox.Bindable = selection.NewValue?.Date;
team1Dropdown.Bindable = selection.NewValue?.Team1;
team2Dropdown.Bindable = selection.NewValue?.Team2;
};
roundDropdown.Bindable.ValueChanged += round =>
{
if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value)
{
editorInfo.Selected.Value.Date.Value = round.NewValue.StartDate.Value;
editorInfo.Selected.TriggerChange();
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeIn();
}
protected override bool OnHover(HoverEvent e)
{
return false;
}
protected override void OnHoverLost(HoverLostEvent e)
{
}
private class SettingsRoundDropdown : LadderSettingsDropdown<TournamentRound>
{
public SettingsRoundDropdown(BindableList<TournamentRound> rounds)
{
Bindable = new Bindable<TournamentRound>();
foreach (var r in rounds.Prepend(new TournamentRound()))
add(r);
rounds.ItemsRemoved += items => items.ForEach(i => Control.RemoveDropdownItem(i));
rounds.ItemsAdded += items => items.ForEach(add);
}
private readonly List<IUnbindable> refBindables = new List<IUnbindable>();
private T boundReference<T>(T obj)
where T : IBindable
{
obj = (T)obj.GetBoundCopy();
refBindables.Add(obj);
return obj;
}
private void add(TournamentRound round)
{
Control.AddDropdownItem(round);
boundReference(round.Name).BindValueChanged(_ =>
{
Control.RemoveDropdownItem(round);
Control.AddDropdownItem(round);
});
}
}
private class SettingsTeamDropdown : LadderSettingsDropdown<TournamentTeam>
{
public SettingsTeamDropdown(BindableList<TournamentTeam> teams)
{
foreach (var t in teams.Prepend(new TournamentTeam()))
add(t);
teams.ItemsRemoved += items => items.ForEach(i => Control.RemoveDropdownItem(i));
teams.ItemsAdded += items => items.ForEach(add);
}
private readonly List<IUnbindable> refBindables = new List<IUnbindable>();
private T boundReference<T>(T obj)
where T : IBindable
{
obj = (T)obj.GetBoundCopy();
refBindables.Add(obj);
return obj;
}
private void add(TournamentTeam team)
{
Control.AddDropdownItem(team);
boundReference(team.FullName).BindValueChanged(_ =>
{
Control.RemoveDropdownItem(team);
Control.AddDropdownItem(team);
});
}
}
private class LadderSettingsDropdown<T> : SettingsDropdown<T>
{
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();
private new class DropdownControl : SettingsDropdown<T>.DropdownControl
{
protected override DropdownMenu CreateMenu() => new Menu();
private new class Menu : OsuDropdownMenu
{
public Menu()
{
MaxHeight = 200;
}
}
}
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Lines;
using osuTK;
namespace osu.Game.Tournament.Screens.Ladder.Components
{
public class ProgressionPath : Path
{
public DrawableTournamentMatch Source { get; private set; }
public DrawableTournamentMatch Destination { get; private set; }
public ProgressionPath(DrawableTournamentMatch source, DrawableTournamentMatch destination)
{
Source = source;
Destination = destination;
PathRadius = 3;
BypassAutoSizeAxes = Axes.Both;
}
protected override void LoadComplete()
{
base.LoadComplete();
Vector2 getCenteredVector(Vector2 top, Vector2 bottom) => new Vector2(top.X, top.Y + (bottom.Y - top.Y) / 2);
var q1 = Source.ScreenSpaceDrawQuad;
var q2 = Destination.ScreenSpaceDrawQuad;
float padding = q1.Width / 20;
bool progressionToRight = q2.TopLeft.X > q1.TopLeft.X;
if (!progressionToRight)
{
var temp = q2;
q2 = q1;
q1 = temp;
}
var c1 = getCenteredVector(q1.TopRight, q1.BottomRight) + new Vector2(padding, 0);
var c2 = getCenteredVector(q2.TopLeft, q2.BottomLeft) - new Vector2(padding, 0);
var p1 = c1;
var p2 = p1 + new Vector2(padding, 0);
if (p2.X > c2.X)
{
c2 = getCenteredVector(q2.TopRight, q2.BottomRight) + new Vector2(padding, 0);
p2.X = c2.X + padding;
}
var p3 = new Vector2(p2.X, c2.Y);
var p4 = new Vector2(c2.X, p3.Y);
var points = new[] { p1, p2, p3, p4 };
float minX = points.Min(p => p.X);
float minY = points.Min(p => p.Y);
var topLeft = new Vector2(minX, minY);
Position = Parent.ToLocalSpace(topLeft);
Vertices = points.Select(p => Parent.ToLocalSpace(p) - Parent.ToLocalSpace(topLeft)).ToList();
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Primitives;
using osu.Framework.Input.Events;
using osuTK;
namespace osu.Game.Tournament.Screens.Ladder
{
public class LadderDragContainer : Container
{
protected override bool OnDragStart(DragStartEvent e) => true;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private Vector2 target;
private float scale = 1;
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
protected override bool OnDrag(DragEvent e)
{
this.MoveTo(target += e.Delta, 1000, Easing.OutQuint);
return true;
}
private const float min_scale = 0.6f;
private const float max_scale = 1.4f;
protected override bool OnScroll(ScrollEvent e)
{
var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale);
this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint);
this.ScaleTo(scale = newScale, 2000, Easing.OutQuint);
return true;
}
}
}

View File

@ -0,0 +1,174 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Caching;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Platform;
using osu.Game.Graphics;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Editors;
using osu.Game.Tournament.Screens.Ladder.Components;
using osuTK.Graphics;
namespace osu.Game.Tournament.Screens.Ladder
{
public class LadderScreen : TournamentScreen, IProvideVideo
{
protected Container<DrawableTournamentMatch> MatchesContainer;
private Container<Path> paths;
private Container headings;
protected LadderDragContainer ScrollContent;
protected Container Content;
[BackgroundDependencyLoader]
private void load(OsuColour colours, Storage storage)
{
normalPathColour = colours.BlueDarker.Darken(2);
losersPathColour = colours.YellowDarker.Darken(2);
RelativeSizeAxes = Axes.Both;
InternalChild = Content = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v"))
{
RelativeSizeAxes = Axes.Both,
Loop = true,
},
ScrollContent = new LadderDragContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
paths = new Container<Path> { RelativeSizeAxes = Axes.Both },
headings = new Container { RelativeSizeAxes = Axes.Both },
MatchesContainer = new Container<DrawableTournamentMatch> { RelativeSizeAxes = Axes.Both },
}
},
}
};
void addMatch(TournamentMatch match) =>
MatchesContainer.Add(new DrawableTournamentMatch(match, this is LadderEditorScreen)
{
Changed = () => layout.Invalidate()
});
foreach (var match in LadderInfo.Matches)
addMatch(match);
LadderInfo.Rounds.ItemsAdded += _ => layout.Invalidate();
LadderInfo.Rounds.ItemsRemoved += _ => layout.Invalidate();
LadderInfo.Matches.ItemsAdded += matches =>
{
foreach (var p in matches)
addMatch(p);
layout.Invalidate();
};
LadderInfo.Matches.ItemsRemoved += matches =>
{
foreach (var p in matches)
foreach (var d in MatchesContainer.Where(d => d.Match == p))
d.Expire();
layout.Invalidate();
};
}
private Cached layout = new Cached();
protected override void Update()
{
base.Update();
if (!layout.IsValid)
UpdateLayout();
}
private Color4 normalPathColour;
private Color4 losersPathColour;
protected virtual bool DrawLoserPaths => false;
protected virtual void UpdateLayout()
{
paths.Clear();
headings.Clear();
int id = 1;
foreach (var match in MatchesContainer.OrderBy(d => d.Y).ThenBy(d => d.X))
{
match.Match.ID = id++;
if (match.Match.Progression.Value != null)
{
var dest = MatchesContainer.FirstOrDefault(p => p.Match == match.Match.Progression.Value);
if (dest == null)
// clean up outdated progressions.
match.Match.Progression.Value = null;
else
paths.Add(new ProgressionPath(match, dest) { Colour = match.Match.Losers.Value ? losersPathColour : normalPathColour });
}
if (DrawLoserPaths)
{
if (match.Match.LosersProgression.Value != null)
{
var dest = MatchesContainer.FirstOrDefault(p => p.Match == match.Match.LosersProgression.Value);
if (dest == null)
// clean up outdated progressions.
match.Match.LosersProgression.Value = null;
else
paths.Add(new ProgressionPath(match, dest) { Colour = losersPathColour.Opacity(0.1f) });
}
}
}
foreach (var round in LadderInfo.Rounds)
{
var topMatch = MatchesContainer.Where(p => !p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault();
if (topMatch == null) continue;
headings.Add(new DrawableTournamentRound(round)
{
Position = headings.ToLocalSpace((topMatch.ScreenSpaceDrawQuad.TopLeft + topMatch.ScreenSpaceDrawQuad.TopRight) / 2),
Margin = new MarginPadding { Bottom = 10 },
Origin = Anchor.BottomCentre,
});
}
foreach (var round in LadderInfo.Rounds)
{
var topMatch = MatchesContainer.Where(p => p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault();
if (topMatch == null) continue;
headings.Add(new DrawableTournamentRound(round, true)
{
Position = headings.ToLocalSpace((topMatch.ScreenSpaceDrawQuad.TopLeft + topMatch.ScreenSpaceDrawQuad.TopRight) / 2),
Margin = new MarginPadding { Bottom = 10 },
Origin = Anchor.BottomCentre,
});
}
layout.Validate();
}
}
}

Some files were not shown because too many files have changed in this diff Show More