mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 06:52:53 +08:00
Merge branch 'master' into bassmix
This commit is contained in:
commit
1476b3b22a
@ -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.
|
||||||
|
@ -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.803.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.723.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.804.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>
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,10 +122,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", () =>
|
||||||
{
|
{
|
||||||
|
@ -130,7 +130,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:
|
||||||
|
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>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,8 +36,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 +70,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 +82,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>
|
||||||
@ -122,10 +108,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
private 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 +123,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;
|
||||||
@ -164,7 +148,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 +161,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>
|
||||||
@ -307,12 +282,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 +298,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 +309,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 +319,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.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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:
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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,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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -88,6 +89,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
// used to test the flow of multiplayer from visual tests.
|
// used to test the flow of multiplayer from visual tests.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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]
|
||||||
public void TestCreateRoomWithoutPassword()
|
public void TestCreateRoomWithoutPassword()
|
||||||
{
|
{
|
||||||
@ -209,7 +232,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
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);
|
||||||
@ -373,7 +396,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("open mod overlay", () => this.ChildrenOfType<PurpleTriangleButton>().ElementAt(2).Click());
|
AddStep("open mod overlay", () => this.ChildrenOfType<PurpleTriangleButton>().ElementAt(2).TriggerClick());
|
||||||
|
|
||||||
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
||||||
|
|
||||||
@ -381,7 +404,7 @@ 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("lounge tab item", () => this.ChildrenOfType<BreadcrumbControl<IScreen>.BreadcrumbTabItem>().First().TriggerClick());
|
||||||
|
|
||||||
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
191
osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
Normal file
191
osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// 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.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.OpenNewRoom(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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"));
|
||||||
|
@ -330,22 +330,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
InputManager.ReleaseKey(Key.AltLeft);
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pressCloseDocumentKeys() => pressKeysFor(PlatformAction.DocumentClose);
|
private void pressCloseDocumentKeys() => InputManager.Keys(PlatformAction.DocumentClose);
|
||||||
|
|
||||||
private void pressNewTabKeys() => pressKeysFor(PlatformAction.TabNew);
|
private void pressNewTabKeys() => InputManager.Keys(PlatformAction.TabNew);
|
||||||
|
|
||||||
private void pressRestoreTabKeys() => pressKeysFor(PlatformAction.TabRestore);
|
private void pressRestoreTabKeys() => InputManager.Keys(PlatformAction.TabRestore);
|
||||||
|
|
||||||
private void pressKeysFor(PlatformAction type)
|
|
||||||
{
|
|
||||||
var binding = host.PlatformKeyBindings.First(b => (PlatformAction)b.Action == type);
|
|
||||||
|
|
||||||
foreach (var k in binding.KeyCombination.Keys)
|
|
||||||
InputManager.PressKey((Key)k);
|
|
||||||
|
|
||||||
foreach (var k in binding.KeyCombination.Keys)
|
|
||||||
InputManager.ReleaseKey((Key)k);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clickDrawable(Drawable d)
|
private void clickDrawable(Drawable d)
|
||||||
{
|
{
|
||||||
|
@ -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 System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
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.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
@ -21,51 +20,44 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
private readonly User streamingUser = new User { Id = 2, Username = "Test user" };
|
private readonly User streamingUser = new User { Id = 2, Username = "Test user" };
|
||||||
|
|
||||||
[Cached(typeof(SpectatorClient))]
|
private TestSpectatorClient spectatorClient;
|
||||||
private TestSpectatorClient testSpectatorClient = new TestSpectatorClient();
|
|
||||||
|
|
||||||
private CurrentlyPlayingDisplay currentlyPlaying;
|
private CurrentlyPlayingDisplay currentlyPlaying;
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
|
||||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
|
||||||
|
|
||||||
private Container nestedContainer;
|
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("add streaming client", () =>
|
AddStep("add streaming client", () =>
|
||||||
{
|
{
|
||||||
nestedContainer?.Remove(testSpectatorClient);
|
spectatorClient = new TestSpectatorClient();
|
||||||
Remove(lookupCache);
|
var lookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
lookupCache,
|
lookupCache,
|
||||||
nestedContainer = new Container
|
spectatorClient,
|
||||||
|
new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
CachedDependencies = new (Type, object)[]
|
||||||
{
|
{
|
||||||
testSpectatorClient,
|
(typeof(SpectatorClient), spectatorClient),
|
||||||
currentlyPlaying = new CurrentlyPlayingDisplay
|
(typeof(UserLookupCache), lookupCache)
|
||||||
|
},
|
||||||
|
Child = currentlyPlaying = new CurrentlyPlayingDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Reset players", () => testSpectatorClient.EndPlay(streamingUser.Id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicDisplay()
|
public void TestBasicDisplay()
|
||||||
{
|
{
|
||||||
AddStep("Add playing user", () => testSpectatorClient.StartPlay(streamingUser.Id, 0));
|
AddStep("Add playing user", () => spectatorClient.StartPlay(streamingUser.Id, 0));
|
||||||
AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType<UserGridPanel>()?.FirstOrDefault()?.User.Id == 2);
|
AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType<UserGridPanel>()?.FirstOrDefault()?.User.Id == 2);
|
||||||
AddStep("Remove playing user", () => testSpectatorClient.EndPlay(streamingUser.Id));
|
AddStep("Remove playing user", () => spectatorClient.EndPlay(streamingUser.Id));
|
||||||
AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType<UserGridPanel>().Any());
|
AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType<UserGridPanel>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs
Normal file
87
osu.Game.Tests/Visual/Online/TestSceneDrawableComment.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;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Comments;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableComment : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
private Container container;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
container = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(comments))]
|
||||||
|
public void TestComment(string description, string text)
|
||||||
|
{
|
||||||
|
AddStep(description, () =>
|
||||||
|
{
|
||||||
|
comment.Message = text;
|
||||||
|
container.Add(new DrawableComment(comment));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Comment comment = new Comment
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LegacyName = "Test User",
|
||||||
|
CreatedAt = DateTimeOffset.Now,
|
||||||
|
VotesCount = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static object[] comments =
|
||||||
|
{
|
||||||
|
new[] { "Plain", "This is plain comment" },
|
||||||
|
new[] { "Link", "Please visit https://osu.ppy.sh" },
|
||||||
|
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"Heading", @"# Heading 1
|
||||||
|
## Heading 2
|
||||||
|
### Heading 3
|
||||||
|
#### Heading 4
|
||||||
|
##### Heading 5
|
||||||
|
###### Heading 6"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"Problematic", @"My tablet doesn't work :(
|
||||||
|
It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings.
|
||||||
|
Checking the logs, it looks for other Huion tablets before sending the notification (e.g.
|
||||||
|
""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2'
|
||||||
|
20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"")
|
||||||
|
I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts.
|
||||||
|
I have honestly 0 idea of whats going on at this point."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestControl()
|
public void TestControl()
|
||||||
{
|
{
|
||||||
AddAssert("Front page selected", () => header.Current.Value == "frontpage");
|
AddAssert("Front page selected", () => header.Current.Value == NewsHeader.FrontPageString);
|
||||||
AddAssert("1 tab total", () => header.TabCount == 1);
|
AddAssert("1 tab total", () => header.TabCount == 1);
|
||||||
|
|
||||||
AddStep("Set article 1", () => header.SetArticle("1"));
|
AddStep("Set article 1", () => header.SetArticle("1"));
|
||||||
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert("2 tabs total", () => header.TabCount == 2);
|
AddAssert("2 tabs total", () => header.TabCount == 2);
|
||||||
|
|
||||||
AddStep("Set front page", () => header.SetFrontPage());
|
AddStep("Set front page", () => header.SetFrontPage());
|
||||||
AddAssert("Front page selected", () => header.Current.Value == "frontpage");
|
AddAssert("Front page selected", () => header.Current.Value == NewsHeader.FrontPageString);
|
||||||
AddAssert("1 tab total", () => header.TabCount == 1);
|
AddAssert("1 tab total", () => header.TabCount == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("Show", () => overlay.Show());
|
AddStep("Show", () => overlay.Show());
|
||||||
AddUntilStep("Show More button is visible", () => showMoreButton?.Alpha == 1);
|
AddUntilStep("Show More button is visible", () => showMoreButton?.Alpha == 1);
|
||||||
setUpNewsResponse(responseWithNoCursor, "Set up no cursor response");
|
setUpNewsResponse(responseWithNoCursor, "Set up no cursor response");
|
||||||
AddStep("Click Show More", () => showMoreButton?.Click());
|
AddStep("Click Show More", () => showMoreButton?.TriggerClick());
|
||||||
AddUntilStep("Show More button is hidden", () => showMoreButton?.Alpha == 0);
|
AddUntilStep("Show More button is hidden", () => showMoreButton?.Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,19 +32,19 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("click button", () => button.Click());
|
AddStep("click button", () => button.TriggerClick());
|
||||||
|
|
||||||
AddAssert("action fired once", () => fireCount == 1);
|
AddAssert("action fired once", () => fireCount == 1);
|
||||||
AddAssert("is in loading state", () => button.IsLoading);
|
AddAssert("is in loading state", () => button.IsLoading);
|
||||||
|
|
||||||
AddStep("click button", () => button.Click());
|
AddStep("click button", () => button.TriggerClick());
|
||||||
|
|
||||||
AddAssert("action not fired", () => fireCount == 1);
|
AddAssert("action not fired", () => fireCount == 1);
|
||||||
AddAssert("is in loading state", () => button.IsLoading);
|
AddAssert("is in loading state", () => button.IsLoading);
|
||||||
|
|
||||||
AddUntilStep("wait for loaded", () => !button.IsLoading);
|
AddUntilStep("wait for loaded", () => !button.IsLoading);
|
||||||
|
|
||||||
AddStep("click button", () => button.Click());
|
AddStep("click button", () => button.TriggerClick());
|
||||||
|
|
||||||
AddAssert("action fired twice", () => fireCount == 2);
|
AddAssert("action fired twice", () => fireCount == 2);
|
||||||
AddAssert("is in loading state", () => button.IsLoading);
|
AddAssert("is in loading state", () => button.IsLoading);
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("Log in", logIn);
|
AddStep("Log in", logIn);
|
||||||
AddStep("User comment", () => addVotePill(getUserComment()));
|
AddStep("User comment", () => addVotePill(getUserComment()));
|
||||||
AddAssert("Background is transparent", () => votePill.Background.Alpha == 0);
|
AddAssert("Background is transparent", () => votePill.Background.Alpha == 0);
|
||||||
AddStep("Click", () => votePill.Click());
|
AddStep("Click", () => votePill.TriggerClick());
|
||||||
AddAssert("Not loading", () => !votePill.IsLoading);
|
AddAssert("Not loading", () => !votePill.IsLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("Log in", logIn);
|
AddStep("Log in", logIn);
|
||||||
AddStep("Random comment", () => addVotePill(getRandomComment()));
|
AddStep("Random comment", () => addVotePill(getRandomComment()));
|
||||||
AddAssert("Background is visible", () => votePill.Background.Alpha == 1);
|
AddAssert("Background is visible", () => votePill.Background.Alpha == 1);
|
||||||
AddStep("Click", () => votePill.Click());
|
AddStep("Click", () => votePill.TriggerClick());
|
||||||
AddAssert("Loading", () => votePill.IsLoading);
|
AddAssert("Loading", () => votePill.IsLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("Hide login overlay", () => login.Hide());
|
AddStep("Hide login overlay", () => login.Hide());
|
||||||
AddStep("Log out", API.Logout);
|
AddStep("Log out", API.Logout);
|
||||||
AddStep("Random comment", () => addVotePill(getRandomComment()));
|
AddStep("Random comment", () => addVotePill(getRandomComment()));
|
||||||
AddStep("Click", () => votePill.Click());
|
AddStep("Click", () => votePill.TriggerClick());
|
||||||
AddAssert("Login overlay is visible", () => login.State.Value == Visibility.Visible);
|
AddAssert("Login overlay is visible", () => login.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestWikiHeader()
|
public void TestWikiHeader()
|
||||||
{
|
{
|
||||||
AddAssert("Current is index", () => checkCurrent("index"));
|
AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
|
||||||
|
|
||||||
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
|
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
|
||||||
{
|
{
|
||||||
@ -54,8 +54,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert("Current is welcome", () => checkCurrent("Welcome"));
|
AddAssert("Current is welcome", () => checkCurrent("Welcome"));
|
||||||
AddAssert("Check breadcrumb", checkBreadcrumb);
|
AddAssert("Check breadcrumb", checkBreadcrumb);
|
||||||
|
|
||||||
AddStep("Change current to index", () => header.Current.Value = "index");
|
AddStep("Change current to index", () => header.Current.Value = WikiHeader.IndexPageString);
|
||||||
AddAssert("Current is index", () => checkCurrent("index"));
|
AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
|
||||||
|
|
||||||
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
|
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
|
||||||
{
|
{
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert("Check breadcrumb", checkBreadcrumb);
|
AddAssert("Check breadcrumb", checkBreadcrumb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkCurrent(string expectedCurrent) => header.Current.Value == expectedCurrent;
|
private bool checkCurrent(LocalisableString expectedCurrent) => header.Current.Value == expectedCurrent;
|
||||||
|
|
||||||
private bool checkBreadcrumb()
|
private bool checkBreadcrumb()
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
|
|
||||||
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
|
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
|
||||||
|
|
||||||
AddStep("select last room", () => roomsContainer.Rooms[^1].Click());
|
AddStep("select last room", () => roomsContainer.Rooms[^1].TriggerClick());
|
||||||
|
|
||||||
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms[0]));
|
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms[0]));
|
||||||
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
|
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
|
||||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start match", () => match.ChildrenOfType<PlaylistsReadyButton>().First().Click());
|
AddStep("start match", () => match.ChildrenOfType<PlaylistsReadyButton>().First().TriggerClick());
|
||||||
AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader);
|
AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
var resetButton = settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First();
|
var resetButton = settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First();
|
||||||
|
|
||||||
resetButton.Click();
|
resetButton.TriggerClick();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||||
@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
var resetButton = panel.ChildrenOfType<ResetButton>().First();
|
var resetButton = panel.ChildrenOfType<ResetButton>().First();
|
||||||
|
|
||||||
resetButton.Click();
|
resetButton.TriggerClick();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||||
|
@ -143,9 +143,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
|
public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (blockCalculation)
|
if (blockCalculation)
|
||||||
await calculationBlocker.Task;
|
await calculationBlocker.Task.ConfigureAwait(false);
|
||||||
|
|
||||||
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken);
|
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -32,6 +33,7 @@ using osuTK.Graphics;
|
|||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[HeadlessTest]
|
||||||
public class TestSceneOsuGame : OsuTestScene
|
public class TestSceneOsuGame : OsuTestScene
|
||||||
{
|
{
|
||||||
private IReadOnlyList<Type> requiredGameDependencies => new[]
|
private IReadOnlyList<Type> requiredGameDependencies => new[]
|
||||||
@ -83,10 +85,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 +107,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 +150,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)
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
// 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.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public class TestSceneLabelledColourPalette : OsuTestScene
|
public class TestSceneLabelledColourPalette : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private LabelledColourPalette component;
|
private LabelledColourPalette component;
|
||||||
|
|
||||||
@ -30,10 +35,29 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}, 8);
|
}, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserInteractions()
|
||||||
|
{
|
||||||
|
createColourPalette();
|
||||||
|
assertColourCount(4);
|
||||||
|
|
||||||
|
clickAddColour();
|
||||||
|
assertColourCount(5);
|
||||||
|
|
||||||
|
deleteFirstColour();
|
||||||
|
assertColourCount(4);
|
||||||
|
|
||||||
|
clickFirstColour();
|
||||||
|
AddAssert("colour picker spawned", () => this.ChildrenOfType<OsuColourPicker>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
private void createColourPalette(bool hasDescription = false)
|
private void createColourPalette(bool hasDescription = false)
|
||||||
{
|
{
|
||||||
AddStep("create component", () =>
|
AddStep("create component", () =>
|
||||||
{
|
{
|
||||||
|
Child = new OsuContextMenuContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -46,6 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
ColourNamePrefix = "My colour #"
|
ColourNamePrefix = "My colour #"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
component.Label = "a sample component";
|
component.Label = "a sample component";
|
||||||
@ -53,18 +78,49 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
component.Colours.AddRange(new[]
|
component.Colours.AddRange(new[]
|
||||||
{
|
{
|
||||||
Color4.DarkRed,
|
Colour4.DarkRed,
|
||||||
Color4.Aquamarine,
|
Colour4.Aquamarine,
|
||||||
Color4.Goldenrod,
|
Colour4.Goldenrod,
|
||||||
Color4.Gainsboro
|
Colour4.Gainsboro
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 randomColour() => new Color4(
|
private Colour4 randomColour() => new Color4(
|
||||||
RNG.NextSingle(),
|
RNG.NextSingle(),
|
||||||
RNG.NextSingle(),
|
RNG.NextSingle(),
|
||||||
RNG.NextSingle(),
|
RNG.NextSingle(),
|
||||||
1);
|
1);
|
||||||
|
|
||||||
|
private void assertColourCount(int count) => AddAssert($"colour count is {count}", () => component.Colours.Count == count);
|
||||||
|
|
||||||
|
private void clickAddColour() => AddStep("click new colour button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ColourPalette.AddColourButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void clickFirstColour() => AddStep("click first colour", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ColourDisplay>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void deleteFirstColour()
|
||||||
|
{
|
||||||
|
AddStep("right-click first colour", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ColourDisplay>().First());
|
||||||
|
InputManager.Click(MouseButton.Right);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
|
||||||
|
|
||||||
|
AddStep("click delete", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableOsuMenuItem>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2);
|
AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2);
|
||||||
|
|
||||||
AddStep("deselect", () => modSelect.DeselectAllButton.Click());
|
AddStep("deselect", () => modSelect.DeselectAllButton.TriggerClick());
|
||||||
AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0);
|
AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0);
|
||||||
|
|
||||||
AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).Click());
|
AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).TriggerClick());
|
||||||
AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true);
|
AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded);
|
AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded);
|
||||||
AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod));
|
AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod));
|
||||||
AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
|
AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
|
||||||
AddStep("open Customisation", () => modSelect.CustomiseButton.Click());
|
AddStep("open Customisation", () => modSelect.CustomiseButton.TriggerClick());
|
||||||
AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod));
|
AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod));
|
||||||
AddAssert("controls hidden", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden);
|
AddAssert("controls hidden", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,18 @@
|
|||||||
// 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.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Tournament.Configuration;
|
|
||||||
using osu.Game.Tests;
|
using osu.Game.Tests;
|
||||||
|
using osu.Game.Tournament.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Tests.NonVisual
|
namespace osu.Game.Tournament.Tests.NonVisual
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class CustomTourneyDirectoryTest
|
public class CustomTourneyDirectoryTest : TournamentHostTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDefaultDirectory()
|
public void TestDefaultDirectory()
|
||||||
@ -24,7 +21,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = LoadTournament(host);
|
||||||
var storage = osu.Dependencies.Get<Storage>();
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
|
||||||
@ -54,7 +51,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = LoadTournament(host);
|
||||||
|
|
||||||
storage = osu.Dependencies.Get<Storage>();
|
storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
@ -111,7 +108,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = LoadTournament(host);
|
||||||
|
|
||||||
var storage = osu.Dependencies.Get<Storage>();
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
@ -151,25 +148,6 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TournamentGameBase loadOsu(GameHost host)
|
|
||||||
{
|
|
||||||
var osu = new TournamentGameBase();
|
|
||||||
Task.Run(() => host.Run(osu))
|
|
||||||
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
|
||||||
return osu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 90000)
|
|
||||||
{
|
|
||||||
Task task = Task.Run(() =>
|
|
||||||
{
|
|
||||||
while (!result()) Thread.Sleep(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance);
|
private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs
Normal file
45
osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Tests;
|
||||||
|
|
||||||
|
namespace osu.Game.Tournament.Tests.NonVisual
|
||||||
|
{
|
||||||
|
public class DataLoadTest : TournamentHostTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestUnavailableRuleset()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUnavailableRuleset)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = new TestTournament();
|
||||||
|
|
||||||
|
LoadTournament(host, osu);
|
||||||
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestTournament : TournamentGameBase
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Ruleset.Value = new RulesetInfo(); // not available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +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;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -15,7 +12,7 @@ using osu.Game.Tournament.IPC;
|
|||||||
namespace osu.Game.Tournament.Tests.NonVisual
|
namespace osu.Game.Tournament.Tests.NonVisual
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class IPCLocationTest
|
public class IPCLocationTest : TournamentHostTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void CheckIPCLocation()
|
public void CheckIPCLocation()
|
||||||
@ -34,11 +31,11 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = LoadTournament(host);
|
||||||
TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>();
|
TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>();
|
||||||
FileBasedIPC ipc = null;
|
FileBasedIPC ipc = null;
|
||||||
|
|
||||||
waitForOrAssert(() => (ipc = osu.Dependencies.Get<MatchIPCInfo>() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time");
|
WaitForOrAssert(() => (ipc = osu.Dependencies.Get<MatchIPCInfo>() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time");
|
||||||
|
|
||||||
Assert.True(ipc.SetIPCLocation(testStableInstallDirectory));
|
Assert.True(ipc.SetIPCLocation(testStableInstallDirectory));
|
||||||
Assert.True(storage.AllTournaments.Exists("stable.json"));
|
Assert.True(storage.AllTournaments.Exists("stable.json"));
|
||||||
@ -51,24 +48,5 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TournamentGameBase loadOsu(GameHost host)
|
|
||||||
{
|
|
||||||
var osu = new TournamentGameBase();
|
|
||||||
Task.Run(() => host.Run(osu))
|
|
||||||
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
|
||||||
return osu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 90000)
|
|
||||||
{
|
|
||||||
Task task = Task.Run(() =>
|
|
||||||
{
|
|
||||||
while (!result()) Thread.Sleep(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs
Normal file
33
osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace osu.Game.Tournament.Tests.NonVisual
|
||||||
|
{
|
||||||
|
public abstract class TournamentHostTest
|
||||||
|
{
|
||||||
|
public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase tournament = null)
|
||||||
|
{
|
||||||
|
tournament ??= new TournamentGameBase();
|
||||||
|
Task.Run(() => host.Run(tournament))
|
||||||
|
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
||||||
|
WaitForOrAssert(() => tournament.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||||
|
return tournament;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WaitForOrAssert(Func<bool> result, string failureMessage, int timeout = 90000)
|
||||||
|
{
|
||||||
|
Task task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
while (!result()) Thread.Sleep(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,6 @@ namespace osu.Game.Tournament.Tests.Screens
|
|||||||
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));
|
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));
|
||||||
|
|
||||||
private void toggleWarmup()
|
private void toggleWarmup()
|
||||||
=> AddStep("toggle warmup", () => this.ChildrenOfType<TourneyButton>().First().Click());
|
=> AddStep("toggle warmup", () => this.ChildrenOfType<TourneyButton>().First().TriggerClick());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
@ -198,8 +198,8 @@ namespace osu.Game.Tournament.Components
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))),
|
new DiffPiece(("Length", length.ToFormattedDuration().ToString())),
|
||||||
new DiffPiece(("BPM", $"{bpm:0.#}"))
|
new DiffPiece(("BPM", $"{bpm:0.#}")),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
|
@ -27,6 +27,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
{
|
{
|
||||||
var teams = new List<TournamentTeam>();
|
var teams = new List<TournamentTeam>();
|
||||||
|
|
||||||
|
if (!storage.Exists(teams_filename))
|
||||||
|
return teams;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = storage.GetStream(teams_filename, FileAccess.Read, FileMode.Open))
|
using (Stream stream = storage.GetStream(teams_filename, FileAccess.Read, FileMode.Open))
|
||||||
|
@ -9,11 +9,13 @@ using System.Threading.Tasks;
|
|||||||
using osu.Framework.Allocation;
|
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.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Tournament.Components;
|
using osu.Game.Tournament.Components;
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
using osu.Game.Tournament.Screens.Drawings.Components;
|
using osu.Game.Tournament.Screens.Drawings.Components;
|
||||||
@ -51,6 +53,29 @@ namespace osu.Game.Tournament.Screens.Drawings
|
|||||||
|
|
||||||
if (!TeamList.Teams.Any())
|
if (!TeamList.Teams.Any())
|
||||||
{
|
{
|
||||||
|
LinkFlowContainer links;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Height = 0.3f,
|
||||||
|
},
|
||||||
|
new WarningBox("No drawings.txt file found. Please create one and restart the client."),
|
||||||
|
links = new LinkFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 60,
|
||||||
|
AutoSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
links.AddLink("Click for details on the file format", "https://osu.ppy.sh/wiki/en/Tournament_Drawings", t => t.Colour = Color4.White);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,9 @@ namespace osu.Game.Tournament
|
|||||||
}
|
}
|
||||||
|
|
||||||
ladder ??= new LadderInfo();
|
ladder ??= new LadderInfo();
|
||||||
ladder.Ruleset.Value ??= RulesetStore.AvailableRulesets.First();
|
|
||||||
|
ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value?.ShortName)
|
||||||
|
?? RulesetStore.AvailableRulesets.First();
|
||||||
|
|
||||||
bool addedInfo = false;
|
bool addedInfo = false;
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
[*.cs]
|
[*.cs]
|
||||||
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
|
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
|
||||||
|
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.
|
@ -1,22 +1,34 @@
|
|||||||
// 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 osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
[LocalisableEnum(typeof(BeatmapSetOnlineStatusEnumLocalisationMapper))]
|
|
||||||
public enum BeatmapSetOnlineStatus
|
public enum BeatmapSetOnlineStatus
|
||||||
{
|
{
|
||||||
None = -3,
|
None = -3,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusGraveyard))]
|
||||||
Graveyard = -2,
|
Graveyard = -2,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusWip))]
|
||||||
WIP = -1,
|
WIP = -1,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusPending))]
|
||||||
Pending = 0,
|
Pending = 0,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusRanked))]
|
||||||
Ranked = 1,
|
Ranked = 1,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusApproved))]
|
||||||
Approved = 2,
|
Approved = 2,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusQualified))]
|
||||||
Qualified = 3,
|
Qualified = 3,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusLoved))]
|
||||||
Loved = 4,
|
Loved = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,40 +37,4 @@ namespace osu.Game.Beatmaps
|
|||||||
public static bool GrantsPerformancePoints(this BeatmapSetOnlineStatus status)
|
public static bool GrantsPerformancePoints(this BeatmapSetOnlineStatus status)
|
||||||
=> status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved;
|
=> status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BeatmapSetOnlineStatusEnumLocalisationMapper : EnumLocalisationMapper<BeatmapSetOnlineStatus>
|
|
||||||
{
|
|
||||||
public override LocalisableString Map(BeatmapSetOnlineStatus value)
|
|
||||||
{
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case BeatmapSetOnlineStatus.None:
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.Graveyard:
|
|
||||||
return BeatmapsetsStrings.ShowStatusGraveyard;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.WIP:
|
|
||||||
return BeatmapsetsStrings.ShowStatusWip;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.Pending:
|
|
||||||
return BeatmapsetsStrings.ShowStatusPending;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.Ranked:
|
|
||||||
return BeatmapsetsStrings.ShowStatusRanked;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.Approved:
|
|
||||||
return BeatmapsetsStrings.ShowStatusApproved;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.Qualified:
|
|
||||||
return BeatmapsetsStrings.ShowStatusQualified;
|
|
||||||
|
|
||||||
case BeatmapSetOnlineStatus.Loved:
|
|
||||||
return BeatmapsetsStrings.ShowStatusLoved;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +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;
|
|
||||||
|
|
||||||
namespace osu.Game.Extensions
|
|
||||||
{
|
|
||||||
public static class EditorDisplayExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get an editor formatted string (mm:ss:mss)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="milliseconds">A time value in milliseconds.</param>
|
|
||||||
/// <returns>An editor formatted display string.</returns>
|
|
||||||
public static string ToEditorFormattedString(this double milliseconds) =>
|
|
||||||
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get an editor formatted string (mm:ss:mss)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="timeSpan">A time value.</param>
|
|
||||||
/// <returns>An editor formatted display string.</returns>
|
|
||||||
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
|
|
||||||
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{timeSpan:mm\\:ss\\:fff}";
|
|
||||||
}
|
|
||||||
}
|
|
51
osu.Game/Extensions/TimeDisplayExtensions.cs
Normal file
51
osu.Game/Extensions/TimeDisplayExtensions.cs
Normal file
@ -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 osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Extensions
|
||||||
|
{
|
||||||
|
public static class TimeDisplayExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get an editor formatted string (mm:ss:mss)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="milliseconds">A time value in milliseconds.</param>
|
||||||
|
/// <returns>An editor formatted display string.</returns>
|
||||||
|
public static string ToEditorFormattedString(this double milliseconds) =>
|
||||||
|
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an editor formatted string (mm:ss:mss)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpan">A time value.</param>
|
||||||
|
/// <returns>An editor formatted display string.</returns>
|
||||||
|
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
|
||||||
|
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{(int)timeSpan.TotalMinutes:00}:{timeSpan:ss\\:fff}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a formatted duration (dd:hh:mm:ss with days/hours omitted if zero).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="milliseconds">A duration in milliseconds.</param>
|
||||||
|
/// <returns>A formatted duration string.</returns>
|
||||||
|
public static LocalisableString ToFormattedDuration(this double milliseconds) =>
|
||||||
|
ToFormattedDuration(TimeSpan.FromMilliseconds(milliseconds));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a formatted duration (dd:hh:mm:ss with days/hours omitted if zero).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpan">A duration value.</param>
|
||||||
|
/// <returns>A formatted duration string.</returns>
|
||||||
|
public static LocalisableString ToFormattedDuration(this TimeSpan timeSpan)
|
||||||
|
{
|
||||||
|
if (timeSpan.TotalDays >= 1)
|
||||||
|
return new LocalisableFormattableString(timeSpan, @"dd\:hh\:mm\:ss");
|
||||||
|
|
||||||
|
if (timeSpan.TotalHours >= 1)
|
||||||
|
return new LocalisableFormattableString(timeSpan, @"hh\:mm\:ss");
|
||||||
|
|
||||||
|
return new LocalisableFormattableString(timeSpan, @"mm\:ss");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@ -25,6 +26,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuGame game { get; set; }
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
public void AddLinks(string text, List<Link> links)
|
public void AddLinks(string text, List<Link> links)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(text) || links == null)
|
if (string.IsNullOrEmpty(text) || links == null)
|
||||||
@ -91,8 +95,11 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
if (action != null)
|
if (action != null)
|
||||||
action();
|
action();
|
||||||
else
|
else if (game != null)
|
||||||
game?.HandleLink(link);
|
game.HandleLink(link);
|
||||||
|
// fallback to handle cases where OsuGame is not available, ie. tournament client.
|
||||||
|
else if (link.Action == LinkAction.External)
|
||||||
|
host.OpenUrlExternally(link.Argument);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
|
|
||||||
public override SpriteText CreateSpriteText() => new OsuSpriteText
|
public override SpriteText CreateSpriteText() => new OsuSpriteText
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: 14),
|
Font = OsuFont.GetFont(Typeface.Inter, size: 14, weight: FontWeight.Regular),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer();
|
public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer();
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
public FontWeight FontWeight;
|
public FontWeight FontWeight;
|
||||||
|
|
||||||
protected override SpriteText CreateSpriteText()
|
protected override SpriteText CreateSpriteText()
|
||||||
=> base.CreateSpriteText().With(t => t.Font = t.Font.With(size: FontSize, weight: FontWeight));
|
=> base.CreateSpriteText().With(t => t.Font = t.Font.With(Typeface.Torus, size: FontSize, weight: FontWeight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -59,7 +60,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = new GridContainer
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -86,6 +89,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
},
|
},
|
||||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||||
|
},
|
||||||
|
new HoverClickSounds()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Graphics
|
|||||||
|
|
||||||
public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
|
public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
|
||||||
|
|
||||||
|
public static FontUsage Inter => GetFont(Typeface.Inter, weight: FontWeight.Regular);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a <see cref="FontUsage"/>.
|
/// Retrieves a <see cref="FontUsage"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -54,6 +56,9 @@ namespace osu.Game.Graphics
|
|||||||
|
|
||||||
case Typeface.Torus:
|
case Typeface.Torus:
|
||||||
return "Torus";
|
return "Torus";
|
||||||
|
|
||||||
|
case Typeface.Inter:
|
||||||
|
return "Inter";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -107,7 +112,8 @@ namespace osu.Game.Graphics
|
|||||||
public enum Typeface
|
public enum Typeface
|
||||||
{
|
{
|
||||||
Venera,
|
Venera,
|
||||||
Torus
|
Torus,
|
||||||
|
Inter,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FontWeight
|
public enum FontWeight
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||||
|
|
||||||
Child = button = new TwoLayerButton
|
Child = button = new TwoLayerButton(HoverSampleSet.Submit)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopLeft,
|
Anchor = Anchor.TopLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Add(receptor = new Receptor());
|
Add(receptor = new Receptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
receptor.OnBackPressed = () => button.Click();
|
receptor.OnBackPressed = () => button.TriggerClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -56,6 +56,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
||||||
|
|
||||||
public DialogButton()
|
public DialogButton()
|
||||||
|
: base(HoverSampleSet.Submit)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
@ -23,14 +23,20 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
|
private readonly SpriteIcon linkIcon;
|
||||||
|
|
||||||
public ExternalLinkButton(string link = null)
|
public ExternalLinkButton(string link = null)
|
||||||
{
|
{
|
||||||
Link = link;
|
Link = link;
|
||||||
Size = new Vector2(12);
|
Size = new Vector2(12);
|
||||||
InternalChild = new SpriteIcon
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
linkIcon = new SpriteIcon
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new HoverClickSounds(HoverSampleSet.Submit)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,13 +48,13 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint);
|
linkIcon.FadeColour(hoverColour, 500, Easing.OutQuint);
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint);
|
linkIcon.FadeColour(Color4.White, 500, Easing.OutQuint);
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[Description("default")]
|
[Description("default")]
|
||||||
Default,
|
Default,
|
||||||
|
|
||||||
[Description("soft")]
|
[Description("submit")]
|
||||||
Soft,
|
Submit,
|
||||||
|
|
||||||
[Description("button")]
|
[Description("button")]
|
||||||
Button,
|
Button,
|
||||||
|
@ -71,7 +71,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TwoLayerButton()
|
public TwoLayerButton(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
||||||
|
: base(sampleSet)
|
||||||
{
|
{
|
||||||
Size = SIZE_RETRACTED;
|
Size = SIZE_RETRACTED;
|
||||||
Shear = shear;
|
Shear = shear;
|
||||||
|
@ -1,32 +1,39 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component which displays a colour along with related description text.
|
/// A component which displays a colour along with related description text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ColourDisplay : CompositeDrawable, IHasCurrentValue<Color4>
|
public class ColourDisplay : CompositeDrawable, IHasCurrentValue<Colour4>
|
||||||
{
|
{
|
||||||
private readonly BindableWithCurrent<Color4> current = new BindableWithCurrent<Color4>();
|
/// <summary>
|
||||||
|
/// Invoked when the user has requested the colour corresponding to this <see cref="ColourDisplay"/>
|
||||||
|
/// to be removed from its palette.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<ColourDisplay> DeleteRequested;
|
||||||
|
|
||||||
|
private readonly BindableWithCurrent<Colour4> current = new BindableWithCurrent<Colour4>();
|
||||||
|
|
||||||
private Box fill;
|
|
||||||
private OsuSpriteText colourHexCode;
|
|
||||||
private OsuSpriteText colourName;
|
private OsuSpriteText colourName;
|
||||||
|
|
||||||
public Bindable<Color4> Current
|
public Bindable<Colour4> Current
|
||||||
{
|
{
|
||||||
get => current.Current;
|
get => current.Current;
|
||||||
set => current.Current = value;
|
set => current.Current = value;
|
||||||
@ -62,11 +69,37 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
Spacing = new Vector2(0, 10),
|
Spacing = new Vector2(0, 10),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new CircularContainer
|
new ColourCircle
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
Current = { BindTarget = Current },
|
||||||
Height = 100,
|
DeleteRequested = () => DeleteRequested?.Invoke(this)
|
||||||
Masking = true,
|
},
|
||||||
|
colourName = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ColourCircle : OsuClickableContainer, IHasPopover, IHasContextMenu
|
||||||
|
{
|
||||||
|
public Bindable<Colour4> Current { get; } = new Bindable<Colour4>();
|
||||||
|
|
||||||
|
public Action DeleteRequested { get; set; }
|
||||||
|
|
||||||
|
private readonly Box fill;
|
||||||
|
private readonly OsuSpriteText colourHexCode;
|
||||||
|
|
||||||
|
public ColourCircle()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 100;
|
||||||
|
CornerRadius = 50;
|
||||||
|
Masking = true;
|
||||||
|
Action = this.ShowPopover;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
fill = new Box
|
fill = new Box
|
||||||
@ -79,14 +112,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.Default.With(size: 12)
|
Font = OsuFont.Default.With(size: 12)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
colourName = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,14 +119,28 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
current.BindValueChanged(_ => updateColour(), true);
|
Current.BindValueChanged(_ => updateColour(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateColour()
|
private void updateColour()
|
||||||
{
|
{
|
||||||
fill.Colour = current.Value;
|
fill.Colour = Current.Value;
|
||||||
colourHexCode.Text = current.Value.ToHex();
|
colourHexCode.Text = Current.Value.ToHex();
|
||||||
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value);
|
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new OsuPopover(false)
|
||||||
|
{
|
||||||
|
Child = new OsuColourPicker
|
||||||
|
{
|
||||||
|
Current = { BindTarget = Current }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
|
{
|
||||||
|
new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
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.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
@ -17,7 +22,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ColourPalette : CompositeDrawable
|
public class ColourPalette : CompositeDrawable
|
||||||
{
|
{
|
||||||
public BindableList<Color4> Colours { get; } = new BindableList<Color4>();
|
public BindableList<Colour4> Colours { get; } = new BindableList<Colour4>();
|
||||||
|
|
||||||
private string colourNamePrefix = "Colour";
|
private string colourNamePrefix = "Colour";
|
||||||
|
|
||||||
@ -36,36 +41,24 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private FillFlowContainer<ColourDisplay> palette;
|
private FillFlowContainer palette;
|
||||||
private Container placeholder;
|
|
||||||
|
private IEnumerable<ColourDisplay> colourDisplays => palette.OfType<ColourDisplay>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
AutoSizeDuration = fade_duration;
|
||||||
|
AutoSizeEasing = Easing.OutQuint;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChild = palette = new FillFlowContainer
|
||||||
{
|
|
||||||
palette = new FillFlowContainer<ColourDisplay>
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Spacing = new Vector2(10),
|
Spacing = new Vector2(10),
|
||||||
Direction = FillDirection.Full
|
Direction = FillDirection.Full
|
||||||
},
|
|
||||||
placeholder = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Child = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
Text = "(none)",
|
|
||||||
Font = OsuFont.Default.With(weight: FontWeight.Bold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +66,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Colours.BindCollectionChanged((_, __) => updatePalette(), true);
|
Colours.BindCollectionChanged((_, args) =>
|
||||||
|
{
|
||||||
|
if (args.Action != NotifyCollectionChangedAction.Replace)
|
||||||
|
updatePalette();
|
||||||
|
}, true);
|
||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,37 +80,103 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
palette.Clear();
|
palette.Clear();
|
||||||
|
|
||||||
if (Colours.Any())
|
for (int i = 0; i < Colours.Count; ++i)
|
||||||
{
|
{
|
||||||
palette.FadeIn(fade_duration, Easing.OutQuint);
|
// copy to avoid accesses to modified closure.
|
||||||
placeholder.FadeOut(fade_duration, Easing.OutQuint);
|
int colourIndex = i;
|
||||||
}
|
ColourDisplay display;
|
||||||
else
|
|
||||||
|
palette.Add(display = new ColourDisplay
|
||||||
{
|
{
|
||||||
palette.FadeOut(fade_duration, Easing.OutQuint);
|
Current = { Value = Colours[colourIndex] }
|
||||||
placeholder.FadeIn(fade_duration, Easing.OutQuint);
|
});
|
||||||
|
|
||||||
|
display.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue);
|
||||||
|
display.DeleteRequested += colourDeletionRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in Colours)
|
palette.Add(new AddColourButton
|
||||||
{
|
{
|
||||||
palette.Add(new ColourDisplay
|
Action = () => Colours.Add(Colour4.White)
|
||||||
{
|
|
||||||
Current = { Value = item }
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
reindexItems();
|
reindexItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void colourDeletionRequested(ColourDisplay display) => Colours.RemoveAt(palette.IndexOf(display));
|
||||||
|
|
||||||
private void reindexItems()
|
private void reindexItems()
|
||||||
{
|
{
|
||||||
int index = 1;
|
int index = 1;
|
||||||
|
|
||||||
foreach (var colour in palette)
|
foreach (var colourDisplay in colourDisplays)
|
||||||
{
|
{
|
||||||
colour.ColourName = $"{colourNamePrefix} {index}";
|
colourDisplay.ColourName = $"{colourNamePrefix} {index}";
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class AddColourButton : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Action Action
|
||||||
|
{
|
||||||
|
set => circularButton.Action = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly OsuClickableContainer circularButton;
|
||||||
|
|
||||||
|
public AddColourButton()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Width = 100;
|
||||||
|
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
circularButton = new OsuClickableContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 100,
|
||||||
|
CornerRadius = 50,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 5,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Transparent,
|
||||||
|
AlwaysPresent = true
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
Icon = FontAwesome.Solid.Plus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "New"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
circularButton.BorderColour = colours.BlueDarker;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// 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.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osuTK.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
@ -13,7 +13,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BindableList<Color4> Colours => Component.Colours;
|
public BindableList<Colour4> Colours => Component.Colours;
|
||||||
|
|
||||||
public string ColourNamePrefix
|
public string ColourNamePrefix
|
||||||
{
|
{
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
Depth = 1
|
Depth = 1
|
||||||
},
|
},
|
||||||
new HoverClickSounds(HoverSampleSet.Soft)
|
new HoverClickSounds()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
Depth = 1
|
Depth = 1
|
||||||
},
|
},
|
||||||
new HoverClickSounds(HoverSampleSet.Soft)
|
new HoverClickSounds()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
osu.Game/Localisation/NamedOverlayComponentStrings.cs
Normal file
44
osu.Game/Localisation/NamedOverlayComponentStrings.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class NamedOverlayComponentStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.NamedOverlayComponent";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "browse for new beatmaps"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BeatmapListingDescription => new TranslatableString(getKey(@"beatmap_listing_description"), @"browse for new beatmaps");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "track recent dev updates in the osu! ecosystem"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ChangelogDescription => new TranslatableString(getKey(@"changelog_description"), @"track recent dev updates in the osu! ecosystem");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "view your friends and other information"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and other information");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "find out who's the best right now"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString RankingsDescription => new TranslatableString(getKey(@"rankings_description"), @"find out who's the best right now");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "get up-to-date on community happenings"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NewsDescription => new TranslatableString(getKey(@"news_description"), @"get up-to-date on community happenings");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "knowledge base"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString WikiDescription => new TranslatableString(getKey(@"wiki_description"), @"knowledge base");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -150,7 +150,7 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
userReq.Failure += ex =>
|
userReq.Failure += ex =>
|
||||||
{
|
{
|
||||||
if (ex.InnerException is WebException webException && webException.Message == @"Unauthorized")
|
if (ex is WebException webException && webException.Message == @"Unauthorized")
|
||||||
{
|
{
|
||||||
log.Add(@"Login no longer valid");
|
log.Add(@"Login no longer valid");
|
||||||
Logout();
|
Logout();
|
||||||
@ -257,8 +257,8 @@ namespace osu.Game.Online.API
|
|||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHubClientConnector GetHubConnector(string clientName, string endpoint) =>
|
public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) =>
|
||||||
new HubClientConnector(clientName, endpoint, this, versionHash);
|
new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack);
|
||||||
|
|
||||||
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
||||||
{
|
{
|
||||||
|
@ -86,8 +86,6 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private APIRequestCompletionState completionState;
|
private APIRequestCompletionState completionState;
|
||||||
|
|
||||||
private Action pendingFailure;
|
|
||||||
|
|
||||||
public void Perform(IAPIProvider api)
|
public void Perform(IAPIProvider api)
|
||||||
{
|
{
|
||||||
if (!(api is APIAccess apiAccess))
|
if (!(api is APIAccess apiAccess))
|
||||||
@ -99,29 +97,23 @@ namespace osu.Game.Online.API
|
|||||||
API = apiAccess;
|
API = apiAccess;
|
||||||
User = apiAccess.LocalUser.Value;
|
User = apiAccess.LocalUser.Value;
|
||||||
|
|
||||||
if (checkAndScheduleFailure())
|
if (isFailing) return;
|
||||||
return;
|
|
||||||
|
|
||||||
WebRequest = CreateWebRequest();
|
WebRequest = CreateWebRequest();
|
||||||
WebRequest.Failed += Fail;
|
WebRequest.Failed += Fail;
|
||||||
WebRequest.AllowRetryOnTimeout = false;
|
WebRequest.AllowRetryOnTimeout = false;
|
||||||
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
||||||
|
|
||||||
if (checkAndScheduleFailure())
|
if (isFailing) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (!WebRequest.Aborted) // could have been aborted by a Cancel() call
|
|
||||||
{
|
|
||||||
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
||||||
WebRequest.Perform();
|
WebRequest.Perform();
|
||||||
}
|
|
||||||
|
|
||||||
if (checkAndScheduleFailure())
|
if (isFailing) return;
|
||||||
return;
|
|
||||||
|
|
||||||
PostProcess();
|
PostProcess();
|
||||||
|
|
||||||
API.Schedule(TriggerSuccess);
|
TriggerSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -141,7 +133,10 @@ namespace osu.Game.Online.API
|
|||||||
completionState = APIRequestCompletionState.Completed;
|
completionState = APIRequestCompletionState.Completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (API == null)
|
||||||
Success?.Invoke();
|
Success?.Invoke();
|
||||||
|
else
|
||||||
|
API.Schedule(() => Success?.Invoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void TriggerFailure(Exception e)
|
internal void TriggerFailure(Exception e)
|
||||||
@ -154,7 +149,10 @@ namespace osu.Game.Online.API
|
|||||||
completionState = APIRequestCompletionState.Failed;
|
completionState = APIRequestCompletionState.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (API == null)
|
||||||
Failure?.Invoke(e);
|
Failure?.Invoke(e);
|
||||||
|
else
|
||||||
|
API.Schedule(() => Failure?.Invoke(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
||||||
@ -163,17 +161,18 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
lock (completionStateLock)
|
lock (completionStateLock)
|
||||||
{
|
{
|
||||||
// while it doesn't matter if code following this check is run more than once,
|
|
||||||
// this avoids unnecessarily performing work where we are already sure the user has been informed.
|
|
||||||
if (completionState != APIRequestCompletionState.Waiting)
|
if (completionState != APIRequestCompletionState.Waiting)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
WebRequest?.Abort();
|
WebRequest?.Abort();
|
||||||
|
|
||||||
|
// in the case of a cancellation we don't care about whether there's an error in the response.
|
||||||
|
if (!(e is OperationCanceledException))
|
||||||
|
{
|
||||||
string responseString = WebRequest?.GetResponseString();
|
string responseString = WebRequest?.GetResponseString();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(responseString))
|
// naive check whether there's an error in the response to avoid unnecessary JSON deserialisation.
|
||||||
|
if (!string.IsNullOrEmpty(responseString) && responseString.Contains(@"""error"""))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -186,31 +185,23 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
|
Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
|
||||||
pendingFailure = () => TriggerFailure(e);
|
TriggerFailure(e);
|
||||||
checkAndScheduleFailure();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checked for cancellation or error. Also queues up the Failed event if we can.
|
/// Whether this request is in a failing or failed state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Whether we are in a failed or cancelled state.</returns>
|
private bool isFailing
|
||||||
private bool checkAndScheduleFailure()
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
lock (completionStateLock)
|
lock (completionStateLock)
|
||||||
{
|
|
||||||
if (pendingFailure == null)
|
|
||||||
return completionState == APIRequestCompletionState.Failed;
|
return completionState == APIRequestCompletionState.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (API == null)
|
|
||||||
pendingFailure();
|
|
||||||
else
|
|
||||||
API.Schedule(pendingFailure);
|
|
||||||
|
|
||||||
pendingFailure = null;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DisplayableError
|
private class DisplayableError
|
||||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Online.API
|
|||||||
state.Value = APIState.Offline;
|
state.Value = APIState.Offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHubClientConnector GetHubConnector(string clientName, string endpoint) => null;
|
public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;
|
||||||
|
|
||||||
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
||||||
{
|
{
|
||||||
|
@ -102,7 +102,8 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientName">The name of the client this connector connects for, used for logging.</param>
|
/// <param name="clientName">The name of the client this connector connects for, used for logging.</param>
|
||||||
/// <param name="endpoint">The endpoint to the hub.</param>
|
/// <param name="endpoint">The endpoint to the hub.</param>
|
||||||
IHubClientConnector? GetHubConnector(string clientName, string endpoint);
|
/// <param name="preferMessagePack">Whether to use MessagePack for serialisation if available on this platform.</param>
|
||||||
|
IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new user account. This is a blocking operation.
|
/// Create a new user account. This is a blocking operation.
|
||||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Online.Chat
|
|||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||||
|
|
||||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
|
: base(HoverSampleSet.Submit)
|
||||||
{
|
{
|
||||||
Parts = parts.ToList();
|
Parts = parts.ToList();
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ namespace osu.Game.Online
|
|||||||
private readonly string clientName;
|
private readonly string clientName;
|
||||||
private readonly string endpoint;
|
private readonly string endpoint;
|
||||||
private readonly string versionHash;
|
private readonly string versionHash;
|
||||||
|
private readonly bool preferMessagePack;
|
||||||
private readonly IAPIProvider api;
|
private readonly IAPIProvider api;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -51,12 +52,14 @@ namespace osu.Game.Online
|
|||||||
/// <param name="endpoint">The endpoint to the hub.</param>
|
/// <param name="endpoint">The endpoint to the hub.</param>
|
||||||
/// <param name="api"> An API provider used to react to connection state changes.</param>
|
/// <param name="api"> An API provider used to react to connection state changes.</param>
|
||||||
/// <param name="versionHash">The hash representing the current game version, used for verification purposes.</param>
|
/// <param name="versionHash">The hash representing the current game version, used for verification purposes.</param>
|
||||||
public HubClientConnector(string clientName, string endpoint, IAPIProvider api, string versionHash)
|
/// <param name="preferMessagePack">Whether to use MessagePack for serialisation if available on this platform.</param>
|
||||||
|
public HubClientConnector(string clientName, string endpoint, IAPIProvider api, string versionHash, bool preferMessagePack = true)
|
||||||
{
|
{
|
||||||
this.clientName = clientName;
|
this.clientName = clientName;
|
||||||
this.endpoint = endpoint;
|
this.endpoint = endpoint;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.versionHash = versionHash;
|
this.versionHash = versionHash;
|
||||||
|
this.preferMessagePack = preferMessagePack;
|
||||||
|
|
||||||
apiState.BindTo(api.State);
|
apiState.BindTo(api.State);
|
||||||
apiState.BindValueChanged(state =>
|
apiState.BindValueChanged(state =>
|
||||||
@ -116,10 +119,7 @@ namespace osu.Game.Online
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Log($"{clientName} connection error: {e}", LoggingTarget.Network);
|
await handleErrorAndDelay(e, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// retry on any failure.
|
|
||||||
await Task.Delay(5000, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,6 +129,15 @@ namespace osu.Game.Online
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles an exception and delays an async flow.
|
||||||
|
/// </summary>
|
||||||
|
private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Logger.Log($"{clientName} connection error: {exception}", LoggingTarget.Network);
|
||||||
|
await Task.Delay(5000, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
private HubConnection buildConnection(CancellationToken cancellationToken)
|
private HubConnection buildConnection(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var builder = new HubConnectionBuilder()
|
var builder = new HubConnectionBuilder()
|
||||||
@ -138,13 +147,19 @@ namespace osu.Game.Online
|
|||||||
options.Headers.Add("OsuVersionHash", versionHash);
|
options.Headers.Add("OsuVersionHash", versionHash);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (RuntimeInfo.SupportsJIT)
|
if (RuntimeInfo.SupportsJIT && preferMessagePack)
|
||||||
builder.AddMessagePackProtocol();
|
builder.AddMessagePackProtocol();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// eventually we will precompile resolvers for messagepack, but this isn't working currently
|
// eventually we will precompile resolvers for messagepack, but this isn't working currently
|
||||||
// see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308.
|
// see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308.
|
||||||
builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });
|
builder.AddNewtonsoftJsonProtocol(options =>
|
||||||
|
{
|
||||||
|
options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
|
// TODO: This should only be required to be `TypeNameHandling.Auto`.
|
||||||
|
// See usage in osu-server-spectator for further documentation as to why this is required.
|
||||||
|
options.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.All;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var newConnection = builder.Build();
|
var newConnection = builder.Build();
|
||||||
@ -155,17 +170,18 @@ namespace osu.Game.Online
|
|||||||
return newConnection;
|
return newConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken)
|
private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
isConnected.Value = false;
|
isConnected.Value = false;
|
||||||
|
|
||||||
Logger.Log(ex != null ? $"{clientName} lost connection: {ex}" : $"{clientName} disconnected", LoggingTarget.Network);
|
if (ex != null)
|
||||||
|
await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false);
|
||||||
|
else
|
||||||
|
Logger.Log($"{clientName} disconnected", LoggingTarget.Network);
|
||||||
|
|
||||||
// make sure a disconnect wasn't triggered (and this is still the active connection).
|
// make sure a disconnect wasn't triggered (and this is still the active connection).
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
Task.Run(connect, default);
|
await Task.Run(connect, default).ConfigureAwait(false);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task disconnect(bool takeLock)
|
private async Task disconnect(bool takeLock)
|
||||||
|
@ -50,6 +50,25 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="state">The new state of the user.</param>
|
/// <param name="state">The new state of the user.</param>
|
||||||
Task UserStateChanged(int userId, MultiplayerUserState state);
|
Task UserStateChanged(int userId, MultiplayerUserState state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signals that the match type state has changed for a user in this room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The ID of the user performing a state change.</param>
|
||||||
|
/// <param name="state">The new state of the user.</param>
|
||||||
|
Task MatchUserStateChanged(int userId, MatchUserState state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signals that the match type state has changed for this room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">The new state of the room.</param>
|
||||||
|
Task MatchRoomStateChanged(MatchRoomState state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a match type specific request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e">The event to handle.</param>
|
||||||
|
Task MatchEvent(MatchServerEvent e);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals that a user in this room changed their beatmap availability state.
|
/// Signals that a user in this room changed their beatmap availability state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -55,6 +55,12 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="newMods">The proposed new mods, excluding any required by the room itself.</param>
|
/// <param name="newMods">The proposed new mods, excluding any required by the room itself.</param>
|
||||||
Task ChangeUserMods(IEnumerable<APIMod> newMods);
|
Task ChangeUserMods(IEnumerable<APIMod> newMods);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a match type specific request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request to send.</param>
|
||||||
|
Task SendMatchRequest(MatchUserRequest request);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As the host of a room, start the match.
|
/// As the host of a room, start the match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
23
osu.Game/Online/Multiplayer/MatchRoomState.cs
Normal file
23
osu.Game/Online/Multiplayer/MatchRoomState.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MessagePack;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Room-wide state for the current match type.
|
||||||
|
/// Can be used to contain any state which should be used before or during match gameplay.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
[MessagePackObject]
|
||||||
|
[Union(0, typeof(TeamVersusRoomState))]
|
||||||
|
// TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation.
|
||||||
|
public class MatchRoomState
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
17
osu.Game/Online/Multiplayer/MatchServerEvent.cs
Normal file
17
osu.Game/Online/Multiplayer/MatchServerEvent.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MessagePack;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event from the server to allow clients to update gameplay to an expected state.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
[MessagePackObject]
|
||||||
|
public abstract class MatchServerEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using MessagePack;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer.MatchTypes.TeamVersus
|
||||||
|
{
|
||||||
|
public class ChangeTeamRequest : MatchUserRequest
|
||||||
|
{
|
||||||
|
[Key(0)]
|
||||||
|
public int TeamID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// 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 MessagePack;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer.MatchTypes.TeamVersus
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
[MessagePackObject]
|
||||||
|
public class MultiplayerTeam
|
||||||
|
{
|
||||||
|
[Key(0)]
|
||||||
|
public int ID { get; set; }
|
||||||
|
|
||||||
|
[Key(1)]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user