mirror of
https://github.com/ppy/osu.git
synced 2025-03-05 19:12:56 +08:00
Merge branch 'master' into limit-match-settings-textbox
This commit is contained in:
commit
07951fe18c
@ -190,3 +190,5 @@ dotnet_diagnostic.CA2225.severity = none
|
|||||||
|
|
||||||
# Banned APIs
|
# Banned APIs
|
||||||
dotnet_diagnostic.RS0030.severity = error
|
dotnet_diagnostic.RS0030.severity = error
|
||||||
|
|
||||||
|
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# osu!
|
# osu!
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/peppy/osu)
|
[](https://github.com/ppy/osu/actions/workflows/ci.yml)
|
||||||
[](https://github.com/ppy/osu/releases/latest)
|
[](https://github.com/ppy/osu/releases/latest)
|
||||||
[](https://www.codefactor.io/repository/github/ppy/osu)
|
[](https://www.codefactor.io/repository/github/ppy/osu)
|
||||||
[](https://discord.gg/ppy)
|
[](https://discord.gg/ppy)
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.723.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.818.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
@ -5,23 +5,23 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Desktop.Windows
|
namespace osu.Desktop.Windows
|
||||||
{
|
{
|
||||||
public class GameplayWinKeyBlocker : Component
|
public class GameplayWinKeyBlocker : Component
|
||||||
{
|
{
|
||||||
private Bindable<bool> disableWinKey;
|
private Bindable<bool> disableWinKey;
|
||||||
private Bindable<bool> localUserPlaying;
|
private IBindable<bool> localUserPlaying;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGame game, OsuConfigManager config)
|
private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
|
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
|
||||||
localUserPlaying.BindValueChanged(_ => updateBlocking());
|
localUserPlaying.BindValueChanged(_ => updateBlocking());
|
||||||
|
|
||||||
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
||||||
|
120
osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
Normal file
120
osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CatchModMirrorTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestModMirror()
|
||||||
|
{
|
||||||
|
IBeatmap original = createBeatmap(false);
|
||||||
|
IBeatmap mirrored = createBeatmap(true);
|
||||||
|
|
||||||
|
assertEffectivePositionsMirrored(original, mirrored);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBeatmap createBeatmap(bool withMirrorMod)
|
||||||
|
{
|
||||||
|
var beatmap = createRawBeatmap();
|
||||||
|
var mirrorMod = new CatchModMirror();
|
||||||
|
|
||||||
|
var beatmapProcessor = new CatchBeatmapProcessor(beatmap);
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
if (withMirrorMod)
|
||||||
|
mirrorMod.ApplyToBeatmap(beatmap);
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBeatmap createRawBeatmap() => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
OriginalX = 150,
|
||||||
|
StartTime = 0
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
OriginalX = 450,
|
||||||
|
StartTime = 500
|
||||||
|
},
|
||||||
|
new JuiceStream
|
||||||
|
{
|
||||||
|
OriginalX = 250,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(-100, 1)),
|
||||||
|
new PathControlPoint(new Vector2(0, 2)),
|
||||||
|
new PathControlPoint(new Vector2(100, 3)),
|
||||||
|
new PathControlPoint(new Vector2(0, 4))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StartTime = 1000,
|
||||||
|
},
|
||||||
|
new BananaShower
|
||||||
|
{
|
||||||
|
StartTime = 5000,
|
||||||
|
Duration = 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static void assertEffectivePositionsMirrored(IBeatmap original, IBeatmap mirrored)
|
||||||
|
{
|
||||||
|
if (original.HitObjects.Count != mirrored.HitObjects.Count)
|
||||||
|
Assert.Fail($"Top-level object count mismatch (original: {original.HitObjects.Count}, mirrored: {mirrored.HitObjects.Count})");
|
||||||
|
|
||||||
|
for (int i = 0; i < original.HitObjects.Count; ++i)
|
||||||
|
{
|
||||||
|
var originalObject = (CatchHitObject)original.HitObjects[i];
|
||||||
|
var mirroredObject = (CatchHitObject)mirrored.HitObjects[i];
|
||||||
|
|
||||||
|
// banana showers themselves are exempt, as we only really care about their nested bananas' positions.
|
||||||
|
if (!effectivePositionMirrored(originalObject, mirroredObject) && !(originalObject is BananaShower))
|
||||||
|
Assert.Fail($"{originalObject.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalObject, mirroredObject)})");
|
||||||
|
|
||||||
|
if (originalObject.NestedHitObjects.Count != mirroredObject.NestedHitObjects.Count)
|
||||||
|
Assert.Fail($"{originalObject.GetType().Name} nested object count mismatch (original: {originalObject.NestedHitObjects.Count}, mirrored: {mirroredObject.NestedHitObjects.Count})");
|
||||||
|
|
||||||
|
for (int j = 0; j < originalObject.NestedHitObjects.Count; ++j)
|
||||||
|
{
|
||||||
|
var originalNested = (CatchHitObject)originalObject.NestedHitObjects[j];
|
||||||
|
var mirroredNested = (CatchHitObject)mirroredObject.NestedHitObjects[j];
|
||||||
|
|
||||||
|
if (!effectivePositionMirrored(originalNested, mirroredNested))
|
||||||
|
Assert.Fail($"{originalObject.GetType().Name}'s nested {originalNested.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalNested, mirroredNested)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string printEffectivePositions(CatchHitObject original, CatchHitObject mirrored)
|
||||||
|
=> $"original X: {original.EffectiveX}, mirrored X is: {mirrored.EffectiveX}, mirrored X should be: {CatchPlayfield.WIDTH - original.EffectiveX}";
|
||||||
|
|
||||||
|
private static bool effectivePositionMirrored(CatchHitObject original, CatchHitObject mirrored)
|
||||||
|
=> Precision.AlmostEquals(original.EffectiveX, CatchPlayfield.WIDTH - mirrored.EffectiveX);
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
var skin = new TestSkin { FlipCatcherPlate = flip };
|
var skin = new TestSkin { FlipCatcherPlate = flip };
|
||||||
container.Child = new SkinProvidingContainer(skin)
|
container.Child = new SkinProvidingContainer(skin)
|
||||||
{
|
{
|
||||||
Child = catcher = new Catcher(new Container(), new DroppedObjectContainer())
|
Child = catcher = new Catcher(new DroppedObjectContainer())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre
|
Anchor = Anchor.Centre
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Container trailContainer;
|
|
||||||
|
|
||||||
private DroppedObjectContainer droppedObjectContainer;
|
private DroppedObjectContainer droppedObjectContainer;
|
||||||
|
|
||||||
private TestCatcher catcher;
|
private TestCatcher catcher;
|
||||||
@ -45,7 +43,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
CircleSize = 0,
|
CircleSize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
trailContainer = new Container();
|
|
||||||
droppedObjectContainer = new DroppedObjectContainer();
|
droppedObjectContainer = new DroppedObjectContainer();
|
||||||
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -54,8 +51,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
droppedObjectContainer,
|
droppedObjectContainer,
|
||||||
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty),
|
catcher = new TestCatcher(droppedObjectContainer, difficulty),
|
||||||
trailContainer,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -294,8 +290,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
|
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
|
||||||
|
|
||||||
public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty)
|
public TestCatcher(DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty)
|
||||||
: base(trailsTarget, droppedObjectTarget, difficulty)
|
: base(droppedObjectTarget, difficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
||||||
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
||||||
|
AddToggleStep("toggle hit lighting", lighting => config.SetValue(OsuSetting.HitLighting, lighting));
|
||||||
|
|
||||||
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
|
||||||
AddStep("catch many random fruit", () =>
|
AddStep("catch many random fruit", () =>
|
||||||
@ -122,10 +123,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
|
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new DroppedObjectContainer();
|
var droppedObjectContainer = new DroppedObjectContainer();
|
||||||
|
|
||||||
Add(droppedObjectContainer);
|
Add(droppedObjectContainer);
|
||||||
|
|
||||||
Catcher = new Catcher(this, droppedObjectContainer, beatmapDifficulty)
|
Catcher = new Catcher(droppedObjectContainer, beatmapDifficulty)
|
||||||
{
|
{
|
||||||
X = CatchPlayfield.CENTER_X
|
X = CatchPlayfield.CENTER_X
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCustomEndGlowColour()
|
public void TestCustomAfterImageColour()
|
||||||
{
|
{
|
||||||
var skin = new TestSkin
|
var skin = new TestSkin
|
||||||
{
|
{
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCustomEndGlowColourPriority()
|
public void TestCustomAfterImageColourPriority()
|
||||||
{
|
{
|
||||||
var skin = new TestSkin
|
var skin = new TestSkin
|
||||||
{
|
{
|
||||||
@ -111,39 +111,37 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
checkHyperDashFruitColour(skin, skin.HyperDashColour);
|
checkHyperDashFruitColour(skin, skin.HyperDashColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
|
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedAfterImageColour = null)
|
||||||
{
|
{
|
||||||
Container trailsContainer = null;
|
|
||||||
Catcher catcher = null;
|
|
||||||
CatcherTrailDisplay trails = null;
|
CatcherTrailDisplay trails = null;
|
||||||
|
Catcher catcher = null;
|
||||||
|
|
||||||
AddStep("create hyper-dashing catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
trailsContainer = new Container();
|
CatcherArea catcherArea;
|
||||||
Child = setupSkinHierarchy(new Container
|
Child = setupSkinHierarchy(new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Child = catcherArea = new CatcherArea
|
||||||
{
|
{
|
||||||
catcher = new Catcher(trailsContainer, new DroppedObjectContainer())
|
Catcher = catcher = new Catcher(new DroppedObjectContainer())
|
||||||
{
|
{
|
||||||
Scale = new Vector2(4)
|
Scale = new Vector2(4)
|
||||||
},
|
}
|
||||||
trailsContainer
|
|
||||||
}
|
}
|
||||||
}, skin);
|
}, skin);
|
||||||
|
trails = catcherArea.ChildrenOfType<CatcherTrailDisplay>().Single();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("get trails container", () =>
|
AddStep("start hyper-dash", () =>
|
||||||
{
|
{
|
||||||
trails = trailsContainer.OfType<CatcherTrailDisplay>().Single();
|
|
||||||
catcher.SetHyperDashState(2);
|
catcher.SetHyperDashState(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("catcher colour is correct", () => catcher.Colour == expectedCatcherColour);
|
AddUntilStep("catcher colour is correct", () => catcher.Colour == expectedCatcherColour);
|
||||||
|
|
||||||
AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
|
AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
|
||||||
AddAssert("catcher end-glow colours are correct", () => trails.EndGlowSpritesColour == (expectedEndGlowColour ?? expectedCatcherColour));
|
AddAssert("catcher after-image colours are correct", () => trails.HyperDashAfterImageColour == (expectedAfterImageColour ?? expectedCatcherColour));
|
||||||
|
|
||||||
AddStep("finish hyper-dashing", () =>
|
AddStep("finish hyper-dashing", () =>
|
||||||
{
|
{
|
||||||
|
@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
{
|
{
|
||||||
new CatchModDifficultyAdjust(),
|
new CatchModDifficultyAdjust(),
|
||||||
new CatchModClassic(),
|
new CatchModClassic(),
|
||||||
|
new CatchModMirror(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -130,7 +131,8 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
return new Mod[]
|
return new Mod[]
|
||||||
{
|
{
|
||||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||||
new CatchModFloatingFruits()
|
new CatchModFloatingFruits(),
|
||||||
|
new CatchModMuted(),
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
Banana,
|
Banana,
|
||||||
Droplet,
|
Droplet,
|
||||||
Catcher,
|
Catcher,
|
||||||
CatchComboCounter
|
CatchComboCounter,
|
||||||
|
HitExplosion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public class Movement : StrainSkill
|
public class Movement : StrainDecaySkill
|
||||||
{
|
{
|
||||||
private const float absolute_player_positioning_error = 16f;
|
private const float absolute_player_positioning_error = 16f;
|
||||||
private const float normalized_hitobject_radius = 41.0f;
|
private const float normalized_hitobject_radius = 41.0f;
|
||||||
|
87
osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
Normal file
87
osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
|
{
|
||||||
|
public class CatchModMirror : ModMirror, IApplicableToBeatmap
|
||||||
|
{
|
||||||
|
public override string Description => "Fruits are flipped horizontally.";
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="IApplicableToBeatmap"/> is used instead of <see cref="IApplicableToHitObject"/>,
|
||||||
|
/// as <see cref="CatchBeatmapProcessor"/> applies offsets in <see cref="CatchBeatmapProcessor.PostProcess"/>.
|
||||||
|
/// <see cref="IApplicableToBeatmap"/> runs after post-processing, while <see cref="IApplicableToHitObject"/> runs before it.
|
||||||
|
/// </remarks>
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
applyToHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyToHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
var catchObject = (CatchHitObject)hitObject;
|
||||||
|
|
||||||
|
switch (catchObject)
|
||||||
|
{
|
||||||
|
case Fruit fruit:
|
||||||
|
mirrorEffectiveX(fruit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JuiceStream juiceStream:
|
||||||
|
mirrorEffectiveX(juiceStream);
|
||||||
|
mirrorJuiceStreamPath(juiceStream);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BananaShower bananaShower:
|
||||||
|
mirrorBananaShower(bananaShower);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the effective X position of <paramref name="catchObject"/> and its nested hit objects.
|
||||||
|
/// </summary>
|
||||||
|
private static void mirrorEffectiveX(CatchHitObject catchObject)
|
||||||
|
{
|
||||||
|
catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX;
|
||||||
|
catchObject.XOffset = -catchObject.XOffset;
|
||||||
|
|
||||||
|
foreach (var nested in catchObject.NestedHitObjects.Cast<CatchHitObject>())
|
||||||
|
{
|
||||||
|
nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX;
|
||||||
|
nested.XOffset = -nested.XOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the path of the <paramref name="juiceStream"/>.
|
||||||
|
/// </summary>
|
||||||
|
private static void mirrorJuiceStreamPath(JuiceStream juiceStream)
|
||||||
|
{
|
||||||
|
var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y);
|
||||||
|
|
||||||
|
juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors X positions of all bananas in the <paramref name="bananaShower"/>.
|
||||||
|
/// </summary>
|
||||||
|
private static void mirrorBananaShower(BananaShower bananaShower)
|
||||||
|
{
|
||||||
|
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
|
||||||
|
banana.XOffset = CatchPlayfield.WIDTH - banana.XOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs
Normal file
12
osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
|
{
|
||||||
|
public class CatchModMuted : ModMuted<CatchHitObject>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
129
osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
Normal file
129
osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.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.UI;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
private CircularContainer largeFaint;
|
||||||
|
private CircularContainer smallFaint;
|
||||||
|
private CircularContainer directionalGlow1;
|
||||||
|
private CircularContainer directionalGlow2;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Size = new Vector2(20);
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
|
// scale roughly in-line with visual appearance of notes
|
||||||
|
const float initial_height = 10;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
largeFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
smallFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow1 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow2 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(HitExplosionEntry entry)
|
||||||
|
{
|
||||||
|
X = entry.Position;
|
||||||
|
Scale = new Vector2(entry.HitObject.Scale);
|
||||||
|
setColour(entry.ObjectColour);
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
applyTransforms(entry.HitObject.RandomSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTransforms(int randomSeed)
|
||||||
|
{
|
||||||
|
const double duration = 400;
|
||||||
|
|
||||||
|
// we want our size to be very small so the glow dominates it.
|
||||||
|
largeFaint.Size = new Vector2(0.8f);
|
||||||
|
largeFaint
|
||||||
|
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
||||||
|
.FadeOut(duration * 2);
|
||||||
|
|
||||||
|
const float angle_variangle = 15; // should be less than 45
|
||||||
|
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
||||||
|
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
||||||
|
|
||||||
|
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setColour(Color4 objectColour)
|
||||||
|
{
|
||||||
|
const float roundness = 100;
|
||||||
|
|
||||||
|
largeFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
||||||
|
Roundness = 160,
|
||||||
|
Radius = 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
smallFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
||||||
|
Roundness = 20,
|
||||||
|
Radius = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
||||||
|
Roundness = roundness,
|
||||||
|
Radius = 40,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -70,13 +70,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
if (version < 2.3m)
|
if (version < 2.3m)
|
||||||
{
|
{
|
||||||
if (GetTexture(@"fruit-ryuuta") != null ||
|
if (hasOldStyleCatcherSprite())
|
||||||
GetTexture(@"fruit-ryuuta-0") != null)
|
|
||||||
return new LegacyCatcherOld();
|
return new LegacyCatcherOld();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetTexture(@"fruit-catcher-idle") != null ||
|
if (hasNewStyleCatcherSprite())
|
||||||
GetTexture(@"fruit-catcher-idle-0") != null)
|
|
||||||
return new LegacyCatcherNew();
|
return new LegacyCatcherNew();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -86,12 +84,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
return new LegacyCatchComboCounter(Skin);
|
return new LegacyCatchComboCounter(Skin);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case CatchSkinComponents.HitExplosion:
|
||||||
|
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||||
|
return new LegacyHitExplosion();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool hasOldStyleCatcherSprite() =>
|
||||||
|
GetTexture(@"fruit-ryuuta") != null
|
||||||
|
|| GetTexture(@"fruit-ryuuta-0") != null;
|
||||||
|
|
||||||
|
private bool hasNewStyleCatcherSprite() =>
|
||||||
|
GetTexture(@"fruit-catcher-idle") != null
|
||||||
|
|| GetTexture(@"fruit-catcher-idle-0") != null;
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private Catcher catcher { get; set; }
|
||||||
|
|
||||||
|
private const float catch_margin = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2;
|
||||||
|
|
||||||
|
private readonly Sprite explosion1;
|
||||||
|
private readonly Sprite explosion2;
|
||||||
|
|
||||||
|
public LegacyHitExplosion()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Scale = new Vector2(0.5f);
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
explosion1 = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Rotation = -90
|
||||||
|
},
|
||||||
|
explosion2 = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Rotation = -90
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(SkinManager skins)
|
||||||
|
{
|
||||||
|
var defaultLegacySkin = skins.DefaultLegacySkin;
|
||||||
|
|
||||||
|
// sprite names intentionally swapped to match stable member naming / ease of cross-referencing
|
||||||
|
explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
|
||||||
|
explosion2.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(HitExplosionEntry entry)
|
||||||
|
{
|
||||||
|
Colour = entry.ObjectColour;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
{
|
||||||
|
float halfCatchWidth = catcher.CatchWidth / 2;
|
||||||
|
float explosionOffset = Math.Clamp(entry.Position, -halfCatchWidth + catch_margin * 3, halfCatchWidth - catch_margin * 3);
|
||||||
|
|
||||||
|
if (!(entry.HitObject is Droplet))
|
||||||
|
{
|
||||||
|
float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
|
||||||
|
|
||||||
|
explosion1.Scale = new Vector2(1, 0.9f);
|
||||||
|
explosion1.Position = new Vector2(explosionOffset, 0);
|
||||||
|
|
||||||
|
explosion1.FadeOutFromOne(300);
|
||||||
|
explosion1.ScaleTo(new Vector2(16 * scale, 1.1f), 160, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
explosion2.Scale = new Vector2(0.9f, 1);
|
||||||
|
explosion2.Position = new Vector2(explosionOffset, 0);
|
||||||
|
|
||||||
|
explosion2.FadeOutFromOne(700);
|
||||||
|
explosion2.ScaleTo(new Vector2(0.9f, 1.3f), 500, Easing.Out);
|
||||||
|
|
||||||
|
this.Delay(700).FadeOutFromOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
@ -45,14 +44,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
var trailContainer = new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.TopLeft
|
|
||||||
};
|
|
||||||
var droppedObjectContainer = new DroppedObjectContainer();
|
var droppedObjectContainer = new DroppedObjectContainer();
|
||||||
|
|
||||||
Catcher = new Catcher(trailContainer, droppedObjectContainer, difficulty)
|
Catcher = new Catcher(droppedObjectContainer, difficulty)
|
||||||
{
|
{
|
||||||
X = CENTER_X
|
X = CENTER_X
|
||||||
};
|
};
|
||||||
@ -70,7 +64,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
Catcher = Catcher,
|
Catcher = Catcher,
|
||||||
},
|
},
|
||||||
trailContainer,
|
|
||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class Catcher : SkinReloadableDrawable
|
public class Catcher : SkinReloadableDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,8 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail and after-image during a hyper-dash.
|
||||||
/// and end glow/after-image during a hyper-dash.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
|
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
|
||||||
|
|
||||||
@ -71,11 +71,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float caught_fruit_scale_adjust = 0.5f;
|
private const float caught_fruit_scale_adjust = 0.5f;
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
private readonly Container trailsTarget;
|
|
||||||
|
|
||||||
private CatcherTrailDisplay trails;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains caught objects on the plate.
|
/// Contains caught objects on the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -88,30 +83,22 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatcherAnimationState CurrentState
|
public CatcherAnimationState CurrentState
|
||||||
{
|
{
|
||||||
get => Body.AnimationState.Value;
|
get => body.AnimationState.Value;
|
||||||
private set => Body.AnimationState.Value = value;
|
private set => body.AnimationState.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool dashing;
|
/// <summary>
|
||||||
|
/// Whether the catcher is currently dashing.
|
||||||
public bool Dashing
|
/// </summary>
|
||||||
{
|
public bool Dashing { get; set; }
|
||||||
get => dashing;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == dashing) return;
|
|
||||||
|
|
||||||
dashing = value;
|
|
||||||
|
|
||||||
updateTrailVisibility();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently facing direction.
|
/// The currently facing direction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Direction VisualDirection { get; set; } = Direction.Right;
|
public Direction VisualDirection { get; set; } = Direction.Right;
|
||||||
|
|
||||||
|
public Vector2 BodyScale => Scale * body.Scale;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
|
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -120,12 +107,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
public readonly float CatchWidth;
|
||||||
|
|
||||||
internal readonly SkinnableCatcher Body;
|
private readonly SkinnableCatcher body;
|
||||||
|
|
||||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
|
||||||
|
|
||||||
private double hyperDashModifier = 1;
|
private double hyperDashModifier = 1;
|
||||||
private int hyperDashDirection;
|
private int hyperDashDirection;
|
||||||
@ -138,9 +124,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
||||||
private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
|
private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
|
||||||
|
|
||||||
public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
this.trailsTarget = trailsTarget;
|
|
||||||
this.droppedObjectTarget = droppedObjectTarget;
|
this.droppedObjectTarget = droppedObjectTarget;
|
||||||
|
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
@ -149,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
catchWidth = CalculateCatchWidth(Scale);
|
CatchWidth = CalculateCatchWidth(Scale);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -164,7 +149,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
// offset fruit vertically to better place "above" the plate.
|
// offset fruit vertically to better place "above" the plate.
|
||||||
Y = -5
|
Y = -5
|
||||||
},
|
},
|
||||||
Body = new SkinnableCatcher(),
|
body = new SkinnableCatcher(),
|
||||||
hitExplosionContainer = new HitExplosionContainer
|
hitExplosionContainer = new HitExplosionContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -177,15 +162,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||||
trails = new CatcherTrailDisplay(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
// don't add in above load as we may potentially modify a parent in an unsafe manner.
|
|
||||||
trailsTarget.Add(trails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -218,7 +194,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (!(hitObject is PalpableCatchHitObject fruit))
|
if (!(hitObject is PalpableCatchHitObject fruit))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
float halfCatchWidth = catchWidth * 0.5f;
|
float halfCatchWidth = CatchWidth * 0.5f;
|
||||||
return fruit.EffectiveX >= X - halfCatchWidth &&
|
return fruit.EffectiveX >= X - halfCatchWidth &&
|
||||||
fruit.EffectiveX <= X + halfCatchWidth;
|
fruit.EffectiveX <= X + halfCatchWidth;
|
||||||
}
|
}
|
||||||
@ -241,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
placeCaughtObject(palpableObject, positionInStack);
|
placeCaughtObject(palpableObject, positionInStack);
|
||||||
|
|
||||||
if (hitLighting.Value)
|
if (hitLighting.Value)
|
||||||
addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
|
addLighting(result, drawableObject.AccentColour.Value, positionInStack.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
// droplet doesn't affect the catcher state
|
// droplet doesn't affect the catcher state
|
||||||
@ -307,12 +283,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
hyperDashTargetPosition = targetPosition;
|
hyperDashTargetPosition = targetPosition;
|
||||||
|
|
||||||
if (!wasHyperDashing)
|
if (!wasHyperDashing)
|
||||||
{
|
|
||||||
trails.DisplayEndGlow();
|
|
||||||
runHyperDashStateTransition(true);
|
runHyperDashStateTransition(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop any fruit off the plate.
|
/// Drop any fruit off the plate.
|
||||||
@ -326,13 +299,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private void runHyperDashStateTransition(bool hyperDashing)
|
private void runHyperDashStateTransition(bool hyperDashing)
|
||||||
{
|
{
|
||||||
updateTrailVisibility();
|
|
||||||
|
|
||||||
this.FadeColour(hyperDashing ? hyperDashColour : Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
this.FadeColour(hyperDashing ? hyperDashColour : Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin)
|
protected override void SkinChanged(ISkinSource skin)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin);
|
base.SkinChanged(skin);
|
||||||
@ -341,13 +310,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||||
DEFAULT_HYPER_DASH_COLOUR;
|
DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
hyperDashEndGlowColour =
|
|
||||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
|
||||||
hyperDashColour;
|
|
||||||
|
|
||||||
trails.HyperDashTrailsColour = hyperDashColour;
|
|
||||||
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
|
||||||
|
|
||||||
flipCatcherPlate = skin.GetConfig<CatchSkinConfiguration, bool>(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true;
|
flipCatcherPlate = skin.GetConfig<CatchSkinConfiguration, bool>(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true;
|
||||||
|
|
||||||
runHyperDashStateTransition(HyperDashing);
|
runHyperDashStateTransition(HyperDashing);
|
||||||
@ -358,7 +320,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
||||||
Body.Scale = scaleFromDirection;
|
body.Scale = scaleFromDirection;
|
||||||
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||||
|
|
||||||
// Correct overshooting.
|
// Correct overshooting.
|
||||||
@ -404,8 +366,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
|
private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
|
||||||
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
|
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
|
||||||
|
|
||||||
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
|
@ -25,17 +25,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public Catcher Catcher
|
public Catcher Catcher
|
||||||
{
|
{
|
||||||
get => catcher;
|
get => catcher;
|
||||||
set
|
set => catcherContainer.Child = catcher = value;
|
||||||
{
|
}
|
||||||
if (catcher != null)
|
|
||||||
Remove(catcher);
|
|
||||||
|
|
||||||
Add(catcher = value);
|
private readonly Container<Catcher> catcherContainer;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
|
private readonly CatcherTrailDisplay catcherTrails;
|
||||||
|
|
||||||
private Catcher catcher;
|
private Catcher catcher;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -45,13 +43,20 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int currentDirection;
|
private int currentDirection;
|
||||||
|
|
||||||
|
// TODO: support replay rewind
|
||||||
|
private bool lastHyperDashState;
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <see cref="Catcher"/> must be set before loading.
|
/// <see cref="Catcher"/> must be set before loading.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public CatcherArea()
|
public CatcherArea()
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, Catcher.BASE_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, Catcher.BASE_SIZE);
|
||||||
Child = comboDisplay = new CatchComboDisplay
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
catcherContainer = new Container<Catcher> { RelativeSizeAxes = Axes.Both },
|
||||||
|
catcherTrails = new CatcherTrailDisplay(),
|
||||||
|
comboDisplay = new CatchComboDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
@ -59,6 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Margin = new MarginPadding { Bottom = 350f },
|
Margin = new MarginPadding { Bottom = 350f },
|
||||||
X = CatchPlayfield.CENTER_X
|
X = CatchPlayfield.CENTER_X
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +108,27 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
comboDisplay.X = Catcher.X;
|
comboDisplay.X = Catcher.X;
|
||||||
|
|
||||||
|
if (Time.Elapsed <= 0)
|
||||||
|
{
|
||||||
|
// This is probably a wrong value, but currently the true value is not recorded.
|
||||||
|
// Setting `true` will prevent generation of false-positive after-images (with more false-negatives).
|
||||||
|
lastHyperDashState = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lastHyperDashState && Catcher.HyperDashing)
|
||||||
|
displayCatcherTrail(CatcherTrailAnimation.HyperDashAfterImage);
|
||||||
|
|
||||||
|
if (Catcher.Dashing || Catcher.HyperDashing)
|
||||||
|
{
|
||||||
|
double generationInterval = Catcher.HyperDashing ? 25 : 50;
|
||||||
|
|
||||||
|
if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval)
|
||||||
|
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHyperDashState = Catcher.HyperDashing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCatcherPosition(float X)
|
public void SetCatcherPosition(float X)
|
||||||
@ -154,5 +181,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayCatcherTrail(CatcherTrailAnimation animation) => catcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Pooling;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
@ -12,13 +12,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// A trail of the catcher.
|
/// A trail of the catcher.
|
||||||
/// It also represents a hyper dash afterimage.
|
/// It also represents a hyper dash afterimage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CatcherTrail : PoolableDrawable
|
public class CatcherTrail : PoolableDrawableWithLifetime<CatcherTrailEntry>
|
||||||
{
|
{
|
||||||
public CatcherAnimationState AnimationState
|
|
||||||
{
|
|
||||||
set => body.AnimationState.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly SkinnableCatcher body;
|
private readonly SkinnableCatcher body;
|
||||||
|
|
||||||
public CatcherTrail()
|
public CatcherTrail()
|
||||||
@ -34,11 +29,40 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FreeAfterUse()
|
protected override void OnApply(CatcherTrailEntry entry)
|
||||||
{
|
{
|
||||||
|
Position = new Vector2(entry.Position, 0);
|
||||||
|
Scale = entry.Scale;
|
||||||
|
|
||||||
|
body.AnimationState.Value = entry.CatcherState;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart, false))
|
||||||
|
applyTransforms(entry.Animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree(CatcherTrailEntry entry)
|
||||||
|
{
|
||||||
|
ApplyTransformsAt(double.MinValue);
|
||||||
ClearTransforms();
|
ClearTransforms();
|
||||||
Alpha = 1;
|
}
|
||||||
base.FreeAfterUse();
|
|
||||||
|
private void applyTransforms(CatcherTrailAnimation animation)
|
||||||
|
{
|
||||||
|
switch (animation)
|
||||||
|
{
|
||||||
|
case CatcherTrailAnimation.Dashing:
|
||||||
|
case CatcherTrailAnimation.HyperDashing:
|
||||||
|
this.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CatcherTrailAnimation.HyperDashAfterImage:
|
||||||
|
this.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
|
||||||
|
this.ScaleTo(Scale * 0.95f).ScaleTo(Scale * 1.2f, 1200, Easing.In);
|
||||||
|
this.FadeOut(1200);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
osu.Game.Rulesets.Catch/UI/CatcherTrailAnimation.cs
Normal file
12
osu.Game.Rulesets.Catch/UI/CatcherTrailAnimation.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public enum CatcherTrailAnimation
|
||||||
|
{
|
||||||
|
Dashing,
|
||||||
|
HyperDashing,
|
||||||
|
HyperDashAfterImage
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using JetBrains.Annotations;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osuTK;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
@ -15,70 +17,32 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// Represents a component responsible for displaying
|
/// Represents a component responsible for displaying
|
||||||
/// the appropriate catcher trails when requested to.
|
/// the appropriate catcher trails when requested to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CatcherTrailDisplay : CompositeDrawable
|
public class CatcherTrailDisplay : PooledDrawableWithLifetimeContainer<CatcherTrailEntry, CatcherTrail>
|
||||||
{
|
{
|
||||||
private readonly Catcher catcher;
|
/// <summary>
|
||||||
|
/// The most recent time a dash trail was added to this container.
|
||||||
|
/// Only alive (not faded out) trails are considered.
|
||||||
|
/// Returns <see cref="double.NegativeInfinity"/> if no dash trail is alive.
|
||||||
|
/// </summary>
|
||||||
|
public double LastDashTrailTime => getLastDashTrailTime();
|
||||||
|
|
||||||
|
public Color4 HyperDashTrailsColour => hyperDashTrails.Colour;
|
||||||
|
|
||||||
|
public Color4 HyperDashAfterImageColour => hyperDashAfterImages.Colour;
|
||||||
|
|
||||||
|
protected override bool RemoveRewoundEntry => true;
|
||||||
|
|
||||||
private readonly DrawablePool<CatcherTrail> trailPool;
|
private readonly DrawablePool<CatcherTrail> trailPool;
|
||||||
|
|
||||||
private readonly Container<CatcherTrail> dashTrails;
|
private readonly Container<CatcherTrail> dashTrails;
|
||||||
private readonly Container<CatcherTrail> hyperDashTrails;
|
private readonly Container<CatcherTrail> hyperDashTrails;
|
||||||
private readonly Container<CatcherTrail> endGlowSprites;
|
private readonly Container<CatcherTrail> hyperDashAfterImages;
|
||||||
|
|
||||||
private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
[Resolved]
|
||||||
|
private ISkinSource skin { get; set; }
|
||||||
|
|
||||||
public Color4 HyperDashTrailsColour
|
public CatcherTrailDisplay()
|
||||||
{
|
{
|
||||||
get => hyperDashTrailsColour;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (hyperDashTrailsColour == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
hyperDashTrailsColour = value;
|
|
||||||
hyperDashTrails.Colour = hyperDashTrailsColour;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
|
||||||
|
|
||||||
public Color4 EndGlowSpritesColour
|
|
||||||
{
|
|
||||||
get => endGlowSpritesColour;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (endGlowSpritesColour == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
endGlowSpritesColour = value;
|
|
||||||
endGlowSprites.Colour = endGlowSpritesColour;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool trail;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to start displaying trails following the catcher.
|
|
||||||
/// </summary>
|
|
||||||
public bool DisplayTrail
|
|
||||||
{
|
|
||||||
get => trail;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (trail == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
trail = value;
|
|
||||||
|
|
||||||
if (trail)
|
|
||||||
displayTrail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CatcherTrailDisplay([NotNull] Catcher catcher)
|
|
||||||
{
|
|
||||||
this.catcher = catcher ?? throw new ArgumentNullException(nameof(catcher));
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
@ -86,47 +50,86 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
trailPool = new DrawablePool<CatcherTrail>(30),
|
trailPool = new DrawablePool<CatcherTrail>(30),
|
||||||
dashTrails = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both },
|
dashTrails = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both },
|
||||||
hyperDashTrails = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
hyperDashTrails = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||||
endGlowSprites = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
hyperDashAfterImages = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override void LoadComplete()
|
||||||
/// Displays a single end-glow catcher sprite.
|
|
||||||
/// </summary>
|
|
||||||
public void DisplayEndGlow()
|
|
||||||
{
|
{
|
||||||
var endGlow = createTrailSprite(endGlowSprites);
|
base.LoadComplete();
|
||||||
|
|
||||||
endGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
|
skin.SourceChanged += skinSourceChanged;
|
||||||
endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In);
|
skinSourceChanged();
|
||||||
endGlow.FadeOut(1200);
|
|
||||||
endGlow.Expire(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayTrail()
|
private void skinSourceChanged()
|
||||||
{
|
{
|
||||||
if (!DisplayTrail)
|
hyperDashTrails.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ?? Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||||
return;
|
hyperDashAfterImages.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashTrails.Colour;
|
||||||
|
|
||||||
var sprite = createTrailSprite(catcher.HyperDashing ? hyperDashTrails : dashTrails);
|
|
||||||
|
|
||||||
sprite.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
|
|
||||||
sprite.Expire(true);
|
|
||||||
|
|
||||||
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CatcherTrail createTrailSprite(Container<CatcherTrail> target)
|
protected override void AddDrawable(CatcherTrailEntry entry, CatcherTrail drawable)
|
||||||
{
|
{
|
||||||
CatcherTrail sprite = trailPool.Get();
|
switch (entry.Animation)
|
||||||
|
{
|
||||||
|
case CatcherTrailAnimation.Dashing:
|
||||||
|
dashTrails.Add(drawable);
|
||||||
|
break;
|
||||||
|
|
||||||
sprite.AnimationState = catcher.CurrentState;
|
case CatcherTrailAnimation.HyperDashing:
|
||||||
sprite.Scale = catcher.Scale * catcher.Body.Scale;
|
hyperDashTrails.Add(drawable);
|
||||||
sprite.Position = catcher.Position;
|
break;
|
||||||
|
|
||||||
target.Add(sprite);
|
case CatcherTrailAnimation.HyperDashAfterImage:
|
||||||
|
hyperDashAfterImages.Add(drawable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sprite;
|
protected override void RemoveDrawable(CatcherTrailEntry entry, CatcherTrail drawable)
|
||||||
|
{
|
||||||
|
switch (entry.Animation)
|
||||||
|
{
|
||||||
|
case CatcherTrailAnimation.Dashing:
|
||||||
|
dashTrails.Remove(drawable);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CatcherTrailAnimation.HyperDashing:
|
||||||
|
hyperDashTrails.Remove(drawable);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CatcherTrailAnimation.HyperDashAfterImage:
|
||||||
|
hyperDashAfterImages.Remove(drawable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CatcherTrail GetDrawable(CatcherTrailEntry entry)
|
||||||
|
{
|
||||||
|
CatcherTrail trail = trailPool.Get();
|
||||||
|
trail.Apply(entry);
|
||||||
|
return trail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getLastDashTrailTime()
|
||||||
|
{
|
||||||
|
double maxTime = double.NegativeInfinity;
|
||||||
|
|
||||||
|
foreach (var trail in dashTrails)
|
||||||
|
maxTime = Math.Max(maxTime, trail.LifetimeStart);
|
||||||
|
|
||||||
|
foreach (var trail in hyperDashTrails)
|
||||||
|
maxTime = Math.Max(maxTime, trail.LifetimeStart);
|
||||||
|
|
||||||
|
return maxTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (skin != null)
|
||||||
|
skin.SourceChanged -= skinSourceChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
osu.Game.Rulesets.Catch/UI/CatcherTrailEntry.cs
Normal file
31
osu.Game.Rulesets.Catch/UI/CatcherTrailEntry.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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.Performance;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public class CatcherTrailEntry : LifetimeEntry
|
||||||
|
{
|
||||||
|
public readonly CatcherAnimationState CatcherState;
|
||||||
|
|
||||||
|
public readonly float Position;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scaling of the catcher body. It also represents a flipped catcher (negative x component).
|
||||||
|
/// </summary>
|
||||||
|
public readonly Vector2 Scale;
|
||||||
|
|
||||||
|
public readonly CatcherTrailAnimation Animation;
|
||||||
|
|
||||||
|
public CatcherTrailEntry(double startTime, CatcherAnimationState catcherState, float position, Vector2 scale, CatcherTrailAnimation animation)
|
||||||
|
{
|
||||||
|
LifetimeStart = startTime;
|
||||||
|
CatcherState = catcherState;
|
||||||
|
Position = position;
|
||||||
|
Scale = scale;
|
||||||
|
Animation = animation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,129 +1,56 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// 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;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
||||||
{
|
{
|
||||||
private readonly CircularContainer largeFaint;
|
private readonly SkinnableDrawable skinnableExplosion;
|
||||||
private readonly CircularContainer smallFaint;
|
|
||||||
private readonly CircularContainer directionalGlow1;
|
|
||||||
private readonly CircularContainer directionalGlow2;
|
|
||||||
|
|
||||||
public HitExplosion()
|
public HitExplosion()
|
||||||
{
|
{
|
||||||
Size = new Vector2(20);
|
RelativeSizeAxes = Axes.Both;
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
// scale roughly in-line with visual appearance of notes
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
|
||||||
const float initial_height = 10;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
{
|
||||||
largeFaint = new CircularContainer
|
CentreComponent = false,
|
||||||
{
|
Anchor = Anchor.BottomCentre,
|
||||||
Anchor = Anchor.Centre,
|
Origin = Anchor.BottomCentre
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
smallFaint = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
directionalGlow1 = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
directionalGlow2 = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply(HitExplosionEntry entry)
|
protected override void OnApply(HitExplosionEntry entry)
|
||||||
{
|
{
|
||||||
X = entry.Position;
|
base.OnApply(entry);
|
||||||
Scale = new Vector2(entry.Scale);
|
if (IsLoaded)
|
||||||
setColour(entry.ObjectColour);
|
apply(entry);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
|
||||||
applyTransforms(entry.RNGSeed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTransforms(int randomSeed)
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
apply(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(HitExplosionEntry? entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplyTransformsAt(double.MinValue, true);
|
||||||
ClearTransforms(true);
|
ClearTransforms(true);
|
||||||
|
|
||||||
const double duration = 400;
|
(skinnableExplosion.Drawable as IHitExplosion)?.Animate(entry);
|
||||||
|
LifetimeEnd = skinnableExplosion.Drawable.LatestTransformEndTime;
|
||||||
// we want our size to be very small so the glow dominates it.
|
|
||||||
largeFaint.Size = new Vector2(0.8f);
|
|
||||||
largeFaint
|
|
||||||
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
|
||||||
.FadeOut(duration * 2);
|
|
||||||
|
|
||||||
const float angle_variangle = 15; // should be less than 45
|
|
||||||
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
|
||||||
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
|
||||||
|
|
||||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setColour(Color4 objectColour)
|
|
||||||
{
|
|
||||||
const float roundness = 100;
|
|
||||||
|
|
||||||
largeFaint.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
|
||||||
Roundness = 160,
|
|
||||||
Radius = 200,
|
|
||||||
};
|
|
||||||
|
|
||||||
smallFaint.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
|
||||||
Roundness = 20,
|
|
||||||
Radius = 50,
|
|
||||||
};
|
|
||||||
|
|
||||||
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
|
||||||
Roundness = roundness,
|
|
||||||
Radius = 40,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public HitExplosionContainer()
|
public HitExplosionContainer()
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,24 +2,42 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosionEntry : LifetimeEntry
|
public class HitExplosionEntry : LifetimeEntry
|
||||||
{
|
{
|
||||||
public readonly float Position;
|
/// <summary>
|
||||||
public readonly float Scale;
|
/// The judgement result that triggered this explosion.
|
||||||
public readonly Color4 ObjectColour;
|
/// </summary>
|
||||||
public readonly int RNGSeed;
|
public JudgementResult JudgementResult { get; }
|
||||||
|
|
||||||
public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
|
/// <summary>
|
||||||
|
/// The hitobject which triggered this explosion.
|
||||||
|
/// </summary>
|
||||||
|
public CatchHitObject HitObject => (CatchHitObject)JudgementResult.HitObject;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The accent colour of the object caught.
|
||||||
|
/// </summary>
|
||||||
|
public Color4 ObjectColour { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The position at which the object was caught.
|
||||||
|
/// </summary>
|
||||||
|
public float Position { get; }
|
||||||
|
|
||||||
|
public HitExplosionEntry(double startTime, JudgementResult judgementResult, Color4 objectColour, float position)
|
||||||
{
|
{
|
||||||
LifetimeStart = startTime;
|
LifetimeStart = startTime;
|
||||||
Position = position;
|
Position = position;
|
||||||
Scale = scale;
|
JudgementResult = judgementResult;
|
||||||
ObjectColour = objectColour;
|
ObjectColour = objectColour;
|
||||||
RNGSeed = rngSeed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
Normal file
18
osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for all hit explosion skinnables.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHitExplosion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Begins animating this <see cref="IHitExplosion"/>.
|
||||||
|
/// </summary>
|
||||||
|
void Animate(HitExplosionEntry entry);
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public class Strain : StrainSkill
|
public class Strain : StrainDecaySkill
|
||||||
{
|
{
|
||||||
private const double individual_decay_base = 0.125;
|
private const double individual_decay_base = 0.125;
|
||||||
private const double overall_decay_base = 0.30;
|
private const double overall_decay_base = 0.30;
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
return individualStrain + overallStrain - CurrentStrain;
|
return individualStrain + overallStrain - CurrentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetPeakStrain(double offset)
|
protected override double CalculateInitialStrain(double offset)
|
||||||
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
|
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
|
||||||
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
|
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
|
||||||
|
|
||||||
|
@ -253,7 +253,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
case ModType.Fun:
|
case ModType.Fun:
|
||||||
return new Mod[]
|
return new Mod[]
|
||||||
{
|
{
|
||||||
new MultiMod(new ModWindUp(), new ModWindDown())
|
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||||
|
new ManiaModMuted(),
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -10,13 +10,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModMirror : Mod, IApplicableToBeatmap
|
public class ManiaModMirror : ModMirror, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Name => "Mirror";
|
|
||||||
public override string Acronym => "MR";
|
|
||||||
public override ModType Type => ModType.Conversion;
|
|
||||||
public override string Description => "Notes are flipped horizontally.";
|
public override string Description => "Notes are flipped horizontally.";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
12
osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs
Normal file
12
osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModMuted : ModMuted<ManiaHitObject>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
52
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs
Normal file
52
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs
Normal file
@ -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 System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModMuted : OsuModTestScene
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a final volume combo of 0 (i.e. "always muted" mode) constantly plays metronome and completely mutes track.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestZeroFinalCombo() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModMuted
|
||||||
|
{
|
||||||
|
MuteComboCount = { Value = 0 },
|
||||||
|
},
|
||||||
|
PassCondition = () => Beatmap.Value.Track.AggregateVolume.Value == 0.0 &&
|
||||||
|
Player.ChildrenOfType<Metronome>().SingleOrDefault()?.AggregateVolume.Value == 1.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that copying from a normal mod with 0 final combo while originally inversed does not yield incorrect results.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestModCopy()
|
||||||
|
{
|
||||||
|
OsuModMuted muted = null;
|
||||||
|
|
||||||
|
AddStep("create inversed mod", () => muted = new OsuModMuted
|
||||||
|
{
|
||||||
|
MuteComboCount = { Value = 100 },
|
||||||
|
InverseMuting = { Value = true },
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("copy from normal", () => muted.CopyFrom(new OsuModMuted
|
||||||
|
{
|
||||||
|
MuteComboCount = { Value = 0 },
|
||||||
|
InverseMuting = { Value = false },
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddAssert("mute combo count = 0", () => muted.MuteComboCount.Value == 0);
|
||||||
|
AddAssert("inverse muting = false", () => muted.InverseMuting.Value == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ using osu.Framework.Utils;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public abstract class OsuStrainSkill : StrainSkill
|
public abstract class OsuStrainSkill : StrainDecaySkill
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
||||||
|
@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
protected override GameplayCursorContainer CreateCursor() => null;
|
protected override GameplayCursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
|
public OsuEditorPlayfield()
|
||||||
|
{
|
||||||
|
HitPolicy = new AnyOrderHitPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
@ -58,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (hitObject is DrawableHitCircle circle)
|
if (hitObject is DrawableHitCircle circle)
|
||||||
|
{
|
||||||
|
using (circle.BeginAbsoluteSequence(circle.HitStateUpdateTime))
|
||||||
{
|
{
|
||||||
circle.ApproachCircle
|
circle.ApproachCircle
|
||||||
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
|
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
|
||||||
@ -65,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
|
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hitObject is IHasMainCirclePiece mainPieceContainer)
|
if (hitObject is IHasMainCirclePiece mainPieceContainer)
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
|
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield, drawableRuleset.Beatmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||||
@ -128,8 +129,21 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
float start, end;
|
||||||
float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
|
||||||
|
if (Precision.AlmostEquals(restrictTo.Rotation, 0))
|
||||||
|
{
|
||||||
|
start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
||||||
|
end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent).X;
|
||||||
|
float halfDiagonal = (restrictTo.DrawSize / 2).LengthFast;
|
||||||
|
|
||||||
|
start = center - halfDiagonal;
|
||||||
|
end = center + halfDiagonal;
|
||||||
|
}
|
||||||
|
|
||||||
float rawWidth = end - start;
|
float rawWidth = end - start;
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -15,23 +14,13 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => 1.06;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
|
||||||
|
|
||||||
public void ApplyToHitObject(HitObject hitObject)
|
public void ApplyToHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
var osuObject = (OsuHitObject)hitObject;
|
var osuObject = (OsuHitObject)hitObject;
|
||||||
|
|
||||||
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
|
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
|
||||||
|
|
||||||
if (!(hitObject is Slider slider))
|
|
||||||
return;
|
|
||||||
|
|
||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
|
||||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
|
||||||
|
|
||||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
|
||||||
foreach (var point in controlPoints)
|
|
||||||
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
|
|
||||||
|
|
||||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
Normal file
50
osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModMirror : ModMirror, IApplicableToHitObject
|
||||||
|
{
|
||||||
|
public override string Description => "Flip objects on the chosen axes.";
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
||||||
|
|
||||||
|
[SettingSource("Mirrored axes", "Choose which axes objects are mirrored over.")]
|
||||||
|
public Bindable<MirrorType> Reflection { get; } = new Bindable<MirrorType>();
|
||||||
|
|
||||||
|
public void ApplyToHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
var osuObject = (OsuHitObject)hitObject;
|
||||||
|
|
||||||
|
switch (Reflection.Value)
|
||||||
|
{
|
||||||
|
case MirrorType.Horizontal:
|
||||||
|
OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MirrorType.Vertical:
|
||||||
|
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MirrorType.Both:
|
||||||
|
OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
|
||||||
|
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MirrorType
|
||||||
|
{
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
Both
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs
Normal file
12
osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModMuted : ModMuted<OsuHitObject>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -16,7 +14,6 @@ using osu.Game.Beatmaps.ControlPoints;
|
|||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -29,7 +26,6 @@ using osu.Game.Rulesets.Osu.UI;
|
|||||||
using osu.Game.Rulesets.Osu.Utils;
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -67,11 +63,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float distance_cap = 380f;
|
private const float distance_cap = 380f;
|
||||||
|
|
||||||
// The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle.
|
|
||||||
// The closer the hit objects draw to the border, the sharper the turn
|
|
||||||
private const byte border_distance_x = 192;
|
|
||||||
private const byte border_distance_y = 144;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The extent of rotation towards playfield centre when a circle is near the edge
|
/// The extent of rotation towards playfield centre when a circle is near the edge
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -341,46 +332,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Overlays.Add(new TargetBeatContainer(drawableRuleset.Beatmap.HitObjects.First().StartTime));
|
drawableRuleset.Overlays.Add(new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime));
|
||||||
}
|
|
||||||
|
|
||||||
public class TargetBeatContainer : BeatSyncedContainer
|
|
||||||
{
|
|
||||||
private readonly double firstHitTime;
|
|
||||||
|
|
||||||
private PausableSkinnableSound sample;
|
|
||||||
|
|
||||||
public TargetBeatContainer(double firstHitTime)
|
|
||||||
{
|
|
||||||
this.firstHitTime = firstHitTime;
|
|
||||||
AllowMistimedEventFiring = false;
|
|
||||||
Divisor = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
|
||||||
{
|
|
||||||
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
|
|
||||||
|
|
||||||
if (!IsBeatSyncedWithTrack) return;
|
|
||||||
|
|
||||||
int timeSignature = (int)timingPoint.TimeSignature;
|
|
||||||
|
|
||||||
// play metronome from one measure before the first object.
|
|
||||||
if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature)
|
|
||||||
return;
|
|
||||||
|
|
||||||
sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f;
|
|
||||||
sample.Play();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -166,6 +166,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModDifficultyAdjust(),
|
new OsuModDifficultyAdjust(),
|
||||||
new OsuModClassic(),
|
new OsuModClassic(),
|
||||||
new OsuModRandom(),
|
new OsuModRandom(),
|
||||||
|
new OsuModMirror(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -188,6 +189,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModTraceable(),
|
new OsuModTraceable(),
|
||||||
new OsuModBarrelRoll(),
|
new OsuModBarrelRoll(),
|
||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
|
new OsuModMuted(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
private double lastTrailTime;
|
private double lastTrailTime;
|
||||||
private IBindable<float> cursorSize;
|
private IBindable<float> cursorSize;
|
||||||
|
|
||||||
|
private Vector2? currentPosition;
|
||||||
|
|
||||||
public LegacyCursorTrail(ISkin skin)
|
public LegacyCursorTrail(ISkin skin)
|
||||||
{
|
{
|
||||||
this.skin = skin;
|
this.skin = skin;
|
||||||
@ -54,22 +57,34 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override double FadeDuration => disjointTrail ? 150 : 500;
|
protected override double FadeDuration => disjointTrail ? 150 : 500;
|
||||||
|
protected override float FadeExponent => 1;
|
||||||
|
|
||||||
protected override bool InterpolateMovements => !disjointTrail;
|
protected override bool InterpolateMovements => !disjointTrail;
|
||||||
|
|
||||||
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
|
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!disjointTrail || !currentPosition.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
|
||||||
|
{
|
||||||
|
lastTrailTime = Time.Current;
|
||||||
|
AddTrail(currentPosition.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
if (!disjointTrail)
|
if (!disjointTrail)
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
|
|
||||||
if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
|
currentPosition = e.ScreenSpaceMousePosition;
|
||||||
{
|
|
||||||
lastTrailTime = Time.Current;
|
|
||||||
return base.OnMouseMove(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Intentionally block the base call as we're adding the trails ourselves.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
// Roughly matches osu!stable's slider border portions.
|
// Roughly matches osu!stable's slider border portions.
|
||||||
=> base.CalculatedBorderPortion * 0.77f;
|
=> base.CalculatedBorderPortion * 0.77f;
|
||||||
|
|
||||||
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
|
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.7f);
|
||||||
|
|
||||||
protected override Color4 ColourAt(float position)
|
protected override Color4 ColourAt(float position)
|
||||||
{
|
{
|
||||||
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Color4 outerColour = AccentColour.Darken(0.1f);
|
Color4 outerColour = AccentColour.Darken(0.1f);
|
||||||
Color4 innerColour = lighten(AccentColour, 0.5f);
|
Color4 innerColour = lighten(AccentColour, 0.5f);
|
||||||
|
|
||||||
return Interpolation.ValueAt(position / realGradientPortion, outerColour, innerColour, 0, 1);
|
return LegacyUtils.InterpolateNonLinear(position / realGradientPortion, outerColour, innerColour, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
22
osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
Normal file
22
osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IHitPolicy"/> which allows hitobjects to be hit in any order.
|
||||||
|
/// </summary>
|
||||||
|
public class AnyOrderHitPolicy : IHitPolicy
|
||||||
|
{
|
||||||
|
public IHitObjectContainer HitObjectContainer { get; set; }
|
||||||
|
|
||||||
|
public bool IsHittable(DrawableHitObject hitObject, double time) => true;
|
||||||
|
|
||||||
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
private const int max_sprites = 2048;
|
private const int max_sprites = 2048;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An exponentiating factor to ease the trail fade.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual float FadeExponent => 1.7f;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private int currentIndex;
|
private int currentIndex;
|
||||||
private IShader shader;
|
private IShader shader;
|
||||||
@ -141,22 +146,25 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
Vector2 pos = e.ScreenSpaceMousePosition;
|
AddTrail(e.ScreenSpaceMousePosition);
|
||||||
|
|
||||||
if (lastPosition == null)
|
|
||||||
{
|
|
||||||
lastPosition = pos;
|
|
||||||
resampler.AddPosition(lastPosition.Value);
|
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Vector2 pos2 in resampler.AddPosition(pos))
|
protected void AddTrail(Vector2 position)
|
||||||
|
{
|
||||||
|
if (InterpolateMovements)
|
||||||
|
{
|
||||||
|
if (!lastPosition.HasValue)
|
||||||
|
{
|
||||||
|
lastPosition = position;
|
||||||
|
resampler.AddPosition(lastPosition.Value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Vector2 pos2 in resampler.AddPosition(position))
|
||||||
{
|
{
|
||||||
Trace.Assert(lastPosition.HasValue);
|
Trace.Assert(lastPosition.HasValue);
|
||||||
|
|
||||||
if (InterpolateMovements)
|
|
||||||
{
|
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
|
||||||
Vector2 pos1 = lastPosition.Value;
|
Vector2 pos1 = lastPosition.Value;
|
||||||
Vector2 diff = pos2 - pos1;
|
Vector2 diff = pos2 - pos1;
|
||||||
float distance = diff.Length;
|
float distance = diff.Length;
|
||||||
@ -170,16 +178,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
addPart(lastPosition.Value);
|
addPart(lastPosition.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lastPosition = pos2;
|
lastPosition = position;
|
||||||
addPart(lastPosition.Value);
|
addPart(lastPosition.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnMouseMove(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addPart(Vector2 screenSpacePosition)
|
private void addPart(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
parts[currentIndex].Position = screenSpacePosition;
|
parts[currentIndex].Position = screenSpacePosition;
|
||||||
@ -206,10 +212,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private Texture texture;
|
private Texture texture;
|
||||||
|
|
||||||
private float time;
|
private float time;
|
||||||
|
private float fadeExponent;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
|
|
||||||
private Vector2 originPosition;
|
private Vector2 originPosition;
|
||||||
|
|
||||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
@ -227,6 +233,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.partSize;
|
size = Source.partSize;
|
||||||
time = Source.time;
|
time = Source.time;
|
||||||
|
fadeExponent = Source.FadeExponent;
|
||||||
|
|
||||||
originPosition = Vector2.Zero;
|
originPosition = Vector2.Zero;
|
||||||
|
|
||||||
@ -249,6 +256,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||||
|
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
||||||
|
|
||||||
texture.TextureGL.Bind();
|
texture.TextureGL.Bind();
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
private void onJudgementLoaded(DrawableOsuJudgement judgement)
|
private void onJudgementLoaded(DrawableOsuJudgement judgement)
|
||||||
{
|
{
|
||||||
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
|
judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -150,6 +150,10 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
|
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
|
||||||
|
|
||||||
judgementLayer.Add(explosion);
|
judgementLayer.Add(explosion);
|
||||||
|
|
||||||
|
// the proxied content is added to judgementAboveHitObjectLayer once, on first load, and never removed from it.
|
||||||
|
// ensure that ordering is consistent with expectations (latest judgement should be front-most).
|
||||||
|
judgementAboveHitObjectLayer.ChangeChildDepth(explosion.ProxiedAboveHitObjectsContent, (float)-result.TimeAbsolute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Utils
|
namespace osu.Game.Rulesets.Osu.Utils
|
||||||
@ -100,5 +104,47 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
initial.Length * MathF.Sin(finalAngleRad)
|
initial.Length * MathF.Sin(finalAngleRad)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reflects the position of the <see cref="OsuHitObject"/> in the playfield horizontally.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="osuObject">The object to reflect.</param>
|
||||||
|
public static void ReflectHorizontally(OsuHitObject osuObject)
|
||||||
|
{
|
||||||
|
osuObject.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - osuObject.X, osuObject.Position.Y);
|
||||||
|
|
||||||
|
if (!(osuObject is Slider slider))
|
||||||
|
return;
|
||||||
|
|
||||||
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||||
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||||
|
|
||||||
|
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y);
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reflects the position of the <see cref="OsuHitObject"/> in the playfield vertically.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="osuObject">The object to reflect.</param>
|
||||||
|
public static void ReflectVertically(OsuHitObject osuObject)
|
||||||
|
{
|
||||||
|
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
|
||||||
|
|
||||||
|
if (!(osuObject is Slider slider))
|
||||||
|
return;
|
||||||
|
|
||||||
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
|
|
||||||
|
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the colour coefficient of taiko difficulty.
|
/// Calculates the colour coefficient of taiko difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Colour : StrainSkill
|
public class Colour : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
protected override double StrainDecayBase => 0.4;
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the rhythm coefficient of taiko difficulty.
|
/// Calculates the rhythm coefficient of taiko difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Rhythm : StrainSkill
|
public class Rhythm : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 10;
|
protected override double SkillMultiplier => 10;
|
||||||
protected override double StrainDecayBase => 0;
|
protected override double StrainDecayBase => 0;
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
|
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class Stamina : StrainSkill
|
public class Stamina : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
protected override double StrainDecayBase => 0.4;
|
||||||
|
@ -2,10 +2,30 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModClassic : ModClassic
|
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
|
||||||
{
|
{
|
||||||
|
private DrawableTaikoRuleset drawableTaikoRuleset;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
// Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
|
||||||
|
const float scroll_rate = 10;
|
||||||
|
|
||||||
|
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
||||||
|
float ratio = drawableTaikoRuleset.DrawHeight / 480;
|
||||||
|
|
||||||
|
drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,23 +12,11 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModHidden : ModHidden, IApplicableToDifficulty
|
public class TaikoModHidden : ModHidden
|
||||||
{
|
{
|
||||||
public override string Description => @"Beats fade out before you hit them!";
|
public override string Description => @"Beats fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => 1.06;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// In osu-stable, the hit position is 160, so the active playfield is essentially 160 pixels shorter
|
|
||||||
/// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the
|
|
||||||
/// playfield ratio of the active area up to the hit position will actually be (640 - 160) / 480 = 1.
|
|
||||||
/// For custom resolutions/aspect ratios (x:y), the screen width given the normalized height becomes 480 * x / y instead,
|
|
||||||
/// and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3.
|
|
||||||
/// This constant is equal to the playfield ratio on 4:3 screens divided by the playfield ratio on 16:9 screens.
|
|
||||||
/// </summary>
|
|
||||||
private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0);
|
|
||||||
|
|
||||||
private double originalSliderMultiplier;
|
|
||||||
|
|
||||||
private ControlPointInfo controlPointInfo;
|
private ControlPointInfo controlPointInfo;
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
@ -41,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
double beatLength = controlPointInfo.TimingPointAt(position).BeatLength;
|
double beatLength = controlPointInfo.TimingPointAt(position).BeatLength;
|
||||||
double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier;
|
double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier;
|
||||||
|
|
||||||
return originalSliderMultiplier * speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength;
|
return speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
@ -69,22 +57,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
// needs to be read after all processing has been run (TaikoBeatmapConverter applies an adjustment which would otherwise be omitted).
|
|
||||||
originalSliderMultiplier = difficulty.SliderMultiplier;
|
|
||||||
|
|
||||||
// osu-stable has an added playfield cover that essentially forces a 4:3 playfield ratio, by cutting off all objects past that size.
|
|
||||||
// This is not yet implemented; instead a playfield adjustment container is present which maintains a 16:9 ratio.
|
|
||||||
// For now, increase the slider multiplier proportionally so that the notes stay on the screen for the same amount of time as on stable.
|
|
||||||
// Note that this means that the notes will scroll faster as they have a longer distance to travel on the screen in that same amount of time.
|
|
||||||
difficulty.SliderMultiplier /= hd_sv_scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyToBeatmap(IBeatmap beatmap)
|
public override void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
controlPointInfo = beatmap.ControlPointInfo;
|
controlPointInfo = beatmap.ControlPointInfo;
|
||||||
|
12
osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs
Normal file
12
osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
|
{
|
||||||
|
public class TaikoModMuted : ModMuted<TaikoHitObject>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -149,7 +149,8 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
case ModType.Fun:
|
case ModType.Fun:
|
||||||
return new Mod[]
|
return new Mod[]
|
||||||
{
|
{
|
||||||
new MultiMod(new ModWindUp(), new ModWindDown())
|
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||||
|
new TaikoModMuted(),
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -24,12 +25,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
public class DrawableTaikoRuleset : DrawableScrollingRuleset<TaikoHitObject>
|
public class DrawableTaikoRuleset : DrawableScrollingRuleset<TaikoHitObject>
|
||||||
{
|
{
|
||||||
private SkinnableDrawable scroller;
|
public new BindableDouble TimeRange => base.TimeRange;
|
||||||
|
|
||||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
||||||
|
|
||||||
protected override bool UserScrollSpeedAdjustment => false;
|
protected override bool UserScrollSpeedAdjustment => false;
|
||||||
|
|
||||||
|
private SkinnableDrawable scroller;
|
||||||
|
|
||||||
public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
|
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float DEFAULT_HEIGHT = 178;
|
public const float DEFAULT_HEIGHT = 212;
|
||||||
|
|
||||||
private Container<HitExplosion> hitExplosionContainer;
|
private Container<HitExplosion> hitExplosionContainer;
|
||||||
private Container<KiaiHitExplosion> kiaiExplosionContainer;
|
private Container<KiaiHitExplosion> kiaiExplosionContainer;
|
||||||
|
@ -169,7 +169,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
protected override ISkin GetSkin() => throw new NotImplementedException();
|
protected internal override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -19,6 +20,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
{
|
{
|
||||||
private ChannelManager channelManager;
|
private ChannelManager channelManager;
|
||||||
private int currentMessageId;
|
private int currentMessageId;
|
||||||
|
private List<Message> sentMessages;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
@ -34,6 +36,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddStep("register request handling", () =>
|
AddStep("register request handling", () =>
|
||||||
{
|
{
|
||||||
currentMessageId = 0;
|
currentMessageId = 0;
|
||||||
|
sentMessages = new List<Message>();
|
||||||
|
|
||||||
((DummyAPIAccess)API).HandleRequest = req =>
|
((DummyAPIAccess)API).HandleRequest = req =>
|
||||||
{
|
{
|
||||||
@ -44,16 +47,11 @@ namespace osu.Game.Tests.Chat
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case PostMessageRequest postMessage:
|
case PostMessageRequest postMessage:
|
||||||
postMessage.TriggerSuccess(new Message(++currentMessageId)
|
handlePostMessageRequest(postMessage);
|
||||||
{
|
return true;
|
||||||
IsAction = postMessage.Message.IsAction,
|
|
||||||
ChannelId = postMessage.Message.ChannelId,
|
|
||||||
Content = postMessage.Message.Content,
|
|
||||||
Links = postMessage.Message.Links,
|
|
||||||
Timestamp = postMessage.Message.Timestamp,
|
|
||||||
Sender = postMessage.Message.Sender
|
|
||||||
});
|
|
||||||
|
|
||||||
|
case MarkChannelAsReadRequest markRead:
|
||||||
|
handleMarkChannelAsReadRequest(markRead);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,12 +81,65 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
|
AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMarkAsReadIgnoringLocalMessages()
|
||||||
|
{
|
||||||
|
Channel channel = null;
|
||||||
|
|
||||||
|
AddStep("join channel and select it", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("post message", () => channelManager.PostMessage("Something interesting"));
|
||||||
|
|
||||||
|
AddStep("post /help command", () => channelManager.PostCommand("help", channel));
|
||||||
|
AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel));
|
||||||
|
AddStep("post /join command with no channel", () => channelManager.PostCommand("join", channel));
|
||||||
|
AddStep("post /join command with non-existent channel", () => channelManager.PostCommand("join i-dont-exist", channel));
|
||||||
|
AddStep("post non-existent command", () => channelManager.PostCommand("non-existent-cmd arg", channel));
|
||||||
|
|
||||||
|
AddStep("mark channel as read", () => channelManager.MarkChannelAsRead(channel));
|
||||||
|
AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePostMessageRequest(PostMessageRequest request)
|
||||||
|
{
|
||||||
|
var message = new Message(++currentMessageId)
|
||||||
|
{
|
||||||
|
IsAction = request.Message.IsAction,
|
||||||
|
ChannelId = request.Message.ChannelId,
|
||||||
|
Content = request.Message.Content,
|
||||||
|
Links = request.Message.Links,
|
||||||
|
Timestamp = request.Message.Timestamp,
|
||||||
|
Sender = request.Message.Sender
|
||||||
|
};
|
||||||
|
|
||||||
|
sentMessages.Add(message);
|
||||||
|
request.TriggerSuccess(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMarkChannelAsReadRequest(MarkChannelAsReadRequest request)
|
||||||
|
{
|
||||||
|
// only accept messages that were sent through the API
|
||||||
|
if (sentMessages.Contains(request.Message))
|
||||||
|
{
|
||||||
|
request.TriggerSuccess();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.TriggerFailure(new APIException("unknown message!", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Channel createChannel(int id, ChannelType type) => new Channel(new User())
|
private Channel createChannel(int id, ChannelType type) => new Channel(new User())
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Name = $"Channel {id}",
|
Name = $"Channel {id}",
|
||||||
Topic = $"Topic of channel {id} with type {type}",
|
Topic = $"Topic of channel {id} with type {type}",
|
||||||
Type = type,
|
Type = type,
|
||||||
|
LastMessageId = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
private class ChannelManagerContainer : CompositeDrawable
|
private class ChannelManagerContainer : CompositeDrawable
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Gameplay
|
namespace osu.Game.Tests.Gameplay
|
||||||
@ -121,6 +123,18 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999);
|
AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStateChangeBeforeLoadComplete()
|
||||||
|
{
|
||||||
|
TestDrawableHitObject dho = null;
|
||||||
|
AddStep("Add DHO and apply result", () =>
|
||||||
|
{
|
||||||
|
Child = dho = new TestDrawableHitObject(new HitObject { StartTime = Time.Current });
|
||||||
|
dho.MissForcefully();
|
||||||
|
});
|
||||||
|
AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDrawableHitObject : DrawableHitObject
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
{
|
{
|
||||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||||
@ -141,6 +155,19 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
if (SetLifetimeStartOnApply)
|
if (SetLifetimeStartOnApply)
|
||||||
LifetimeStart = LIFETIME_ON_APPLY;
|
LifetimeStart = LIFETIME_ON_APPLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
||||||
|
|
||||||
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
{
|
||||||
|
if (state != ArmedState.Miss)
|
||||||
|
{
|
||||||
|
base.UpdateHitStateTransforms(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOut(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
||||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
|
protected internal override ISkin GetSkin() => new TestSkin("test-sample", resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
||||||
|
@ -24,6 +24,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
|||||||
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
|
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
|
||||||
checkPlayingUserCount(0);
|
checkPlayingUserCount(0);
|
||||||
|
|
||||||
|
AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
|
||||||
|
|
||||||
changeState(3, MultiplayerUserState.WaitingForLoad);
|
changeState(3, MultiplayerUserState.WaitingForLoad);
|
||||||
checkPlayingUserCount(3);
|
checkPlayingUserCount(3);
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
|||||||
|
|
||||||
AddStep("leave room", () => Client.LeaveRoom());
|
AddStep("leave room", () => Client.LeaveRoom());
|
||||||
checkPlayingUserCount(0);
|
checkPlayingUserCount(0);
|
||||||
|
|
||||||
|
AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
41
osu.Game.Tests/NonVisual/TimeDisplayExtensionTest.cs
Normal file
41
osu.Game.Tests/NonVisual/TimeDisplayExtensionTest.cs
Normal file
@ -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 System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.NonVisual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TimeDisplayExtensionTest
|
||||||
|
{
|
||||||
|
private static readonly object[][] editor_formatted_duration_tests =
|
||||||
|
{
|
||||||
|
new object[] { new TimeSpan(0, 0, 0, 0, 50), "00:00:050" },
|
||||||
|
new object[] { new TimeSpan(0, 0, 0, 10, 50), "00:10:050" },
|
||||||
|
new object[] { new TimeSpan(0, 0, 5, 10), "05:10:000" },
|
||||||
|
new object[] { new TimeSpan(0, 1, 5, 10), "65:10:000" },
|
||||||
|
};
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(editor_formatted_duration_tests))]
|
||||||
|
public void TestEditorFormat(TimeSpan input, string expectedOutput)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expectedOutput, input.ToEditorFormattedString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object[][] formatted_duration_tests =
|
||||||
|
{
|
||||||
|
new object[] { new TimeSpan(0, 0, 10), "00:10" },
|
||||||
|
new object[] { new TimeSpan(0, 5, 10), "05:10" },
|
||||||
|
new object[] { new TimeSpan(1, 5, 10), "01:05:10" },
|
||||||
|
new object[] { new TimeSpan(1, 1, 5, 10), "01:01:05:10" },
|
||||||
|
};
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(formatted_duration_tests))]
|
||||||
|
public void TestFormattedDuration(TimeSpan input, string expectedOutput)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expectedOutput, input.ToFormattedDuration().ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -168,8 +168,8 @@ namespace osu.Game.Tests.Online
|
|||||||
|
|
||||||
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await AllowImport.Task;
|
await AllowImport.Task.ConfigureAwait(false);
|
||||||
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken));
|
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,10 +50,10 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk"));
|
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk"));
|
||||||
|
|
||||||
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(1));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(1));
|
||||||
|
|
||||||
// the first should be overwritten by the second import.
|
// the first should be overwritten by the second import.
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -76,10 +76,10 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
|
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
|
||||||
|
|
||||||
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(2));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
|
||||||
|
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -101,10 +101,10 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk"));
|
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk"));
|
||||||
|
|
||||||
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(2));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
|
||||||
|
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
|
||||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -133,11 +133,12 @@ namespace osu.Game.Tests.Skins
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEmptyComboColoursNoFallback()
|
public void TestEmptyComboColoursNoFallback()
|
||||||
{
|
{
|
||||||
AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours(
|
AddStep("Add custom combo colours to user skin", () => userSource.Configuration.CustomComboColours = new List<Color4>
|
||||||
|
{
|
||||||
new Color4(100, 150, 200, 255),
|
new Color4(100, 150, 200, 255),
|
||||||
new Color4(55, 110, 166, 255),
|
new Color4(55, 110, 166, 255),
|
||||||
new Color4(75, 125, 175, 255)
|
new Color4(75, 125, 175, 255)
|
||||||
));
|
});
|
||||||
|
|
||||||
AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false);
|
AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false);
|
||||||
|
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Colours
|
||||||
|
{
|
||||||
|
public class TestSceneStarDifficultyColours : OsuTestScene
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestColours()
|
||||||
|
{
|
||||||
|
AddStep("load colour displays", () =>
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5f),
|
||||||
|
ChildrenEnumerable = Enumerable.Range(0, 10).Select(i => new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(10f),
|
||||||
|
ChildrenEnumerable = Enumerable.Range(0, 10).Select(j =>
|
||||||
|
{
|
||||||
|
var colour = colours.ForStarDifficulty(1f * i + 0.1f * j);
|
||||||
|
|
||||||
|
return new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0f, 10f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Size = new Vector2(75f, 25f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colour,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = OsuColour.ForegroundTextColourFor(colour),
|
||||||
|
Text = colour.ToHex(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = $"*{(1f * i + 0.1f * j):0.00}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -15,6 +16,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Components
|
namespace osu.Game.Tests.Visual.Components
|
||||||
{
|
{
|
||||||
|
[HeadlessTest]
|
||||||
public class TestScenePollingComponent : OsuTestScene
|
public class TestScenePollingComponent : OsuTestScene
|
||||||
{
|
{
|
||||||
private Container pollBox;
|
private Container pollBox;
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Enabled.Value);
|
AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Enabled.Value);
|
||||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||||
AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Enabled.Value);
|
AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Enabled.Value);
|
||||||
AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Click());
|
AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").TriggerClick());
|
||||||
AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType<ComposeBlueprintContainer>().First().CurrentTool is HitCircleCompositionTool);
|
AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType<ComposeBlueprintContainer>().First().CurrentTool is HitCircleCompositionTool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
this.beatmapSkin = beatmapSkin;
|
this.beatmapSkin = beatmapSkin;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ISkin GetSkin() => beatmapSkin;
|
protected internal override ISkin GetSkin() => beatmapSkin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestOsuRuleset : OsuRuleset
|
private class TestOsuRuleset : OsuRuleset
|
||||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
|
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
|
||||||
|
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
|
||||||
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,19 +84,38 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
|
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMaxHeight()
|
||||||
|
{
|
||||||
|
int playerNumber = 1;
|
||||||
|
AddRepeatStep("add 3 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 3);
|
||||||
|
checkHeight(4);
|
||||||
|
|
||||||
|
AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
|
||||||
|
checkHeight(8);
|
||||||
|
|
||||||
|
AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
|
||||||
|
checkHeight(8);
|
||||||
|
|
||||||
|
void checkHeight(int panelCount)
|
||||||
|
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
|
||||||
|
}
|
||||||
|
|
||||||
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
||||||
|
|
||||||
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
|
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
|
||||||
{
|
{
|
||||||
var leaderboardScore = leaderboard.AddPlayer(user, isTracked);
|
var leaderboardScore = leaderboard.Add(user, isTracked);
|
||||||
leaderboardScore.TotalScore.BindTo(score);
|
leaderboardScore.TotalScore.BindTo(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestGameplayLeaderboard : GameplayLeaderboard
|
private class TestGameplayLeaderboard : GameplayLeaderboard
|
||||||
{
|
{
|
||||||
|
public float Spacing => Flow.Spacing.Y;
|
||||||
|
|
||||||
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
||||||
{
|
{
|
||||||
var scoreItem = this.FirstOrDefault(i => i.User?.Username == username);
|
var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username);
|
||||||
|
|
||||||
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
|
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var lastAction = pauseOverlay.OnRetry;
|
var lastAction = pauseOverlay.OnRetry;
|
||||||
pauseOverlay.OnRetry = () => triggered = true;
|
pauseOverlay.OnRetry = () => triggered = true;
|
||||||
|
|
||||||
getButton(1).Click();
|
getButton(1).TriggerClick();
|
||||||
pauseOverlay.OnRetry = lastAction;
|
pauseOverlay.OnRetry = lastAction;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,12 +12,15 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
|
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
|
private OsuConfigManager localConfig;
|
||||||
|
|
||||||
private HUDOverlay hudOverlay;
|
private HUDOverlay hudOverlay;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
@ -30,8 +33,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
||||||
|
|
||||||
[Resolved]
|
[BackgroundDependencyLoader]
|
||||||
private OsuConfigManager config { get; set; }
|
private void load()
|
||||||
|
{
|
||||||
|
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboCounterIncrementing()
|
public void TestComboCounterIncrementing()
|
||||||
@ -84,11 +93,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
|
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
||||||
|
|
||||||
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
|
||||||
|
|
||||||
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
|
||||||
|
|
||||||
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
||||||
|
|
||||||
@ -97,37 +102,28 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
|
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||||
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
||||||
|
|
||||||
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExternalHideDoesntAffectConfig()
|
public void TestExternalHideDoesntAffectConfig()
|
||||||
{
|
{
|
||||||
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
|
|
||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
|
||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
|
||||||
|
|
||||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestChangeHUDVisibilityOnHiddenKeyCounter()
|
public void TestChangeHUDVisibilityOnHiddenKeyCounter()
|
||||||
{
|
{
|
||||||
bool keyCounterVisibleValue = false;
|
|
||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
AddStep("save keycounter visible value", () => keyCounterVisibleValue = config.Get<bool>(OsuSetting.KeyOverlay));
|
|
||||||
|
|
||||||
AddStep("set keycounter visible false", () =>
|
AddStep("hide key overlay", () =>
|
||||||
{
|
{
|
||||||
config.SetValue(OsuSetting.KeyOverlay, false);
|
localConfig.SetValue(OsuSetting.KeyOverlay, false);
|
||||||
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
|
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,8 +134,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||||
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
|
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
|
||||||
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
|
[Test]
|
||||||
|
public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
|
||||||
|
{
|
||||||
|
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
||||||
|
|
||||||
|
createNew();
|
||||||
|
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||||
|
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNew(Action<HUDOverlay> action = null)
|
private void createNew(Action<HUDOverlay> action = null)
|
||||||
@ -158,5 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Child = hudOverlay;
|
Child = hudOverlay;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
localConfig?.Dispose();
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Online.Spectator;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
using osu.Game.Tests.Visual.Multiplayer;
|
using osu.Game.Tests.Visual.Multiplayer;
|
||||||
@ -25,41 +26,43 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" };
|
private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" };
|
||||||
|
|
||||||
[Cached(typeof(SpectatorClient))]
|
|
||||||
private TestSpectatorClient testSpectatorClient = new TestSpectatorClient();
|
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
[Cached(typeof(UserLookupCache))]
|
||||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
// used just to show beatmap card for the time being.
|
// used just to show beatmap card for the time being.
|
||||||
protected override bool UseOnlineAPI => true;
|
protected override bool UseOnlineAPI => true;
|
||||||
|
|
||||||
private SoloSpectator spectatorScreen;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
private BeatmapSetInfo importedBeatmap;
|
private TestSpectatorClient spectatorClient;
|
||||||
|
private SoloSpectator spectatorScreen;
|
||||||
|
|
||||||
|
private BeatmapSetInfo importedBeatmap;
|
||||||
private int importedBeatmapId;
|
private int importedBeatmapId;
|
||||||
|
|
||||||
public override void SetUpSteps()
|
[SetUpSteps]
|
||||||
|
public void SetupSteps()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
DependenciesScreen dependenciesScreen = null;
|
||||||
|
|
||||||
|
AddStep("load dependencies", () =>
|
||||||
|
{
|
||||||
|
spectatorClient = new TestSpectatorClient();
|
||||||
|
|
||||||
|
// The screen gets suspended so it stops receiving updates.
|
||||||
|
Child = spectatorClient;
|
||||||
|
|
||||||
|
LoadScreen(dependenciesScreen = new DependenciesScreen(spectatorClient));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
|
||||||
|
|
||||||
AddStep("import beatmap", () =>
|
AddStep("import beatmap", () =>
|
||||||
{
|
{
|
||||||
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
|
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
|
||||||
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1;
|
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add streaming client", () =>
|
|
||||||
{
|
|
||||||
Remove(testSpectatorClient);
|
|
||||||
Add(testSpectatorClient);
|
|
||||||
});
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -206,22 +209,36 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
||||||
|
|
||||||
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||||
|
|
||||||
private void finish() => AddStep("end play", () => testSpectatorClient.EndPlay(streamingUser.Id));
|
private void finish() => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id));
|
||||||
|
|
||||||
private void checkPaused(bool state) =>
|
private void checkPaused(bool state) =>
|
||||||
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
||||||
|
|
||||||
private void sendFrames(int count = 10)
|
private void sendFrames(int count = 10)
|
||||||
{
|
{
|
||||||
AddStep("send frames", () => testSpectatorClient.SendFrames(streamingUser.Id, count));
|
AddStep("send frames", () => spectatorClient.SendFrames(streamingUser.Id, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSpectatingScreen()
|
private void loadSpectatingScreen()
|
||||||
{
|
{
|
||||||
AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser)));
|
AddStep("load spectator", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser)));
|
||||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
|
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for the sole purpose of adding <see cref="TestSpectatorClient"/> as a resolvable dependency.
|
||||||
|
/// </summary>
|
||||||
|
private class DependenciesScreen : OsuScreen
|
||||||
|
{
|
||||||
|
[Cached(typeof(SpectatorClient))]
|
||||||
|
public readonly TestSpectatorClient Client;
|
||||||
|
|
||||||
|
public DependenciesScreen(TestSpectatorClient client)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
Normal file
42
osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Tests.Visual.Navigation;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
public class TestSceneSideOverlays : OsuGameTestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddAssert("no screen offset applied", () => Game.ScreenOffsetContainer.X == 0f);
|
||||||
|
AddUntilStep("wait for overlays", () => Game.Settings.IsLoaded && Game.Notifications.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScreenOffsettingOnSettingsOverlay()
|
||||||
|
{
|
||||||
|
AddStep("open settings", () => Game.Settings.Show());
|
||||||
|
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
|
||||||
|
|
||||||
|
AddStep("hide settings", () => Game.Settings.Hide());
|
||||||
|
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScreenOffsettingOnNotificationOverlay()
|
||||||
|
{
|
||||||
|
AddStep("open notifications", () => Game.Notifications.Show());
|
||||||
|
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
|
||||||
|
|
||||||
|
AddStep("hide notifications", () => Game.Notifications.Hide());
|
||||||
|
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs
Normal file
55
osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneModFailCondition : ModTestScene
|
||||||
|
{
|
||||||
|
private bool restartRequested;
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override TestPlayer CreateModPlayer(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
var player = base.CreateModPlayer(ruleset);
|
||||||
|
player.RestartRequested = () => restartRequested = true;
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
AddStep("reset flag", () => restartRequested = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRestartOnFailDisabled() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Autoplay = false,
|
||||||
|
Mod = new OsuModSuddenDeath(),
|
||||||
|
PassCondition = () => !restartRequested && Player.ChildrenOfType<FailOverlay>().Single().State.Value == Visibility.Visible
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRestartOnFailEnabled() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Autoplay = false,
|
||||||
|
Mod = new OsuModSuddenDeath
|
||||||
|
{
|
||||||
|
Restart = { Value = true }
|
||||||
|
},
|
||||||
|
PassCondition = () => restartRequested && Player.ChildrenOfType<FailOverlay>().Single().State.Value == Visibility.Hidden
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
168
osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
Normal file
168
osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Online.Rooms.RoomStatuses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableRoom : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleStatuses()
|
||||||
|
{
|
||||||
|
AddStep("create rooms", () =>
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.9f),
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Flyte's Trash Playlist" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StarDifficulty = 2.5
|
||||||
|
}
|
||||||
|
}.BeatmapInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 2" },
|
||||||
|
Status = { Value = new RoomStatusPlaying() },
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StarDifficulty = 2.5
|
||||||
|
}
|
||||||
|
}.BeatmapInfo,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StarDifficulty = 4.5
|
||||||
|
}
|
||||||
|
}.BeatmapInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 3" },
|
||||||
|
Status = { Value = new RoomStatusEnded() },
|
||||||
|
EndDate = { Value = DateTimeOffset.Now },
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 4 (realtime)" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Category = { Value = RoomCategory.Realtime },
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 4 (spotlight)" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Category = { Value = RoomCategory.Spotlight },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEnableAndDisablePassword()
|
||||||
|
{
|
||||||
|
DrawableRoom drawableRoom = null;
|
||||||
|
Room room = null;
|
||||||
|
|
||||||
|
AddStep("create room", () => Child = drawableRoom = createDrawableRoom(room = new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room with password" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Category = { Value = RoomCategory.Realtime },
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||||
|
|
||||||
|
AddStep("set password", () => room.Password.Value = "password");
|
||||||
|
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||||
|
|
||||||
|
AddStep("unset password", () => room.Password.Value = string.Empty);
|
||||||
|
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableRoom createDrawableRoom(Room room)
|
||||||
|
{
|
||||||
|
room.Host.Value ??= new User { Username = "peppy", Id = 2 };
|
||||||
|
|
||||||
|
if (room.RecentParticipants.Count == 0)
|
||||||
|
{
|
||||||
|
room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new User
|
||||||
|
{
|
||||||
|
Id = i,
|
||||||
|
Username = $"User {i}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawableRoom = new DrawableRoom(room) { MatchingFilter = true };
|
||||||
|
drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
|
||||||
|
|
||||||
|
return drawableRoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
assertDownloadButtonVisible(false);
|
assertDownloadButtonVisible(false);
|
||||||
|
|
||||||
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
|
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
|
||||||
() => playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().Single().Alpha == (visible ? 1 : 0));
|
() => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().Single().Alpha == (visible ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
createPlaylist(byOnlineId, byChecksum);
|
createPlaylist(byOnlineId, byChecksum);
|
||||||
|
|
||||||
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().All(d => d.IsPresent));
|
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().All(d => d.IsPresent));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -1,49 +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 NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
|
||||||
{
|
|
||||||
public class TestSceneLoungeRoomInfo : OnlinePlayTestScene
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public new void Setup() => Schedule(() =>
|
|
||||||
{
|
|
||||||
SelectedRoom.Value = new Room();
|
|
||||||
|
|
||||||
Child = new RoomInfo
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Width = 500
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNonSelectedRoom()
|
|
||||||
{
|
|
||||||
AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestOpenRoom()
|
|
||||||
{
|
|
||||||
AddStep("set open room", () =>
|
|
||||||
{
|
|
||||||
SelectedRoom.Value.RoomID.Value = 0;
|
|
||||||
SelectedRoom.Value.Name.Value = "Room 0";
|
|
||||||
SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 };
|
|
||||||
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
|
|
||||||
SelectedRoom.Value.Status.Value = new RoomStatusOpen();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
|
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
|
||||||
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
|
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
|
||||||
|
|
||||||
AddStep("select first room", () => container.Rooms.First().Click());
|
AddStep("select first room", () => container.Rooms.First().TriggerClick());
|
||||||
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
|
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var room = RoomManager.Rooms[1];
|
var room = RoomManager.Rooms[1];
|
||||||
|
|
||||||
RoomManager.RemoveRoom(room);
|
RoomManager.RemoveRoom(room);
|
||||||
RoomManager.AddRoom(room);
|
RoomManager.AddOrUpdateRoom(room);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("no selection", () => checkRoomSelected(null));
|
AddAssert("no selection", () => checkRoomSelected(null));
|
||||||
@ -115,11 +115,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
|
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
|
||||||
|
|
||||||
AddStep("filter one room", () => container.Filter(new FilterCriteria { SearchString = "1" }));
|
AddStep("filter one room", () => container.Filter.Value = new FilterCriteria { SearchString = "1" });
|
||||||
|
|
||||||
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
|
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
|
||||||
|
|
||||||
AddStep("remove filter", () => container.Filter(null));
|
AddStep("remove filter", () => container.Filter.Value = null);
|
||||||
|
|
||||||
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
|
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
|
||||||
}
|
}
|
||||||
@ -131,13 +131,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
|
AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
|
||||||
|
|
||||||
// Todo: What even is this case...?
|
// Todo: What even is this case...?
|
||||||
AddStep("set empty filter criteria", () => container.Filter(null));
|
AddStep("set empty filter criteria", () => container.Filter.Value = null);
|
||||||
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
|
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
|
||||||
|
|
||||||
AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }));
|
AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo });
|
||||||
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
|
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
|
||||||
|
|
||||||
AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo }));
|
AddStep("filter catch rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo });
|
||||||
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
|
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,11 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -31,7 +33,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (userId, _) in clocks)
|
foreach (var (userId, _) in clocks)
|
||||||
|
{
|
||||||
SpectatorClient.StartPlay(userId, 0);
|
SpectatorClient.StartPlay(userId, 0);
|
||||||
|
OnlinePlayDependencies.Client.AddUser(new User { Id = userId });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create leaderboard", () =>
|
AddStep("create leaderboard", () =>
|
||||||
@ -41,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var scoreProcessor = new OsuScoreProcessor();
|
var scoreProcessor = new OsuScoreProcessor();
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
scoreProcessor.ApplyBeatmap(playable);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
|
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
@ -6,12 +6,19 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -20,12 +27,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
private BeatmapManager beatmapManager { get; set; }
|
||||||
|
|
||||||
private MultiSpectatorScreen spectatorScreen;
|
private MultiSpectatorScreen spectatorScreen;
|
||||||
|
|
||||||
private readonly List<int> playingUserIds = new List<int>();
|
private readonly List<MultiplayerRoomUser> playingUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
private BeatmapSetInfo importedSet;
|
private BeatmapSetInfo importedSet;
|
||||||
private BeatmapInfo importedBeatmap;
|
private BeatmapInfo importedBeatmap;
|
||||||
@ -40,17 +50,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public new void Setup() => Schedule(() => playingUserIds.Clear());
|
public new void Setup() => Schedule(() => playingUsers.Clear());
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDelayedStart()
|
public void TestDelayedStart()
|
||||||
{
|
{
|
||||||
AddStep("start players silently", () =>
|
AddStep("start players silently", () =>
|
||||||
{
|
{
|
||||||
Client.CurrentMatchPlayingUserIds.Add(PLAYER_1_ID);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
|
||||||
Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
||||||
playingUserIds.Add(PLAYER_1_ID);
|
|
||||||
playingUserIds.Add(PLAYER_2_ID);
|
playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
|
||||||
|
playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
|
||||||
});
|
});
|
||||||
|
|
||||||
loadSpectateScreen(false);
|
loadSpectateScreen(false);
|
||||||
@ -76,6 +87,64 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddWaitStep("wait a bit", 20);
|
AddWaitStep("wait a bit", 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpectatorPlayerInteractiveElementsHidden()
|
||||||
|
{
|
||||||
|
HUDVisibilityMode originalConfigValue = default;
|
||||||
|
|
||||||
|
AddStep("get original config hud visibility", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
||||||
|
AddStep("set config hud visibility to always", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
|
||||||
|
|
||||||
|
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||||
|
loadSpectateScreen(false);
|
||||||
|
|
||||||
|
AddUntilStep("wait for player loaders", () => this.ChildrenOfType<PlayerLoader>().Count() == 2);
|
||||||
|
AddAssert("all player loader settings hidden", () => this.ChildrenOfType<PlayerLoader>().All(l => !l.ChildrenOfType<FillFlowContainer<PlayerSettingsGroup>>().Any()));
|
||||||
|
|
||||||
|
AddUntilStep("wait for players to load", () => spectatorScreen.AllPlayersLoaded);
|
||||||
|
|
||||||
|
// components wrapped in skinnable target containers load asynchronously, potentially taking more than one frame to load.
|
||||||
|
// therefore use until step rather than direct assert to account for that.
|
||||||
|
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p =>
|
||||||
|
!p.ChildrenOfType<PlayerSettingsOverlay>().Any() &&
|
||||||
|
!p.ChildrenOfType<HoldForMenuButton>().Any() &&
|
||||||
|
p.ChildrenOfType<SongProgressBar>().SingleOrDefault()?.ShowHandle == false));
|
||||||
|
|
||||||
|
AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTeamDisplay()
|
||||||
|
{
|
||||||
|
AddStep("start players", () =>
|
||||||
|
{
|
||||||
|
var player1 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
|
||||||
|
player1.MatchState = new TeamVersusUserState
|
||||||
|
{
|
||||||
|
TeamID = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
var player2 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
||||||
|
player2.MatchState = new TeamVersusUserState
|
||||||
|
{
|
||||||
|
TeamID = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
SpectatorClient.StartPlay(player1.UserID, importedBeatmapId);
|
||||||
|
SpectatorClient.StartPlay(player2.UserID, importedBeatmapId);
|
||||||
|
|
||||||
|
playingUsers.Add(player1);
|
||||||
|
playingUsers.Add(player2);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadSpectateScreen();
|
||||||
|
|
||||||
|
sendFrames(PLAYER_1_ID, 1000);
|
||||||
|
sendFrames(PLAYER_2_ID, 1000);
|
||||||
|
|
||||||
|
AddWaitStep("wait a bit", 20);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
|
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
|
||||||
{
|
{
|
||||||
@ -252,7 +321,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
||||||
Ruleset.Value = importedBeatmap.Ruleset;
|
Ruleset.Value = importedBeatmap.Ruleset;
|
||||||
|
|
||||||
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUserIds.ToArray()));
|
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray()));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
|
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
|
||||||
@ -264,9 +333,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
foreach (int id in userIds)
|
foreach (int id in userIds)
|
||||||
{
|
{
|
||||||
Client.CurrentMatchPlayingUserIds.Add(id);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
|
||||||
|
|
||||||
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
||||||
playingUserIds.Add(id);
|
playingUsers.Add(new MultiplayerRoomUser(id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -24,7 +25,7 @@ using osu.Game.Screens;
|
|||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
@ -43,6 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private TestMultiplayer multiplayerScreen;
|
private TestMultiplayer multiplayerScreen;
|
||||||
private TestMultiplayerClient client;
|
private TestMultiplayerClient client;
|
||||||
|
|
||||||
|
private TestRequestHandlingMultiplayerRoomManager roomManager => multiplayerScreen.RoomManager;
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
[Cached(typeof(UserLookupCache))]
|
||||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
@ -67,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("load dependencies", () =>
|
AddStep("load dependencies", () =>
|
||||||
{
|
{
|
||||||
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
|
client = new TestMultiplayerClient(roomManager);
|
||||||
|
|
||||||
// The screen gets suspended so it stops receiving updates.
|
// The screen gets suspended so it stops receiving updates.
|
||||||
Child = client;
|
Child = client;
|
||||||
@ -86,6 +89,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public void TestEmpty()
|
public void TestEmpty()
|
||||||
{
|
{
|
||||||
// used to test the flow of multiplayer from visual tests.
|
// used to test the flow of multiplayer from visual tests.
|
||||||
|
AddStep("empty step", () => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateRoomViaKeyboard()
|
||||||
|
{
|
||||||
|
// create room dialog
|
||||||
|
AddStep("Press new document", () => InputManager.Keys(PlatformAction.DocumentNew));
|
||||||
|
AddUntilStep("wait for settings", () => InputManager.ChildrenOfType<MultiplayerMatchSettingsOverlay>().FirstOrDefault() != null);
|
||||||
|
|
||||||
|
// edit playlist item
|
||||||
|
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||||
|
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault() != null);
|
||||||
|
|
||||||
|
// select beatmap
|
||||||
|
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||||
|
AddUntilStep("wait for return to screen", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault() == null);
|
||||||
|
|
||||||
|
// create room
|
||||||
|
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
AddUntilStep("wait for join", () => client.Room != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -108,11 +134,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitMidJoin()
|
public void TestExitMidJoin()
|
||||||
{
|
{
|
||||||
Room room = null;
|
|
||||||
|
|
||||||
AddStep("create room", () =>
|
AddStep("create room", () =>
|
||||||
{
|
{
|
||||||
room = new Room
|
roomManager.AddServerSideRoom(new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
Playlist =
|
Playlist =
|
||||||
@ -123,14 +147,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
|
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||||
|
|
||||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||||
AddStep("join room and immediately exit", () =>
|
AddStep("join room and immediately exit select", () =>
|
||||||
{
|
{
|
||||||
multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room);
|
InputManager.Key(Key.Enter);
|
||||||
Schedule(() => Stack.CurrentScreen.Exit());
|
Schedule(() => Stack.CurrentScreen.Exit());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -140,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
AddStep("create room", () =>
|
AddStep("create room", () =>
|
||||||
{
|
{
|
||||||
multiplayerScreen.RoomManager.AddRoom(new Room
|
roomManager.AddServerSideRoom(new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
Playlist =
|
Playlist =
|
||||||
@ -154,7 +180,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
|
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||||
|
|
||||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
@ -187,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
AddStep("create room", () =>
|
AddStep("create room", () =>
|
||||||
{
|
{
|
||||||
multiplayerScreen.RoomManager.AddRoom(new Room
|
roomManager.AddServerSideRoom(new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
Password = { Value = "password" },
|
Password = { Value = "password" },
|
||||||
@ -202,14 +230,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
|
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||||
|
|
||||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
|
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
|
||||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().Click());
|
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||||
|
|
||||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
AddUntilStep("wait for join", () => client.Room != null);
|
AddUntilStep("wait for join", () => client.Room != null);
|
||||||
@ -289,6 +319,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddStep("start match externally", () => client.StartMatch());
|
AddStep("start match externally", () => client.StartMatch());
|
||||||
|
|
||||||
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
|
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
|
||||||
@ -325,6 +357,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddStep("start match externally", () => client.StartMatch());
|
AddStep("start match externally", () => client.StartMatch());
|
||||||
|
|
||||||
AddStep("restore beatmap", () =>
|
AddStep("restore beatmap", () =>
|
||||||
@ -373,7 +407,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("open mod overlay", () => this.ChildrenOfType<PurpleTriangleButton>().ElementAt(2).Click());
|
AddStep("open mod overlay", () => this.ChildrenOfType<RoomSubScreen.UserModSelectButton>().Single().TriggerClick());
|
||||||
|
|
||||||
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
||||||
|
|
||||||
@ -381,8 +415,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
testLeave("lounge tab item", () => this.ChildrenOfType<BreadcrumbControl<IScreen>.BreadcrumbTabItem>().First().Click());
|
|
||||||
|
|
||||||
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
||||||
|
|
||||||
// mimics home button and OS window close
|
// mimics home button and OS window close
|
||||||
@ -400,10 +432,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void createRoom(Func<Room> room)
|
private void createRoom(Func<Room> room)
|
||||||
{
|
{
|
||||||
AddStep("open room", () =>
|
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||||
{
|
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||||
multiplayerScreen.OpenNewRoom(room());
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
AddWaitStep("wait for transition", 2);
|
AddWaitStep("wait for transition", 2);
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
@ -20,6 +21,7 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
using osu.Game.Tests.Visual.Spectator;
|
using osu.Game.Tests.Visual.Spectator;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -50,22 +52,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
OsuScoreProcessor scoreProcessor;
|
OsuScoreProcessor scoreProcessor;
|
||||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
var multiplayerUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
foreach (var user in users)
|
foreach (var user in users)
|
||||||
|
{
|
||||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
|
multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true));
|
||||||
// Todo: This is REALLY bad.
|
}
|
||||||
Client.CurrentMatchPlayingUserIds.AddRange(users);
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
scoreProcessor = new OsuScoreProcessor(),
|
scoreProcessor = new OsuScoreProcessor(),
|
||||||
};
|
};
|
||||||
|
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray())
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
using osu.Game.Tests.Visual.Spectator;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerGameplayLeaderboardTeams : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
private static IEnumerable<int> users => Enumerable.Range(0, 16);
|
||||||
|
|
||||||
|
public new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient SpectatorClient =>
|
||||||
|
(TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient;
|
||||||
|
|
||||||
|
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
|
||||||
|
|
||||||
|
protected class TestDependencies : MultiplayerTestSceneDependencies
|
||||||
|
{
|
||||||
|
protected override TestSpectatorClient CreateSpectatorClient() => new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiplayerGameplayLeaderboard leaderboard;
|
||||||
|
private GameplayMatchScoreDisplay gameplayScoreDisplay;
|
||||||
|
|
||||||
|
protected override Room CreateRoom()
|
||||||
|
{
|
||||||
|
var room = base.CreateRoom();
|
||||||
|
room.Type.Value = MatchType.TeamVersus;
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
|
||||||
|
|
||||||
|
AddStep("create leaderboard", () =>
|
||||||
|
{
|
||||||
|
leaderboard?.Expire();
|
||||||
|
|
||||||
|
OsuScoreProcessor scoreProcessor;
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
|
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
var multiplayerUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
|
var roomUser = OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
|
||||||
|
|
||||||
|
roomUser.MatchState = new TeamVersusUserState
|
||||||
|
{
|
||||||
|
TeamID = RNG.Next(0, 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
multiplayerUsers.Add(roomUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
scoreProcessor = new OsuScoreProcessor(),
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}, gameplayLeaderboard =>
|
||||||
|
{
|
||||||
|
LoadComponentAsync(new MatchScoreDisplay
|
||||||
|
{
|
||||||
|
Team1Score = { BindTarget = leaderboard.TeamScores[0] },
|
||||||
|
Team2Score = { BindTarget = leaderboard.TeamScores[1] }
|
||||||
|
}, Add);
|
||||||
|
|
||||||
|
LoadComponentAsync(gameplayScoreDisplay = new GameplayMatchScoreDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Team1Score = { BindTarget = leaderboard.TeamScores[0] },
|
||||||
|
Team2Score = { BindTarget = leaderboard.TeamScores[1] }
|
||||||
|
}, Add);
|
||||||
|
|
||||||
|
Add(gameplayLeaderboard);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreUpdates()
|
||||||
|
{
|
||||||
|
AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100);
|
||||||
|
AddToggleStep("switch compact mode", expanded =>
|
||||||
|
{
|
||||||
|
leaderboard.Expanded.Value = expanded;
|
||||||
|
gameplayScoreDisplay.Expanded.Value = expanded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().Click());
|
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||||
|
|
||||||
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
|
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
|
||||||
AddAssert("room join password correct", () => lastJoinedPassword == "password");
|
AddAssert("room join password correct", () => lastJoinedPassword == "password");
|
||||||
|
@ -129,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
|
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
AddStep("click ready button", () =>
|
AddStep("click ready button", () =>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -48,9 +49,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
|
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
|
||||||
|
|
||||||
AddStep("add non-resolvable user", () => Client.AddNullUser(-3));
|
AddStep("add non-resolvable user", () => Client.AddNullUser());
|
||||||
|
AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
|
||||||
|
|
||||||
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
|
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
|
||||||
|
|
||||||
|
AddStep("kick null user", () => this.ChildrenOfType<ParticipantPanel>().Single(p => p.User.User == null)
|
||||||
|
.ChildrenOfType<ParticipantPanel.KickButton>().Single().TriggerClick());
|
||||||
|
|
||||||
|
AddAssert("null user kicked", () => Client.Room.AsNonNull().Users.Count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -155,6 +162,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKickButtonOnlyPresentWhenHost()
|
||||||
|
{
|
||||||
|
AddStep("add user", () => Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "Second",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
|
||||||
|
|
||||||
|
AddStep("make second user host", () => Client.TransferHost(3));
|
||||||
|
|
||||||
|
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0);
|
||||||
|
|
||||||
|
AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id));
|
||||||
|
|
||||||
|
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKickButtonKicks()
|
||||||
|
{
|
||||||
|
AddStep("add user", () => Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "Second",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("kick second user", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Single(d => d.IsPresent).TriggerClick());
|
||||||
|
|
||||||
|
AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestManyUsers()
|
public void TestManyUsers()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
// 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.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerResults : ScreenTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDisplayResults()
|
||||||
|
{
|
||||||
|
MultiplayerResultsScreen screen = null;
|
||||||
|
|
||||||
|
AddStep("show results screen", () =>
|
||||||
|
{
|
||||||
|
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||||
|
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
|
||||||
|
|
||||||
|
var score = new ScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
TotalScore = 987654,
|
||||||
|
Accuracy = 0.8,
|
||||||
|
MaxCombo = 500,
|
||||||
|
Combo = 250,
|
||||||
|
Beatmap = beatmapInfo,
|
||||||
|
User = new User { Username = "Test user" },
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
OnlineScoreID = 12345,
|
||||||
|
Ruleset = rulesetInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
PlaylistItem playlistItem = new PlaylistItem
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapInfo.ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,157 +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 NUnit.Framework;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
|
||||||
using osu.Game.Tests.Beatmaps;
|
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
|
||||||
{
|
|
||||||
[HeadlessTest]
|
|
||||||
public class TestSceneMultiplayerRoomManager : MultiplayerTestScene
|
|
||||||
{
|
|
||||||
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
|
|
||||||
|
|
||||||
public TestSceneMultiplayerRoomManager()
|
|
||||||
: base(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestPollsInitially()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a few rooms", () =>
|
|
||||||
{
|
|
||||||
RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
RoomManager.ClearRooms();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
|
|
||||||
AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRoomsClearedOnDisconnection()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a few rooms", () =>
|
|
||||||
{
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("disconnect", () => Client.Disconnect());
|
|
||||||
|
|
||||||
AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0);
|
|
||||||
AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRoomsPolledOnReconnect()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a few rooms", () =>
|
|
||||||
{
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("disconnect", () => Client.Disconnect());
|
|
||||||
AddStep("connect", () => Client.Connect());
|
|
||||||
|
|
||||||
AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
|
|
||||||
AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRoomsNotPolledWhenJoined()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a room", () =>
|
|
||||||
{
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
RoomManager.ClearRooms();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0);
|
|
||||||
AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMultiplayerRoomJoinedWhenCreated()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a room", () =>
|
|
||||||
{
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("multiplayer room joined", () => Client.Room != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMultiplayerRoomPartedWhenAPIRoomParted()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a room", () =>
|
|
||||||
{
|
|
||||||
RoomManager.CreateRoom(createRoom());
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("multiplayer room parted", () => Client.Room == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMultiplayerRoomJoinedWhenAPIRoomJoined()
|
|
||||||
{
|
|
||||||
AddStep("create room manager with a room", () =>
|
|
||||||
{
|
|
||||||
var r = createRoom();
|
|
||||||
RoomManager.CreateRoom(r);
|
|
||||||
RoomManager.PartRoom();
|
|
||||||
RoomManager.JoinRoom(r);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("multiplayer room joined", () => Client.Room != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Room createRoom(Action<Room> initFunc = null)
|
|
||||||
{
|
|
||||||
var room = new Room
|
|
||||||
{
|
|
||||||
Name =
|
|
||||||
{
|
|
||||||
Value = "test room"
|
|
||||||
},
|
|
||||||
Playlist =
|
|
||||||
{
|
|
||||||
new PlaylistItem
|
|
||||||
{
|
|
||||||
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
|
|
||||||
Ruleset = { Value = Ruleset.Value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
initFunc?.Invoke(room);
|
|
||||||
return room;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestDependencies : MultiplayerTestSceneDependencies
|
|
||||||
{
|
|
||||||
public TestDependencies()
|
|
||||||
{
|
|
||||||
// Need to set these values as early as possible.
|
|
||||||
RoomManager.TimeBetweenListingPolls.Value = 1;
|
|
||||||
RoomManager.TimeBetweenSelectionPolls.Value = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,61 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerTeamResults : ScreenTestScene
|
||||||
|
{
|
||||||
|
[TestCase(7483253, 1048576)]
|
||||||
|
[TestCase(1048576, 7483253)]
|
||||||
|
[TestCase(1048576, 1048576)]
|
||||||
|
public void TestDisplayTeamResults(int team1Score, int team2Score)
|
||||||
|
{
|
||||||
|
MultiplayerResultsScreen screen = null;
|
||||||
|
|
||||||
|
AddStep("show results screen", () =>
|
||||||
|
{
|
||||||
|
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||||
|
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
|
||||||
|
|
||||||
|
var score = new ScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
TotalScore = 987654,
|
||||||
|
Accuracy = 0.8,
|
||||||
|
MaxCombo = 500,
|
||||||
|
Combo = 250,
|
||||||
|
Beatmap = beatmapInfo,
|
||||||
|
User = new User { Username = "Test user" },
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
OnlineScoreID = 12345,
|
||||||
|
Ruleset = rulesetInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
PlaylistItem playlistItem = new PlaylistItem
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapInfo.ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
SortedDictionary<int, BindableInt> teamScores = new SortedDictionary<int, BindableInt>
|
||||||
|
{
|
||||||
|
{ 0, new BindableInt(team1Score) },
|
||||||
|
{ 1, new BindableInt(team2Score) }
|
||||||
|
};
|
||||||
|
|
||||||
|
Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, playlistItem, teamScores));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
Normal file
95
osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// 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.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneRankRangePill : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public new void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new RankRangePill
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleUser()
|
||||||
|
{
|
||||||
|
AddStep("add user", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Statistics = { GlobalRank = 1234 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the local user so only the one above is displayed.
|
||||||
|
Client.RemoveUser(API.LocalUser.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleUsers()
|
||||||
|
{
|
||||||
|
AddStep("add users", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Statistics = { GlobalRank = 1234 }
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Statistics = { GlobalRank = 3333 }
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
Statistics = { GlobalRank = 4321 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the local user so only the ones above are displayed.
|
||||||
|
Client.RemoveUser(API.LocalUser.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(1, 10)]
|
||||||
|
[TestCase(10, 100)]
|
||||||
|
[TestCase(100, 1000)]
|
||||||
|
[TestCase(1000, 10000)]
|
||||||
|
[TestCase(10000, 100000)]
|
||||||
|
[TestCase(100000, 1000000)]
|
||||||
|
[TestCase(1000000, 10000000)]
|
||||||
|
public void TestRange(int min, int max)
|
||||||
|
{
|
||||||
|
AddStep("add users", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Statistics = { GlobalRank = min }
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Statistics = { GlobalRank = max }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the local user so only the ones above are displayed.
|
||||||
|
Client.RemoveUser(API.LocalUser.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneRecentParticipantsList : OnlinePlayTestScene
|
||||||
|
{
|
||||||
|
private RecentParticipantsList list;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public new void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
SelectedRoom.Value = new Room { Name = { Value = "test room" } };
|
||||||
|
|
||||||
|
Child = list = new RecentParticipantsList
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
NumberOfCircles = 4
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleCountNearLimit()
|
||||||
|
{
|
||||||
|
AddStep("add 8 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 8 circles", () => list.NumberOfCircles = 8);
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
|
||||||
|
AddStep("add one more user", () => addUser(9));
|
||||||
|
AddAssert("2 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 2);
|
||||||
|
|
||||||
|
AddStep("remove first user", () => removeUserAt(0));
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
|
||||||
|
AddStep("add one more user", () => addUser(9));
|
||||||
|
AddAssert("2 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 2);
|
||||||
|
|
||||||
|
AddStep("remove last user", () => removeUserAt(8));
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHiddenUsersBecomeDisplayed()
|
||||||
|
{
|
||||||
|
AddStep("add 8 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 3 circles", () => list.NumberOfCircles = 3);
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
AddStep("remove user", () => removeUserAt(0));
|
||||||
|
int remainingUsers = 7 - i;
|
||||||
|
|
||||||
|
int displayedUsers = remainingUsers > 3 ? 2 : remainingUsers;
|
||||||
|
AddAssert($"{displayedUsers} avatars displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == displayedUsers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleCount()
|
||||||
|
{
|
||||||
|
AddStep("add 50 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 3 circles", () => list.NumberOfCircles = 3);
|
||||||
|
AddAssert("2 users displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 2);
|
||||||
|
AddAssert("48 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 48);
|
||||||
|
|
||||||
|
AddStep("set 10 circles", () => list.NumberOfCircles = 10);
|
||||||
|
AddAssert("9 users displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 9);
|
||||||
|
AddAssert("41 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 41);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddAndRemoveUsers()
|
||||||
|
{
|
||||||
|
AddStep("add 50 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("remove from start", () => removeUserAt(0));
|
||||||
|
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
|
||||||
|
AddAssert("46 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 46);
|
||||||
|
|
||||||
|
AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1));
|
||||||
|
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
|
||||||
|
AddAssert("45 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 45);
|
||||||
|
|
||||||
|
AddRepeatStep("remove 45 users", () => removeUserAt(0), 45);
|
||||||
|
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
AddAssert("hidden users bubble hidden", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Alpha < 0.5f);
|
||||||
|
|
||||||
|
AddStep("remove another user", () => removeUserAt(0));
|
||||||
|
AddAssert("2 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 2);
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
|
||||||
|
AddRepeatStep("remove the remaining two users", () => removeUserAt(0), 2);
|
||||||
|
AddAssert("0 circles displayed", () => !list.ChildrenOfType<UpdateableAvatar>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addUser(int id)
|
||||||
|
{
|
||||||
|
SelectedRoom.Value.RecentParticipants.Add(new User
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Username = $"User {id}"
|
||||||
|
});
|
||||||
|
SelectedRoom.Value.ParticipantCount.Value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeUserAt(int index)
|
||||||
|
{
|
||||||
|
SelectedRoom.Value.RecentParticipants.RemoveAt(index);
|
||||||
|
SelectedRoom.Value.ParticipantCount.Value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +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.Linq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
|
||||||
{
|
|
||||||
public class TestSceneRoomStatus : OsuTestScene
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void TestMultipleStatuses()
|
|
||||||
{
|
|
||||||
AddStep("create rooms", () =>
|
|
||||||
{
|
|
||||||
Child = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Width = 0.5f,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Open - ending in 1 day" },
|
|
||||||
Status = { Value = new RoomStatusOpen() },
|
|
||||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Playing - ending in 1 day" },
|
|
||||||
Status = { Value = new RoomStatusPlaying() },
|
|
||||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Ended" },
|
|
||||||
Status = { Value = new RoomStatusEnded() },
|
|
||||||
EndDate = { Value = DateTimeOffset.Now }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Open" },
|
|
||||||
Status = { Value = new RoomStatusOpen() },
|
|
||||||
Category = { Value = RoomCategory.Realtime }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestEnableAndDisablePassword()
|
|
||||||
{
|
|
||||||
DrawableRoom drawableRoom = null;
|
|
||||||
Room room = null;
|
|
||||||
|
|
||||||
AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Room with password" },
|
|
||||||
Status = { Value = new RoomStatusOpen() },
|
|
||||||
Category = { Value = RoomCategory.Realtime },
|
|
||||||
}) { MatchingFilter = true });
|
|
||||||
|
|
||||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
|
||||||
|
|
||||||
AddStep("set password", () => room.Password.Value = "password");
|
|
||||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
|
||||||
|
|
||||||
AddStep("unset password", () => room.Password.Value = string.Empty);
|
|
||||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
189
osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
Normal file
189
osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneTeamVersus : ScreenTestScene
|
||||||
|
{
|
||||||
|
private BeatmapManager beatmaps;
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
private BeatmapSetInfo importedSet;
|
||||||
|
|
||||||
|
private DependenciesScreen dependenciesScreen;
|
||||||
|
private TestMultiplayer multiplayerScreen;
|
||||||
|
private TestMultiplayerClient client;
|
||||||
|
|
||||||
|
[Cached(typeof(UserLookupCache))]
|
||||||
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, AudioManager audio)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
|
||||||
|
|
||||||
|
AddStep("load dependencies", () =>
|
||||||
|
{
|
||||||
|
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
|
||||||
|
|
||||||
|
// The screen gets suspended so it stops receiving updates.
|
||||||
|
Child = client;
|
||||||
|
|
||||||
|
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
|
||||||
|
|
||||||
|
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
|
||||||
|
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
|
||||||
|
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateWithType()
|
||||||
|
{
|
||||||
|
createRoom(() => new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Test Room" },
|
||||||
|
Type = { Value = MatchType.TeamVersus },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||||
|
AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeTeamsViaButton()
|
||||||
|
{
|
||||||
|
createRoom(() => new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Test Room" },
|
||||||
|
Type = { Value = MatchType.TeamVersus },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
|
||||||
|
|
||||||
|
AddStep("press button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(multiplayerScreen.ChildrenOfType<TeamDisplay>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1);
|
||||||
|
|
||||||
|
AddStep("press button", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeTypeViaMatchSettings()
|
||||||
|
{
|
||||||
|
createRoom(() => new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Test Room" },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
|
||||||
|
|
||||||
|
AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus));
|
||||||
|
|
||||||
|
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRoom(Func<Room> room)
|
||||||
|
{
|
||||||
|
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||||
|
|
||||||
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
AddWaitStep("wait for transition", 2);
|
||||||
|
|
||||||
|
AddStep("create room", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for join", () => client.Room != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
|
||||||
|
/// </summary>
|
||||||
|
private class DependenciesScreen : OsuScreen
|
||||||
|
{
|
||||||
|
[Cached(typeof(MultiplayerClient))]
|
||||||
|
public readonly TestMultiplayerClient Client;
|
||||||
|
|
||||||
|
public DependenciesScreen(TestMultiplayerClient client)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
||||||
|
{
|
||||||
|
public new TestRequestHandlingMultiplayerRoomManager RoomManager { get; private set; }
|
||||||
|
|
||||||
|
protected override RoomManager CreateRoomManager() => RoomManager = new TestRequestHandlingMultiplayerRoomManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -95,6 +96,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
public class TestOsuGame : OsuGame
|
public class TestOsuGame : OsuGame
|
||||||
{
|
{
|
||||||
|
public new const float SIDE_OVERLAY_OFFSET_RATIO = OsuGame.SIDE_OVERLAY_OFFSET_RATIO;
|
||||||
|
|
||||||
public new ScreenStack ScreenStack => base.ScreenStack;
|
public new ScreenStack ScreenStack => base.ScreenStack;
|
||||||
|
|
||||||
public new BackButton BackButton => base.BackButton;
|
public new BackButton BackButton => base.BackButton;
|
||||||
@ -103,7 +106,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
public new ScoreManager ScoreManager => base.ScoreManager;
|
public new ScoreManager ScoreManager => base.ScoreManager;
|
||||||
|
|
||||||
public new SettingsPanel Settings => base.Settings;
|
public new Container ScreenOffsetContainer => base.ScreenOffsetContainer;
|
||||||
|
|
||||||
|
public new SettingsOverlay Settings => base.Settings;
|
||||||
|
|
||||||
|
public new NotificationOverlay Notifications => base.Notifications;
|
||||||
|
|
||||||
public new MusicController MusicController => base.MusicController;
|
public new MusicController MusicController => base.MusicController;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ using osu.Game.Skinning;
|
|||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneOsuGame : OsuTestScene
|
public class TestSceneOsuGame : OsuTestScene
|
||||||
@ -83,10 +83,15 @@ namespace osu.Game.Tests.Visual
|
|||||||
typeof(PreviewTrackManager),
|
typeof(PreviewTrackManager),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private OsuGame game;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGameBase gameBase { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, OsuGameBase gameBase)
|
private void load(GameHost host)
|
||||||
{
|
{
|
||||||
OsuGame game = new OsuGame();
|
game = new OsuGame();
|
||||||
game.SetHost(host);
|
game.SetHost(host);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -100,7 +105,39 @@ namespace osu.Game.Tests.Visual
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => game.IsLoaded);
|
AddUntilStep("wait for load", () => game.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNullRulesetHandled()
|
||||||
|
{
|
||||||
|
RulesetInfo ruleset = null;
|
||||||
|
|
||||||
|
AddStep("store current ruleset", () => ruleset = Ruleset.Value);
|
||||||
|
AddStep("set global ruleset to null value", () => Ruleset.Value = null);
|
||||||
|
|
||||||
|
AddAssert("ruleset still valid", () => Ruleset.Value.Available);
|
||||||
|
AddAssert("ruleset unchanged", () => ReferenceEquals(Ruleset.Value, ruleset));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnavailableRulesetHandled()
|
||||||
|
{
|
||||||
|
RulesetInfo ruleset = null;
|
||||||
|
|
||||||
|
AddStep("store current ruleset", () => ruleset = Ruleset.Value);
|
||||||
|
AddStep("set global ruleset to invalid value", () => Ruleset.Value = new RulesetInfo
|
||||||
|
{
|
||||||
|
Name = "unavailable",
|
||||||
|
Available = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ruleset still valid", () => Ruleset.Value.Available);
|
||||||
|
AddAssert("ruleset unchanged", () => ReferenceEquals(Ruleset.Value, ruleset));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAvailableDependencies()
|
||||||
|
{
|
||||||
AddAssert("check OsuGame DI members", () =>
|
AddAssert("check OsuGame DI members", () =>
|
||||||
{
|
{
|
||||||
foreach (var type in requiredGameDependencies)
|
foreach (var type in requiredGameDependencies)
|
||||||
@ -111,6 +148,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("check OsuGameBase DI members", () =>
|
AddAssert("check OsuGameBase DI members", () =>
|
||||||
{
|
{
|
||||||
foreach (var type in requiredGameBaseDependencies)
|
foreach (var type in requiredGameBaseDependencies)
|
@ -16,6 +16,7 @@ using osu.Game.Overlays.Toolbar;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -316,7 +317,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
PushAndConfirm(() => multiplayer = new TestMultiplayer());
|
PushAndConfirm(() => multiplayer = new TestMultiplayer());
|
||||||
|
|
||||||
AddStep("open room", () => multiplayer.OpenNewRoom());
|
AddUntilStep("wait for lounge", () => multiplayer.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
AddStep("open room", () => multiplayer.ChildrenOfType<LoungeSubScreen>().Single().Open());
|
||||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
||||||
AddWaitStep("wait two frames", 2);
|
AddWaitStep("wait two frames", 2);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("show manually", () => accountCreation.Show());
|
AddStep("show manually", () => accountCreation.Show());
|
||||||
AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible);
|
AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
AddStep("click button", () => accountCreation.ChildrenOfType<SettingsButton>().Single().Click());
|
AddStep("click button", () => accountCreation.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||||
AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType<ScreenWarning>().SingleOrDefault()?.IsPresent == true);
|
AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType<ScreenWarning>().SingleOrDefault()?.IsPresent == true);
|
||||||
|
|
||||||
AddStep("log back in", () => API.Login("dummy", "password"));
|
AddStep("log back in", () => API.Login("dummy", "password"));
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -95,9 +96,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
|
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(false)]
|
||||||
public void ShowWithBuild()
|
[TestCase(true)]
|
||||||
|
public void ShowWithBuild(bool isSupporter)
|
||||||
{
|
{
|
||||||
|
AddStep(@"set supporter", () => dummyAPI.LocalUser.Value.IsSupporter = isSupporter);
|
||||||
showBuild(() => new APIChangelogBuild
|
showBuild(() => new APIChangelogBuild
|
||||||
{
|
{
|
||||||
Version = "2018.712.0",
|
Version = "2018.712.0",
|
||||||
@ -155,6 +158,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
|
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
|
||||||
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
|
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
|
||||||
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
|
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
|
||||||
|
AddUntilStep(@"wait for content load", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().Any());
|
||||||
|
AddAssert(@"supporter promo showed", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().First().Alpha == (isSupporter ? 0 : 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user