Compare commits
561 Commits
2020.229.0
...
2020.317.0
@@ -9,6 +9,8 @@ about: Issues regarding encountered bugs.
|
||||
**osu!lazer version:**
|
||||
|
||||
**Logs:**
|
||||
<!--
|
||||
*please attach logs here, which are located at:*
|
||||
- `%AppData%/osu/logs` *(on Windows),*
|
||||
- `~/.local/share/osu/logs` *(on Linux & macOS).*
|
||||
-->
|
||||
|
||||
@@ -9,8 +9,10 @@ about: Issues regarding crashes or permanent freezes.
|
||||
**osu!lazer version:**
|
||||
|
||||
**Logs:**
|
||||
<!--
|
||||
*please attach logs here, which are located at:*
|
||||
- `%AppData%/osu/logs` *(on Windows),*
|
||||
- `~/.local/share/osu/logs` *(on Linux & macOS).*
|
||||
-->
|
||||
|
||||
**Computer Specifications:**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ If you are looking to install or test osu! without setting up a development envi
|
||||
|
||||
**Latest build:**
|
||||
|
||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.x86_64.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
|
||||
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.221.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.229.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.317.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -30,11 +30,6 @@ namespace osu.Android
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Add(new SimpleUpdateManager());
|
||||
}
|
||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 130 KiB |
@@ -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);
|
||||
|
||||
@@ -18,7 +18,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BananaShower),
|
||||
typeof(Banana),
|
||||
typeof(DrawableBananaShower),
|
||||
typeof(DrawableBanana),
|
||||
|
||||
typeof(CatchRuleset),
|
||||
typeof(DrawableCatchRuleset),
|
||||
|
||||
@@ -17,12 +17,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(CatcherArea),
|
||||
typeof(CatcherSprite)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new CatcherArea.Catcher
|
||||
SetContents(() => new Catcher
|
||||
{
|
||||
RelativePositionAxes = Axes.None,
|
||||
Anchor = Anchor.Centre,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(CatcherArea),
|
||||
};
|
||||
|
||||
public TestSceneCatcherArea()
|
||||
{
|
||||
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
||||
@@ -34,9 +33,41 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
CreatedDrawables.OfType<CatchInputManager>().Select(i => i.Child)
|
||||
.OfType<TestCatcherArea>().ForEach(c => c.ToggleHyperDash(t)));
|
||||
|
||||
AddRepeatStep("catch fruit", () =>
|
||||
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
||||
area.MovableCatcher.PlaceOnPlate(new DrawableFruit(new TestSceneFruitObjects.TestCatchFruit(FruitVisualRepresentation.Grape)))), 20);
|
||||
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
||||
{
|
||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X
|
||||
}), 20);
|
||||
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
||||
{
|
||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X,
|
||||
LastInCombo = true,
|
||||
}), 20);
|
||||
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
||||
{
|
||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X,
|
||||
}), 20);
|
||||
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
||||
{
|
||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X + 100,
|
||||
LastInCombo = true,
|
||||
}, true), 20);
|
||||
}
|
||||
|
||||
private void catchFruit(Fruit fruit, bool miss = false)
|
||||
{
|
||||
this.ChildrenOfType<CatcherArea>().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<CatchHitObject>)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)
|
||||
|
||||
@@ -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<Type> 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++)
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<Type> 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<CatcherArea>().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<CatchHitObject> 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)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
ApplyPositionOffsets(Beatmap);
|
||||
|
||||
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>())
|
||||
@@ -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<CatchHitObject> objects)
|
||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||
{
|
||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Catch
|
||||
FruitGrapes,
|
||||
FruitOrange,
|
||||
FruitPear,
|
||||
Droplet
|
||||
Droplet,
|
||||
CatcherIdle,
|
||||
CatcherFail,
|
||||
CatcherKiai
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DifficultyHitObject> 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[]
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||
{
|
||||
private readonly CatcherArea.Catcher catcher;
|
||||
private readonly Catcher catcher;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||
private readonly Container dropletContainer;
|
||||
|
||||
public override Vector2 OriginPosition => base.OriginPosition - new Vector2(0, CatchHitObject.OBJECT_RADIUS);
|
||||
|
||||
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> 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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.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<CatchAction>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether we are hyper-dashing or not.
|
||||
/// </summary>
|
||||
public bool HyperDashing => hyperDashModifier != 1;
|
||||
|
||||
/// <summary>
|
||||
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
|
||||
/// </summary>
|
||||
public const double BASE_SPEED = 1.0 / 512;
|
||||
|
||||
public Container ExplodingFruitTarget;
|
||||
|
||||
public Container AdditiveTarget;
|
||||
|
||||
public CatcherAnimationState CurrentState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Width of the area that can be used to attempt catches during gameplay.
|
||||
/// </summary>
|
||||
internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X);
|
||||
|
||||
protected bool Dashing
|
||||
{
|
||||
get => dashing;
|
||||
set
|
||||
{
|
||||
if (value == dashing) return;
|
||||
|
||||
dashing = value;
|
||||
|
||||
Trail |= dashing;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
|
||||
/// </summary>
|
||||
protected bool Trail
|
||||
{
|
||||
get => trail;
|
||||
set
|
||||
{
|
||||
if (value == trail || AdditiveTarget == null) return;
|
||||
|
||||
trail = value;
|
||||
|
||||
if (Trail)
|
||||
beginTrail();
|
||||
}
|
||||
}
|
||||
|
||||
private Container<DrawableHitObject> 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<DrawableHitObject>
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a caught fruit to the catcher's stack.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit that was caught.</param>
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Let the catcher attempt to catch a fruit.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit to catch.</param>
|
||||
/// <returns>Whether the catch is possible.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set hyper-dash state.
|
||||
/// </summary>
|
||||
/// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.</param>
|
||||
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop any fruit off the plate.
|
||||
/// </summary>
|
||||
public void Drop()
|
||||
{
|
||||
foreach (var f in caughtFruit.ToArray())
|
||||
Drop(f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explode any fruit off the plate.
|
||||
/// </summary>
|
||||
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<DrawableHitObject> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public enum CatcherAnimationState
|
||||
{
|
||||
Idle,
|
||||
Fail,
|
||||
Kiai
|
||||
}
|
||||
}
|
||||
@@ -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<CatchHitObject, DrawableHitObject<CatchHitObject>> 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<CatchAction>
|
||||
{
|
||||
/// <summary>
|
||||
/// Width of the area that can be used to attempt catches during gameplay.
|
||||
/// </summary>
|
||||
internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
|
||||
|
||||
private Container<DrawableHitObject> 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<DrawableHitObject>
|
||||
{
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
/// <summary>
|
||||
/// Add a caught fruit to the catcher's stack.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit that was caught.</param>
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Let the catcher attempt to catch a fruit.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit to catch.</param>
|
||||
/// <returns>Whether the catch is possible.</returns>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we are hyper-dashing or not.
|
||||
/// </summary>
|
||||
public bool HyperDashing => hyperDashModifier != 1;
|
||||
|
||||
/// <summary>
|
||||
/// Set hyper-dash state.
|
||||
/// </summary>
|
||||
/// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.</param>
|
||||
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop any fruit off the plate.
|
||||
/// </summary>
|
||||
public void Drop()
|
||||
{
|
||||
foreach (var f in caughtFruit.ToArray())
|
||||
Drop(f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explode any fruit off the plate.
|
||||
/// </summary>
|
||||
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<DrawableHitObject> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.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);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
@@ -22,21 +22,13 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
private class ManiaFlashlight : Flashlight
|
||||
{
|
||||
private readonly Cached flashlightProperties = new Cached();
|
||||
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
|
||||
|
||||
public ManiaFlashlight()
|
||||
{
|
||||
FlashlightSize = new Vector2(0, default_flashlight_size);
|
||||
}
|
||||
|
||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||
{
|
||||
if ((invalidation & Invalidation.DrawSize) > 0)
|
||||
{
|
||||
flashlightProperties.Invalidate();
|
||||
}
|
||||
|
||||
return base.Invalidate(invalidation, source, shallPropagate);
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Caching;
|
||||
using osuTK.Graphics;
|
||||
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.Layout;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
@@ -65,6 +65,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddLayout(subtractionCache);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -100,15 +102,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Cached subtractionCache = new Cached();
|
||||
|
||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||
{
|
||||
if ((invalidation & Invalidation.DrawSize) > 0)
|
||||
subtractionCache.Invalidate();
|
||||
|
||||
return base.Invalidate(invalidation, source, shallPropagate);
|
||||
}
|
||||
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
|
||||
@@ -115,9 +115,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Y = HIT_TARGET_POSITION + 150,
|
||||
BypassAutoSizeAxes = Axes.Both
|
||||
},
|
||||
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.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<DrawableHitCircle>();
|
||||
if (!objects.Any())
|
||||
return false;
|
||||
|
||||
return objects.All(o => o.HitObject.TimePreempt == target);
|
||||
}
|
||||
|
||||
private bool checkObjectsScale(float target)
|
||||
{
|
||||
var objects = Player.ChildrenOfType<DrawableHitCircle>();
|
||||
if (!objects.Any())
|
||||
return false;
|
||||
|
||||
return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType<ShakeContainer>().First().Children.OfType<Container>().Single().Scale.X, target));
|
||||
}
|
||||
|
||||
private bool checkSomeHit()
|
||||
{
|
||||
return Player.ScoreProcessor.JudgedHits >= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<FollowPoint>().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];
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
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 osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneHitCircleArea : ManualInputManagerTestScene
|
||||
{
|
||||
private HitCircle hitCircle;
|
||||
private DrawableHitCircle drawableHitCircle;
|
||||
private DrawableHitCircle.HitReceptor hitAreaReceptor => drawableHitCircle.HitArea;
|
||||
|
||||
[SetUp]
|
||||
public new void SetUp()
|
||||
{
|
||||
base.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(), };
|
||||
|
||||
|
||||
@@ -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<DrawableHitCircle>().FirstOrDefault();
|
||||
var firstObject = Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType<DrawableHitCircle>().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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -104,8 +104,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;
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public Drawable ProxiedLayer => ApproachCircle;
|
||||
|
||||
public class HitReceptor : Drawable, IKeyBindingHandler<OsuAction>
|
||||
public class HitReceptor : CompositeDrawable, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
// 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)
|
||||
|
||||
@@ -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<Vector2> positionBindable = new Bindable<Vector2>();
|
||||
|
||||
|
||||
@@ -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<OsuAction?> 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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Batches;
|
||||
using osu.Framework.Graphics.OpenGL.Vertices;
|
||||
@@ -14,6 +13,7 @@ using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Timing;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
// -1 signals that the part is unusable, and should not be drawn
|
||||
parts[i].InvalidationID = -1;
|
||||
}
|
||||
|
||||
AddLayout(partSizeCache);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -72,20 +74,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Cached<Vector2> partSizeCache = new Cached<Vector2>();
|
||||
private readonly LayoutValue<Vector2> partSizeCache = new LayoutValue<Vector2>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence);
|
||||
|
||||
private Vector2 partSize => partSizeCache.IsValid
|
||||
? partSizeCache.Value
|
||||
: (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy);
|
||||
|
||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||
{
|
||||
if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0)
|
||||
partSizeCache.Invalidate();
|
||||
|
||||
return base.Invalidate(invalidation, source, shallPropagate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time to fade the cursor trail pieces.
|
||||
/// </summary>
|
||||
@@ -97,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Invalidate(Invalidation.DrawNode, shallPropagate: false);
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
|
||||
const int fade_clock_reset_threshold = 1000000;
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.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 CentreHit { StartTime = 1000 }), 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using 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<JudgementResult> Results = new List<JudgementResult>();
|
||||
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
public TestPlayer()
|
||||
: base(false, false)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ScoreProcessor.NewJudgement += r => Results.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
@@ -30,13 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
private class TaikoFlashlight : Flashlight
|
||||
{
|
||||
private readonly Cached flashlightProperties = new Cached();
|
||||
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
|
||||
private readonly TaikoPlayfield taikoPlayfield;
|
||||
|
||||
public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
|
||||
{
|
||||
this.taikoPlayfield = taikoPlayfield;
|
||||
FlashlightSize = new Vector2(0, getSizeFor(0));
|
||||
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
private float getSizeFor(int combo)
|
||||
@@ -56,16 +58,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
|
||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||
{
|
||||
if ((invalidation & Invalidation.DrawSize) > 0)
|
||||
{
|
||||
flashlightProperties.Invalidate();
|
||||
}
|
||||
|
||||
return base.Invalidate(invalidation, source, shallPropagate);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.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<Hit>().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
|
||||
hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType<Hit>().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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Variables]
|
||||
$var=1234
|
||||
$var=34
|
||||
|
||||
[Events]
|
||||
Sprite,Background,TopCentre,"img.jpg",$var56,240
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="SoloResults"/>
|
||||
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="ResultsScreen"/>
|
||||
/// </summary>
|
||||
[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));
|
||||
@@ -335,7 +336,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 +348,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 +361,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public readonly Bindable<bool> ReplacesBackground = new Bindable<bool>();
|
||||
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
|
||||
|
||||
public TestPlayer(bool allowPause = true)
|
||||
public LoadBlockingTestPlayer(bool allowPause = true)
|
||||
: base(allowPause)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using 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<JudgementResult> AppliedResults = new List<JudgementResult>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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<IReadOnlyList<Mod>> 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);
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
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<Type> 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<IResultPageInfo> 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<IEnumerable<APIUserScoreAggregate>> 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,14 +62,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
var frameworkConfig = host.Dependencies.Get<FrameworkConfigManager>();
|
||||
frameworkConfig.GetBindable<double>(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<Screen> 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<WorkingBeatmap> Beatmap => base.Beatmap;
|
||||
|
||||
public new Bindable<RulesetInfo> 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<IScreen> action, IEnumerable<Type> validScreens = null) => base.PerformFromScreen(action, validScreens);
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.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<double>(OsuSetting.DisplayStarsMaximum), 10.1));
|
||||
|
||||
AddStep("set value again", () => Game.LocalConfig.Set<double>(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<double>(OsuSetting.DisplayStarsMaximum), 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(UpdateStreamBadgeArea),
|
||||
typeof(UpdateStreamBadge),
|
||||
typeof(ChangelogUpdateStreamControl),
|
||||
typeof(ChangelogUpdateStreamItem),
|
||||
typeof(ChangelogHeader),
|
||||
typeof(ChangelogContent),
|
||||
typeof(ChangelogListing),
|
||||
|
||||
@@ -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<OsuSpriteText>().All(sprite => sprite.Font.Italics);
|
||||
//bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast<OsuSpriteText>().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);
|
||||
|
||||
@@ -38,8 +38,13 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private TestChatOverlay chatOverlay;
|
||||
private ChannelManager channelManager;
|
||||
|
||||
private IEnumerable<Channel> visibleChannels => chatOverlay.ChannelTabControl.VisibleItems.Where(channel => channel.Name != "+");
|
||||
private IEnumerable<Channel> joinedChannels => chatOverlay.ChannelTabControl.Items.Where(channel => channel.Name != "+");
|
||||
private readonly List<Channel> 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];
|
||||
|
||||
@@ -91,7 +96,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -102,12 +107,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
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));
|
||||
AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
|
||||
|
||||
AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
|
||||
AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
|
||||
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
|
||||
|
||||
AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
|
||||
AddStep("Close channel 1", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
|
||||
|
||||
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
|
||||
}
|
||||
@@ -140,10 +145,67 @@ namespace osu.Game.Tests.Visual.Online
|
||||
var targetNumberKey = oneBasedIndex % 10;
|
||||
var targetChannel = channels[zeroBasedIndex];
|
||||
AddStep($"press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey));
|
||||
AddAssert($"channel #{oneBasedIndex} is selected", () => channelManager.CurrentChannel.Value == targetChannel);
|
||||
AddAssert($"channel #{oneBasedIndex} is selected", () => currentChannel == targetChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private Channel expectedChannel;
|
||||
|
||||
[Test]
|
||||
public void TestCloseChannelWhileActive()
|
||||
{
|
||||
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
|
||||
Channel toAdd = new Channel { Name = $"test channel {joinedChannels.Count()}", Type = ChannelType.Temporary };
|
||||
channelManager.JoinChannel(toAdd);
|
||||
|
||||
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);
|
||||
|
||||
// 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("Channel changed to previous", () => 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("Channel changed to next", () => currentChannel == expectedChannel);
|
||||
}
|
||||
|
||||
private void pressChannelHotkey(int number)
|
||||
{
|
||||
var channelKey = Key.Number0 + number;
|
||||
@@ -187,6 +249,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 +260,22 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private class TestTabControl : ChannelTabControl
|
||||
{
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value) => new TestChannelTabItem(value);
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value)
|
||||
{
|
||||
switch (value.Type)
|
||||
{
|
||||
case ChannelType.PM:
|
||||
return new TestPrivateChannelTabItem(value);
|
||||
|
||||
default:
|
||||
return new TestChannelTabItem(value);
|
||||
}
|
||||
}
|
||||
|
||||
public new IReadOnlyDictionary<Channel, TabItem<Channel>> TabMap => base.TabMap;
|
||||
}
|
||||
|
||||
private class TestChannelTabItem : PrivateChannelTabItem
|
||||
private class TestChannelTabItem : ChannelTabItem
|
||||
{
|
||||
public TestChannelTabItem(Channel channel)
|
||||
: base(channel)
|
||||
@@ -210,5 +284,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(UserPanel),
|
||||
typeof(SocialPanel),
|
||||
typeof(FilterControl),
|
||||
typeof(SocialGridPanel),
|
||||
typeof(SocialListPanel)
|
||||
typeof(UserGridPanel),
|
||||
typeof(UserListPanel)
|
||||
};
|
||||
|
||||
public TestSceneSocialOverlay()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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<Type> RequiredTypes => new[]
|
||||
{
|
||||
UserPanel flyte;
|
||||
typeof(UserPanel),
|
||||
typeof(UserListPanel),
|
||||
typeof(UserGridPanel),
|
||||
};
|
||||
|
||||
Add(new FillFlowContainer
|
||||
private readonly Bindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
private readonly Bindable<UserStatus> status = new Bindable<UserStatus>();
|
||||
|
||||
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<UserActivity> activity = new Bindable<UserActivity>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.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<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(AccuracyCircle),
|
||||
typeof(RankBadge),
|
||||
typeof(RankNotch),
|
||||
typeof(RankText),
|
||||
typeof(SmoothCircularProgress)
|
||||
};
|
||||
|
||||
[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 },
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
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;
|
||||
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
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(ExpandedPanelMiddleContent),
|
||||
typeof(AccuracyCircle),
|
||||
typeof(AccuracyStatistic),
|
||||
typeof(ComboStatistic),
|
||||
typeof(CounterStatistic),
|
||||
typeof(StarRatingDisplay),
|
||||
typeof(StatisticDisplay),
|
||||
typeof(TotalScoreCounter)
|
||||
};
|
||||
|
||||
public TestSceneExpandedPanelMiddleContent()
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
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(createTestScore())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 },
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.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" }),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Type> 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;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.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<Type> 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 },
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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 }),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,7 +399,7 @@ 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 == null);
|
||||
|
||||
AddStep("remove mixed set", () =>
|
||||
{
|
||||
@@ -497,7 +497,7 @@ 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.BeatmapSetsChanged = () => changed = true;
|
||||
@@ -697,6 +697,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
public new List<DrawableCarouselItem> Items => base.Items;
|
||||
|
||||
public bool PendingFilterTask => PendingFilter != null;
|
||||
|
||||
protected override IEnumerable<BeatmapSetInfo> GetLoadableBeatmaps() => Enumerable.Empty<BeatmapSetInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SearchTextBox>().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<DrawableCarouselBeatmapSet>().First(s => s.Item.State.Value == CarouselItemState.Selected);
|
||||
|
||||
// special case for converts checked here.
|
||||
return selectedPanel.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>().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<SearchTextBox>().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<SearchTextBox>().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<SearchTextBox>().First().Text = string.Empty);
|
||||
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().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<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>()
|
||||
.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<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>().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<DrawableCarouselBeatmapSet>().FirstOrDefault();
|
||||
return set != null;
|
||||
});
|
||||
|
||||
DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null;
|
||||
AddStep("Find an icon for different ruleset", () =>
|
||||
{
|
||||
difficultyIcon = set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>()
|
||||
.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<DrawableCarouselBeatmapSet>().FirstOrDefault();
|
||||
return set != null;
|
||||
});
|
||||
|
||||
DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon groupIcon = null;
|
||||
AddStep("Find group icon for different ruleset", () =>
|
||||
{
|
||||
groupIcon = set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon>()
|
||||
.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<BeatmapInfo>();
|
||||
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Home.Friends;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneFriendsOnlineStatusControl : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(FriendsOnlineStatusControl),
|
||||
typeof(FriendsOnlineStatusItem),
|
||||
typeof(OverlayStreamControl<>),
|
||||
typeof(OverlayStreamItem<>),
|
||||
typeof(FriendsBundle)
|
||||
};
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
private FriendsOnlineStatusControl control;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() => Child = control = new FriendsOnlineStatusControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void Populate()
|
||||
{
|
||||
AddStep("Populate", () => control.Populate(new List<User>
|
||||
{
|
||||
new User
|
||||
{
|
||||
IsOnline = true
|
||||
},
|
||||
new User
|
||||
{
|
||||
IsOnline = false
|
||||
},
|
||||
new User
|
||||
{
|
||||
IsOnline = false
|
||||
}
|
||||
}));
|
||||
|
||||
AddAssert("3 users", () => control.Items.FirstOrDefault(item => item.Status == FriendsOnlineStatus.All)?.Count == 3);
|
||||
AddAssert("1 online user", () => control.Items.FirstOrDefault(item => item.Status == FriendsOnlineStatus.Online)?.Count == 1);
|
||||
AddAssert("2 offline users", () => control.Items.FirstOrDefault(item => item.Status == FriendsOnlineStatus.Offline)?.Count == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using 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<Type> 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<int?>(2), 6)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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<Type> 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 },
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||