diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml
index fe63f5faf3..366f172c30 100644
--- a/.idea/.idea.osu.Desktop/.idea/modules.xml
+++ b/.idea/.idea.osu.Desktop/.idea/modules.xml
@@ -2,6 +2,7 @@
+
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 6480612b2e..4e8af405a2 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,11 +11,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -28,11 +23,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -45,11 +35,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -62,11 +47,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -80,11 +60,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -98,11 +73,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -116,11 +86,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -134,11 +99,6 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
- }
- },
"console": "internalConsole"
},
{
@@ -169,4 +129,4 @@
"externalConsole": false
}
]
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index 77c7eb9d2d..59d72247f5 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
-If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
+If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label).
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 510b53054b..4fd0e5e8c7 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -97,8 +97,10 @@ platform :ios do
changelog.gsub!('$BUILD_ID', options[:build])
pilot(
- wait_processing_interval: 1800,
+ wait_processing_interval: 900,
changelog: changelog,
+ groups: ['osu! supporters', 'public'],
+ distribute_external: true,
ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
)
end
diff --git a/osu.Android.props b/osu.Android.props
index 1c4a6ffe75..7e17f9da16 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index a91c010809..84f215f930 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -30,11 +30,6 @@ namespace osu.Android
}
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Add(new SimpleUpdateManager());
- }
+ protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
}
}
\ No newline at end of file
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index f70cc24159..f05ee48914 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -47,20 +47,25 @@ namespace osu.Desktop
return null;
}
+ protected override UpdateManager CreateUpdateManager()
+ {
+ switch (RuntimeInfo.OS)
+ {
+ case RuntimeInfo.Platform.Windows:
+ return new SquirrelUpdateManager();
+
+ default:
+ return new SimpleUpdateManager();
+ }
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
if (!noVersionOverlay)
- {
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
- if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
- Add(new SquirrelUpdateManager());
- else
- Add(new SimpleUpdateManager());
- }
-
LoadComponentAsync(new DiscordRichPresence(), Add);
}
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
new file mode 100644
index 0000000000..47e91e50d4
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Tests.Mods
+{
+ public class TestSceneCatchModPerfect : ModPerfectTestScene
+ {
+ public TestSceneCatchModPerfect()
+ : base(new CatchRuleset(), new CatchModPerfect())
+ {
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestBananaShower(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new BananaShower { StartTime = 1000, EndTime = 3000 }, false), shouldMiss);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestFruit(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Fruit { StartTime = 1000 }), shouldMiss);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestJuiceStream(bool shouldMiss)
+ {
+ var stream = new JuiceStream
+ {
+ StartTime = 1000,
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(100, 0),
+ })
+ };
+
+ CreateHitObjectTest(new HitObjectTestData(stream), shouldMiss);
+ }
+
+ // We only care about testing misses, hits are tested via JuiceStream
+ [TestCase(true)]
+ public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
+
+ // We only care about testing misses, hits are tested via JuiceStream
+ [TestCase(true)]
+ public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0@2x.png
new file mode 100644
index 0000000000..786e5cc25a
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1@2x.png
new file mode 100644
index 0000000000..e93530fb16
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2@2x.png
new file mode 100644
index 0000000000..6f51257742
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3@2x.png
new file mode 100644
index 0000000000..953a04d4e4
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4@2x.png
new file mode 100644
index 0000000000..66a3cf9e0b
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5@2x.png
new file mode 100644
index 0000000000..ec4487f8fb
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle.png
deleted file mode 100755
index 17177f3246..0000000000
Binary files a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index 74a9c05bf9..ed7bfb9a44 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -7,7 +7,6 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
using osuTK;
@@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests
return beatmap;
}
- protected override Player CreatePlayer(Ruleset ruleset)
+ protected override TestPlayer CreatePlayer(Ruleset ruleset)
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index 20911b8d06..024c4cefb0 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -18,7 +18,9 @@ namespace osu.Game.Rulesets.Catch.Tests
public override IReadOnlyList RequiredTypes => new[]
{
typeof(BananaShower),
+ typeof(Banana),
typeof(DrawableBananaShower),
+ typeof(DrawableBanana),
typeof(CatchRuleset),
typeof(DrawableCatchRuleset),
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 4ff9f7a7fe..fe0d512166 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -17,12 +17,13 @@ namespace osu.Game.Rulesets.Catch.Tests
public override IReadOnlyList RequiredTypes => new[]
{
typeof(CatcherArea),
+ typeof(CatcherSprite)
};
[BackgroundDependencyLoader]
private void load()
{
- SetContents(() => new CatcherArea.Catcher
+ SetContents(() => new Catcher
{
RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index df1ac4c725..cf68c5424d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -10,9 +8,15 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
@@ -22,11 +26,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{
private RulesetInfo catchRuleset;
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(CatcherArea),
- };
-
public TestSceneCatcherArea()
{
AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
@@ -34,9 +33,41 @@ namespace osu.Game.Rulesets.Catch.Tests
CreatedDrawables.OfType().Select(i => i.Child)
.OfType().ForEach(c => c.ToggleHyperDash(t)));
- AddRepeatStep("catch fruit", () =>
- this.ChildrenOfType().ForEach(area =>
- area.MovableCatcher.PlaceOnPlate(new DrawableFruit(new TestSceneFruitObjects.TestCatchFruit(FruitVisualRepresentation.Grape)))), 20);
+ AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
+ {
+ X = this.ChildrenOfType().First().MovableCatcher.X
+ }), 20);
+ AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
+ {
+ X = this.ChildrenOfType().First().MovableCatcher.X,
+ LastInCombo = true,
+ }), 20);
+ AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
+ {
+ X = this.ChildrenOfType().First().MovableCatcher.X,
+ }), 20);
+ AddRepeatStep("miss fruit", () => catchFruit(new Fruit
+ {
+ X = this.ChildrenOfType().First().MovableCatcher.X + 100,
+ LastInCombo = true,
+ }, true), 20);
+ }
+
+ private void catchFruit(Fruit fruit, bool miss = false)
+ {
+ this.ChildrenOfType().ForEach(area =>
+ {
+ DrawableFruit drawable = new DrawableFruit(fruit);
+ area.Add(drawable);
+
+ Schedule(() =>
+ {
+ area.AttemptCatch(fruit);
+ area.OnResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
+
+ drawable.Expire();
+ });
+ });
}
private void createCatcher(float size)
@@ -47,7 +78,8 @@ namespace osu.Game.Rulesets.Catch.Tests
Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.CentreLeft,
- Origin = Anchor.TopLeft
+ Origin = Anchor.TopLeft,
+ CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
},
});
}
@@ -58,6 +90,17 @@ namespace osu.Game.Rulesets.Catch.Tests
catchRuleset = rulesets.GetRuleset(2);
}
+ public class TestFruit : Fruit
+ {
+ public TestFruit(bool kiai)
+ {
+ var kiaiCpi = new ControlPointInfo();
+ kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
+
+ ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty());
+ }
+ }
+
private class TestCatcherArea : CatcherArea
{
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 070847c0c1..df5494aab0 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Allocation;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(CatcherArea.Catcher),
+ typeof(Catcher),
typeof(DrawableCatchRuleset),
typeof(DrawableFruit),
typeof(DrawableJuiceStream),
@@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableCatchRuleset drawableRuleset;
private double playfieldTime => drawableRuleset.Playfield.Time.Current;
- [BackgroundDependencyLoader]
- private void load()
+ [SetUp]
+ public void Setup() => Schedule(() =>
{
var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint());
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Catch.Tests
ControlPointInfo = controlPointInfo
});
- Add(new Container
+ Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -66,16 +66,49 @@ namespace osu.Game.Rulesets.Catch.Tests
{
drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap.GetPlayableBeatmap(new CatchRuleset().RulesetInfo))
}
- });
+ };
+ });
+
+ [Test]
+ public void TestFruits()
+ {
+ AddStep("hit fruits", () => spawnFruits(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
AddStep("miss fruits", () => spawnFruits());
- AddStep("hit fruits", () => spawnFruits(true));
- AddStep("miss juicestream", () => spawnJuiceStream());
- AddStep("hit juicestream", () => spawnJuiceStream(true));
- AddStep("miss bananas", () => spawnBananas());
- AddStep("hit bananas", () => spawnBananas(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is failed", () => catcherState == CatcherAnimationState.Fail);
}
+ [Test]
+ public void TestJuicestream()
+ {
+ AddStep("hit juicestream", () => spawnJuiceStream(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
+
+ AddStep("miss juicestream", () => spawnJuiceStream());
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is failed", () => catcherState == CatcherAnimationState.Fail);
+ }
+
+ [Test]
+ public void TestBananas()
+ {
+ AddStep("hit bananas", () => spawnBananas(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
+
+ AddStep("miss bananas", () => spawnBananas());
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
+ }
+
+ private bool playfieldIsEmpty => !((CatchPlayfield)drawableRuleset.Playfield).AllHitObjects.Any(h => h.IsAlive);
+
+ private CatcherAnimationState catcherState => ((CatchPlayfield)drawableRuleset.Playfield).CatcherArea.MovableCatcher.CurrentState;
+
private void spawnFruits(bool hit = false)
{
for (int i = 1; i <= 4; i++)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index da36673930..49ff9df4d7 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -1,16 +1,28 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Visual;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestSceneHyperDash : PlayerTestScene
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherArea),
+ };
+
public TestSceneHyperDash()
: base(new CatchRuleset())
{
@@ -22,8 +34,19 @@ namespace osu.Game.Rulesets.Catch.Tests
public void TestHyperDash()
{
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
+ AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast.
+
+ AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0);
+
+ for (int i = 0; i < 3; i++)
+ {
+ AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing);
+ AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing);
+ }
}
+ private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher;
+
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
@@ -35,17 +58,52 @@ namespace osu.Game.Rulesets.Catch.Tests
}
};
- // Should produce a hyper-dash
- beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true });
- beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
+ // Should produce a hyper-dash (edge case test)
+ beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56 / 512f, NewCombo = true });
+ beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308 / 512f, NewCombo = true });
- for (int i = 0; i < 512; i++)
- {
- if (i % 5 < 3)
- beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 });
- }
+ double startTime = 3000;
+
+ const float left_x = 0.02f;
+ const float right_x = 0.98f;
+
+ createObjects(() => new Fruit { X = left_x });
+ createObjects(() => new TestJuiceStream(right_x), 1);
+ createObjects(() => new TestJuiceStream(left_x), 1);
+ createObjects(() => new Fruit { X = right_x });
+ createObjects(() => new Fruit { X = left_x });
+ createObjects(() => new Fruit { X = right_x });
+ createObjects(() => new TestJuiceStream(left_x), 1);
return beatmap;
+
+ void createObjects(Func createObject, int count = 3)
+ {
+ const float spacing = 140;
+
+ for (int i = 0; i < count; i++)
+ {
+ var hitObject = createObject();
+ hitObject.StartTime = startTime + i * spacing;
+ beatmap.HitObjects.Add(hitObject);
+ }
+
+ startTime += 700;
+ }
+ }
+
+ private class TestJuiceStream : JuiceStream
+ {
+ public TestJuiceStream(float x)
+ {
+ X = x;
+
+ Path = new SliderPath(new[]
+ {
+ new PathControlPoint(Vector2.Zero),
+ new PathControlPoint(new Vector2(30, 0)),
+ });
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 1a5d0f983b..7c81bcdf0c 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
ApplyPositionOffsets(Beatmap);
- initialiseHyperDash((List)Beatmap.HitObjects);
-
int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType())
@@ -76,6 +74,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
break;
case JuiceStream juiceStream:
+ // Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
+ lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X / CatchPlayfield.BASE_WIDTH;
+
+ // Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
+ lastStartTime = juiceStream.StartTime;
+
foreach (var nested in juiceStream.NestedHitObjects)
{
var catchObject = (CatchHitObject)nested;
@@ -90,20 +94,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
break;
}
}
+
+ initialiseHyperDash(beatmap);
}
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
{
- if (hitObject is JuiceStream stream)
- {
- lastPosition = stream.EndX;
- lastStartTime = stream.EndTime;
- return;
- }
-
- if (!(hitObject is Fruit))
- return;
-
float offsetPosition = hitObject.X;
double startTime = hitObject.StartTime;
@@ -116,7 +112,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
float positionDiff = offsetPosition - lastPosition.Value;
- double timeDiff = startTime - lastStartTime;
+
+ // Todo: BUG!! Stable calculated time deltas as ints, which affects randomisation. This should be changed to a double.
+ int timeDiff = (int)(startTime - lastStartTime);
if (timeDiff > 1000)
{
@@ -132,7 +130,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
return;
}
- if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
+ // ReSharper disable once PossibleLossOfFraction
+ if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3)
applyOffset(ref offsetPosition, positionDiff);
hitObject.XOffset = offsetPosition - hitObject.X;
@@ -191,14 +190,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
- private void initialiseHyperDash(List objects)
+ private static void initialiseHyperDash(IBeatmap beatmap)
{
List objectWithDroplets = new List();
- foreach (var currentObject in objects)
+ foreach (var currentObject in beatmap.HitObjects)
{
- if (currentObject is Fruit)
- objectWithDroplets.Add(currentObject);
+ if (currentObject is Fruit fruitObject)
+ objectWithDroplets.Add(fruitObject);
if (currentObject is JuiceStream)
{
@@ -212,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
- double halfCatcherWidth = CatcherArea.GetCatcherSize(Beatmap.BeatmapInfo.BaseDifficulty) / 2;
+ double halfCatcherWidth = CatcherArea.GetCatcherSize(beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
@@ -221,10 +220,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject currentObject = objectWithDroplets[i];
CatchHitObject nextObject = objectWithDroplets[i + 1];
+ // Reset variables in-case values have changed (e.g. after applying HR)
+ currentObject.HyperDashTarget = null;
+ currentObject.DistanceToHyperDash = 0;
+
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
- float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
+ float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
{
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
index 02c045f363..80390705fe 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Catch
FruitGrapes,
FruitOrange,
FruitPear,
- Droplet
+ Droplet,
+ CatcherIdle,
+ CatcherFail,
+ CatcherKiai
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 44e1a8e5cc..5880a227c2 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override int SectionLength => 750;
+ private float halfCatcherWidth;
+
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@@ -48,14 +50,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
- float halfCatchWidth;
-
- using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty))
- {
- halfCatchWidth = catcher.CatchWidth * 0.5f;
- halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
- }
-
CatchHitObject lastObject = null;
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
@@ -69,16 +63,25 @@ namespace osu.Game.Rulesets.Catch.Difficulty
continue;
if (lastObject != null)
- yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth);
+ yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatcherWidth);
lastObject = hitObject;
}
}
- protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
+ protected override Skill[] CreateSkills(IBeatmap beatmap)
{
- new Movement(),
- };
+ using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
+ {
+ halfCatcherWidth = catcher.CatchWidth * 0.5f;
+ halfCatcherWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
+ }
+
+ return new Skill[]
+ {
+ new Movement(halfCatcherWidth),
+ };
+ }
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index 7cd569035b..fd164907e0 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -20,9 +20,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
protected override double DecayWeight => 0.94;
+ protected readonly float HalfCatcherWidth;
+
private float? lastPlayerPosition;
private float lastDistanceMoved;
+ public Movement(float halfCatcherWidth)
+ {
+ HalfCatcherWidth = halfCatcherWidth;
+ }
+
protected override double StrainValueOf(DifficultyHitObject current)
{
var catchCurrent = (CatchDifficultyHitObject)current;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
index fb92399102..e3391c47f1 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
@@ -1,11 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Catch.Judgements;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModPerfect : ModPerfect
{
+ protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
+ => !(result.Judgement is CatchBananaJudgement)
+ && base.FailCondition(healthProcessor, result);
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 4c72b9fd3e..1ef235f764 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition
{
- private readonly CatcherArea.Catcher catcher;
+ private readonly Catcher catcher;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
index cf7231ebb2..01b76ceed9 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Framework.Utils;
using osuTK.Graphics;
@@ -22,6 +23,23 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
return colour ??= getBananaColour();
}
+ protected override void UpdateInitialTransforms()
+ {
+ base.UpdateInitialTransforms();
+
+ const float end_scale = 0.6f;
+ const float random_scale_range = 1.6f;
+
+ ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle()))
+ .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
+
+ ScaleContainer.RotateTo(getRandomAngle())
+ .Then()
+ .RotateTo(getRandomAngle(), HitObject.TimePreempt);
+
+ float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
+ }
+
private Color4 getBananaColour()
{
switch (RNG.Next(0, 3))
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index 5bfe0515a1..6844be5941 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -91,10 +91,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
}
- protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
-
- protected override void UpdateInitialTransforms() => this.FadeInFromZero(200);
-
protected override void UpdateStateTransforms(ArmedState state)
{
var endTime = HitObject.GetEndTime();
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
index 0a8e830af9..cad8892283 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
float startRotation = RNG.NextSingle() * 20;
double duration = HitObject.TimePreempt + 2000;
- this.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
+ ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
index 197ad41247..fae5a10d04 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -13,7 +13,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public DrawableFruit(Fruit h)
: base(h)
{
- Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
}
[BackgroundDependencyLoader]
@@ -21,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
ScaleContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece());
+
+ ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
}
private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation)
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
index 932464cfd1..7bc016d94f 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
@@ -14,11 +15,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
private readonly Func> createDrawableRepresentation;
private readonly Container dropletContainer;
+ public override Vector2 OriginPosition => base.OriginPosition - new Vector2(0, CatchHitObject.OBJECT_RADIUS);
+
public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null)
: base(s)
{
this.createDrawableRepresentation = createDrawableRepresentation;
- RelativeSizeAxes = Axes.Both;
+ RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
X = 0;
@@ -27,6 +30,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
+ hitObject.Origin = Anchor.BottomCentre;
+
base.AddNestedHitObject(hitObject);
dropletContainer.Add(hitObject);
}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 642ff0246e..01011645bd 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -24,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects
public int RepeatCount { get; set; }
- public double Velocity;
- public double TickDistance;
+ public double Velocity { get; private set; }
+ public double TickDistance { get; private set; }
///
/// The length of one span of this .
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
base.CreateNestedHitObjects();
- var tickSamples = Samples.Select(s => new HitSampleInfo
+ var dropletSamples = Samples.Select(s => new HitSampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
@@ -75,7 +75,6 @@ namespace osu.Game.Rulesets.Catch.Objects
{
AddNested(new TinyDroplet
{
- Samples = tickSamples,
StartTime = t + lastEvent.Value.Time,
X = X + Path.PositionAt(
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH,
@@ -93,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Objects
case SliderEventType.Tick:
AddNested(new Droplet
{
- Samples = tickSamples,
+ Samples = dropletSamples,
StartTime = e.Time,
X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
});
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 4649dcae90..b90b5812a6 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Replays
public override Replay Generate()
{
// todo: add support for HT DT
- const double dash_speed = CatcherArea.Catcher.BASE_SPEED;
+ const double dash_speed = Catcher.BASE_SPEED;
const double movement_speed = dash_speed / 2;
float lastPosition = 0.5f;
double lastTime = 0;
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
index 36164c5543..65e6e6f209 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
@@ -44,6 +44,18 @@ namespace osu.Game.Rulesets.Catch.Skinning
return new LegacyFruitPiece("fruit-drop") { Scale = new Vector2(0.8f) };
break;
+
+ case CatchSkinComponents.CatcherIdle:
+ return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
+ this.GetAnimation("fruit-ryuuta", true, true, true);
+
+ case CatchSkinComponents.CatcherFail:
+ return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
+ this.GetAnimation("fruit-ryuuta", true, true, true);
+
+ case CatchSkinComponents.CatcherKiai:
+ return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
+ this.GetAnimation("fruit-ryuuta", true, true, true);
}
return null;
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
new file mode 100644
index 0000000000..e361b29a9d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -0,0 +1,458 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class Catcher : Container, IKeyBindingHandler
+ {
+ ///
+ /// Whether we are hyper-dashing or not.
+ ///
+ public bool HyperDashing => hyperDashModifier != 1;
+
+ ///
+ /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
+ ///
+ public const double BASE_SPEED = 1.0 / 512;
+
+ public Container ExplodingFruitTarget;
+
+ public Container AdditiveTarget;
+
+ public CatcherAnimationState CurrentState { get; private set; }
+
+ ///
+ /// Width of the area that can be used to attempt catches during gameplay.
+ ///
+ internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X);
+
+ protected bool Dashing
+ {
+ get => dashing;
+ set
+ {
+ if (value == dashing) return;
+
+ dashing = value;
+
+ Trail |= dashing;
+ }
+ }
+
+ ///
+ /// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
+ ///
+ protected bool Trail
+ {
+ get => trail;
+ set
+ {
+ if (value == trail || AdditiveTarget == null) return;
+
+ trail = value;
+
+ if (Trail)
+ beginTrail();
+ }
+ }
+
+ private Container caughtFruit;
+
+ private CatcherSprite catcherIdle;
+ private CatcherSprite catcherKiai;
+ private CatcherSprite catcherFail;
+
+ private CatcherSprite currentCatcher;
+
+ private int currentDirection;
+
+ private bool dashing;
+
+ private bool trail;
+
+ private double hyperDashModifier = 1;
+ private int hyperDashDirection;
+ private float hyperDashTargetPosition;
+
+ public Catcher(BeatmapDifficulty difficulty = null)
+ {
+ RelativePositionAxes = Axes.X;
+ X = 0.5f;
+
+ Origin = Anchor.TopCentre;
+
+ Size = new Vector2(CatcherArea.CATCHER_SIZE);
+ if (difficulty != null)
+ Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ caughtFruit = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ },
+ catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
+ {
+ Anchor = Anchor.TopCentre,
+ Alpha = 0,
+ },
+ catcherKiai = new CatcherSprite(CatcherAnimationState.Kiai)
+ {
+ Anchor = Anchor.TopCentre,
+ Alpha = 0,
+ },
+ catcherFail = new CatcherSprite(CatcherAnimationState.Fail)
+ {
+ Anchor = Anchor.TopCentre,
+ Alpha = 0,
+ }
+ };
+
+ updateCatcher();
+ }
+
+ ///
+ /// Add a caught fruit to the catcher's stack.
+ ///
+ /// The fruit that was caught.
+ public void PlaceOnPlate(DrawableCatchHitObject fruit)
+ {
+ var ourRadius = fruit.DisplayRadius;
+ float theirRadius = 0;
+
+ const float allowance = 6;
+
+ while (caughtFruit.Any(f =>
+ f.LifetimeEnd == double.MaxValue &&
+ Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
+ {
+ var diff = (ourRadius + theirRadius) / allowance;
+ fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
+ fruit.Y -= RNG.NextSingle() * diff;
+ }
+
+ fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
+
+ caughtFruit.Add(fruit);
+
+ Add(new HitExplosion(fruit)
+ {
+ X = fruit.X,
+ Scale = new Vector2(fruit.HitObject.Scale)
+ });
+ }
+
+ ///
+ /// Let the catcher attempt to catch a fruit.
+ ///
+ /// The fruit to catch.
+ /// Whether the catch is possible.
+ public bool AttemptCatch(CatchHitObject fruit)
+ {
+ var halfCatchWidth = CatchWidth * 0.5f;
+
+ // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
+ var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
+ var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
+
+ var validCatch =
+ catchObjectPosition >= catcherPosition - halfCatchWidth &&
+ catchObjectPosition <= catcherPosition + halfCatchWidth;
+
+ // only update hyperdash state if we are catching a fruit.
+ // exceptions are Droplets and JuiceStreams.
+ if (!(fruit is Fruit)) return validCatch;
+
+ if (validCatch && fruit.HyperDash)
+ {
+ var target = fruit.HyperDashTarget;
+ var timeDifference = target.StartTime - fruit.StartTime;
+ double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
+ var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
+
+ SetHyperDashState(Math.Abs(velocity), target.X);
+ }
+ else
+ SetHyperDashState();
+
+ if (validCatch)
+ updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
+ else if (!(fruit is Banana))
+ updateState(CatcherAnimationState.Fail);
+
+ return validCatch;
+ }
+
+ ///
+ /// Set hyper-dash state.
+ ///
+ /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.
+ /// When this catcher crosses this position, this catcher ends hyper-dashing.
+ public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
+ {
+ const float hyper_dash_transition_length = 180;
+
+ var wasHyperDashing = HyperDashing;
+
+ if (modifier <= 1 || X == targetPosition)
+ {
+ hyperDashModifier = 1;
+ hyperDashDirection = 0;
+
+ if (wasHyperDashing)
+ {
+ this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
+ this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
+ Trail &= Dashing;
+ }
+ }
+ else
+ {
+ hyperDashModifier = modifier;
+ hyperDashDirection = Math.Sign(targetPosition - X);
+ hyperDashTargetPosition = targetPosition;
+
+ if (!wasHyperDashing)
+ {
+ this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
+ this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
+ Trail = true;
+
+ var hyperDashEndGlow = createAdditiveSprite();
+
+ hyperDashEndGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
+ hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.95f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In);
+ hyperDashEndGlow.FadeOut(1200);
+ hyperDashEndGlow.Expire(true);
+ }
+ }
+ }
+
+ public bool OnPressed(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection--;
+ return true;
+
+ case CatchAction.MoveRight:
+ currentDirection++;
+ return true;
+
+ case CatchAction.Dash:
+ Dashing = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection++;
+ break;
+
+ case CatchAction.MoveRight:
+ currentDirection--;
+ break;
+
+ case CatchAction.Dash:
+ Dashing = false;
+ break;
+ }
+ }
+
+ public void UpdatePosition(float position)
+ {
+ position = Math.Clamp(position, 0, 1);
+
+ if (position == X)
+ return;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
+ X = position;
+ }
+
+ ///
+ /// Drop any fruit off the plate.
+ ///
+ public void Drop()
+ {
+ foreach (var f in caughtFruit.ToArray())
+ Drop(f);
+ }
+
+ ///
+ /// Explode any fruit off the plate.
+ ///
+ public void Explode()
+ {
+ foreach (var f in caughtFruit.ToArray())
+ Explode(f);
+ }
+
+ public void Drop(DrawableHitObject fruit)
+ {
+ removeFromPlateWithTransform(fruit, f =>
+ {
+ f.MoveToY(f.Y + 75, 750, Easing.InSine);
+ f.FadeOut(750);
+ });
+ }
+
+ public void Explode(DrawableHitObject fruit)
+ {
+ var originalX = fruit.X * Scale.X;
+
+ removeFromPlateWithTransform(fruit, f =>
+ {
+ f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
+ f.MoveToX(f.X + originalX * 6, 1000);
+ f.FadeOut(750);
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (currentDirection == 0) return;
+
+ var direction = Math.Sign(currentDirection);
+
+ var dashModifier = Dashing ? 1 : 0.5;
+ var speed = BASE_SPEED * dashModifier * hyperDashModifier;
+
+ UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
+
+ // Correct overshooting.
+ if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
+ (hyperDashDirection < 0 && hyperDashTargetPosition > X))
+ {
+ X = hyperDashTargetPosition;
+ SetHyperDashState();
+ }
+ }
+
+ private void updateCatcher()
+ {
+ currentCatcher?.Hide();
+
+ switch (CurrentState)
+ {
+ default:
+ currentCatcher = catcherIdle;
+ break;
+
+ case CatcherAnimationState.Fail:
+ currentCatcher = catcherFail;
+ break;
+
+ case CatcherAnimationState.Kiai:
+ currentCatcher = catcherKiai;
+ break;
+ }
+
+ currentCatcher.Show();
+ (currentCatcher.Drawable as IAnimation)?.GotoFrame(0);
+ }
+
+ private void beginTrail()
+ {
+ if (!dashing && !HyperDashing)
+ {
+ Trail = false;
+ return;
+ }
+
+ var additive = createAdditiveSprite();
+
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
+ additive.Expire(true);
+
+ Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
+ }
+
+ private void updateState(CatcherAnimationState state)
+ {
+ if (CurrentState == state)
+ return;
+
+ CurrentState = state;
+ updateCatcher();
+ }
+
+ private CatcherTrailSprite createAdditiveSprite()
+ {
+ var tex = (currentCatcher.Drawable as TextureAnimation)?.CurrentFrame ?? ((Sprite)currentCatcher.Drawable).Texture;
+
+ var sprite = new CatcherTrailSprite(tex)
+ {
+ Anchor = Anchor,
+ Scale = Scale,
+ Colour = HyperDashing ? Color4.Red : Color4.White,
+ Blending = BlendingParameters.Additive,
+ RelativePositionAxes = RelativePositionAxes,
+ Position = Position
+ };
+
+ AdditiveTarget?.Add(sprite);
+
+ return sprite;
+ }
+
+ private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
+ {
+ if (ExplodingFruitTarget != null)
+ {
+ fruit.Anchor = Anchor.TopLeft;
+ fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
+
+ if (!caughtFruit.Remove(fruit))
+ // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
+ // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
+ return;
+
+ ExplodingFruitTarget.Add(fruit);
+ }
+
+ var actionTime = Clock.CurrentTime;
+
+ fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
+ onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
+
+ void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
+ {
+ using (fruit.BeginAbsoluteSequence(actionTime))
+ action(fruit);
+
+ fruit.Expire();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs b/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs
new file mode 100644
index 0000000000..566e9d1911
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public enum CatcherAnimationState
+ {
+ Idle,
+ Fail,
+ Kiai
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index b977d46611..e0d9ff759d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -2,14 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
@@ -19,7 +13,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -27,8 +20,6 @@ namespace osu.Game.Rulesets.Catch.UI
{
public const float CATCHER_SIZE = 106.75f;
- protected internal readonly Catcher MovableCatcher;
-
public Func> CreateDrawableRepresentation;
public Container ExplodingFruitTarget
@@ -36,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.UI
set => MovableCatcher.ExplodingFruitTarget = value;
}
+ private DrawableCatchHitObject lastPlateableFruit;
+
public CatcherArea(BeatmapDifficulty difficulty = null)
{
RelativeSizeAxes = Axes.X;
@@ -46,7 +39,10 @@ namespace osu.Game.Rulesets.Catch.UI
};
}
- private DrawableCatchHitObject lastPlateableFruit;
+ public static float GetCatcherSize(BeatmapDifficulty difficulty)
+ {
+ return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
{
@@ -99,6 +95,15 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
+ public void OnReleased(CatchAction action)
+ {
+ }
+
+ public bool AttemptCatch(CatchHitObject obj)
+ {
+ return MovableCatcher.AttemptCatch(obj);
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -109,479 +114,6 @@ namespace osu.Game.Rulesets.Catch.UI
MovableCatcher.X = state.CatcherX.Value;
}
- public void OnReleased(CatchAction action)
- {
- }
-
- public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
-
- public static float GetCatcherSize(BeatmapDifficulty difficulty)
- {
- return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
- }
-
- public class Catcher : Container, IKeyBindingHandler
- {
- ///
- /// Width of the area that can be used to attempt catches during gameplay.
- ///
- internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
-
- private Container caughtFruit;
-
- public Container ExplodingFruitTarget;
-
- public Container AdditiveTarget;
-
- public Catcher(BeatmapDifficulty difficulty = null)
- {
- RelativePositionAxes = Axes.X;
- X = 0.5f;
-
- Origin = Anchor.TopCentre;
-
- Size = new Vector2(CATCHER_SIZE);
- if (difficulty != null)
- Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Children = new[]
- {
- caughtFruit = new Container
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
- },
- createCatcherSprite(),
- };
- }
-
- private int currentDirection;
-
- private bool dashing;
-
- protected bool Dashing
- {
- get => dashing;
- set
- {
- if (value == dashing) return;
-
- dashing = value;
-
- Trail |= dashing;
- }
- }
-
- private bool trail;
-
- ///
- /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
- ///
- protected bool Trail
- {
- get => trail;
- set
- {
- if (value == trail) return;
-
- trail = value;
-
- if (Trail)
- beginTrail();
- }
- }
-
- private void beginTrail()
- {
- Trail &= dashing || HyperDashing;
- Trail &= AdditiveTarget != null;
-
- if (!Trail) return;
-
- var additive = createCatcherSprite();
-
- additive.Anchor = Anchor;
- additive.OriginPosition += new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
- additive.Position = Position;
- additive.Scale = Scale;
- additive.Colour = HyperDashing ? Color4.Red : Color4.White;
- additive.RelativePositionAxes = RelativePositionAxes;
- additive.Blending = BlendingParameters.Additive;
-
- AdditiveTarget.Add(additive);
-
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
- additive.Expire(true);
-
- Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
- }
-
- private Drawable createCatcherSprite() => new CatcherSprite();
-
- ///
- /// Add a caught fruit to the catcher's stack.
- ///
- /// The fruit that was caught.
- public void PlaceOnPlate(DrawableCatchHitObject fruit)
- {
- float ourRadius = fruit.DisplayRadius;
- float theirRadius = 0;
-
- const float allowance = 6;
-
- while (caughtFruit.Any(f =>
- f.LifetimeEnd == double.MaxValue &&
- Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
- {
- float diff = (ourRadius + theirRadius) / allowance;
- fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
- fruit.Y -= RNG.NextSingle() * diff;
- }
-
- fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
-
- caughtFruit.Add(fruit);
-
- Add(new HitExplosion(fruit)
- {
- X = fruit.X,
- Scale = new Vector2(fruit.HitObject.Scale)
- });
- }
-
- ///
- /// Let the catcher attempt to catch a fruit.
- ///
- /// The fruit to catch.
- /// Whether the catch is possible.
- public bool AttemptCatch(CatchHitObject fruit)
- {
- float halfCatchWidth = CatchWidth * 0.5f;
-
- // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
- var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
- var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
-
- var validCatch =
- catchObjectPosition >= catcherPosition - halfCatchWidth &&
- catchObjectPosition <= catcherPosition + halfCatchWidth;
-
- if (validCatch && fruit.HyperDash)
- {
- var target = fruit.HyperDashTarget;
- double timeDifference = target.StartTime - fruit.StartTime;
- double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
- double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
-
- SetHyperDashState(Math.Abs(velocity), target.X);
- }
- else
- {
- SetHyperDashState();
- }
-
- return validCatch;
- }
-
- private double hyperDashModifier = 1;
- private int hyperDashDirection;
- private float hyperDashTargetPosition;
-
- ///
- /// Whether we are hyper-dashing or not.
- ///
- public bool HyperDashing => hyperDashModifier != 1;
-
- ///
- /// Set hyper-dash state.
- ///
- /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.
- /// When this catcher crosses this position, this catcher ends hyper-dashing.
- public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
- {
- const float hyper_dash_transition_length = 180;
-
- bool previouslyHyperDashing = HyperDashing;
-
- if (modifier <= 1 || X == targetPosition)
- {
- hyperDashModifier = 1;
- hyperDashDirection = 0;
-
- if (previouslyHyperDashing)
- {
- this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
- this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
- Trail &= Dashing;
- }
- }
- else
- {
- hyperDashModifier = modifier;
- hyperDashDirection = Math.Sign(targetPosition - X);
- hyperDashTargetPosition = targetPosition;
-
- if (!previouslyHyperDashing)
- {
- this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
- this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
- Trail = true;
- }
- }
- }
-
- public bool OnPressed(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection--;
- return true;
-
- case CatchAction.MoveRight:
- currentDirection++;
- return true;
-
- case CatchAction.Dash:
- Dashing = true;
- return true;
- }
-
- return false;
- }
-
- public void OnReleased(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection++;
- break;
-
- case CatchAction.MoveRight:
- currentDirection--;
- break;
-
- case CatchAction.Dash:
- Dashing = false;
- break;
- }
- }
-
- ///
- /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
- ///
- public const double BASE_SPEED = 1.0 / 512;
-
- protected override void Update()
- {
- base.Update();
-
- if (currentDirection == 0) return;
-
- var direction = Math.Sign(currentDirection);
-
- double dashModifier = Dashing ? 1 : 0.5;
- double speed = BASE_SPEED * dashModifier * hyperDashModifier;
-
- UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
-
- // Correct overshooting.
- if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
- (hyperDashDirection < 0 && hyperDashTargetPosition > X))
- {
- X = hyperDashTargetPosition;
- SetHyperDashState();
- }
- }
-
- public void UpdatePosition(float position)
- {
- position = Math.Clamp(position, 0, 1);
-
- if (position == X)
- return;
-
- Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
- X = position;
- }
-
- ///
- /// Drop any fruit off the plate.
- ///
- public void Drop()
- {
- foreach (var f in caughtFruit.ToArray())
- Drop(f);
- }
-
- ///
- /// Explode any fruit off the plate.
- ///
- public void Explode()
- {
- foreach (var f in caughtFruit.ToArray())
- Explode(f);
- }
-
- public void Drop(DrawableHitObject fruit) => removeFromPlateWithTransform(fruit, f =>
- {
- f.MoveToY(f.Y + 75, 750, Easing.InSine);
- f.FadeOut(750);
- });
-
- public void Explode(DrawableHitObject fruit)
- {
- var originalX = fruit.X * Scale.X;
-
- removeFromPlateWithTransform(fruit, f =>
- {
- f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
- f.MoveToX(f.X + originalX * 6, 1000);
- f.FadeOut(750);
- });
- }
-
- private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
- {
- if (ExplodingFruitTarget != null)
- {
- fruit.Anchor = Anchor.TopLeft;
- fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
-
- if (!caughtFruit.Remove(fruit))
- // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
- // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
- return;
-
- ExplodingFruitTarget.Add(fruit);
- }
-
- double actionTime = Clock.CurrentTime;
-
- fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
- onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
-
- void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
- {
- using (fruit.BeginAbsoluteSequence(actionTime))
- action(fruit);
-
- fruit.Expire();
- }
- }
- }
- }
-
- public class HitExplosion : CompositeDrawable
- {
- private readonly CircularContainer largeFaint;
-
- public HitExplosion(DrawableCatchHitObject fruit)
- {
- Size = new Vector2(20);
- Anchor = Anchor.TopCentre;
- Origin = Anchor.BottomCentre;
-
- Color4 objectColour = fruit.AccentColour.Value;
-
- // scale roughly in-line with visual appearance of notes
-
- const float angle_variangle = 15; // should be less than 45
-
- const float roundness = 100;
-
- const float initial_height = 10;
-
- var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
-
- InternalChildren = new Drawable[]
- {
- largeFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.8f),
- Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- },
- },
- new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- },
- },
- new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
- },
- new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
- }
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- const double duration = 400;
-
- largeFaint
- .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
- .FadeOut(duration * 2);
-
- this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
- Expire(true);
- }
+ protected internal readonly Catcher MovableCatcher;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index 025fa9c56e..52eb8d597e 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -3,31 +3,57 @@
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;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
- public class CatcherSprite : CompositeDrawable
+ public class CatcherSprite : SkinnableDrawable
{
- public CatcherSprite()
+ protected override bool ApplySizeRestrictionsToDefault => true;
+
+ public CatcherSprite(CatcherAnimationState state)
+ : base(new CatchSkinComponent(componentFromState(state)), _ =>
+ new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleDownToFit)
{
+ RelativeSizeAxes = Axes.None;
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;
+ OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
}
- [BackgroundDependencyLoader]
- private void load()
+ private static CatchSkinComponents componentFromState(CatcherAnimationState state)
{
- InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit)
+ switch (state)
{
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- };
+ case CatcherAnimationState.Fail:
+ return CatchSkinComponents.CatcherFail;
+
+ case CatcherAnimationState.Kiai:
+ return CatchSkinComponents.CatcherKiai;
+
+ default:
+ return CatchSkinComponents.CatcherIdle;
+ }
+ }
+
+ private class DefaultCatcherSprite : Sprite
+ {
+ private readonly CatcherAnimationState state;
+
+ public DefaultCatcherSprite(CatcherAnimationState state)
+ {
+ this.state = state;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get($"Gameplay/catch/fruit-catcher-{state.ToString().ToLower()}");
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
new file mode 100644
index 0000000000..56cb7dbfda
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherTrailSprite : Sprite
+ {
+ public CatcherTrailSprite(Texture texture)
+ {
+ Texture = texture;
+
+ 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.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
new file mode 100644
index 0000000000..04a86f83be
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -0,0 +1,122 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class HitExplosion : CompositeDrawable
+ {
+ private readonly CircularContainer largeFaint;
+
+ public HitExplosion(DrawableCatchHitObject fruit)
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.BottomCentre;
+
+ Color4 objectColour = fruit.AccentColour.Value;
+
+ // scale roughly in-line with visual appearance of notes
+
+ const float angle_variangle = 15; // should be less than 45
+
+ const float roundness = 100;
+
+ const float initial_height = 10;
+
+ var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
+
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ // we want our size to be very small so the glow dominates it.
+ Size = new Vector2(0.8f),
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ const double duration = 400;
+
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+
+ this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
+ Expire(true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
new file mode 100644
index 0000000000..607d42a1bb
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public class TestSceneManiaModPerfect : ModPerfectTestScene
+ {
+ public TestSceneManiaModPerfect()
+ : base(new ManiaRuleset(), new ManiaModPerfect())
+ {
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note { StartTime = 1000 }), shouldMiss);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
new file mode 100644
index 0000000000..69415b70e3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
@@ -0,0 +1,86 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModDifficultyAdjust : ModTestScene
+ {
+ public TestSceneOsuModDifficultyAdjust()
+ : base(new OsuRuleset())
+ {
+ }
+
+ [Test]
+ public void TestNoAdjustment() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModDifficultyAdjust(),
+ Autoplay = true,
+ PassCondition = checkSomeHit
+ });
+
+ [Test]
+ public void TestCircleSize1() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 1 } },
+ Autoplay = true,
+ PassCondition = () => checkSomeHit() && checkObjectsScale(0.78f)
+ });
+
+ [Test]
+ public void TestCircleSize10() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } },
+ Autoplay = true,
+ PassCondition = () => checkSomeHit() && checkObjectsScale(0.15f)
+ });
+
+ [Test]
+ public void TestApproachRate1() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } },
+ Autoplay = true,
+ PassCondition = () => checkSomeHit() && checkObjectsPreempt(1680)
+ });
+
+ [Test]
+ public void TestApproachRate10() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } },
+ Autoplay = true,
+ PassCondition = () => checkSomeHit() && checkObjectsPreempt(450)
+ });
+
+ private bool checkObjectsPreempt(double target)
+ {
+ var objects = Player.ChildrenOfType();
+ if (!objects.Any())
+ return false;
+
+ return objects.All(o => o.HitObject.TimePreempt == target);
+ }
+
+ private bool checkObjectsScale(float target)
+ {
+ var objects = Player.ChildrenOfType();
+ if (!objects.Any())
+ return false;
+
+ return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType().First().Children.OfType().Single().Scale.X, target));
+ }
+
+ private bool checkSomeHit()
+ {
+ return Player.ScoreProcessor.JudgedHits >= 2;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
new file mode 100644
index 0000000000..dcf19ad993
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModDoubleTime : ModTestScene
+ {
+ public TestSceneOsuModDoubleTime()
+ : base(new OsuRuleset())
+ {
+ }
+
+ [TestCase(0.5)]
+ [TestCase(1.01)]
+ [TestCase(1.5)]
+ [TestCase(2)]
+ [TestCase(5)]
+ public void TestSpeedChangeCustomisation(double rate)
+ {
+ var mod = new OsuModDoubleTime { SpeedChange = { Value = rate } };
+
+ CreateModTest(new ModTestData
+ {
+ Mod = mod,
+ PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
+ Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value)
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
new file mode 100644
index 0000000000..b03a894085
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModPerfect : ModPerfectTestScene
+ {
+ public TestSceneOsuModPerfect()
+ : base(new OsuRuleset(), new OsuModPerfect())
+ {
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestHitCircle(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HitCircle { StartTime = 1000 }), shouldMiss);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestSlider(bool shouldMiss)
+ {
+ var slider = new Slider
+ {
+ StartTime = 1000,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
+ };
+
+ CreateHitObjectTest(new HitObjectTestData(slider), shouldMiss);
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestSpinner(bool shouldMiss)
+ {
+ var spinner = new Spinner
+ {
+ StartTime = 1000,
+ EndTime = 3000,
+ Position = new Vector2(256, 192)
+ };
+
+ CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
index 94ca2d4cd1..87da7ef417 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
@@ -2,9 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
@@ -114,6 +117,22 @@ namespace osu.Game.Rulesets.Osu.Tests
assertGroups();
}
+ [Test]
+ public void TestStackedObjects()
+ {
+ addObjectsStep(() => new OsuHitObject[]
+ {
+ new HitCircle { Position = new Vector2(300, 100) },
+ new HitCircle
+ {
+ Position = new Vector2(300, 300),
+ StackHeight = 20
+ },
+ });
+
+ assertDirections();
+ }
+
private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[]
{
new HitCircle { Position = new Vector2(100, 100) },
@@ -207,6 +226,33 @@ namespace osu.Game.Rulesets.Osu.Tests
});
}
+ private void assertDirections()
+ {
+ AddAssert("group directions are correct", () =>
+ {
+ for (int i = 0; i < hitObjectContainer.Count; i++)
+ {
+ DrawableOsuHitObject expectedStart = getObject(i);
+ DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
+
+ if (expectedEnd == null)
+ continue;
+
+ var points = getGroup(i).ChildrenOfType().ToArray();
+ if (points.Length == 0)
+ continue;
+
+ float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X);
+ float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X);
+
+ if (!Precision.AlmostEquals(expectedDirection, realDirection))
+ throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}.");
+ }
+
+ return true;
+ });
+ }
+
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
new file mode 100644
index 0000000000..0649989dc0
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
@@ -0,0 +1,103 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneHitCircleArea : OsuManualInputManagerTestScene
+ {
+ private HitCircle hitCircle;
+ private DrawableHitCircle drawableHitCircle;
+ private DrawableHitCircle.HitReceptor hitAreaReceptor => drawableHitCircle.HitArea;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ hitCircle = new HitCircle
+ {
+ Position = new Vector2(100, 100),
+ StartTime = Time.Current + 500
+ };
+
+ hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ Child = new SkinProvidingContainer(new DefaultSkin())
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
+ {
+ Size = new Vector2(100)
+ }
+ };
+ });
+
+ [Test]
+ public void TestCircleHitCentre()
+ {
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(hitAreaReceptor.ScreenSpaceDrawQuad.Centre));
+ scheduleHit();
+
+ AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton);
+ }
+
+ [Test]
+ public void TestCircleHitLeftEdge()
+ {
+ AddStep("move mouse to left edge", () =>
+ {
+ var drawQuad = hitAreaReceptor.ScreenSpaceDrawQuad;
+ var mousePosition = new Vector2(drawQuad.TopLeft.X, drawQuad.Centre.Y);
+
+ InputManager.MoveMouseTo(mousePosition);
+ });
+ scheduleHit();
+
+ AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton);
+ }
+
+ [TestCase(0.95f, OsuAction.LeftButton)]
+ [TestCase(1.05f, null)]
+ public void TestHitsCloseToEdge(float relativeDistanceFromCentre, OsuAction? expectedAction)
+ {
+ AddStep("move mouse to top left circle edge", () =>
+ {
+ var drawQuad = hitAreaReceptor.ScreenSpaceDrawQuad;
+ // sqrt(2) / 2 = sin(45deg) = cos(45deg)
+ // draw width halved to get radius
+ float correction = relativeDistanceFromCentre * (float)Math.Sqrt(2) / 2 * (drawQuad.Width / 2);
+ var mousePosition = new Vector2(drawQuad.Centre.X - correction, drawQuad.Centre.Y - correction);
+
+ InputManager.MoveMouseTo(mousePosition);
+ });
+ scheduleHit();
+
+ AddAssert($"hit {(expectedAction == null ? "not " : string.Empty)}registered", () => hitAreaReceptor.HitAction == expectedAction);
+ }
+
+ [Test]
+ public void TestCircleMissBoundingBoxCorner()
+ {
+ AddStep("move mouse to top left corner of bounding box", () => InputManager.MoveMouseTo(hitAreaReceptor.ScreenSpaceDrawQuad.TopLeft));
+ scheduleHit();
+
+ AddAssert("hit not registered", () => hitAreaReceptor.HitAction == null);
+ }
+
+ private void scheduleHit() => AddStep("schedule action", () =>
+ {
+ var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
+ Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(OsuAction.LeftButton), delay);
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
new file mode 100644
index 0000000000..5f3596976d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
@@ -0,0 +1,101 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Tests.Visual;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneMissHitWindowJudgements : ModTestScene
+ {
+ public TestSceneMissHitWindowJudgements()
+ : base(new OsuRuleset())
+ {
+ }
+
+ [Test]
+ public void TestMissViaEarlyHit()
+ {
+ var beatmap = new Beatmap
+ {
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ };
+
+ var hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
+ CreateModTest(new ModTestData
+ {
+ Autoplay = false,
+ Mod = new TestAutoMod(),
+ Beatmap = new Beatmap
+ {
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ },
+ PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss
+ });
+ }
+
+ [Test]
+ public void TestMissViaNotHitting()
+ {
+ var beatmap = new Beatmap
+ {
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ };
+
+ var hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
+ CreateModTest(new ModTestData
+ {
+ Autoplay = false,
+ Beatmap = beatmap,
+ PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss
+ });
+ }
+
+ private class TestAutoMod : OsuModAutoplay
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
+ Replay = new MissingAutoGenerator(beatmap).Generate()
+ };
+ }
+
+ private class MissingAutoGenerator : OsuAutoGeneratorBase
+ {
+ public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap;
+
+ public MissingAutoGenerator(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ public override Replay Generate()
+ {
+ AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
+
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 450, Beatmap.HitObjects[0].StackedPosition));
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 350, Beatmap.HitObjects[0].StackedPosition, OsuAction.LeftButton));
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 325, Beatmap.HitObjects[0].StackedPosition));
+
+ return Replay;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index 4af4d5f966..0ae49790cd 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -23,7 +23,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
+ public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
index 412effe176..19736a7709 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
@@ -3,13 +3,13 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneOsuFlashlight : TestSceneOsuPlayer
{
- protected override Player CreatePlayer(Ruleset ruleset)
+ protected override TestPlayer CreatePlayer(Ruleset ruleset)
{
SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
index 8e73d6152f..f4809b0c9b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
@@ -12,7 +12,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestSceneResumeOverlay : ManualInputManagerTestScene
+ public class TestSceneResumeOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index 4da1b1dae0..d39e24fc1f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -18,7 +18,6 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Tests.Visual;
@@ -56,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void checkNextHitObject(string skin) =>
AddUntilStep($"check skin from {skin}", () =>
{
- var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault();
+ var firstObject = Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault();
if (firstObject == null)
return false;
@@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
private AudioManager audio { get; set; }
- protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index defd3a6f22..a201364de4 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(DrawableSliderTick),
typeof(DrawableSliderTail),
typeof(DrawableSliderHead),
- typeof(DrawableRepeatPoint),
+ typeof(DrawableSliderRepeat),
typeof(DrawableOsuHitObject)
};
@@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples));
- AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
+ AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
@@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples));
- AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
+ AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 94df239267..67e1b77770 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(SliderBall),
typeof(DrawableSlider),
typeof(DrawableSliderTick),
- typeof(DrawableRepeatPoint),
+ typeof(DrawableSliderRepeat),
typeof(DrawableOsuHitObject),
typeof(DrawableSliderHead),
typeof(DrawableSliderTail),
@@ -327,7 +327,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("Tracking dropped", assertMidSliderJudgementFail);
}
- private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
+ private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == HitResult.Great);
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index 5cf571d961..ea006ec607 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -11,7 +11,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
using osuTK;
using System.Collections.Generic;
using System.Linq;
@@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => track.IsRunning);
- AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First());
+ AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First());
}
[Test]
@@ -89,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep($"seek to {time}", () => track.Seek(time));
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs
new file mode 100644
index 0000000000..e528f65dca
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Judgements
+{
+ public class OsuIgnoreJudgement : OsuJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override int NumericResultFor(HitResult result) => 0;
+
+ protected override double HealthIncreaseFor(HitResult result) => 0;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index bc5f79331f..cf6677a55d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return;
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
+ slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
foreach (var point in slider.Path.ControlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 41daef1f38..44dba7715a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Mods
case DrawableSliderHead _:
case DrawableSliderTail _:
case DrawableSliderTick _:
- case DrawableRepeatPoint _:
+ case DrawableSliderRepeat _:
return;
default:
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index cc2f4c3f70..297a0fea79 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
// Wiggle the repeat points with the slider instead of independently.
// Also fixes an issue with repeat points being positioned incorrectly.
- if (osuObject is RepeatPoint)
+ if (osuObject is SliderRepeat)
return;
Random objRand = new Random((int)osuObject.StartTime);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index 921b23cb13..d0935e46f7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -88,8 +88,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void refresh()
{
- ClearInternal();
-
OsuHitObject osuStart = Start.HitObject;
double startTime = osuStart.GetEndTime();
@@ -104,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
return;
}
- Vector2 startPosition = osuStart.EndPosition;
- Vector2 endPosition = osuEnd.Position;
+ Vector2 startPosition = osuStart.StackedEndPosition;
+ Vector2 endPosition = osuEnd.StackedPosition;
double endTime = osuEnd.StartTime;
Vector2 distanceVector = endPosition - startPosition;
@@ -116,6 +114,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
double? firstTransformStartTime = null;
double finalTransformEndTime = startTime;
+ int point = 0;
+
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@@ -126,13 +126,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
- AddInternal(fp = new FollowPoint
+ if (InternalChildren.Count > point)
{
- Position = pointStartPosition,
- Rotation = rotation,
- Alpha = 0,
- Scale = new Vector2(1.5f * osuEnd.Scale),
- });
+ fp = (FollowPoint)InternalChildren[point];
+ fp.ClearTransforms();
+ }
+ else
+ AddInternal(fp = new FollowPoint());
+
+ fp.Position = pointStartPosition;
+ fp.Rotation = rotation;
+ fp.Alpha = 0;
+ fp.Scale = new Vector2(1.5f * osuEnd.Scale);
if (firstTransformStartTime == null)
firstTransformStartTime = fadeInTime;
@@ -146,8 +151,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn;
}
+
+ point++;
}
+ int excessPoints = InternalChildren.Count - point;
+ for (int i = 0; i < excessPoints; i++)
+ RemoveInternal(InternalChildren[^1]);
+
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
LifetimeStart = firstTransformStartTime ?? startTime;
LifetimeEnd = finalTransformEndTime;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 4ef63bb2a0..da1e666aba 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => ApproachCircle;
- public class HitReceptor : Drawable, IKeyBindingHandler
+ public class HitReceptor : CompositeDrawable, IKeyBindingHandler
{
// IsHovered is used
public override bool HandlePositionalInput => true;
@@ -185,6 +185,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
+
+ CornerRadius = OsuHitObject.OBJECT_RADIUS;
+ CornerExponent = 2;
}
public bool OnPressed(OsuAction action)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 7403649184..2d5b9d874c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -6,13 +6,11 @@ using osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning;
-using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -26,12 +24,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SliderBall Ball;
public readonly SkinnableDrawable Body;
+ public override bool DisplayResult => false;
+
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container headContainer;
private readonly Container tailContainer;
private readonly Container tickContainer;
- private readonly Container repeatContainer;
+ private readonly Container repeatContainer;
private readonly Slider slider;
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
- repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
+ repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
{
GetInitialHitAction = () => HeadCircle.HitAction,
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tickContainer.Add(tick);
break;
- case DrawableRepeatPoint repeat:
+ case DrawableSliderRepeat repeat:
repeatContainer.Add(repeat);
break;
}
@@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
- case RepeatPoint repeat:
- return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
+ case SliderRepeat repeat:
+ return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - slider.Position };
}
return base.CreateNestedHitObject(hitObject);
@@ -193,22 +193,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (userTriggered || Time.Current < slider.EndTime)
return;
- ApplyResult(r =>
- {
- var judgementsCount = NestedHitObjects.Count;
- var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
-
- var hitFraction = (double)judgementsHit / judgementsCount;
-
- if (hitFraction == 1 && HeadCircle.Result.Type == HitResult.Great)
- r.Type = HitResult.Great;
- else if (hitFraction >= 0.5 && HeadCircle.Result.Type >= HitResult.Good)
- r.Type = HitResult.Good;
- else if (hitFraction > 0)
- r.Type = HitResult.Meh;
- else
- r.Type = HitResult.Miss;
- });
+ ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
protected override void UpdateStateTransforms(ArmedState state)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index 8fdcd060e7..b9cee71ca1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -14,19 +14,19 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableRepeatPoint : DrawableOsuHitObject, ITrackSnaking
+ public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking
{
- private readonly RepeatPoint repeatPoint;
+ private readonly SliderRepeat sliderRepeat;
private readonly DrawableSlider drawableSlider;
private double animDuration;
private readonly Drawable scaleContainer;
- public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
- : base(repeatPoint)
+ public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
+ : base(sliderRepeat)
{
- this.repeatPoint = repeatPoint;
+ this.sliderRepeat = sliderRepeat;
this.drawableSlider = drawableSlider;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
@@ -48,13 +48,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
- if (repeatPoint.StartTime <= Time.Current)
+ if (sliderRepeat.StartTime <= Time.Current)
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss);
}
protected override void UpdateInitialTransforms()
{
- animDuration = Math.Min(300, repeatPoint.SpanDuration);
+ animDuration = Math.Min(300, sliderRepeat.SpanDuration);
this.Animate(
d => d.FadeIn(animDuration),
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
- bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
+ bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index 21a3a0d236..29a4929c1b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!userTriggered && timeOffset >= 0)
- ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
+ ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
}
private void updatePosition() => Position = HitObject.Position - slider.Position;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 60b5c335d6..66eb60aa28 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (timeOffset >= 0)
- ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
+ ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
}
protected override void UpdateInitialTransforms()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index de11ab6419..0ec7f2ebfe 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly SpriteIcon symbol;
- private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c");
- private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c");
+ private readonly Color4 baseColour = Color4Extensions.FromHex(@"002c3c");
+ private readonly Color4 fillColour = Color4Extensions.FromHex(@"005b7c");
private readonly IBindable positionBindable = new Bindable();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index c871089acd..5a6dd49c44 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -16,16 +16,24 @@ using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
+using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
- public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
+ public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
{
public Func GetInitialHitAction;
+ public Color4 AccentColour
+ {
+ get => ball.Colour;
+ set => ball.Colour = value;
+ }
+
private readonly Slider slider;
private readonly Drawable followCircle;
private readonly DrawableSlider drawableSlider;
+ private readonly CircularContainer ball;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
@@ -47,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Alpha = 0,
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
- new CircularContainer
+ ball = new CircularContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index b062fc5afa..0c089c1fed 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
}
- this.RotateTo(currentRotation / 2, 500, Easing.OutExpo);
+ Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
public void Rotate(float angle)
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 77f8ec6cc8..db1f46d8e2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Repeat:
- AddNested(new RepeatPoint
+ AddNested(new SliderRepeat
{
RepeatIndex = e.SpanIndex,
SpanDuration = SpanDuration,
@@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var tick in NestedHitObjects.OfType())
tick.Samples = sampleList;
- foreach (var repeat in NestedHitObjects.OfType())
+ foreach (var repeat in NestedHitObjects.OfType())
repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1);
if (HeadCircle != null)
@@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private IList getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
- public override Judgement CreateJudgement() => new OsuJudgement();
+ public override Judgement CreateJudgement() => new OsuIgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
similarity index 77%
rename from osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
rename to osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
index a277517f9f..a8fd3764c5 100644
--- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
- public class RepeatPoint : OsuHitObject
+ public class SliderRepeat : OsuHitObject
{
public int RepeatIndex { get; set; }
public double SpanDuration { get; set; }
@@ -28,8 +28,15 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = Math.Min(SpanDuration * 2, TimePreempt);
}
- public override Judgement CreateJudgement() => new OsuJudgement();
-
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override Judgement CreateJudgement() => new SliderRepeatJudgement();
+
+ public class SliderRepeatJudgement : OsuJudgement
+ {
+ public override bool IsBonus => true;
+
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 127c36fcc0..c11e20c9e7 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
}
- public override Judgement CreateJudgement() => new IgnoreJudgement();
-
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override Judgement CreateJudgement() => new SliderRepeat.SliderRepeatJudgement();
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index a49f4cef8b..212a84c04a 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -30,8 +30,15 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
}
- public override Judgement CreateJudgement() => new OsuJudgement();
-
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override Judgement CreateJudgement() => new SliderTickJudgement();
+
+ public class SliderTickJudgement : OsuJudgement
+ {
+ public override bool IsBonus => true;
+
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
index a6491bb3f3..6f2998006f 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
new DifficultyRange(HitResult.Great, 80, 50, 20),
new DifficultyRange(HitResult.Good, 140, 100, 60),
new DifficultyRange(HitResult.Meh, 200, 150, 100),
- new DifficultyRange(HitResult.Miss, 200, 200, 200),
+ new DifficultyRange(HitResult.Miss, 400, 400, 400),
};
public override bool IsHitResultAllowed(HitResult result)
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
new file mode 100644
index 0000000000..26c90ad295
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
@@ -0,0 +1,47 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Scoring;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Mods
+{
+ public class TestSceneTaikoModPerfect : ModPerfectTestScene
+ {
+ public TestSceneTaikoModPerfect()
+ : base(new TestTaikoRuleset(), new TaikoModPerfect())
+ {
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestHit(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Hit { StartTime = 1000, Type = HitType.Centre }), shouldMiss);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestDrumRoll(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new DrumRoll { StartTime = 1000, EndTime = 3000 }), shouldMiss);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }), shouldMiss);
+
+ private class TestTaikoRuleset : TaikoRuleset
+ {
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TestTaikoHealthProcessor();
+
+ private class TestTaikoHealthProcessor : TaikoHealthProcessor
+ {
+ protected override void Reset(bool storeResults)
+ {
+ base.Reset(storeResults);
+
+ Health.Value = 1; // Don't care about the health condition (only the mod condition)
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index f23fd6d3f9..8c26ca70ac 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -27,8 +27,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
StartTime = hitObject.StartTime,
EndTime = hitObject.GetEndTime(),
- IsRim = hitObject is RimHit,
- IsCentre = hitObject is CentreHit,
+ IsRim = (hitObject as Hit)?.Type == HitType.Rim,
+ IsCentre = (hitObject as Hit)?.Type == HitType.Centre,
IsDrumRoll = hitObject is DrumRoll,
IsSwell = hitObject is Swell,
IsStrong = ((TaikoHitObject)hitObject).IsStrong
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
index ccacc50de1..303f0163b1 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
@@ -1,23 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneSwellJudgements : PlayerTestScene
{
- protected new TestPlayer Player => (TestPlayer)base.Player;
-
public TestSceneSwellJudgements()
: base(new TaikoRuleset())
{
@@ -49,25 +42,5 @@ namespace osu.Game.Rulesets.Taiko.Tests
return beatmap;
}
-
- protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer();
-
- protected class TestPlayer : Player
- {
- public readonly List Results = new List();
-
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
-
- public TestPlayer()
- : base(false, false)
- {
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- ScoreProcessor.NewJudgement += r => Results.Add(r);
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index c01eef5252..0d9e813c60 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{
- HitObjects = new List { new CentreHit() },
+ HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index 140433a523..2ab041e191 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -4,11 +4,9 @@
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
@@ -22,10 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override bool AllowFail => true;
- protected override Player CreatePlayer(Ruleset ruleset)
+ protected override TestPlayer CreatePlayer(Ruleset ruleset)
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
- return new ScoreAccessiblePlayer();
+ return base.CreatePlayer(ruleset);
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) =>
@@ -49,20 +47,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
AddStep("Setup judgements", () =>
{
judged = false;
- ((ScoreAccessiblePlayer)Player).ScoreProcessor.NewJudgement += b => judged = true;
+ Player.ScoreProcessor.NewJudgement += b => judged = true;
});
AddUntilStep("swell judged", () => judged);
AddAssert("not failed", () => !Player.HasFailed);
}
-
- private class ScoreAccessiblePlayer : TestPlayer
- {
- public ScoreAccessiblePlayer()
- : base(false, false)
- {
- }
-
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index cc9d6e4470..695ada3a00 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -124,24 +124,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
- if (isRim)
+ yield return new Hit
{
- yield return new RimHit
- {
- StartTime = j,
- Samples = currentSamples,
- IsStrong = strong
- };
- }
- else
- {
- yield return new CentreHit
- {
- StartTime = j,
- Samples = currentSamples,
- IsStrong = strong
- };
- }
+ StartTime = j,
+ Type = isRim ? HitType.Rim : HitType.Centre,
+ Samples = currentSamples,
+ IsStrong = strong
+ };
i = (i + 1) % allSamples.Count;
}
@@ -180,24 +169,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
- if (isRim)
+ yield return new Hit
{
- yield return new RimHit
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- IsStrong = strong
- };
- }
- else
- {
- yield return new CentreHit
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- IsStrong = strong
- };
- }
+ StartTime = obj.StartTime,
+ Type = isRim ? HitType.Rim : HitType.Centre,
+ Samples = obj.Samples,
+ IsStrong = strong
+ };
break;
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
index 24345275c1..6807142327 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
: base(hitObject, lastObject, clockRate)
{
- HasTypeChange = lastObject is RimHit != hitObject is RimHit;
+ HasTypeChange = (lastObject as Hit)?.Type != (hitObject as Hit)?.Type;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs
deleted file mode 100644
index a6354b16ed..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class CentreHit : Hit
- {
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index 6cc9357580..2aca701515 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -5,5 +5,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class Hit : TaikoHitObject
{
+ ///
+ /// The that actuates this .
+ ///
+ public HitType Type { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/HitType.cs b/osu.Game.Rulesets.Taiko/Objects/HitType.cs
new file mode 100644
index 0000000000..17b3fdbd04
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Objects/HitType.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Taiko.Objects
+{
+ ///
+ /// The type of a .
+ ///
+ public enum HitType
+ {
+ ///
+ /// A that can be hit by the centre portion of the drum.
+ ///
+ Centre,
+
+ ///
+ /// A that can be hit by the rim portion of the drum.
+ ///
+ Rim
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs
deleted file mode 100644
index 6f6b089e03..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class RimHit : Hit
- {
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 48eb33976e..273f4e4105 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{
TaikoAction[] actions;
- if (hit is CentreHit)
+ if (hit.Type == HitType.Centre)
{
actions = h.IsStrong
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
index edb089dbac..dd3c2289ea 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
@@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
{
base.ApplyBeatmap(beatmap);
- hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
+ hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index 0c7495aa52..9196bbf13e 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -48,11 +48,11 @@ namespace osu.Game.Rulesets.Taiko.UI
{
switch (h)
{
- case CentreHit centreHit:
- return new DrawableCentreHit(centreHit);
-
- case RimHit rimHit:
- return new DrawableRimHit(rimHit);
+ case Hit hit:
+ if (hit.Type == HitType.Centre)
+ return new DrawableCentreHit(hit);
+ else
+ return new DrawableRimHit(hit);
case DrumRoll drumRoll:
return new DrawableDrumRoll(drumRoll);
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index a10f70a344..bde9085c23 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -14,9 +14,9 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
using osuTK.Graphics;
@@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Taiko.UI
if (!result.IsHit)
break;
- bool isRim = judgedObject.HitObject is RimHit;
+ bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 96ff6b81e3..76b76aa357 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -99,7 +99,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var storyboard = decoder.Decode(stream);
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
- Assert.AreEqual(123456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X);
+ Assert.AreEqual(3456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X);
}
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index c1bd73ef05..c6095ae404 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -417,7 +417,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithDuplicateHashes()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes)))
{
try
{
diff --git a/osu.Game.Tests/Resources/variable-with-suffix.osb b/osu.Game.Tests/Resources/variable-with-suffix.osb
index 5c9b46ca98..fd284eb055 100644
--- a/osu.Game.Tests/Resources/variable-with-suffix.osb
+++ b/osu.Game.Tests/Resources/variable-with-suffix.osb
@@ -1,5 +1,5 @@
[Variables]
-$var=1234
+$var=34
[Events]
Sprite,Background,TopCentre,"img.jpg",$var56,240
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 6d014ca1ca..f97aa48f11 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -27,6 +27,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
+using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Tests.Resources;
using osu.Game.Users;
@@ -36,7 +37,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
- public class TestSceneUserDimBackgrounds : ManualInputManagerTestScene
+ public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -49,7 +50,7 @@ namespace osu.Game.Tests.Visual.Background
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
- private TestPlayer player;
+ private LoadBlockingTestPlayer player;
private BeatmapManager manager;
private RulesetStore rulesets;
@@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Background
public void PlayerLoaderSettingsHoverTest()
{
setupUserSettings();
- AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true })));
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true })));
AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
AddStep("Trigger background preview", () =>
@@ -203,7 +204,7 @@ namespace osu.Game.Tests.Visual.Background
}
///
- /// Check if the visual settings container removes user dim when suspending for
+ /// Check if the visual settings container removes user dim when suspending for
///
[Test]
public void TransitionTest()
@@ -268,7 +269,7 @@ namespace osu.Game.Tests.Visual.Background
{
setupUserSettings();
- AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause))));
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer(allowPause))));
AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
@@ -277,6 +278,7 @@ namespace osu.Game.Tests.Visual.Background
private void setupUserSettings()
{
+ AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen());
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null);
AddStep("Set default user settings", () =>
{
@@ -335,7 +337,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
}
- private class FadeAccessibleResults : SoloResults
+ private class FadeAccessibleResults : ResultsScreen
{
public FadeAccessibleResults(ScoreInfo score)
: base(score)
@@ -347,7 +349,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
}
- private class TestPlayer : Visual.TestPlayer
+ private class LoadBlockingTestPlayer : TestPlayer
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
@@ -360,7 +362,7 @@ namespace osu.Game.Tests.Visual.Background
public readonly Bindable ReplacesBackground = new Bindable();
public readonly Bindable IsPaused = new Bindable();
- public TestPlayer(bool allowPause = true)
+ public LoadBlockingTestPlayer(bool allowPause = true)
: base(allowPause)
{
}
diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
index 55aaeed8bf..4d64c7d35d 100644
--- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
+++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
@@ -12,7 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Components
{
[TestFixture]
- public class TestSceneIdleTracker : ManualInputManagerTestScene
+ public class TestSceneIdleTracker : OsuManualInputManagerTestScene
{
private IdleTrackingBox box1;
private IdleTrackingBox box2;
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs
index 7531a7be2c..fd7a5980f3 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs
@@ -3,27 +3,83 @@
using System;
using System.Collections.Generic;
-using osu.Framework.Allocation;
+using System.Linq;
+using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Testing;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Editor
{
- public class TestSceneBeatDivisorControl : OsuTestScene
+ public class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) };
+ private BeatDivisorControl beatDivisorControl;
+ private BindableBeatDivisor bindableBeatDivisor;
- [BackgroundDependencyLoader]
- private void load()
+ private SliderBar tickSliderBar;
+ private EquilateralTriangle tickMarkerHead;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Child = new BeatDivisorControl(new BindableBeatDivisor())
+ Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(90, 90)
};
+
+ tickSliderBar = beatDivisorControl.ChildrenOfType>().Single();
+ tickMarkerHead = tickSliderBar.ChildrenOfType().Single();
+ });
+
+ [Test]
+ public void TestBindableBeatDivisor()
+ {
+ AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 4);
+ AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4);
+ AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 3);
+ AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 12);
+ }
+
+ [Test]
+ public void TestMouseInput()
+ {
+ AddStep("hold marker", () =>
+ {
+ InputManager.MoveMouseTo(tickMarkerHead.ScreenSpaceDrawQuad.Centre);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddStep("move to 8 and release", () =>
+ {
+ InputManager.MoveMouseTo(tickSliderBar.ScreenSpaceDrawQuad.Centre);
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ AddAssert("divisor is 8", () => bindableBeatDivisor.Value == 8);
+ AddStep("hold marker", () => InputManager.PressButton(MouseButton.Left));
+ AddStep("move to 16", () => InputManager.MoveMouseTo(getPositionForDivisor(16)));
+ AddStep("move to ~10 and release", () =>
+ {
+ InputManager.MoveMouseTo(getPositionForDivisor(10));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8);
+ }
+
+ private Vector2 getPositionForDivisor(int divisor)
+ {
+ var relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16;
+ var sliderDrawQuad = tickSliderBar.ScreenSpaceDrawQuad;
+ return new Vector2(
+ sliderDrawQuad.TopLeft.X + sliderDrawQuad.Width * relativePosition,
+ sliderDrawQuad.Centre.Y
+ );
}
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
index fd248abbc9..19d19c2759 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
@@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
- public class TestSceneZoomableScrollContainer : ManualInputManagerTestScene
+ public class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
{
private ZoomableScrollContainer scrollContainer;
private Drawable innerBox;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 4daab8d137..afeda5fb7c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -3,53 +3,32 @@
using System.ComponentModel;
using System.Linq;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
-using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("Player instantiated with an autoplay mod.")]
public class TestSceneAutoplay : TestSceneAllRulesetPlayers
{
- private ClockBackedTestWorkingBeatmap.TrackVirtualManual track;
+ protected new TestPlayer Player => (TestPlayer)base.Player;
protected override Player CreatePlayer(Ruleset ruleset)
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
- return new ScoreAccessiblePlayer();
+ return new TestPlayer(false, false);
}
protected override void AddCheckSteps()
{
- AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
- AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
- AddStep("rewind", () => track.Seek(-10000));
- AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
- }
-
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- {
- var working = base.CreateWorkingBeatmap(beatmap, storyboard);
-
- track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;
-
- return working;
- }
-
- private class ScoreAccessiblePlayer : TestPlayer
- {
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
- public new HUDOverlay HUDOverlay => base.HUDOverlay;
-
- public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
-
- public ScoreAccessiblePlayer()
- : base(false, false)
- {
- }
+ AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
+ AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
+ AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime));
+ AddUntilStep("wait for seek to complete", () =>
+ Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
+ AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
+ AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index c1635ffc83..ea3e0c2293 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -18,7 +18,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("player pause/fail screens")]
- public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene
+ public class TestSceneGameplayMenuOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) };
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 78c3b22fb9..310746d179 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -11,12 +10,8 @@ using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
-using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osuTK;
@@ -24,8 +19,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplayRewinding : PlayerTestScene
{
- private RulesetExposingPlayer player => (RulesetExposingPlayer)Player;
-
[Resolved]
private AudioManager audioManager { get; set; }
@@ -48,13 +41,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddUntilStep("wait for track to start running", () => track.IsRunning);
addSeekStep(3000);
- AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
- AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
- AddStep("clear results", () => player.AppliedResults.Clear());
+ AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
+ AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
+ AddStep("clear results", () => Player.Results.Clear());
addSeekStep(0);
- AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
- AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
- AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
+ AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
+ AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
+ AddAssert("no results triggered", () => Player.Results.Count == 0);
}
private void addSeekStep(double time)
@@ -62,13 +55,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep($"seek to {time}", () => track.Seek(time));
// Allow a few frames of lenience
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
- protected override Player CreatePlayer(Ruleset ruleset)
+ protected override TestPlayer CreatePlayer(Ruleset ruleset)
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
- return new RulesetExposingPlayer();
+ return base.CreatePlayer(ruleset);
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
@@ -89,29 +82,5 @@ namespace osu.Game.Tests.Visual.Gameplay
return beatmap;
}
-
- private class RulesetExposingPlayer : Player
- {
- public readonly List AppliedResults = new List();
-
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
-
- public new HUDOverlay HUDOverlay => base.HUDOverlay;
-
- public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
-
- public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
-
- public RulesetExposingPlayer()
- : base(false, false)
- {
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- ScoreProcessor.NewJudgement += r => AppliedResults.Add(r);
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index fc03dc6ed3..c192a7b0e0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -15,7 +15,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestSceneHUDOverlay : ManualInputManagerTestScene
+ public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
private HUDOverlay hudOverlay;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
index 0c5ead10cf..235842acc9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
@@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("'Hold to Quit' UI element")]
- public class TestSceneHoldForMenuButton : ManualInputManagerTestScene
+ public class TestSceneHoldForMenuButton : OsuManualInputManagerTestScene
{
private bool exitAction;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
index e7b3e007fc..593dcd245c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
@@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneKeyCounter : ManualInputManagerTestScene
+ public class TestSceneKeyCounter : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -47,21 +47,22 @@ namespace osu.Game.Tests.Visual.Gameplay
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
- AddStep($"Press {testKey} key", () =>
+ void addPressKeyStep()
{
- InputManager.PressKey(testKey);
- InputManager.ReleaseKey(testKey);
- });
+ AddStep($"Press {testKey} key", () =>
+ {
+ InputManager.PressKey(testKey);
+ InputManager.ReleaseKey(testKey);
+ });
+ }
+ addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
-
- AddStep($"Press {testKey} key", () =>
- {
- InputManager.PressKey(testKey);
- InputManager.ReleaseKey(testKey);
- });
-
+ addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
+ AddStep("Disable counting", () => testCounter.IsCounting = false);
+ addPressKeyStep();
+ AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
Add(kc);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index ad5bab4681..944e6ca6be 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -11,7 +11,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Input;
@@ -282,14 +281,10 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool AllowFail => true;
- protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer();
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer();
protected class PausePlayer : TestPlayer
{
- public new HealthProcessor HealthProcessor => base.HealthProcessor;
-
- public new HUDOverlay HUDOverlay => base.HUDOverlay;
-
public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible;
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
index 3513b6c25a..a83320048b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
@@ -9,15 +9,12 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : PlayerTestScene
{
- protected new TestPlayer Player => (TestPlayer)base.Player;
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
@@ -46,6 +43,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
}
- protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 100f99d130..4c73065087 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -9,7 +9,6 @@ using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
@@ -30,7 +29,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestScenePlayerLoader : ManualInputManagerTestScene
+ public class TestScenePlayerLoader : OsuManualInputManagerTestScene
{
private TestPlayerLoader loader;
private TestPlayerLoaderContainer container;
@@ -307,17 +306,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
}
- private class TestPlayer : Visual.TestPlayer
- {
- public new Bindable> Mods => base.Mods;
-
- public TestPlayer(bool allowPause = true, bool showResults = true)
- : base(allowPause, showResults)
- {
- }
- }
-
- protected class SlowLoadPlayer : Visual.TestPlayer
+ protected class SlowLoadPlayer : TestPlayer
{
public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index 8cb44de8cb..c9561a70fa 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -11,7 +11,7 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets;
-using osu.Game.Screens.Ranking.Pages;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
index ffd6f55b53..030d420ec0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
@@ -3,9 +3,6 @@
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Utils;
-using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
using osuTK;
@@ -45,32 +42,12 @@ namespace osu.Game.Tests.Visual.Gameplay
};
Add(accuracyCounter);
- StarCounter stars = new StarCounter
- {
- Origin = Anchor.BottomLeft,
- Anchor = Anchor.BottomLeft,
- Position = new Vector2(20, -160),
- CountStars = 5,
- };
- Add(stars);
-
- SpriteText starsLabel = new OsuSpriteText
- {
- Origin = Anchor.BottomLeft,
- Anchor = Anchor.BottomLeft,
- Position = new Vector2(20, -190),
- Text = stars.CountStars.ToString("0.00"),
- };
- Add(starsLabel);
-
AddStep(@"Reset all", delegate
{
score.Current.Value = 0;
comboCounter.Current.Value = 0;
numerator = denominator = 0;
accuracyCounter.SetFraction(0, 0);
- stars.CountStars = 0;
- starsLabel.Text = stars.CountStars.ToString("0.00");
});
AddStep(@"Hit! :D", delegate
@@ -88,20 +65,6 @@ namespace osu.Game.Tests.Visual.Gameplay
denominator++;
accuracyCounter.SetFraction(numerator, denominator);
});
-
- AddStep(@"Alter stars", delegate
- {
- stars.CountStars = RNG.NextSingle() * (stars.StarCount + 1);
- starsLabel.Text = stars.CountStars.ToString("0.00");
- });
-
- AddStep(@"Stop counters", delegate
- {
- score.StopRolling();
- comboCounter.StopRolling();
- accuracyCounter.StopRolling();
- stars.StopAnimation();
- });
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 4c5c18f38a..6a0f86fe53 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -14,7 +14,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneSkipOverlay : ManualInputManagerTestScene
+ public class TestSceneSkipOverlay : OsuManualInputManagerTestScene
{
private SkipOverlay skip;
private int requestCount;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs
new file mode 100644
index 0000000000..709e71d195
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs
@@ -0,0 +1,57 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [TestFixture]
+ public class TestSceneStarCounter : OsuTestScene
+ {
+ public TestSceneStarCounter()
+ {
+ StarCounter stars = new StarCounter
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Current = 5,
+ };
+
+ Add(stars);
+
+ SpriteText starsLabel = new OsuSpriteText
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Scale = new Vector2(2),
+ Y = 50,
+ Text = stars.Current.ToString("0.00"),
+ };
+
+ Add(starsLabel);
+
+ AddRepeatStep(@"random value", delegate
+ {
+ stars.Current = RNG.NextSingle() * (stars.StarCount + 1);
+ starsLabel.Text = stars.Current.ToString("0.00");
+ }, 10);
+
+ AddStep(@"Stop animation", delegate
+ {
+ stars.StopAnimation();
+ });
+
+ AddStep(@"Reset", delegate
+ {
+ stars.Current = 0;
+ starsLabel.Text = stars.Current.ToString("0.00");
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
index 681bf1b40b..49fab08ded 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Menus
API.LocalUser.Value = new User
{
Username = API.LocalUser.Value.Username,
- Id = API.LocalUser.Value.Id,
+ Id = API.LocalUser.Value.Id + 1,
IsSupporter = !API.LocalUser.Value.IsSupporter,
};
});
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
similarity index 77%
rename from osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs
rename to osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
index 61fed3013e..b3064ba9be 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
@@ -1,12 +1,15 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
+using osu.Framework.Testing;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
@@ -14,14 +17,14 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
- public class TestSceneLoaderAnimation : ScreenTestScene
+ public class TestSceneLoader : ScreenTestScene
{
private TestLoader loader;
[Cached]
private OsuLogo logo;
- public TestSceneLoaderAnimation()
+ public TestSceneLoader()
{
Child = logo = new OsuLogo
{
@@ -42,33 +45,33 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader);
});
+
+ AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0);
+
+ AddUntilStep("loaded", () => loader.ScreenLoaded);
+ AddUntilStep("not current", () => !loader.IsCurrentScreen());
}
[Test]
public void TestDelayedLoad()
{
AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
- AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0);
+ AddUntilStep("wait for spinner visible", () => loader.LoadingSpinner?.Alpha > 0);
AddStep("finish loading", () => loader.AllowLoad.Set());
- AddUntilStep("loaded", () => loader.Logo != null && loader.ScreenLoaded);
- AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
+ AddUntilStep("spinner gone", () => loader.LoadingSpinner?.Alpha == 0);
+ AddUntilStep("loaded", () => loader.ScreenLoaded);
+ AddUntilStep("not current", () => !loader.IsCurrentScreen());
}
private class TestLoader : Loader
{
public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim();
- public OsuLogo Logo;
+ public LoadingSpinner LoadingSpinner => this.ChildrenOfType().FirstOrDefault();
private TestScreen screen;
public bool ScreenLoaded => screen.IsCurrentScreen();
- protected override void LogoArriving(OsuLogo logo, bool resuming)
- {
- Logo = logo;
- base.LogoArriving(logo, resuming);
- }
-
protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen();
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(AllowLoad);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 9fbe8f7ffe..713ba13439 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -20,7 +20,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneDrawableRoomPlaylist : ManualInputManagerTestScene
+ public class TestSceneDrawableRoomPlaylist : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs
deleted file mode 100644
index 58e9240026..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Game.Beatmaps;
-using osu.Game.Online.API;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Scoring;
-using osu.Game.Screens.Multi.Match.Components;
-using osu.Game.Screens.Multi.Ranking;
-using osu.Game.Screens.Multi.Ranking.Pages;
-using osu.Game.Screens.Multi.Ranking.Types;
-using osu.Game.Screens.Ranking;
-using osu.Game.Users;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public class TestSceneMatchResults : MultiplayerTestScene
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(MatchResults),
- typeof(RoomLeaderboardPageInfo),
- typeof(RoomLeaderboardPage)
- };
-
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
- if (beatmapInfo != null)
- Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
-
- Room.RoomID.Value = 1;
- Room.Name.Value = "an awesome room";
-
- LoadScreen(new TestMatchResults(new ScoreInfo
- {
- User = new User { Id = 10 },
- }));
- }
-
- private class TestMatchResults : MatchResults
- {
- public TestMatchResults(ScoreInfo score)
- : base(score)
- {
- }
-
- protected override IEnumerable CreateResultPages() => new[] { new TestRoomLeaderboardPageInfo(Score, Beatmap.Value) };
- }
-
- private class TestRoomLeaderboardPageInfo : RoomLeaderboardPageInfo
- {
- private readonly ScoreInfo score;
- private readonly WorkingBeatmap beatmap;
-
- public TestRoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap)
- : base(score, beatmap)
- {
- this.score = score;
- this.beatmap = beatmap;
- }
-
- public override ResultsPage CreatePage() => new TestRoomLeaderboardPage(score, beatmap);
- }
-
- private class TestRoomLeaderboardPage : RoomLeaderboardPage
- {
- public TestRoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap)
- : base(score, beatmap)
- {
- }
-
- protected override MatchLeaderboard CreateLeaderboard() => new TestMatchLeaderboard();
- }
-
- private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard
- {
- protected override APIRequest FetchScores(Action> scoresCallback)
- {
- var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray();
-
- scoresCallback?.Invoke(scores);
- ScoresLoaded?.Invoke(scores);
-
- return null;
- }
-
- private APIUserScoreAggregate createRoomScore(int id) => new APIUserScoreAggregate
- {
- User = new User { Id = id, Username = $"User {id}" },
- Accuracy = 0.98,
- TotalScore = 987654,
- TotalAttempts = 13,
- CompletedBeatmaps = 5
- };
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index 70d71d0952..31afce86ae 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Navigation
///
/// A scene which tests full game flow.
///
- public abstract class OsuGameTestScene : ManualInputManagerTestScene
+ public abstract class OsuGameTestScene : OsuManualInputManagerTestScene
{
private GameHost host;
@@ -62,14 +62,7 @@ namespace osu.Game.Tests.Visual.Navigation
var frameworkConfig = host.Dependencies.Get();
frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).Disabled = false;
- Game = new TestOsuGame(LocalStorage, API);
- Game.SetHost(host);
-
- // todo: this can be removed once we can run audio tracks without a device present
- // see https://github.com/ppy/osu/issues/1302
- Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
-
- Add(Game);
+ CreateGame();
});
AddUntilStep("Wait for load", () => Game.IsLoaded);
@@ -78,6 +71,18 @@ namespace osu.Game.Tests.Visual.Navigation
ConfirmAtMainMenu();
}
+ protected void CreateGame()
+ {
+ Game = new TestOsuGame(LocalStorage, API);
+ Game.SetHost(host);
+
+ // todo: this can be removed once we can run audio tracks without a device present
+ // see https://github.com/ppy/osu/issues/1302
+ Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
+
+ Add(Game);
+ }
+
protected void PushAndConfirm(Func newScreen)
{
Screen screen = null;
@@ -97,12 +102,17 @@ namespace osu.Game.Tests.Visual.Navigation
public new SettingsPanel Settings => base.Settings;
+ public new MusicController MusicController => base.MusicController;
+
public new OsuConfigManager LocalConfig => base.LocalConfig;
public new Bindable Beatmap => base.Beatmap;
public new Bindable Ruleset => base.Ruleset;
+ // if we don't do this, when running under nUnit the version that gets populated is that of nUnit.
+ public override string Version => "test game";
+
protected override Loader CreateLoader() => new TestLoader();
public new void PerformFromScreen(Action action, IEnumerable validScreens = null) => base.PerformFromScreen(action, validScreens);
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index 8258cc9465..9d603ac471 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -114,6 +114,22 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden);
}
+ [Test]
+ public void TestWaitForNextTrackInMenu()
+ {
+ bool trackCompleted = false;
+
+ AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded);
+ AddStep("Seek close to end", () =>
+ {
+ Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000);
+ Game.Beatmap.Value.Track.Completed += () => trackCompleted = true;
+ });
+
+ AddUntilStep("Track was completed", () => trackCompleted);
+ AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning);
+ }
+
private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape));
diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
new file mode 100644
index 0000000000..c0b77b580e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
+
+namespace osu.Game.Tests.Visual.Navigation
+{
+ public class TestSettingsMigration : OsuGameTestScene
+ {
+ public override void RecycleLocalStorage()
+ {
+ base.RecycleLocalStorage();
+
+ using (var config = new OsuConfigManager(LocalStorage))
+ {
+ config.Set(OsuSetting.Version, "2020.101.0");
+ config.Set(OsuSetting.DisplayStarsMaximum, 10.0);
+ }
+ }
+
+ [Test]
+ public void TestDisplayStarsMigration()
+ {
+ AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10.1));
+
+ AddStep("set value again", () => Game.LocalConfig.Set(OsuSetting.DisplayStarsMaximum, 10));
+
+ AddStep("force save config", () => Game.LocalConfig.Save());
+
+ AddStep("remove game", () => Remove(Game));
+
+ AddStep("create game again", CreateGame);
+
+ AddUntilStep("Wait for load", () => Game.IsLoaded);
+
+ AddAssert("config did not migrate value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index 31eab7f74e..a53a818065 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online
API.Logout();
localUser = API.LocalUser.GetBoundCopy();
- localUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
+ localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true);
AddStep("logout", API.Logout);
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index 7a8570c09b..864fd31a0f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -17,8 +17,8 @@ namespace osu.Game.Tests.Visual.Online
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(UpdateStreamBadgeArea),
- typeof(UpdateStreamBadge),
+ typeof(ChangelogUpdateStreamControl),
+ typeof(ChangelogUpdateStreamItem),
typeof(ChangelogHeader),
typeof(ChangelogContent),
typeof(ChangelogListing),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
index a1c77e2db0..7a257a1603 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
@@ -7,11 +7,11 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount);
AddAssert($"msg #{index} has the right action", hasExpectedActions);
- AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
+ //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks);
bool hasExpectedActions()
@@ -96,13 +96,13 @@ namespace osu.Game.Tests.Visual.Online
return true;
}
- bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics);
+ //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics);
bool isShowingLinks()
{
bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour);
- Color4 textColour = isAction && hasBackground ? OsuColour.FromHex(newLine.Message.Sender.Colour) : Color4.White;
+ Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White;
var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 19bdaff6ff..14924dda21 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -22,7 +22,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
- public class TestSceneChatOverlay : ManualInputManagerTestScene
+ public class TestSceneChatOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -38,8 +38,13 @@ namespace osu.Game.Tests.Visual.Online
private TestChatOverlay chatOverlay;
private ChannelManager channelManager;
+ private IEnumerable visibleChannels => chatOverlay.ChannelTabControl.VisibleItems.Where(channel => channel.Name != "+");
+ private IEnumerable joinedChannels => chatOverlay.ChannelTabControl.Items.Where(channel => channel.Name != "+");
private readonly List channels;
+ private Channel currentChannel => channelManager.CurrentChannel.Value;
+ private Channel nextChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) + 1);
+ private Channel previousChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) - 1);
private Channel channel1 => channels[0];
private Channel channel2 => channels[1];
@@ -49,7 +54,8 @@ namespace osu.Game.Tests.Visual.Online
.Select(index => new Channel(new User())
{
Name = $"Channel no. {index}",
- Topic = index == 3 ? null : $"We talk about the number {index} here"
+ Topic = index == 3 ? null : $"We talk about the number {index} here",
+ Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary
})
.ToList();
}
@@ -91,32 +97,15 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
- AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
+ AddAssert("Current channel is channel 1", () => currentChannel == channel1);
AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
- [Test]
- public void TestCloseChannelWhileSelectorClosed()
- {
- AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
- AddStep("Join channel 2", () => channelManager.JoinChannel(channel2));
-
- AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
- AddStep("Close channel 2", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
-
- AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
- AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
-
- AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
-
- AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
- }
-
[Test]
public void TestSearchInSelector()
{
- AddStep("search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2");
- AddUntilStep("only channel 2 visible", () =>
+ AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2");
+ AddUntilStep("Only channel 2 visible", () =>
{
var listItems = chatOverlay.ChildrenOfType().Where(c => c.IsPresent);
return listItems.Count() == 1 && listItems.Single().Channel == channel2;
@@ -126,24 +115,116 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestChannelShortcutKeys()
{
- AddStep("join 10 channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel)));
- AddStep("close channel selector", () =>
+ AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel)));
+ AddStep("Close channel selector", () =>
{
InputManager.PressKey(Key.Escape);
InputManager.ReleaseKey(Key.Escape);
});
- AddUntilStep("wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
+ AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex)
{
var oneBasedIndex = zeroBasedIndex + 1;
var targetNumberKey = oneBasedIndex % 10;
var targetChannel = channels[zeroBasedIndex];
- AddStep($"press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey));
- AddAssert($"channel #{oneBasedIndex} is selected", () => channelManager.CurrentChannel.Value == targetChannel);
+ AddStep($"Press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey));
+ AddAssert($"Channel #{oneBasedIndex} is selected", () => currentChannel == targetChannel);
}
}
+ private Channel expectedChannel;
+
+ [Test]
+ public void TestCloseChannelBehaviour()
+ {
+ AddUntilStep("Join until dropdown has channels", () =>
+ {
+ if (visibleChannels.Count() < joinedChannels.Count())
+ return true;
+
+ // Using temporary channels because they don't hide their names when not active
+ channelManager.JoinChannel(new Channel
+ {
+ Name = $"Channel no. {joinedChannels.Count() + 11}",
+ Type = ChannelType.Temporary
+ });
+
+ return false;
+ });
+
+ AddStep("Switch to last tab", () => clickDrawable(chatOverlay.TabMap[visibleChannels.Last()]));
+ AddAssert("Last visible selected", () => currentChannel == visibleChannels.Last());
+
+ // Closing the last channel before dropdown
+ AddStep("Close current channel", () =>
+ {
+ expectedChannel = nextChannel;
+ chatOverlay.ChannelTabControl.RemoveChannel(currentChannel);
+ });
+ AddAssert("Next channel selected", () => currentChannel == expectedChannel);
+ AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
+
+ // Depending on the window size, one more channel might need to be closed for the selectorTab to appear
+ AddUntilStep("Close channels until selector visible", () =>
+ {
+ if (chatOverlay.ChannelTabControl.VisibleItems.Last().Name == "+")
+ return true;
+
+ chatOverlay.ChannelTabControl.RemoveChannel(visibleChannels.Last());
+ return false;
+ });
+ AddAssert("Last visible selected", () => currentChannel == visibleChannels.Last());
+
+ // Closing the last channel with dropdown no longer present
+ AddStep("Close last when selector next", () =>
+ {
+ expectedChannel = previousChannel;
+ chatOverlay.ChannelTabControl.RemoveChannel(currentChannel);
+ });
+ AddAssert("Previous channel selected", () => currentChannel == expectedChannel);
+
+ // Standard channel closing
+ AddStep("Switch to previous channel", () => chatOverlay.ChannelTabControl.SwitchTab(-1));
+ AddStep("Close current channel", () =>
+ {
+ expectedChannel = nextChannel;
+ chatOverlay.ChannelTabControl.RemoveChannel(currentChannel);
+ });
+ AddAssert("Next channel selected", () => currentChannel == expectedChannel);
+
+ // Selector reappearing after all channels closed
+ AddUntilStep("Close all channels", () =>
+ {
+ if (!joinedChannels.Any())
+ return true;
+
+ chatOverlay.ChannelTabControl.RemoveChannel(joinedChannels.Last());
+ return false;
+ });
+ AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
+ }
+
+ [Test]
+ public void TestChannelCloseButton()
+ {
+ AddStep("Join 2 channels", () =>
+ {
+ channelManager.JoinChannel(channel1);
+ channelManager.JoinChannel(channel2);
+ });
+
+ // PM channel close button only appears when active
+ AddStep("Select PM channel", () => clickDrawable(chatOverlay.TabMap[channel2]));
+ AddStep("Click PM close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
+ AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(channel2));
+
+ // Non-PM chat channel close button only appears when hovered
+ AddStep("Hover normal channel tab", () => InputManager.MoveMouseTo(chatOverlay.TabMap[channel1]));
+ AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
+ AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any());
+ }
+
private void pressChannelHotkey(int number)
{
var channelKey = Key.Number0 + number;
@@ -187,6 +268,8 @@ namespace osu.Game.Tests.Visual.Online
{
public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value;
+ public new ChannelTabControl ChannelTabControl => base.ChannelTabControl;
+
public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay;
protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl();
@@ -196,12 +279,22 @@ namespace osu.Game.Tests.Visual.Online
private class TestTabControl : ChannelTabControl
{
- protected override TabItem CreateTabItem(Channel value) => new TestChannelTabItem(value);
+ protected override TabItem CreateTabItem(Channel value)
+ {
+ switch (value.Type)
+ {
+ case ChannelType.PM:
+ return new TestPrivateChannelTabItem(value);
+
+ default:
+ return new TestChannelTabItem(value);
+ }
+ }
public new IReadOnlyDictionary> TabMap => base.TabMap;
}
- private class TestChannelTabItem : PrivateChannelTabItem
+ private class TestChannelTabItem : ChannelTabItem
{
public TestChannelTabItem(Channel channel)
: base(channel)
@@ -210,5 +303,15 @@ namespace osu.Game.Tests.Visual.Online
public new ClickableContainer CloseButton => base.CloseButton;
}
+
+ private class TestPrivateChannelTabItem : PrivateChannelTabItem
+ {
+ public TestPrivateChannelTabItem(Channel channel)
+ : base(channel)
+ {
+ }
+
+ public new ClickableContainer CloseButton => base.CloseButton;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
new file mode 100644
index 0000000000..cf365a7614
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -0,0 +1,87 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Overlays.Dashboard.Friends;
+using osu.Framework.Graphics;
+using osu.Game.Users;
+using osu.Game.Overlays;
+using osu.Framework.Allocation;
+using NUnit.Framework;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneFriendDisplay : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(FriendDisplay),
+ typeof(FriendOnlineStreamControl),
+ typeof(UserListToolbar)
+ };
+
+ protected override bool UseOnlineAPI => true;
+
+ [Cached]
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+
+ private FriendDisplay display;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = new BasicScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = display = new FriendDisplay()
+ };
+ });
+
+ [Test]
+ public void TestOffline()
+ {
+ AddStep("Populate", () => display.Users = getUsers());
+ }
+
+ [Test]
+ public void TestOnline()
+ {
+ AddStep("Fetch online", () => display?.Fetch());
+ }
+
+ private List getUsers() => new List
+ {
+ new User
+ {
+ Username = "flyte",
+ Id = 3103765,
+ IsOnline = true,
+ CurrentModeRank = 1111,
+ Country = new Country { FlagName = "JP" },
+ CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
+ },
+ new User
+ {
+ Username = "peppy",
+ Id = 2,
+ IsOnline = false,
+ CurrentModeRank = 2222,
+ Country = new Country { FlagName = "AU" },
+ CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ IsSupporter = true,
+ SupportLevel = 3,
+ },
+ new User
+ {
+ Username = "Evast",
+ Id = 8195163,
+ Country = new Country { FlagName = "BY" },
+ CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
+ IsOnline = false,
+ LastVisit = DateTimeOffset.Now
+ }
+ };
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
index dbd7544b38..24341cbd05 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
@@ -18,10 +18,9 @@ namespace osu.Game.Tests.Visual.Online
public override IReadOnlyList RequiredTypes => new[]
{
typeof(UserPanel),
- typeof(SocialPanel),
typeof(FilterControl),
- typeof(SocialGridPanel),
- typeof(SocialListPanel)
+ typeof(UserGridPanel),
+ typeof(UserListPanel)
};
public TestSceneSocialOverlay()
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index 54f06d6ad2..ccae778745 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -1,10 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
@@ -13,28 +17,44 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserPanel : OsuTestScene
{
- private readonly UserPanel peppy;
-
- public TestSceneUserPanel()
+ public override IReadOnlyList RequiredTypes => new[]
{
- UserPanel flyte;
+ typeof(UserPanel),
+ typeof(UserListPanel),
+ typeof(UserGridPanel),
+ };
- Add(new FillFlowContainer
+ private readonly Bindable activity = new Bindable();
+ private readonly Bindable status = new Bindable();
+
+ private UserGridPanel peppy;
+ private UserListPanel evast;
+
+ [Resolved]
+ private RulesetStore rulesetStore { get; set; }
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ UserGridPanel flyte;
+
+ Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- AutoSizeAxes = Axes.Both,
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
Spacing = new Vector2(10f),
- Children = new[]
+ Children = new Drawable[]
{
- flyte = new UserPanel(new User
+ flyte = new UserGridPanel(new User
{
Username = @"flyte",
Id = 3103765,
Country = new Country { FlagName = @"JP" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
}) { Width = 300 },
- peppy = new UserPanel(new User
+ peppy = new UserGridPanel(new User
{
Username = @"peppy",
Id = 2,
@@ -43,35 +63,52 @@ namespace osu.Game.Tests.Visual.Online
IsSupporter = true,
SupportLevel = 3,
}) { Width = 300 },
+ evast = new UserListPanel(new User
+ {
+ Username = @"Evast",
+ Id = 8195163,
+ Country = new Country { FlagName = @"BY" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
+ IsOnline = false,
+ LastVisit = DateTimeOffset.Now
+ })
},
- });
+ };
flyte.Status.Value = new UserStatusOnline();
- peppy.Status.Value = null;
- }
-
- [Test]
- public void UserStatusesTests()
- {
- AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); });
- AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); });
- AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); });
- AddStep(@"null status", () => { peppy.Status.Value = null; });
- }
-
- [Test]
- public void UserActivitiesTests()
- {
- Bindable activity = new Bindable();
+ peppy.Status.BindTo(status);
peppy.Activity.BindTo(activity);
- AddStep("idle", () => { activity.Value = null; });
- AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); });
- AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); });
- AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); });
- AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); });
- AddStep("modding", () => { activity.Value = new UserActivity.Modding(); });
+ evast.Status.BindTo(status);
+ evast.Activity.BindTo(activity);
+ });
+
+ [Test]
+ public void TestUserStatus()
+ {
+ AddStep("online", () => status.Value = new UserStatusOnline());
+ AddStep("do not disturb", () => status.Value = new UserStatusDoNotDisturb());
+ AddStep("offline", () => status.Value = new UserStatusOffline());
+ AddStep("null status", () => status.Value = null);
}
+
+ [Test]
+ public void TestUserActivity()
+ {
+ AddStep("set online status", () => peppy.Status.Value = evast.Status.Value = new UserStatusOnline());
+
+ AddStep("idle", () => activity.Value = null);
+ AddStep("spectating", () => activity.Value = new UserActivity.Spectating());
+ AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0));
+ AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1));
+ AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2));
+ AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3));
+ AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap());
+ AddStep("editing", () => activity.Value = new UserActivity.Editing(null));
+ AddStep("modding", () => activity.Value = new UserActivity.Modding());
+ }
+
+ private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId));
}
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
new file mode 100644
index 0000000000..0781cba924
--- /dev/null
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -0,0 +1,165 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Ranking.Expanded.Accuracy;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Ranking
+{
+ public class TestSceneAccuracyCircle : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(AccuracyCircle),
+ typeof(RankBadge),
+ typeof(RankNotch),
+ typeof(RankText),
+ typeof(SmoothCircularProgress)
+ };
+
+ [Test]
+ public void TestLowDRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.2;
+ score.Rank = ScoreRank.D;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestDRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.5;
+ score.Rank = ScoreRank.D;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestCRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.75;
+ score.Rank = ScoreRank.C;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestBRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.85;
+ score.Rank = ScoreRank.B;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestARank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.925;
+ score.Rank = ScoreRank.A;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.975;
+ score.Rank = ScoreRank.S;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestAlmostSSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.9999;
+ score.Rank = ScoreRank.S;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestSSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 1;
+ score.Rank = ScoreRank.X;
+
+ addCircleStep(score);
+ }
+
+ private void addCircleStep(ScoreInfo score) => AddStep("add panel", () =>
+ {
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(500, 700),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333"))
+ }
+ }
+ },
+ new AccuracyCircle(score)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(230)
+ }
+ };
+ });
+
+ private ScoreInfo createScore() => new ScoreInfo
+ {
+ User = new User
+ {
+ Id = 2,
+ Username = "peppy",
+ },
+ Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
+ Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
+ TotalScore = 2845370,
+ Accuracy = 0.95,
+ MaxCombo = 999,
+ Rank = ScoreRank.S,
+ Date = DateTimeOffset.Now,
+ Statistics =
+ {
+ { HitResult.Miss, 1 },
+ { HitResult.Meh, 50 },
+ { HitResult.Good, 100 },
+ { HitResult.Great, 300 },
+ }
+ };
+ }
+}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
new file mode 100644
index 0000000000..52d8ea0480
--- /dev/null
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
@@ -0,0 +1,130 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Ranking.Expanded;
+using osu.Game.Screens.Ranking.Expanded.Accuracy;
+using osu.Game.Screens.Ranking.Expanded.Statistics;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Ranking
+{
+ public class TestSceneExpandedPanelMiddleContent : OsuTestScene
+ {
+ [Resolved]
+ private RulesetStore rulesetStore { get; set; }
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ExpandedPanelMiddleContent),
+ typeof(AccuracyCircle),
+ typeof(AccuracyStatistic),
+ typeof(ComboStatistic),
+ typeof(CounterStatistic),
+ typeof(StarRatingDisplay),
+ typeof(StatisticDisplay),
+ typeof(TotalScoreCounter)
+ };
+
+ [Test]
+ public void TestMapWithKnownMapper()
+ {
+ var author = new User { Username = "mapper_name" };
+
+ AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore()));
+
+ AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name"));
+ }
+
+ [Test]
+ public void TestMapWithUnknownMapper()
+ {
+ AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore()));
+
+ AddAssert("mapped by text not present", () =>
+ this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
+ }
+
+ private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
+ {
+ Child = new ExpandedPanelMiddleContentContainer(workingBeatmap, score);
+ }
+
+ private WorkingBeatmap createTestBeatmap(User author)
+ {
+ var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0));
+ beatmap.Metadata.Author = author;
+
+ return new TestWorkingBeatmap(beatmap);
+ }
+
+ private ScoreInfo createTestScore() => new ScoreInfo
+ {
+ User = new User
+ {
+ Id = 2,
+ Username = "peppy",
+ },
+ Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
+ Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
+ TotalScore = 999999,
+ Accuracy = 0.95,
+ MaxCombo = 999,
+ Rank = ScoreRank.S,
+ Date = DateTimeOffset.Now,
+ Statistics =
+ {
+ { HitResult.Miss, 1 },
+ { HitResult.Meh, 50 },
+ { HitResult.Good, 100 },
+ { HitResult.Great, 300 },
+ }
+ };
+
+ private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains);
+
+ private class ExpandedPanelMiddleContentContainer : Container
+ {
+ [Cached]
+ private Bindable workingBeatmap { get; set; }
+
+ public ExpandedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score)
+ {
+ workingBeatmap = new Bindable(beatmap);
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ Size = new Vector2(500, 700);
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#444"),
+ },
+ new ExpandedPanelMiddleContent(score)
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs
new file mode 100644
index 0000000000..afaa607099
--- /dev/null
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Screens.Ranking.Expanded;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Ranking
+{
+ public class TestSceneExpandedPanelTopContent : OsuTestScene
+ {
+ public TestSceneExpandedPanelTopContent()
+ {
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(500, 200),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#444"),
+ },
+ new ExpandedPanelTopContent(new User { Id = 2, Username = "peppy" }),
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
similarity index 91%
rename from osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs
rename to osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index 2b7a32ba17..bd5b039bc1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -10,29 +10,27 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
-using osu.Game.Screens.Ranking.Pages;
+using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
-namespace osu.Game.Tests.Visual.Gameplay
+namespace osu.Game.Tests.Visual.Ranking
{
[TestFixture]
- public class TestSceneResults : ScreenTestScene
+ public class TestSceneResultsScreen : ScreenTestScene
{
private BeatmapManager beatmaps;
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(Results),
- typeof(ResultsPage),
- typeof(ScoreResultsPage),
+ typeof(ResultsScreen),
typeof(RetryButton),
typeof(ReplayDownloadButton),
- typeof(LocalLeaderboardPage),
typeof(TestPlayer)
};
@@ -65,6 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
+ Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
User = new User
{
Username = "peppy",
@@ -119,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
- private class TestSoloResults : SoloResults
+ private class TestSoloResults : ResultsScreen
{
public HotkeyRetryOverlay RetryOverlay;
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
new file mode 100644
index 0000000000..1e55885385
--- /dev/null
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
@@ -0,0 +1,143 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Ranking;
+using osu.Game.Screens.Ranking.Expanded;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Ranking
+{
+ public class TestSceneScorePanel : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ScorePanel),
+ typeof(PanelState),
+ typeof(ExpandedPanelMiddleContent),
+ typeof(ExpandedPanelTopContent),
+ };
+
+ [Test]
+ public void TestDRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.5;
+ score.Rank = ScoreRank.D;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestCRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.75;
+ score.Rank = ScoreRank.C;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestBRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.85;
+ score.Rank = ScoreRank.B;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestARank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.925;
+ score.Rank = ScoreRank.A;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.975;
+ score.Rank = ScoreRank.S;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestAlmostSSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.9999;
+ score.Rank = ScoreRank.S;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestSSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 1;
+ score.Rank = ScoreRank.X;
+
+ addPanelStep(score);
+ }
+
+ [Test]
+ public void TestAllHitResults()
+ {
+ var score = createScore();
+ score.Statistics[HitResult.Perfect] = 350;
+ score.Statistics[HitResult.Ok] = 200;
+
+ addPanelStep(score);
+ }
+
+ private void addPanelStep(ScoreInfo score) => AddStep("add panel", () =>
+ {
+ Child = new ScorePanel(score)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ State = PanelState.Expanded
+ };
+ });
+
+ private ScoreInfo createScore() => new ScoreInfo
+ {
+ User = new User
+ {
+ Id = 2,
+ Username = "peppy",
+ },
+ Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
+ Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
+ TotalScore = 2845370,
+ Accuracy = 0.95,
+ MaxCombo = 999,
+ Rank = ScoreRank.S,
+ Date = DateTimeOffset.Now,
+ Statistics =
+ {
+ { HitResult.Miss, 1 },
+ { HitResult.Meh, 50 },
+ { HitResult.Good, 100 },
+ { HitResult.Great, 300 },
+ }
+ };
+ }
+}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
new file mode 100644
index 0000000000..d12f32e470
--- /dev/null
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Screens.Ranking.Expanded;
+
+namespace osu.Game.Tests.Visual.Ranking
+{
+ public class TestSceneStarRatingDisplay : OsuTestScene
+ {
+ public TestSceneStarRatingDisplay()
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 1.23 }),
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 2.34 }),
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 3.45 }),
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 4.56 }),
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 5.67 }),
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 6.78 }),
+ new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 10.11 }),
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 71ae47dc66..0cc37bbd57 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -227,6 +227,34 @@ namespace osu.Game.Tests.Visual.SongSelect
waitForSelection(set_count);
}
+ [Test]
+ public void TestSelectionEnteringFromEmptyRuleset()
+ {
+ var sets = new List();
+
+ AddStep("Create beatmaps for taiko only", () =>
+ {
+ sets.Clear();
+
+ var rulesetBeatmapSet = createTestBeatmapSet(1);
+ var taikoRuleset = rulesets.AvailableRulesets.ElementAt(1);
+ rulesetBeatmapSet.Beatmaps.ForEach(b =>
+ {
+ b.Ruleset = taikoRuleset;
+ b.RulesetID = 1;
+ });
+
+ sets.Add(rulesetBeatmapSet);
+ });
+
+ loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) });
+
+ AddStep("Set non-empty mode filter", () =>
+ carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false));
+
+ AddAssert("Something is selected", () => carousel.SelectedBeatmap != null);
+ }
+
///
/// Test sorting
///
@@ -399,27 +427,32 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("filter to ruleset 0", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
- AddAssert("unfiltered beatmap selected", () => carousel.SelectedBeatmap.Equals(testMixed.Beatmaps[0]));
+ AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmap.RulesetID == 0);
AddStep("remove mixed set", () =>
{
carousel.RemoveBeatmapSet(testMixed);
testMixed = null;
});
- var testSingle = createTestBeatmapSet(set_count + 2);
- testSingle.Beatmaps.ForEach(b =>
+ BeatmapSetInfo testSingle = null;
+ AddStep("add single ruleset beatmapset", () =>
{
- b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
- b.RulesetID = b.Ruleset.ID ?? 1;
+ testSingle = createTestBeatmapSet(set_count + 2);
+ testSingle.Beatmaps.ForEach(b =>
+ {
+ b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
+ b.RulesetID = b.Ruleset.ID ?? 1;
+ });
+
+ carousel.UpdateBeatmapSet(testSingle);
});
- AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false));
checkNoSelection();
AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle));
}
[Test]
- public void TestCarouselRootIsRandom()
+ public void TestCarouselRemembersSelection()
{
List manySets = new List();
@@ -429,12 +462,74 @@ namespace osu.Game.Tests.Visual.SongSelect
loadBeatmaps(manySets);
advanceSelection(direction: 1, diff: false);
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- AddAssert("Selection was random", () => eagerSelectedIDs.Count > 1);
+
+ for (int i = 0; i < 5; i++)
+ {
+ AddStep("Toggle non-matching filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
+ });
+
+ AddStep("Restore no filter", () =>
+ {
+ carousel.Filter(new FilterCriteria(), false);
+ eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
+ });
+ }
+
+ // always returns to same selection as long as it's available.
+ AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
+ }
+
+ [Test]
+ public void TestRandomFallbackOnNonMatchingPrevious()
+ {
+ List manySets = new List();
+
+ AddStep("populate maps", () =>
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ var set = createTestBeatmapSet(i);
+
+ foreach (var b in set.Beatmaps)
+ {
+ // all taiko except for first
+ int ruleset = i > 0 ? 1 : 0;
+
+ b.Ruleset = rulesets.GetRuleset(ruleset);
+ b.RulesetID = ruleset;
+ }
+
+ manySets.Add(set);
+ }
+ });
+
+ loadBeatmaps(manySets);
+
+ for (int i = 0; i < 10; i++)
+ {
+ AddStep("Reset filter", () => carousel.Filter(new FilterCriteria(), false));
+
+ AddStep("select first beatmap", () => carousel.SelectBeatmap(manySets.First().Beatmaps.First()));
+
+ AddStep("Toggle non-matching filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
+ });
+
+ AddAssert("selection lost", () => carousel.SelectedBeatmap == null);
+
+ AddStep("Restore different ruleset filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false);
+ eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
+ });
+
+ AddAssert("selection changed", () => carousel.SelectedBeatmap != manySets.First().Beatmaps.First());
+ }
+
+ AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2);
}
[Test]
@@ -484,7 +579,7 @@ namespace osu.Game.Tests.Visual.SongSelect
checkVisibleItemCount(true, 15);
}
- private void loadBeatmaps(List beatmapSets = null)
+ private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null)
{
createCarousel();
@@ -497,9 +592,9 @@ namespace osu.Game.Tests.Visual.SongSelect
}
bool changed = false;
- AddStep($"Load {beatmapSets.Count} Beatmaps", () =>
+ AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () =>
{
- carousel.Filter(new FilterCriteria());
+ carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
carousel.BeatmapSetsChanged = () => changed = true;
carousel.BeatmapSets = beatmapSets;
});
@@ -593,16 +688,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Selection is visible", selectedBeatmapVisible);
}
- private void checkNonmatchingFilter()
- {
- AddStep("Toggle non-matching filter", () =>
- {
- carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false);
- carousel.Filter(new FilterCriteria(), false);
- eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
- });
- }
-
private BeatmapSetInfo createTestBeatmapSet(int id)
{
return new BeatmapSetInfo
@@ -697,6 +782,8 @@ namespace osu.Game.Tests.Visual.SongSelect
public new List Items => base.Items;
public bool PendingFilterTask => PendingFilter != null;
+
+ protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty();
}
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 51d302123b..4405c75744 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -283,7 +283,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
- manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait();
+ manager.Import(createTestBeatmapSet(usableRulesets)).Wait();
});
}
else
@@ -436,6 +436,58 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(0);
+ // used for filter check below
+ AddStep("allow convert display", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
+
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+
+ AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono");
+
+ AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
+
+ AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null);
+
+ BeatmapInfo target = null;
+
+ int targetRuleset = differentRuleset ? 1 : 0;
+
+ AddStep("select beatmap externally", () =>
+ {
+ target = manager.GetAllUsableBeatmapSets()
+ .Where(b => b.Beatmaps.Any(bi => bi.RulesetID == targetRuleset))
+ .ElementAt(5).Beatmaps.First(bi => bi.RulesetID == targetRuleset);
+
+ Beatmap.Value = manager.GetWorkingBeatmap(target);
+ });
+
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+
+ AddAssert("selected only shows expected ruleset (plus converts)", () =>
+ {
+ var selectedPanel = songSelect.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected);
+
+ // special case for converts checked here.
+ return selectedPanel.ChildrenOfType().All(i =>
+ i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0);
+ });
+
+ AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID);
+ AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
+
+ AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty);
+
+ AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
+ AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmap.OnlineBeatmapID == target.OnlineBeatmapID);
+ }
+
+ [Test]
+ public void TestExternalBeatmapChangeWhileFilteredThenRefilter()
+ {
+ createSongSelect();
+ addManyTestMaps();
+
+ changeRuleset(0);
+
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono");
@@ -448,7 +500,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select beatmap externally", () =>
{
- target = manager.GetAllUsableBeatmapSets().Where(b => b.Beatmaps.Any(bi => bi.RulesetID == (differentRuleset ? 1 : 0)))
+ target = manager.GetAllUsableBeatmapSets().Where(b => b.Beatmaps.Any(bi => bi.RulesetID == 1))
.ElementAt(5).Beatmaps.First();
Beatmap.Value = manager.GetWorkingBeatmap(target);
@@ -459,10 +511,10 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
- AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty);
+ AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo");
- AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
- AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmap.OnlineBeatmapID == target.OnlineBeatmapID);
+ AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap);
+ AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
[Test]
@@ -520,6 +572,7 @@ namespace osu.Game.Tests.Visual.SongSelect
difficultyIcon = set.ChildrenOfType()
.First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex());
});
+
AddStep("Click on a difficulty", () =>
{
InputManager.MoveMouseTo(difficultyIcon);
@@ -527,6 +580,7 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.PressButton(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
});
+
AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon));
double? maxBPM = null;
@@ -539,16 +593,16 @@ namespace osu.Game.Tests.Visual.SongSelect
}
}));
+ BeatmapInfo filteredBeatmap = null;
DrawableCarouselBeatmapSet.FilterableDifficultyIcon filteredIcon = null;
+
AddStep("Get filtered icon", () =>
{
- var filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.Find(b => b.BPM < maxBPM);
+ filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First(b => b.BPM < maxBPM);
int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap);
filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex);
});
- int? previousID = null;
- AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID);
AddStep("Click on a filtered difficulty", () =>
{
InputManager.MoveMouseTo(filteredIcon);
@@ -556,7 +610,101 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.PressButton(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
});
- AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID);
+
+ AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap);
+ }
+
+ [Test]
+ public void TestDifficultyIconSelectingForDifferentRuleset()
+ {
+ changeRuleset(0);
+
+ createSongSelect();
+
+ AddStep("import multi-ruleset map", () =>
+ {
+ var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
+ manager.Import(createTestBeatmapSet(usableRulesets)).Wait();
+ });
+
+ DrawableCarouselBeatmapSet set = null;
+ AddUntilStep("Find the DrawableCarouselBeatmapSet", () =>
+ {
+ set = songSelect.Carousel.ChildrenOfType().FirstOrDefault();
+ return set != null;
+ });
+
+ DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null;
+ AddStep("Find an icon for different ruleset", () =>
+ {
+ difficultyIcon = set.ChildrenOfType()
+ .First(icon => icon.Item.Beatmap.Ruleset.ID == 3);
+ });
+
+ AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
+
+ int previousSetID = 0;
+
+ AddStep("record set ID", () => previousSetID = Beatmap.Value.BeatmapSetInfo.ID);
+
+ AddStep("Click on a difficulty", () =>
+ {
+ InputManager.MoveMouseTo(difficultyIcon);
+
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+
+ AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
+
+ AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmap.BeatmapSet.ID == previousSetID);
+ AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3);
+ }
+
+ [Test]
+ public void TestGroupedDifficultyIconSelecting()
+ {
+ changeRuleset(0);
+
+ createSongSelect();
+
+ BeatmapSetInfo imported = null;
+
+ AddStep("import huge difficulty count map", () =>
+ {
+ var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
+ imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result;
+ });
+
+ AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First()));
+
+ DrawableCarouselBeatmapSet set = null;
+ AddUntilStep("Find the DrawableCarouselBeatmapSet", () =>
+ {
+ set = songSelect.Carousel.ChildrenOfType().FirstOrDefault();
+ return set != null;
+ });
+
+ DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon groupIcon = null;
+ AddStep("Find group icon for different ruleset", () =>
+ {
+ groupIcon = set.ChildrenOfType()
+ .First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3);
+ });
+
+ AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
+
+ AddStep("Click on group", () =>
+ {
+ InputManager.MoveMouseTo(groupIcon);
+
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+
+ AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
+
+ AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap);
}
private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.FindIndex(b => b == info);
@@ -570,7 +718,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
- private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
+ private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
private static int importId;
@@ -596,20 +744,22 @@ namespace osu.Game.Tests.Visual.SongSelect
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
for (int i = 0; i < 100; i += 10)
- manager.Import(createTestBeatmapSet(i, usableRulesets)).Wait();
+ manager.Import(createTestBeatmapSet(usableRulesets)).Wait();
});
}
- private BeatmapSetInfo createTestBeatmapSet(int setId, RulesetInfo[] rulesets)
+ private BeatmapSetInfo createTestBeatmapSet(RulesetInfo[] rulesets, int countPerRuleset = 6)
{
int j = 0;
RulesetInfo getRuleset() => rulesets[j++ % rulesets.Length];
+ int setId = getImportId();
+
var beatmaps = new List();
- for (int i = 0; i < 6; i++)
+ for (int i = 0; i < countPerRuleset; i++)
{
- int beatmapId = setId * 10 + i;
+ int beatmapId = setId * 1000 + i;
int length = RNG.Next(30000, 200000);
double bpm = RNG.NextSingle(80, 200);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs
index 7b0b644dab..cef04a4c18 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs
@@ -15,7 +15,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneCommentEditor : ManualInputManagerTestScene
+ public class TestSceneCommentEditor : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
index d1dde4664a..5b74852259 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
@@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneCursors : ManualInputManagerTestScene
+ public class TestSceneCursors : OsuManualInputManagerTestScene
{
private readonly MenuCursorContainer menuCursorContainer;
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index 1e5e26e4c5..a812b4dc79 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -27,7 +27,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneDeleteLocalScore : ManualInputManagerTestScene
+ public class TestSceneDeleteLocalScore : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs
new file mode 100644
index 0000000000..f6dcf78d55
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs
@@ -0,0 +1,63 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Dashboard.Friends;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneFriendsOnlineStatusControl : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(FriendOnlineStreamControl),
+ typeof(FriendsOnlineStatusItem),
+ typeof(OverlayStreamControl<>),
+ typeof(OverlayStreamItem<>),
+ typeof(FriendStream)
+ };
+
+ [Cached]
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
+
+ private FriendOnlineStreamControl control;
+
+ [SetUp]
+ public void SetUp() => Schedule(() => Child = control = new FriendOnlineStreamControl
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+
+ [Test]
+ public void Populate()
+ {
+ AddStep("Populate", () => control.Populate(new List
+ {
+ new User
+ {
+ IsOnline = true
+ },
+ new User
+ {
+ IsOnline = false
+ },
+ new User
+ {
+ IsOnline = false
+ }
+ }));
+
+ AddAssert("3 users", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.All)?.Count == 3);
+ AddAssert("1 online user", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.Online)?.Count == 1);
+ AddAssert("2 offline users", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.Offline)?.Count == 2);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs
index dbef7d1686..396bec51b6 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs
@@ -12,7 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneOsuHoverContainer : ManualInputManagerTestScene
+ public class TestSceneOsuHoverContainer : OsuManualInputManagerTestScene
{
private OsuHoverTestContainer hoverContainer;
private Box colourContainer;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
index 2ada5b927b..85fea73bf5 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
@@ -12,7 +12,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneStatefulMenuItem : ManualInputManagerTestScene
+ public class TestSceneStatefulMenuItem : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs
index 4a104b4a41..37fab75aee 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs
@@ -9,7 +9,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneSwitchButton : ManualInputManagerTestScene
+ public class TestSceneSwitchButton : OsuManualInputManagerTestScene
{
private SwitchButton switchButton;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs
index 02b8839922..1546972580 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
-using osu.Game.Overlays.Home.Friends;
+using osu.Game.Overlays.Dashboard.Friends;
using osuTK;
namespace osu.Game.Tests.Visual.UserInterface
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs
new file mode 100644
index 0000000000..01edcb66e4
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs
@@ -0,0 +1,125 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osu.Game.Tournament.Screens.Drawings.Components;
+using osu.Game.Tournament.Screens.Gameplay.Components;
+using osu.Game.Tournament.Screens.Ladder.Components;
+using osu.Game.Users;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneDrawableTournamentTeam : OsuGridTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableTeamFlag),
+ typeof(DrawableTeamTitle),
+ typeof(DrawableTeamTitleWithHeader),
+ typeof(DrawableMatchTeam),
+ typeof(DrawableTeamWithPlayers),
+ typeof(GroupTeam),
+ typeof(TeamDisplay),
+ };
+
+ public TestSceneDrawableTournamentTeam()
+ : base(4, 3)
+ {
+ var team = new TournamentTeam
+ {
+ FlagName = { Value = "AU" },
+ FullName = { Value = "Australia" },
+ Players =
+ {
+ new User { Username = "ASecretBox" },
+ new User { Username = "Dereban" },
+ new User { Username = "mReKk" },
+ new User { Username = "uyghti" },
+ new User { Username = "Parkes" },
+ new User { Username = "Shiroha" },
+ new User { Username = "Jordan The Bear" },
+ }
+ };
+
+ var match = new TournamentMatch { Team1 = { Value = team } };
+
+ int i = 0;
+
+ Cell(i++).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "DrawableTeamFlag" },
+ new DrawableTeamFlag(team)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+
+ Cell(i++).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "DrawableTeamTitle" },
+ new DrawableTeamTitle(team)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+
+ Cell(i++).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "DrawableTeamTitleWithHeader" },
+ new DrawableTeamTitleWithHeader(team, TeamColour.Red)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+
+ Cell(i++).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "DrawableMatchTeam" },
+ new DrawableMatchTeam(team, match, false)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+
+ Cell(i++).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "TeamWithPlayers" },
+ new DrawableTeamWithPlayers(team, TeamColour.Blue)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+
+ Cell(i++).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "GroupTeam" },
+ new GroupTeam(team)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+
+ Cell(i).AddRange(new Drawable[]
+ {
+ new TournamentSpriteText { Text = "TeamDisplay" },
+ new TeamDisplay(team, TeamColour.Red, new Bindable(2), 6)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchHeader.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchHeader.cs
new file mode 100644
index 0000000000..9f885ed827
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchHeader.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Screens.Gameplay.Components;
+using osuTK;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneMatchHeader : TournamentTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableTournamentHeaderText),
+ typeof(DrawableTournamentHeaderLogo),
+ };
+
+ public TestSceneMatchHeader()
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(50),
+ Children = new Drawable[]
+ {
+ new TournamentSpriteText { Text = "with logo", Font = OsuFont.Torus.With(size: 30) },
+ new MatchHeader(),
+ new TournamentSpriteText { Text = "without logo", Font = OsuFont.Torus.With(size: 30) },
+ new MatchHeader { ShowLogo = false },
+ new TournamentSpriteText { Text = "without scores", Font = OsuFont.Torus.With(size: 30) },
+ new MatchHeader { ShowScores = false },
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneRoundDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneRoundDisplay.cs
new file mode 100644
index 0000000000..6f71627ce4
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneRoundDisplay.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneRoundDisplay : TournamentTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableTournamentHeaderText),
+ typeof(DrawableTournamentHeaderLogo),
+ };
+
+ public TestSceneRoundDisplay()
+ {
+ Children = new Drawable[]
+ {
+ new RoundDisplay(new TournamentMatch
+ {
+ Round =
+ {
+ Value = new TournamentRound
+ {
+ Name = { Value = "Test Round" }
+ }
+ }
+ })
+ {
+ Margin = new MarginPadding(20)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs
index dae0721023..b962d035ab 100644
--- a/osu.Game.Tournament.Tests/LadderTestScene.cs
+++ b/osu.Game.Tournament.Tests/LadderTestScene.cs
@@ -1,16 +1,147 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
using osu.Game.Tournament.Models;
+using osu.Game.Users;
namespace osu.Game.Tournament.Tests
{
[TestFixture]
public abstract class LadderTestScene : TournamentTestScene
{
+ [Cached]
+ protected LadderInfo Ladder { get; private set; } = new LadderInfo();
+
[Resolved]
- protected LadderInfo Ladder { get; private set; }
+ private RulesetStore rulesetStore { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ if (Ladder.Ruleset.Value == null)
+ Ladder.Ruleset.Value = rulesetStore.AvailableRulesets.First();
+
+ Ruleset.BindTo(Ladder.Ruleset);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ TournamentMatch match = CreateSampleMatch();
+
+ Ladder.Rounds.Add(match.Round.Value);
+ Ladder.Matches.Add(match);
+ Ladder.Teams.Add(match.Team1.Value);
+ Ladder.Teams.Add(match.Team2.Value);
+
+ Ladder.CurrentMatch.Value = match;
+ }
+
+ public static TournamentMatch CreateSampleMatch() => new TournamentMatch
+ {
+ Team1 =
+ {
+ Value = new TournamentTeam
+ {
+ FlagName = { Value = "JP" },
+ FullName = { Value = "Japan" },
+ LastYearPlacing = { Value = 10 },
+ Seed = { Value = "Low" },
+ SeedingResults =
+ {
+ new SeedingResult
+ {
+ Mod = { Value = "NM" },
+ Seed = { Value = 10 },
+ Beatmaps =
+ {
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 12345672,
+ Seed = { Value = 24 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 1234567,
+ Seed = { Value = 12 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 1234567,
+ Seed = { Value = 16 },
+ }
+ }
+ },
+ new SeedingResult
+ {
+ Mod = { Value = "DT" },
+ Seed = { Value = 5 },
+ Beatmaps =
+ {
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 234567,
+ Seed = { Value = 3 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 234567,
+ Seed = { Value = 6 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 234567,
+ Seed = { Value = 12 },
+ }
+ }
+ }
+ },
+ Players =
+ {
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } },
+ }
+ }
+ },
+ Team2 =
+ {
+ Value = new TournamentTeam
+ {
+ FlagName = { Value = "US" },
+ FullName = { Value = "United States" },
+ Players =
+ {
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ }
+ }
+ },
+ Round =
+ {
+ Value = new TournamentRound { Name = { Value = "Quarterfinals" } }
+ }
+ };
+
+ public static BeatmapInfo CreateSampleBeatmapInfo() =>
+ new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } };
}
}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs
index 9de00818a5..34fa7a4997 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs
@@ -1,16 +1,31 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Screens;
using osu.Game.Tournament.Screens.Gameplay;
+using osu.Game.Tournament.Screens.Gameplay.Components;
namespace osu.Game.Tournament.Tests.Screens
{
public class TestSceneGameplayScreen : TournamentTestScene
{
[Cached]
- private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay();
+ private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay { Width = 0.5f };
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(TeamScore),
+ typeof(TeamScoreDisplay),
+ typeof(TeamDisplay),
+ typeof(MatchHeader),
+ typeof(MatchScoreDisplay),
+ typeof(BeatmapInfoScreen),
+ typeof(SongBar),
+ };
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
index a7011c6d3c..a4538be384 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
@@ -1,24 +1,140 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.MapPool;
namespace osu.Game.Tournament.Tests.Screens
{
public class TestSceneMapPoolScreen : LadderTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(MapPoolScreen)
- };
+ private MapPoolScreen screen;
[BackgroundDependencyLoader]
private void load()
{
- Add(new MapPoolScreen { Width = 0.7f });
+ Add(screen = new MapPoolScreen { Width = 0.7f });
+ }
+
+ [Test]
+ public void TestFewMaps()
+ {
+ AddStep("load few maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 8; i++)
+ addBeatmap();
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertTwoWide();
+ }
+
+ [Test]
+ public void TestJustEnoughMaps()
+ {
+ AddStep("load just enough maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 18; i++)
+ addBeatmap();
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertTwoWide();
+ }
+
+ [Test]
+ public void TestManyMaps()
+ {
+ AddStep("load many maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 19; i++)
+ addBeatmap();
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertThreeWide();
+ }
+
+ [Test]
+ public void TestJustEnoughMods()
+ {
+ AddStep("load many maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 11; i++)
+ addBeatmap(i > 4 ? $"M{i}" : "NM");
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertTwoWide();
+ }
+
+ private void assertTwoWide() =>
+ AddAssert("ensure layout width is 2", () => screen.ChildrenOfType>>().First().Padding.Left > 0);
+
+ private void assertThreeWide() =>
+ AddAssert("ensure layout width is 3", () => screen.ChildrenOfType>>().First().Padding.Left == 0);
+
+ [Test]
+ public void TestManyMods()
+ {
+ AddStep("load many maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 12; i++)
+ addBeatmap(i > 4 ? $"M{i}" : "NM");
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertThreeWide();
+ }
+
+ private void addBeatmap(string mods = "nm")
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Mods = mods
+ });
}
}
}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs
index 2277302e98..b240ef3ae5 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Tournament.Components;
using osu.Game.Tournament.Screens.Schedule;
namespace osu.Game.Tournament.Tests.Screens
@@ -11,6 +13,7 @@ namespace osu.Game.Tournament.Tests.Screens
[BackgroundDependencyLoader]
private void load()
{
+ Add(new TourneyVideo("main") { RelativeSizeAxes = Axes.Both });
Add(new ScheduleScreen());
}
}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs
index 014cd4663b..17cccd34b6 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tournament.Tests.Screens
public TestSceneSeedingEditorScreen()
{
- var match = TestSceneSeedingScreen.CreateSampleSeededMatch();
+ var match = CreateSampleMatch();
Add(new SeedingEditorScreen(match.Team1.Value)
{
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs
index 335a6c80a1..4269f8f56a 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs
@@ -3,10 +3,8 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.TeamIntro;
-using osu.Game.Users;
namespace osu.Game.Tournament.Tests.Screens
{
@@ -18,110 +16,11 @@ namespace osu.Game.Tournament.Tests.Screens
[BackgroundDependencyLoader]
private void load()
{
- ladder.CurrentMatch.Value = CreateSampleSeededMatch();
-
Add(new SeedingScreen
{
FillMode = FillMode.Fit,
FillAspectRatio = 16 / 9f
});
}
-
- public static TournamentMatch CreateSampleSeededMatch() => new TournamentMatch
- {
- Team1 =
- {
- Value = new TournamentTeam
- {
- FlagName = { Value = "JP" },
- FullName = { Value = "Japan" },
- LastYearPlacing = { Value = 10 },
- Seed = { Value = "Low" },
- SeedingResults =
- {
- new SeedingResult
- {
- Mod = { Value = "NM" },
- Seed = { Value = 10 },
- Beatmaps =
- {
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 12345672,
- Seed = { Value = 24 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 1234567,
- Seed = { Value = 12 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 1234567,
- Seed = { Value = 16 },
- }
- }
- },
- new SeedingResult
- {
- Mod = { Value = "DT" },
- Seed = { Value = 5 },
- Beatmaps =
- {
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 234567,
- Seed = { Value = 3 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 234567,
- Seed = { Value = 6 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 234567,
- Seed = { Value = 12 },
- }
- }
- }
- },
- Players =
- {
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } },
- }
- }
- },
- Team2 =
- {
- Value = new TournamentTeam
- {
- FlagName = { Value = "US" },
- FullName = { Value = "United States" },
- Players =
- {
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- }
- }
- },
- Round =
- {
- Value = new TournamentRound { Name = { Value = "Quarterfinals" } }
- }
- };
}
}
diff --git a/osu.Game.Tournament/Components/ControlPanel.cs b/osu.Game.Tournament/Components/ControlPanel.cs
index fa5c941f1a..ef8c8767e0 100644
--- a/osu.Game.Tournament/Components/ControlPanel.cs
+++ b/osu.Game.Tournament/Components/ControlPanel.cs
@@ -22,9 +22,9 @@ namespace osu.Game.Tournament.Components
public ControlPanel()
{
- RelativeSizeAxes = Axes.Both;
+ RelativeSizeAxes = Axes.Y;
AlwaysPresent = true;
- Width = 0.15f;
+ Width = TournamentSceneManager.CONTROL_AREA_WIDTH;
Anchor = Anchor.TopRight;
InternalChildren = new Drawable[]
@@ -47,8 +47,8 @@ namespace osu.Game.Tournament.Components
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Width = 0.75f,
Position = new Vector2(0, 35f),
+ Padding = new MarginPadding(5),
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5f),
},
diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs
new file mode 100644
index 0000000000..8c85c9a46f
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs
@@ -0,0 +1,33 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTeamFlag : Sprite
+ {
+ private readonly TournamentTeam team;
+
+ [UsedImplicitly]
+ private Bindable flag;
+
+ public DrawableTeamFlag(TournamentTeam team)
+ {
+ this.team = team;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ if (team == null) return;
+
+ (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Texture = textures.Get($@"Flags/{team.FlagName}"), true);
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTeamHeader.cs b/osu.Game.Tournament/Components/DrawableTeamHeader.cs
new file mode 100644
index 0000000000..3d9e8a6e00
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTeamHeader.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tournament.Models;
+using osuTK;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTeamHeader : TournamentSpriteTextWithBackground
+ {
+ public DrawableTeamHeader(TeamColour colour)
+ {
+ Background.Colour = TournamentGame.GetTeamColour(colour);
+
+ Text.Colour = TournamentGame.TEXT_COLOUR;
+ Text.Text = $"Team {colour}".ToUpperInvariant();
+ Text.Scale = new Vector2(0.6f);
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs
new file mode 100644
index 0000000000..5aac37259f
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTeamTitle.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTeamTitle : TournamentSpriteTextWithBackground
+ {
+ private readonly TournamentTeam team;
+
+ [UsedImplicitly]
+ private Bindable acronym;
+
+ public DrawableTeamTitle(TournamentTeam team)
+ {
+ this.team = team;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ if (team == null) return;
+
+ (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(acronym => Text.Text = team?.FullName.Value ?? string.Empty, true);
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs b/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs
new file mode 100644
index 0000000000..ceffe3d315
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Tournament.Models;
+using osuTK;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTeamTitleWithHeader : CompositeDrawable
+ {
+ public DrawableTeamTitleWithHeader(TournamentTeam team, TeamColour colour)
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 10),
+ Children = new Drawable[]
+ {
+ new DrawableTeamHeader(colour),
+ new DrawableTeamTitle(team),
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs
new file mode 100644
index 0000000000..e949bf9881
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs
@@ -0,0 +1,66 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Tournament.Models;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTeamWithPlayers : CompositeDrawable
+ {
+ public DrawableTeamWithPlayers(TournamentTeam team, TeamColour colour)
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(30),
+ Children = new Drawable[]
+ {
+ new DrawableTeamTitleWithHeader(team, colour),
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Padding = new MarginPadding { Left = 10 },
+ Spacing = new Vector2(30),
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ Direction = FillDirection.Vertical,
+ AutoSizeAxes = Axes.Both,
+ ChildrenEnumerable = team?.Players.Select(createPlayerText).Take(5) ?? Enumerable.Empty()
+ },
+ new FillFlowContainer
+ {
+ Direction = FillDirection.Vertical,
+ AutoSizeAxes = Axes.Both,
+ ChildrenEnumerable = team?.Players.Select(createPlayerText).Skip(5) ?? Enumerable.Empty()
+ },
+ }
+ },
+ }
+ },
+ };
+
+ TournamentSpriteText createPlayerText(User p) =>
+ new TournamentSpriteText
+ {
+ Text = p.Username,
+ Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold),
+ Colour = Color4.White,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentHeaderLogo.cs b/osu.Game.Tournament/Components/DrawableTournamentHeaderLogo.cs
new file mode 100644
index 0000000000..3f5ab42fd7
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTournamentHeaderLogo.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTournamentHeaderLogo : CompositeDrawable
+ {
+ public DrawableTournamentHeaderLogo()
+ {
+ InternalChild = new LogoSprite();
+
+ Height = 82;
+ RelativeSizeAxes = Axes.X;
+ }
+
+ private class LogoSprite : Sprite
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Texture = textures.Get("header-logo");
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs b/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
new file mode 100644
index 0000000000..99d914fed4
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTournamentHeaderText : CompositeDrawable
+ {
+ public DrawableTournamentHeaderText(bool center = true)
+ {
+ InternalChild = new TextSprite
+ {
+ Anchor = center ? Anchor.Centre : Anchor.TopLeft,
+ Origin = center ? Anchor.Centre : Anchor.TopLeft,
+ };
+
+ Height = 22;
+ RelativeSizeAxes = Axes.X;
+ }
+
+ private class TextSprite : Sprite
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+
+ Texture = textures.Get("header-text");
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
index 99116d4a17..f8aed26ce1 100644
--- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
+++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
@@ -23,14 +23,11 @@ namespace osu.Game.Tournament.Components
[UsedImplicitly]
private Bindable acronym;
- [UsedImplicitly]
- private Bindable flag;
-
protected DrawableTournamentTeam(TournamentTeam team)
{
Team = team;
- Flag = new Sprite
+ Flag = new DrawableTeamFlag(team)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
@@ -48,7 +45,6 @@ namespace osu.Game.Tournament.Components
if (Team == null) return;
(acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(acronym => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true);
- (flag = Team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Flag.Texture = textures.Get($@"Flags/{Team.FlagName}"), true);
}
}
}
diff --git a/osu.Game.Tournament/Components/RoundDisplay.cs b/osu.Game.Tournament/Components/RoundDisplay.cs
new file mode 100644
index 0000000000..c0002e6804
--- /dev/null
+++ b/osu.Game.Tournament/Components/RoundDisplay.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Components
+{
+ public class RoundDisplay : CompositeDrawable
+ {
+ public RoundDisplay(TournamentMatch match)
+ {
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
+
+ InternalChildren = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new DrawableTournamentHeaderText(false)
+ {
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ },
+ new TournamentSpriteText
+ {
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ Text = match.Round.Value?.Name.Value ?? "Unknown Round",
+ Font = OsuFont.Torus.With(size: 26, weight: FontWeight.SemiBold)
+ },
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs
index 48ea36a8f3..8d766ec9ba 100644
--- a/osu.Game.Tournament/Components/SongBar.cs
+++ b/osu.Game.Tournament/Components/SongBar.cs
@@ -4,10 +4,8 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
@@ -24,6 +22,8 @@ namespace osu.Game.Tournament.Components
{
private BeatmapInfo beatmap;
+ private const float height = 145;
+
[Resolved]
private IBindable ruleset { get; set; }
@@ -52,15 +52,7 @@ namespace osu.Game.Tournament.Components
}
}
- 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 FillFlowContainer flow;
private bool expanded;
@@ -70,86 +62,27 @@ namespace osu.Game.Tournament.Components
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);
- }
+ flow.Direction = expanded ? FillDirection.Full : FillDirection.Vertical;
}
}
[BackgroundDependencyLoader]
private void load()
{
- RelativeSizeAxes = Axes.Both;
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
- outerPanel = new Container
+ flow = new FillFlowContainer
{
- Masking = true,
- EdgeEffect = new EdgeEffectParameters
- {
- Colour = Color4.Black.Opacity(0.2f),
- Type = EdgeEffectType.Shadow,
- Radius = 5,
- },
RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ LayoutDuration = 500,
+ LayoutEasing = Easing.OutQuint,
+ Direction = FillDirection.Full,
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,
- CornerExponent = 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,
- CornerExponent = 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,
- }
- }
- }
- }
}
};
@@ -160,7 +93,7 @@ namespace osu.Game.Tournament.Components
{
if (beatmap == null)
{
- panelContents.Clear();
+ flow.Clear();
return;
}
@@ -219,34 +152,86 @@ namespace osu.Game.Tournament.Components
break;
}
- panelContents.Children = new Drawable[]
+ flow.Children = new Drawable[]
{
- new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss")))
+ new Container
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = height / 2,
+ Width = 0.5f,
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+
+ Children = new Drawable[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new DiffPiece(stats),
+ new DiffPiece(("Star Rating", $"{beatmap.StarDifficulty:0.#}{srExtra}"))
+ }
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))),
+ new DiffPiece(("BPM", $"{bpm:0.#}"))
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.1f,
+ },
+ new OsuLogo
+ {
+ Triangles = false,
+ Scale = new Vector2(0.08f),
+ Margin = new MarginPadding(50),
+ X = -10,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ },
+ }
+ },
+ },
+ }
+ }
+ }
},
- new DiffPiece(("BPM", $"{bpm:0.#}"))
+ new TournamentBeatmapPanel(beatmap)
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.TopLeft
- },
- new DiffPiece(stats)
- {
- 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)
+ RelativeSizeAxes = Axes.X,
+ Width = 0.5f,
+ Height = height / 2,
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
}
};
}
@@ -258,10 +243,9 @@ namespace osu.Game.Tournament.Components
Margin = new MarginPadding { Horizontal = 15, Vertical = 1 };
AutoSizeAxes = Axes.Both;
- static void cp(SpriteText s, Color4 colour)
+ static void cp(SpriteText s, bool bold)
{
- s.Colour = colour;
- s.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 15);
+ s.Font = OsuFont.Torus.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, size: 15);
}
for (var i = 0; i < tuples.Length; i++)
@@ -272,14 +256,14 @@ namespace osu.Game.Tournament.Components
{
AddText(" / ", s =>
{
- cp(s, OsuColour.Gray(0.33f));
+ cp(s, false);
s.Spacing = new Vector2(-2, 0);
});
}
- AddText(new TournamentSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f)));
- AddText(" ", s => cp(s, OsuColour.Gray(0.33f)));
- AddText(new TournamentSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f)));
+ AddText(new TournamentSpriteText { Text = heading }, s => cp(s, false));
+ AddText(" ", s => cp(s, false));
+ AddText(new TournamentSpriteText { Text = content }, s => cp(s, true));
}
}
}
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
index 394ffe304e..477bf4bd63 100644
--- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -16,7 +16,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Tournament.Models;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tournament.Components
@@ -27,7 +26,7 @@ namespace osu.Game.Tournament.Components
private readonly string mods;
private const float horizontal_padding = 10;
- private const float vertical_padding = 5;
+ private const float vertical_padding = 10;
public const float HEIGHT = 50;
@@ -50,8 +49,6 @@ namespace osu.Game.Tournament.Components
currentMatch.BindValueChanged(matchChanged);
currentMatch.BindTo(ladder.CurrentMatch);
- CornerRadius = HEIGHT / 2;
- CornerExponent = 2;
Masking = true;
AddRangeInternal(new Drawable[]
@@ -70,16 +67,14 @@ namespace osu.Game.Tournament.Components
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Padding = new MarginPadding(vertical_padding),
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Padding = new MarginPadding(15),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new TournamentSpriteText
{
- 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}")),
@@ -88,9 +83,6 @@ namespace osu.Game.Tournament.Components
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Padding = new MarginPadding(vertical_padding),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
@@ -132,13 +124,21 @@ namespace osu.Game.Tournament.Components
if (!string.IsNullOrEmpty(mods))
{
- AddInternal(new Sprite
+ AddInternal(new Container
{
- Texture = textures.Get($"mods/{mods}"),
+ RelativeSizeAxes = Axes.Y,
+ Width = 60,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
- Margin = new MarginPadding(20),
- Scale = new Vector2(0.5f)
+ Margin = new MarginPadding(10),
+ Child = new Sprite
+ {
+ FillMode = FillMode.Fit,
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Texture = textures.Get($"mods/{mods}"),
+ }
});
}
}
@@ -170,16 +170,7 @@ namespace osu.Game.Tournament.Components
BorderThickness = 6;
- switch (found.Team)
- {
- case TeamColour.Red:
- BorderColour = Color4.Red;
- break;
-
- case TeamColour.Blue:
- BorderColour = Color4.Blue;
- break;
- }
+ BorderColour = TournamentGame.GetTeamColour(found.Team);
switch (found.Type)
{
diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
index 48c5b9bd35..fe22d1e76d 100644
--- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
+++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
@@ -9,8 +9,6 @@ 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
{
@@ -23,11 +21,11 @@ namespace osu.Game.Tournament.Components
public TournamentMatchChatDisplay()
{
RelativeSizeAxes = Axes.X;
- Y = 100;
- Size = new Vector2(0.45f, 112);
- Margin = new MarginPadding(10);
- Anchor = Anchor.BottomCentre;
- Origin = Anchor.BottomCentre;
+ Height = 144;
+ Anchor = Anchor.BottomLeft;
+ Origin = Anchor.BottomLeft;
+
+ CornerRadius = 0;
}
[BackgroundDependencyLoader(true)]
@@ -66,8 +64,23 @@ namespace osu.Game.Tournament.Components
}
}
+ public void Expand() => this.FadeIn(300);
+
+ public void Contract() => this.FadeOut(200);
+
protected override ChatLine CreateMessage(Message message) => new MatchMessage(message);
+ protected override StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new MatchChannel(channel);
+
+ public class MatchChannel : StandAloneDrawableChannel
+ {
+ public MatchChannel(Channel channel)
+ : base(channel)
+ {
+ ScrollbarVisible = false;
+ }
+ }
+
protected class MatchMessage : StandAloneMessage
{
public MatchMessage(Message message)
@@ -75,19 +88,15 @@ namespace osu.Game.Tournament.Components
{
}
- [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);
+ // if (info.CurrentMatch.Value.Team1.Value.Players.Any(u => u.Id == Message.Sender.Id))
+ // SenderText.Colour = TournamentGame.COLOUR_RED;
+ // else if (info.CurrentMatch.Value.Team2.Value.Players.Any(u => u.Id == Message.Sender.Id))
+ // SenderText.Colour = TournamentGame.COLOUR_BLUE;
+ // else if (Message.Sender.Colour != null)
+ // SenderText.Colour = ColourBox.Colour = Color4Extensions.FromHex(Message.Sender.Colour);
}
-
- private readonly Color4 red = new Color4(186, 0, 18, 255);
- private readonly Color4 blue = new Color4(17, 136, 170, 255);
}
}
}
diff --git a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs
new file mode 100644
index 0000000000..d92b9eb605
--- /dev/null
+++ b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+
+namespace osu.Game.Tournament.Components
+{
+ public class TournamentSpriteTextWithBackground : CompositeDrawable
+ {
+ protected readonly TournamentSpriteText Text;
+ protected readonly Box Background;
+
+ public TournamentSpriteTextWithBackground(string text = "")
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ Background = new Box
+ {
+ Colour = TournamentGame.ELEMENT_BACKGROUND_COLOUR,
+ RelativeSizeAxes = Axes.Both,
+ },
+ Text = new TournamentSpriteText
+ {
+ Colour = TournamentGame.ELEMENT_FOREGROUND_COLOUR,
+ Font = OsuFont.Torus.With(weight: FontWeight.SemiBold, size: 50),
+ Padding = new MarginPadding { Left = 10, Right = 20 },
+ Text = text
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs
index 206689ca1a..bc66fad8c1 100644
--- a/osu.Game.Tournament/Components/TourneyVideo.cs
+++ b/osu.Game.Tournament/Components/TourneyVideo.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.IO;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@@ -14,13 +14,34 @@ namespace osu.Game.Tournament.Components
{
public class TourneyVideo : CompositeDrawable
{
- private readonly VideoSprite video;
+ private readonly string filename;
+ private readonly bool drawFallbackGradient;
+ private VideoSprite video;
- private readonly ManualClock manualClock;
+ private ManualClock manualClock;
- public TourneyVideo(Stream stream)
+ public TourneyVideo(string filename, bool drawFallbackGradient = false)
{
- if (stream == null)
+ this.filename = filename;
+ this.drawFallbackGradient = drawFallbackGradient;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TournamentStorage storage)
+ {
+ var stream = storage.GetStream($@"videos/{filename}");
+
+ if (stream != null)
+ {
+ InternalChild = video = new VideoSprite(stream, false)
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Clock = new FramedClock(manualClock = new ManualClock()),
+ Loop = loop,
+ };
+ }
+ else if (drawFallbackGradient)
{
InternalChild = new Box
{
@@ -28,26 +49,26 @@ namespace osu.Game.Tournament.Components
RelativeSizeAxes = Axes.Both,
};
}
- else
- {
- InternalChild = video = new VideoSprite(stream)
- {
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- Clock = new FramedClock(manualClock = new ManualClock())
- };
- }
}
+ private bool loop;
+
public bool Loop
{
set
{
+ loop = value;
if (video != null)
video.Loop = value;
}
}
+ public void Reset()
+ {
+ if (manualClock != null)
+ manualClock.CurrentTime = 0;
+ }
+
protected override void Update()
{
base.Update();
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index b19f2bedf0..eefa9fcfe6 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -163,12 +163,7 @@ namespace osu.Game.Tournament.IPC
{
try
{
- stableInstallPath = "G:\\My Drive\\Main\\osu!tourney";
-
- if (checkExists(stableInstallPath))
- return stableInstallPath;
-
- stableInstallPath = "G:\\My Drive\\Main\\osu!mappool";
+ stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH");
if (checkExists(stableInstallPath))
return stableInstallPath;
diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs
index 5db0b01547..c2e6da9ca5 100644
--- a/osu.Game.Tournament/Models/LadderInfo.cs
+++ b/osu.Game.Tournament/Models/LadderInfo.cs
@@ -24,7 +24,13 @@ namespace osu.Game.Tournament.Models
// only used for serialisation
public List Progressions = new List();
- [JsonIgnore]
+ [JsonIgnore] // updated manually in TournamentGameBase
public Bindable CurrentMatch = new Bindable();
+
+ public Bindable ChromaKeyWidth = new BindableInt(1024)
+ {
+ MinValue = 640,
+ MaxValue = 1366,
+ };
}
}
diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs
index 06cce3d59e..8ebcbf4e15 100644
--- a/osu.Game.Tournament/Models/TournamentMatch.cs
+++ b/osu.Game.Tournament/Models/TournamentMatch.cs
@@ -90,6 +90,8 @@ namespace osu.Game.Tournament.Models
[JsonIgnore]
public TournamentTeam Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value;
+ public TeamColour WinnerColour => Winner == Team1.Value ? TeamColour.Red : TeamColour.Blue;
+
public int PointsToWin => Round.Value?.BestOf.Value / 2 + 1 ?? 0;
///
diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
index fccd35ca9e..0a3163ef43 100644
--- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
+++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
@@ -21,6 +21,7 @@ namespace osu.Game.Tournament.Screens
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
+ Depth = float.MinValue,
});
}
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs
index 4126f2db65..ece1c431e2 100644
--- a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
-using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osuTK;
using osuTK.Graphics;
@@ -116,53 +115,5 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
sb.AppendLine(gt.Team.FullName.Value);
return sb.ToString();
}
-
- private class GroupTeam : DrawableTournamentTeam
- {
- private readonly FillFlowContainer innerContainer;
-
- public GroupTeam(TournamentTeam team)
- : base(team)
- {
- Width = 36;
- AutoSizeAxes = Axes.Y;
-
- 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.Torus.With(weight: FontWeight.Bold, size: 10);
-
- InternalChildren = new Drawable[]
- {
- innerContainer = new FillFlowContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
-
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
-
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 5f),
-
- Children = new Drawable[]
- {
- Flag,
- AcronymText
- }
- }
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- innerContainer.ScaleTo(1.5f);
- innerContainer.ScaleTo(1f, 200);
- }
- }
}
}
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs
new file mode 100644
index 0000000000..4f0ce0bbe7
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs
@@ -0,0 +1,60 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+using osuTK;
+
+namespace osu.Game.Tournament.Screens.Drawings.Components
+{
+ public class GroupTeam : DrawableTournamentTeam
+ {
+ private readonly FillFlowContainer innerContainer;
+
+ public GroupTeam(TournamentTeam team)
+ : base(team)
+ {
+ Width = 36;
+ AutoSizeAxes = Axes.Y;
+
+ 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.Torus.With(weight: FontWeight.Bold, size: 10);
+
+ InternalChildren = new Drawable[]
+ {
+ innerContainer = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 5f),
+
+ Children = new Drawable[]
+ {
+ Flag,
+ AcronymText
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ innerContainer.ScaleTo(1.5f);
+ innerContainer.ScaleTo(1f, 200);
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
index 7119533743..8b8078e119 100644
--- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
@@ -129,8 +129,6 @@ namespace osu.Game.Tournament.Screens.Editors
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- LayoutDuration = 200,
- LayoutEasing = Easing.OutQuint,
ChildrenEnumerable = round.Beatmaps.Select(p => new RoundBeatmapRow(round, p))
};
}
diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
index e68946aaf2..46bb7b83e3 100644
--- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
@@ -124,8 +124,6 @@ namespace osu.Game.Tournament.Screens.Editors
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- LayoutDuration = 200,
- LayoutEasing = Easing.OutQuint,
ChildrenEnumerable = round.Beatmaps.Select(p => new SeedingBeatmapRow(round, p))
};
}
diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
index ca8bce1cca..81487f1bcf 100644
--- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
@@ -172,19 +172,6 @@ namespace osu.Game.Tournament.Screens.Editors
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;
@@ -202,8 +189,6 @@ namespace osu.Game.Tournament.Screens.Editors
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- LayoutDuration = 200,
- LayoutEasing = Easing.OutQuint,
ChildrenEnumerable = team.Players.Select(p => new PlayerRow(team, p))
};
}
@@ -311,7 +296,7 @@ namespace osu.Game.Tournament.Screens.Editors
private void updatePanel()
{
- drawableContainer.Child = new UserPanel(user) { Width = 300 };
+ drawableContainer.Child = new UserGridPanel(user) { Width = 300 };
}
}
}
diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
index 5598910824..8e5df72cc8 100644
--- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
@@ -40,7 +40,6 @@ namespace osu.Game.Tournament.Screens.Editors
new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
- Width = 0.9f,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Child = flow = new FillFlowContainer
@@ -48,8 +47,6 @@ namespace osu.Game.Tournament.Screens.Editors
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- LayoutDuration = 200,
- LayoutEasing = Easing.OutQuint,
Spacing = new Vector2(20)
},
},
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs
index ce17c392d0..d790f4b754 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs
@@ -2,24 +2,54 @@
// 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.Input.Events;
-using osu.Game.Graphics;
-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
{
+ private TeamScoreDisplay teamDisplay1;
+ private TeamScoreDisplay teamDisplay2;
+ private DrawableTournamentHeaderLogo logo;
+
+ private bool showScores = true;
+
+ public bool ShowScores
+ {
+ get => showScores;
+ set
+ {
+ if (value == showScores)
+ return;
+
+ showScores = value;
+
+ if (IsLoaded)
+ updateDisplay();
+ }
+ }
+
+ private bool showLogo = true;
+
+ public bool ShowLogo
+ {
+ get => showLogo;
+ set
+ {
+ if (value == showLogo)
+ return;
+
+ showLogo = value;
+
+ if (IsLoaded)
+ updateDisplay();
+ }
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -27,200 +57,54 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
Height = 95;
Children = new Drawable[]
{
- new TournamentLogo(),
- new RoundDisplay
+ new FillFlowContainer
{
- Y = 5,
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Padding = new MarginPadding(20),
+ Spacing = new Vector2(5),
+ Children = new Drawable[]
+ {
+ logo = new DrawableTournamentHeaderLogo
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Alpha = showLogo ? 1 : 0
+ },
+ new DrawableTournamentHeaderText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ },
+ new MatchRoundDisplay
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Scale = new Vector2(0.4f)
+ },
+ }
},
- new TeamScoreDisplay(TeamColour.Red)
+ teamDisplay1 = new TeamScoreDisplay(TeamColour.Red)
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
- new TeamScoreDisplay(TeamColour.Blue)
+ teamDisplay2 = new TeamScoreDisplay(TeamColour.Blue)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
};
+
+ updateDisplay();
}
- private class TeamScoreDisplay : CompositeDrawable
+ private void updateDisplay()
{
- private readonly TeamColour teamColour;
+ teamDisplay1.ShowScore = showScores;
+ teamDisplay2.ShowScore = showScores;
- private readonly Bindable currentMatch = new Bindable();
- private readonly Bindable currentTeam = new Bindable();
- private readonly Bindable currentTeamScore = new Bindable();
-
- 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 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 ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_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 currentTeamScore = new Bindable();
- private readonly StarCounter counter;
-
- public TeamScore(Bindable 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 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 TournamentSpriteText
- {
- Text = team?.FullName.Value.ToUpper() ?? "???",
- X = (flip ? -1 : 1) * 90,
- Y = -10,
- Colour = colour,
- Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 20),
- Origin = anchor,
- Anchor = anchor,
- },
- }
- };
- }
- }
-
- private class RoundDisplay : CompositeDrawable
- {
- private readonly Bindable currentMatch = new Bindable();
-
- private readonly TournamentSpriteText text;
-
- public RoundDisplay()
- {
- Width = 200;
- Height = 20;
-
- Masking = true;
- CornerRadius = 10;
-
- InternalChildren = new Drawable[]
- {
- new Box
- {
- Colour = OsuColour.Gray(0.18f),
- RelativeSizeAxes = Axes.Both,
- },
- text = new TournamentSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Colour = Color4.White,
- Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 16),
- },
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(LadderInfo ladder)
- {
- currentMatch.BindValueChanged(matchChanged);
- currentMatch.BindTo(ladder.CurrentMatch);
- }
-
- private void matchChanged(ValueChangedEvent