1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-27 11:12:59 +08:00

Merge branch 'master' into skin-colour-fix-2

This commit is contained in:
smoogipoo 2020-08-25 19:39:01 +09:00
commit 8dde512657
27 changed files with 571 additions and 168 deletions

View File

@ -0,0 +1,84 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests.Mods
{
public class TestSceneCatchModRelax : ModTestScene
{
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
[Test]
public void TestModRelax() => CreateModTest(new ModTestData
{
Mod = new CatchModRelax(),
Autoplay = false,
PassCondition = passCondition,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Fruit
{
X = CatchPlayfield.CENTER_X,
StartTime = 0
},
new Fruit
{
X = 0,
StartTime = 250
},
new Fruit
{
X = CatchPlayfield.WIDTH,
StartTime = 500
},
new JuiceStream
{
X = CatchPlayfield.CENTER_X,
StartTime = 750,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
}
}
}
});
private bool passCondition()
{
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
switch (Player.ScoreProcessor.Combo.Value)
{
case 0:
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
break;
case 1:
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft);
break;
case 2:
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight);
break;
case 3:
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
break;
}
return Player.ScoreProcessor.Combo.Value >= 6;
}
}
}

View File

@ -19,25 +19,43 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
protected override bool Autoplay => true; protected override bool Autoplay => true;
private int hyperDashCount;
private bool inHyperDash;
[Test] [Test]
public void TestHyperDash() public void TestHyperDash()
{ {
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); AddStep("reset count", () =>
AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast.
AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0);
for (int i = 0; i < 3; i++)
{ {
AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); inHyperDash = false;
AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing); hyperDashCount = 0;
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
{
var catcher = Player.ChildrenOfType<CatcherArea>().FirstOrDefault()?.MovableCatcher;
if (catcher == null)
return;
if (catcher.HyperDashing != inHyperDash)
{
inHyperDash = catcher.HyperDashing;
if (catcher.HyperDashing)
hyperDashCount++;
}
};
});
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
for (int i = 0; i < 9; i++)
{
int count = i + 1;
AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
} }
AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing);
} }
private Catcher getCatcher() => Player.ChildrenOfType<CatcherArea>().First().MovableCatcher;
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{ {
var beatmap = new Beatmap var beatmap = new Beatmap

View File

@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
// Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins.
// This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible.
// For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size.
halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE;
int lastDirection = 0; int lastDirection = 0;
double lastExcess = halfCatcherWidth; double lastExcess = halfCatcherWidth;

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
protected override bool OnMouseMove(MouseMoveEvent e) protected override bool OnMouseMove(MouseMoveEvent e)
{ {
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X); catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
} }

View File

@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary> /// <summary>
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
/// </summary> /// </summary>
private const float allowed_catch_range = 0.8f; public const float ALLOWED_CATCH_RANGE = 0.8f;
/// <summary> /// <summary>
/// The drawable catcher for <see cref="CurrentState"/>. /// The drawable catcher for <see cref="CurrentState"/>.
@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
/// <param name="scale">The scale of the catcher.</param> /// <param name="scale">The scale of the catcher.</param>
internal static float CalculateCatchWidth(Vector2 scale) internal static float CalculateCatchWidth(Vector2 scale)
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range; => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
/// <summary> /// <summary>
/// Calculates the width of the area used for attempting catches in gameplay. /// Calculates the width of the area used for attempting catches in gameplay.
@ -285,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI
private void runHyperDashStateTransition(bool hyperDashing) private void runHyperDashStateTransition(bool hyperDashing)
{ {
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
updateTrailVisibility(); updateTrailVisibility();
if (hyperDashing) if (hyperDashing)
@ -403,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ?? skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
hyperDashColour; hyperDashColour;
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
runHyperDashStateTransition(HyperDashing); runHyperDashStateTransition(HyperDashing);
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly Container<CatcherTrailSprite> hyperDashTrails; private readonly Container<CatcherTrailSprite> hyperDashTrails;
private readonly Container<CatcherTrailSprite> endGlowSprites; private readonly Container<CatcherTrailSprite> endGlowSprites;
private Color4 hyperDashTrailsColour; private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
public Color4 HyperDashTrailsColour public Color4 HyperDashTrailsColour
{ {
@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.UI
return; return;
hyperDashTrailsColour = value; hyperDashTrailsColour = value;
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); hyperDashTrails.Colour = hyperDashTrailsColour;
} }
} }
private Color4 endGlowSpritesColour; private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
public Color4 EndGlowSpritesColour public Color4 EndGlowSpritesColour
{ {
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI
return; return;
endGlowSpritesColour = value; endGlowSpritesColour = value;
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); endGlowSprites.Colour = endGlowSpritesColour;
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }),
_ => new DefaultStageBackground())
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Tests.Skinning namespace osu.Game.Rulesets.Mania.Tests.Skinning
@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -21,14 +21,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
/// <param name="column">The 0-based column index.</param> /// <param name="column">The 0-based column index.</param>
/// <returns>Whether the column is a special column.</returns> /// <returns>Whether the column is a special column.</returns>
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
/// <summary> /// <summary>
/// Get the type of column given a column index. /// Get the type of column given a column index.
/// </summary> /// </summary>
/// <param name="column">The 0-based column index.</param> /// <param name="column">The 0-based column index.</param>
/// <returns>The type of the column.</returns> /// <returns>The type of the column.</returns>
public ColumnType GetTypeOfColumn(int column) public readonly ColumnType GetTypeOfColumn(int column)
{ {
if (IsSpecialColumn(column)) if (IsSpecialColumn(column))
return ColumnType.Special; return ColumnType.Special;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -14,15 +15,23 @@ namespace osu.Game.Rulesets.Mania
/// </summary> /// </summary>
public readonly int? TargetColumn; public readonly int? TargetColumn;
/// <summary>
/// The intended <see cref="StageDefinition"/> for this component.
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
/// </summary>
public readonly StageDefinition? StageDefinition;
/// <summary> /// <summary>
/// Creates a new <see cref="ManiaSkinComponent"/>. /// Creates a new <see cref="ManiaSkinComponent"/>.
/// </summary> /// </summary>
/// <param name="component">The component.</param> /// <param name="component">The component.</param>
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param> /// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null) /// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null)
: base(component) : base(component)
{ {
TargetColumn = targetColumn; TargetColumn = targetColumn;
StageDefinition = stageDefinition;
} }
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@ -32,6 +33,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container<DrawableHoldNoteTail> tailContainer; private readonly Container<DrawableHoldNoteTail> tailContainer;
private readonly Container<DrawableHoldNoteTick> tickContainer; private readonly Container<DrawableHoldNoteTick> tickContainer;
/// <summary>
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
/// </summary>
private readonly Container sizingContainer;
/// <summary>
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
/// </summary>
private readonly Container maskingContainer;
private readonly SkinnableDrawable bodyPiece; private readonly SkinnableDrawable bodyPiece;
/// <summary> /// <summary>
@ -44,24 +55,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
public bool HasBroken { get; private set; } public bool HasBroken { get; private set; }
/// <summary>
/// Whether the hold note has been released potentially without having caused a break.
/// </summary>
private double? releaseTime;
public DrawableHoldNote(HoldNote hitObject) public DrawableHoldNote(HoldNote hitObject)
: base(hitObject) : base(hitObject)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Container maskedContents;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
sizingContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
maskingContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = maskedContents = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
}
},
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
}
},
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both,
}) })
{ {
RelativeSizeAxes = Axes.X RelativeSizeAxes = Axes.X
}, },
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both }, tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both }, tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
}); });
maskedContents.AddRange(new[]
{
bodyPiece.CreateProxy(),
tickContainer.CreateProxy(),
tailContainer.CreateProxy(),
});
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject) protected override void AddNestedHitObject(DrawableHitObject hitObject)
@ -127,7 +168,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
base.OnDirectionChanged(e); base.OnDirectionChanged(e);
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; if (e.NewValue == ScrollingDirection.Up)
{
bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft;
sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft;
}
else
{
bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft;
sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft;
}
} }
public override void PlaySamples() public override void PlaySamples()
@ -145,9 +195,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
base.Update(); base.Update();
// Make the body piece not lie under the head note if (Time.Current < releaseTime)
releaseTime = null;
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
sizingContainer.Padding = new MarginPadding
{
Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0,
Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0,
};
// Pad the masking container to the starting position of the body piece (half-way under the head).
// This is required to make the body start getting masked immediately as soon as the note is held.
maskingContainer.Padding = new MarginPadding
{
Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0,
Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0,
};
// Position and resize the body to lie half-way under the head and the tail notes.
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
// As the note is being held, adjust the size of the sizing container. This has two effects:
// 1. The contained masking container will mask the body and ticks.
// 2. The head note will move along with the new "head position" in the container.
if (Head.IsHit && releaseTime == null)
{
// How far past the hit target this hold note is. Always a positive value.
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
}
} }
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)
@ -212,6 +291,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// If the key has been released too early, the user should not receive full score for the release // If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit) if (!Tail.IsHit)
HasBroken = true; HasBroken = true;
releaseTime = Time.Current;
} }
private void endHold() private void endHold()

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
/// <summary> /// <summary>
@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true); public void UpdateResult() => base.UpdateResult(true);
protected override void UpdateStateTransforms(ArmedState state)
{
// This hitobject should never expire, so this is just a safe maximum.
LifetimeEnd = LifetimeStart + 30000;
}
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
public override void OnReleased(ManiaAction action) public override void OnReleased(ManiaAction action)

View File

@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
break; break;
case ArmedState.Hit: case ArmedState.Hit:
this.FadeOut(150, Easing.OutQuint); this.FadeOut();
break; break;
} }
} }

View File

@ -5,7 +5,6 @@ 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.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -18,14 +17,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction> public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction>
{ {
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly bool isLastColumn;
private Container lightContainer; private Container lightContainer;
private Sprite light; private Sprite light;
public LegacyColumnBackground(bool isLastColumn) public LegacyColumnBackground()
{ {
this.isLastColumn = isLastColumn;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
@ -35,57 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
string lightImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.LightImage)?.Value string lightImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.LightImage)?.Value
?? "mania-stage-light"; ?? "mania-stage-light";
float leftLineWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
?.Value ?? 1;
float rightLineWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
?.Value ?? 1;
bool hasLeftLine = leftLineWidth > 0;
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|| isLastColumn;
float lightPosition = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value float lightPosition = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
?? 0; ?? 0;
Color4 lineColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
?? Color4.White;
Color4 backgroundColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
?? Color4.Black;
Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
?? Color4.White; ?? Color4.White;
InternalChildren = new Drawable[] InternalChildren = new[]
{ {
LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
{
RelativeSizeAxes = Axes.Both
}, backgroundColour),
new Container
{
RelativeSizeAxes = Axes.Y,
Width = leftLineWidth,
Scale = new Vector2(0.740f, 1),
Alpha = hasLeftLine ? 1 : 0,
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
{
RelativeSizeAxes = Axes.Both
}, lineColour)
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = rightLineWidth,
Scale = new Vector2(0.740f, 1),
Alpha = hasRightLine ? 1 : 0,
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
{
RelativeSizeAxes = Axes.Both
}, lineColour)
},
lightContainer = new Container lightContainer = new Container
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,

View File

@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Container directionContainer; private Container directionContainer;
public LegacyHitTarget()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo) private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{ {

View File

@ -2,21 +2,31 @@
// 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.Allocation; using osu.Framework.Allocation;
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.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning namespace osu.Game.Rulesets.Mania.Skinning
{ {
public class LegacyStageBackground : CompositeDrawable public class LegacyStageBackground : CompositeDrawable
{ {
private readonly StageDefinition stageDefinition;
private Drawable leftSprite; private Drawable leftSprite;
private Drawable rightSprite; private Drawable rightSprite;
private ColumnFlow<Drawable> columnBackgrounds;
public LegacyStageBackground() public LegacyStageBackground(StageDefinition stageDefinition)
{ {
this.stageDefinition = stageDefinition;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
@ -44,8 +54,19 @@ namespace osu.Game.Rulesets.Mania.Skinning
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
X = -0.05f, X = -0.05f,
Texture = skin.GetTexture(rightImage) Texture = skin.GetTexture(rightImage)
},
columnBackgrounds = new ColumnFlow<Drawable>(stageDefinition)
{
RelativeSizeAxes = Axes.Y
},
new HitTargetInsetContainer
{
Child = new LegacyHitTarget { RelativeSizeAxes = Axes.Both }
} }
}; };
for (int i = 0; i < stageDefinition.Columns; i++)
columnBackgrounds.SetContentForColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1));
} }
protected override void Update() protected override void Update()
@ -58,5 +79,104 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (rightSprite?.Height > 0) if (rightSprite?.Height > 0)
rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height); rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
} }
private class ColumnBackground : CompositeDrawable
{
private readonly int columnIndex;
private readonly bool isLastColumn;
public ColumnBackground(int columnIndex, bool isLastColumn)
{
this.columnIndex = columnIndex;
this.isLastColumn = isLastColumn;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
float leftLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1;
float rightLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1;
bool hasLeftLine = leftLineWidth > 0;
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|| isLastColumn;
Color4 lineColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White;
Color4 backgroundColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black;
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour
},
},
new HitTargetInsetContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Y,
Width = leftLineWidth,
Scale = new Vector2(0.740f, 1),
Colour = lineColour,
Alpha = hasLeftLine ? 1 : 0
},
new Box
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = rightLineWidth,
Scale = new Vector2(0.740f, 1),
Colour = lineColour,
Alpha = hasRightLine ? 1 : 0
},
}
}
};
}
}
private class HitTargetInsetContainer : Container
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
protected override Container<Drawable> Content => content;
private readonly Container content;
private float hitPosition;
public HitTargetInsetContainer()
{
RelativeSizeAxes = Axes.Both;
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
content.Padding = direction.NewValue == ScrollingDirection.Up
? new MarginPadding { Top = hitPosition }
: new MarginPadding { Bottom = hitPosition };
}
}
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning; using osu.Game.Skinning;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
@ -88,10 +89,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
switch (maniaComponent.Component) switch (maniaComponent.Component)
{ {
case ManiaSkinComponents.ColumnBackground: case ManiaSkinComponents.ColumnBackground:
return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1); return new LegacyColumnBackground();
case ManiaSkinComponents.HitTarget: case ManiaSkinComponents.HitTarget:
return new LegacyHitTarget(); // Legacy skins sandwich the hit target between the column background and the column light.
// To preserve this ordering, it's created manually inside LegacyStageBackground.
return Drawable.Empty();
case ManiaSkinComponents.KeyArea: case ManiaSkinComponents.KeyArea:
return new LegacyKeyArea(); return new LegacyKeyArea();
@ -112,7 +115,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
return new LegacyHitExplosion(); return new LegacyHitExplosion();
case ManiaSkinComponents.StageBackground: case ManiaSkinComponents.StageBackground:
return new LegacyStageBackground(); Debug.Assert(maniaComponent.StageDefinition != null);
return new LegacyStageBackground(maniaComponent.StageDefinition.Value);
case ManiaSkinComponents.StageForeground: case ManiaSkinComponents.StageForeground:
return new LegacyStageForeground(); return new LegacyStageForeground();

View File

@ -68,8 +68,6 @@ namespace osu.Game.Rulesets.Mania.UI
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
} }
public override Axes RelativeSizeAxes => Axes.Y;
public ColumnType ColumnType { get; set; } public ColumnType ColumnType { get; set; }
public bool IsSpecial => ColumnType == ColumnType.Special; public bool IsSpecial => ColumnType == ColumnType.Special;

View File

@ -0,0 +1,105 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.UI
{
/// <summary>
/// A <see cref="Drawable"/> which flows its contents according to the <see cref="Column"/>s in a <see cref="Stage"/>.
/// Content can be added to individual columns via <see cref="SetContentForColumn"/>.
/// </summary>
/// <typeparam name="TContent">The type of content in each column.</typeparam>
public class ColumnFlow<TContent> : CompositeDrawable
where TContent : Drawable
{
/// <summary>
/// All contents added to this <see cref="ColumnFlow{TContent}"/>.
/// </summary>
public IReadOnlyList<TContent> Content => columns.Children.Select(c => c.Count == 0 ? null : (TContent)c.Child).ToList();
private readonly FillFlowContainer<Container> columns;
private readonly StageDefinition stageDefinition;
public ColumnFlow(StageDefinition stageDefinition)
{
this.stageDefinition = stageDefinition;
AutoSizeAxes = Axes.X;
InternalChild = columns = new FillFlowContainer<Container>
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
};
for (int i = 0; i < stageDefinition.Columns; i++)
columns.Add(new Container { RelativeSizeAxes = Axes.Y });
}
private ISkinSource currentSkin;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
currentSkin = skin;
skin.SourceChanged += onSkinChanged;
onSkinChanged();
}
private void onSkinChanged()
{
for (int i = 0; i < stageDefinition.Columns; i++)
{
if (i > 0)
{
float spacing = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1))
?.Value ?? Stage.COLUMN_SPACING;
columns[i].Margin = new MarginPadding { Left = spacing };
}
float? width = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i))
?.Value;
if (width == null)
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
else
columns[i].Width = width.Value;
}
}
/// <summary>
/// Sets the content of one of the columns of this <see cref="ColumnFlow{TContent}"/>.
/// </summary>
/// <param name="column">The index of the column to set the content of.</param>
/// <param name="content">The content.</param>
public void SetContentForColumn(int column, TContent content) => columns[column].Child = content;
public new MarginPadding Padding
{
get => base.Padding;
set => base.Padding = value;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (currentSkin != null)
currentSkin.SourceChanged -= onSkinChanged;
}
}
}

View File

@ -3,7 +3,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
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;
@ -11,7 +10,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -31,14 +29,13 @@ namespace osu.Game.Rulesets.Mania.UI
public const float HIT_TARGET_POSITION = 110; public const float HIT_TARGET_POSITION = 110;
public IReadOnlyList<Column> Columns => columnFlow.Children; public IReadOnlyList<Column> Columns => columnFlow.Content;
private readonly FillFlowContainer<Column> columnFlow; private readonly ColumnFlow<Column> columnFlow;
private readonly JudgementContainer<DrawableManiaJudgement> judgements; private readonly JudgementContainer<DrawableManiaJudgement> judgements;
private readonly DrawablePool<DrawableManiaJudgement> judgementPool; private readonly DrawablePool<DrawableManiaJudgement> judgementPool;
private readonly Drawable barLineContainer; private readonly Drawable barLineContainer;
private readonly Container topLevelContainer;
private readonly Dictionary<ColumnType, Color4> columnColours = new Dictionary<ColumnType, Color4> private readonly Dictionary<ColumnType, Color4> columnColours = new Dictionary<ColumnType, Color4>
{ {
@ -62,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;
Container topLevelContainer;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
judgementPool = new DrawablePool<DrawableManiaJudgement>(2), judgementPool = new DrawablePool<DrawableManiaJudgement>(2),
@ -73,16 +72,13 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Children = new Drawable[] Children = new Drawable[]
{ {
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground())
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
columnFlow = new FillFlowContainer<Column> columnFlow = new ColumnFlow<Column>(definition)
{ {
Name = "Columns",
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
}, },
new Container new Container
@ -102,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
} }
}, },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null)
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
@ -121,60 +117,22 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++) for (int i = 0; i < definition.Columns; i++)
{ {
var columnType = definition.GetTypeOfColumn(i); var columnType = definition.GetTypeOfColumn(i);
var column = new Column(firstColumnIndex + i) var column = new Column(firstColumnIndex + i)
{ {
RelativeSizeAxes = Axes.Both,
Width = 1,
ColumnType = columnType, ColumnType = columnType,
AccentColour = columnColours[columnType], AccentColour = columnColours[columnType],
Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ }
}; };
AddColumn(column); topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
columnFlow.SetContentForColumn(i, column);
AddNested(column);
} }
} }
private ISkin currentSkin;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
currentSkin = skin;
skin.SourceChanged += onSkinChanged;
onSkinChanged();
}
private void onSkinChanged()
{
foreach (var col in columnFlow)
{
if (col.Index > 0)
{
float spacing = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1))
?.Value ?? COLUMN_SPACING;
col.Margin = new MarginPadding { Left = spacing };
}
float? width = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index))
?.Value;
if (width == null)
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
else
col.Width = width.Value;
}
}
public void AddColumn(Column c)
{
topLevelContainer.Add(c.TopLevelContainer.CreateProxy());
columnFlow.Add(c);
AddNested(c);
}
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
var maniaObject = (ManiaHitObject)h.HitObject; var maniaObject = (ManiaHitObject)h.HitObject;

View File

@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
} }
[Test] [Test]
public void TestApplyDrainRateQueries() public void TestApplyDrainRateQueriesByDrKeyword()
{ {
const string query = "dr>2 quite specific dr<:6"; const string query = "dr>2 quite specific dr<:6";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
@ -73,6 +73,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.Less(filterCriteria.DrainRate.Min, 6.1f); Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
} }
[Test]
public void TestApplyDrainRateQueriesByHpKeyword()
{
const string query = "hp>2 quite specific hp<=6";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim());
Assert.AreEqual(2, filterCriteria.SearchTerms.Length);
Assert.Greater(filterCriteria.DrainRate.Min, 2.0f);
Assert.Less(filterCriteria.DrainRate.Min, 2.1f);
Assert.Greater(filterCriteria.DrainRate.Max, 6.0f);
Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
}
[Test] [Test]
public void TestApplyBPMQueries() public void TestApplyBPMQueries()
{ {

View File

@ -7,8 +7,10 @@ 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.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
@ -72,6 +74,32 @@ namespace osu.Game.Tests.Visual.Online
}; };
} }
[Test]
public void TestOnlyFailMetrics()
{
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).ToArray(),
}
});
AddAssert("graph max values correct",
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 100));
}
[Test]
public void TestEmptyMetrics()
{
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
{
Metrics = new BeatmapMetrics()
});
AddAssert("graph max values correct",
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 0));
}
private class GraphExposingSuccessRate : SuccessRate private class GraphExposingSuccessRate : SuccessRate
{ {
public new FailRetryGraph Graph => base.Graph; public new FailRetryGraph Graph => base.Graph;

View File

@ -68,22 +68,22 @@ namespace osu.Game.Tests.Visual.Settings
[Test] [Test]
public void TestClearButtonOnBindings() public void TestClearButtonOnBindings()
{ {
KeyBindingRow backBindingRow = null; KeyBindingRow multiBindingRow = null;
AddStep("click back binding row", () => AddStep("click first row with two bindings", () =>
{ {
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10); multiBindingRow = panel.ChildrenOfType<KeyBindingRow>().First(row => row.Defaults.Count() > 1);
InputManager.MoveMouseTo(backBindingRow); InputManager.MoveMouseTo(multiBindingRow);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
clickClearButton(); clickClearButton();
AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text)); AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text));
AddStep("click second binding", () => AddStep("click second binding", () =>
{ {
var target = backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1); var target = multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
InputManager.MoveMouseTo(target); InputManager.MoveMouseTo(target);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual.Settings
clickClearButton(); clickClearButton();
AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text)); AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text));
void clickClearButton() void clickClearButton()
{ {
AddStep("click clear button", () => AddStep("click clear button", () =>
{ {
var clearButton = backBindingRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single(); var clearButton = multiBindingRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
InputManager.MoveMouseTo(clearButton); InputManager.MoveMouseTo(clearButton);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
@ -108,20 +108,20 @@ namespace osu.Game.Tests.Visual.Settings
[Test] [Test]
public void TestClickRowSelectsFirstBinding() public void TestClickRowSelectsFirstBinding()
{ {
KeyBindingRow backBindingRow = null; KeyBindingRow multiBindingRow = null;
AddStep("click back binding row", () => AddStep("click first row with two bindings", () =>
{ {
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10); multiBindingRow = panel.ChildrenOfType<KeyBindingRow>().First(row => row.Defaults.Count() > 1);
InputManager.MoveMouseTo(backBindingRow); InputManager.MoveMouseTo(multiBindingRow);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddAssert("first binding selected", () => backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding); AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
AddStep("click second binding", () => AddStep("click second binding", () =>
{ {
var target = backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1); var target = multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
InputManager.MoveMouseTo(target); InputManager.MoveMouseTo(target);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
@ -129,12 +129,12 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("click back binding row", () => AddStep("click back binding row", () =>
{ {
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10); multiBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
InputManager.MoveMouseTo(backBindingRow); InputManager.MoveMouseTo(multiBindingRow);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddAssert("first binding selected", () => backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding); AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
} }
} }
} }

View File

@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select
private void updateMetrics() private void updateMetrics()
{ {
var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false;
var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) && (beatmap?.Metrics.Fails?.Any() ?? false); var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false);
if (hasRatings) if (hasRatings)
{ {

View File

@ -29,16 +29,30 @@ namespace osu.Game.Screens.Select.Details
var retries = Metrics?.Retries ?? Array.Empty<int>(); var retries = Metrics?.Retries ?? Array.Empty<int>();
var fails = Metrics?.Fails ?? Array.Empty<int>(); var fails = Metrics?.Fails ?? Array.Empty<int>();
var retriesAndFails = sumRetriesAndFails(retries, fails);
float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; float maxValue = retriesAndFails.Any() ? retriesAndFails.Max() : 0;
failGraph.MaxValue = maxValue; failGraph.MaxValue = maxValue;
retryGraph.MaxValue = maxValue; retryGraph.MaxValue = maxValue;
failGraph.Values = fails.Select(f => (float)f); failGraph.Values = fails.Select(v => (float)v);
retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); retryGraph.Values = retriesAndFails.Select(v => (float)v);
} }
} }
private int[] sumRetriesAndFails(int[] retries, int[] fails)
{
var result = new int[Math.Max(retries.Length, fails.Length)];
for (int i = 0; i < retries.Length; ++i)
result[i] = retries[i];
for (int i = 0; i < fails.Length; ++i)
result[i] += fails[i];
return result;
}
public FailRetryGraph() public FailRetryGraph()
{ {
Children = new[] Children = new[]

View File

@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select
internal static class FilterQueryParser internal static class FilterQueryParser
{ {
private static readonly Regex query_syntax_regex = new Regex( private static readonly Regex query_syntax_regex = new Regex(
@"\b(?<key>stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?<op>[=:><]+)(?<value>("".*"")|(\S*))", @"\b(?<key>stars|ar|dr|hp|cs|divisor|length|objects|bpm|status|creator|artist)(?<op>[=:><]+)(?<value>("".*"")|(\S*))",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static void ApplyQueries(FilterCriteria criteria, string query) internal static void ApplyQueries(FilterCriteria criteria, string query)
@ -43,6 +43,7 @@ namespace osu.Game.Screens.Select
break; break;
case "dr" when parseFloatWithPoint(value, out var dr): case "dr" when parseFloatWithPoint(value, out var dr):
case "hp" when parseFloatWithPoint(value, out dr):
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
break; break;

View File

@ -76,6 +76,8 @@ namespace osu.Game.Storyboards.Drawables
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
Channel?.Stop(); Channel?.Stop();
Channel = null;
base.Dispose(isDisposing); base.Dispose(isDisposing);
} }
} }