diff --git a/.editorconfig b/.editorconfig
index 67f98f94eb..a5f7795882 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -191,4 +191,7 @@ dotnet_diagnostic.IDE0052.severity = silent
#Rules for disposable
dotnet_diagnostic.IDE0067.severity = none
dotnet_diagnostic.IDE0068.severity = none
-dotnet_diagnostic.IDE0069.severity = none
\ No newline at end of file
+dotnet_diagnostic.IDE0069.severity = none
+
+#Disable operator overloads requiring alternate named methods
+dotnet_diagnostic.CA2225.severity = none
\ No newline at end of file
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 4fd0e5e8c7..8c278604aa 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -113,7 +113,7 @@ platform :ios do
souyuz(
platform: "ios",
- plist_path: "../osu.iOS/Info.plist"
+ plist_path: "osu.iOS/Info.plist"
)
end
@@ -127,7 +127,7 @@ platform :ios do
end
lane :update_version do |options|
- options[:plist_path] = '../osu.iOS/Info.plist'
+ options[:plist_path] = 'osu.iOS/Info.plist'
app_version(options)
end
diff --git a/osu.Android.props b/osu.Android.props
index f3fb949f76..1a76a24496 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index df54df7b01..8c48158acd 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
+ [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })]
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
new file mode 100644
index 0000000000..1eb0975010
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.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
+ {
+ 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().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;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index d6bba3d55e..3c636a5b97 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
@@ -38,7 +40,11 @@ namespace osu.Game.Rulesets.Catch.Tests
new Vector2(width, 0)
}),
StartTime = i * 2000,
- NewCombo = i % 8 == 0
+ NewCombo = i % 8 == 0,
+ Samples = new List(new[]
+ {
+ new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 }
+ })
});
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index c07e4fdad3..385d8ed7fa 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -20,19 +20,19 @@ namespace osu.Game.Rulesets.Catch.Tests
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
- AddStep("show droplet", () => SetContents(createDrawableDroplet));
-
+ AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
+
+ AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
}
private Drawable createDrawableTinyDroplet()
{
- var droplet = new TinyDroplet
+ var droplet = new TestCatchTinyDroplet
{
- StartTime = Clock.CurrentTime,
Scale = 1.5f,
};
@@ -47,12 +47,12 @@ namespace osu.Game.Rulesets.Catch.Tests
};
}
- private Drawable createDrawableDroplet()
+ private Drawable createDrawableDroplet(bool hyperdash = false)
{
- var droplet = new Droplet
+ var droplet = new TestCatchDroplet
{
- StartTime = Clock.CurrentTime,
Scale = 1.5f,
+ HyperDashTarget = hyperdash ? new Banana() : null
};
return new DrawableDroplet(droplet)
@@ -95,5 +95,21 @@ namespace osu.Game.Rulesets.Catch.Tests
public override FruitVisualRepresentation VisualRepresentation { get; }
}
+
+ public class TestCatchDroplet : Droplet
+ {
+ public TestCatchDroplet()
+ {
+ StartTime = 1000000000000;
+ }
+ }
+
+ public class TestCatchTinyDroplet : TinyDroplet
+ {
+ public TestCatchTinyDroplet()
+ {
+ StartTime = 1000000000000;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index ad24adf352..db09b2bc6b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
@@ -18,23 +19,43 @@ namespace osu.Game.Rulesets.Catch.Tests
{
protected override bool Autoplay => true;
+ private int hyperDashCount;
+ private bool inHyperDash;
+
[Test]
public void TestHyperDash()
{
- AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
- AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast.
-
- AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0);
-
- for (int i = 0; i < 3; i++)
+ AddStep("reset count", () =>
{
- AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing);
- AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing);
+ inHyperDash = false;
+ 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().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);
}
}
- private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher;
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
@@ -46,6 +67,8 @@ namespace osu.Game.Rulesets.Catch.Tests
}
};
+ beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
+
// Should produce a hyper-dash (edge case test)
beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });
@@ -63,6 +86,20 @@ namespace osu.Game.Rulesets.Catch.Tests
createObjects(() => new Fruit { X = right_x });
createObjects(() => new TestJuiceStream(left_x), 1);
+ beatmap.ControlPointInfo.Add(startTime, new TimingControlPoint
+ {
+ BeatLength = 50
+ });
+
+ createObjects(() => new TestJuiceStream(left_x)
+ {
+ Path = new SliderPath(new[]
+ {
+ new PathControlPoint(Vector2.Zero),
+ new PathControlPoint(new Vector2(512, 0))
+ })
+ }, 1);
+
return beatmap;
void createObjects(Func createObject, int count = 3)
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index bb14988414..a08c5b6fb1 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (amount > 0)
{
// Clamp to the right bound
- if (position + amount < 1)
+ if (position + amount < CatchPlayfield.WIDTH)
position += amount;
}
else
@@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
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;
double lastExcess = halfCatcherWidth;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index c1d24395e4..1e42c6a240 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
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);
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 04932ecdbb..5985ec9b68 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -27,6 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects
set => x = value;
}
+ ///
+ /// Whether this object can be placed on the catcher's plate.
+ ///
+ public virtual bool CanBePlated => false;
+
///
/// A random offset applied to , set by the .
///
@@ -100,6 +105,14 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
+ ///
+ /// Represents a single object that can be caught by the catcher.
+ ///
+ public abstract class PalpableCatchHitObject : CatchHitObject
+ {
+ public override bool CanBePlated => true;
+ }
+
public enum FruitVisualRepresentation
{
Pear,
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index c6345a9df7..2fe017dc62 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -15,14 +15,12 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public abstract class PalpableCatchHitObject : DrawableCatchHitObject
- where TObject : CatchHitObject
+ public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject
+ where TObject : PalpableCatchHitObject
{
- public override bool CanBePlated => true;
-
protected Container ScaleContainer { get; private set; }
- protected PalpableCatchHitObject(TObject hitObject)
+ protected PalpableDrawableCatchHitObject(TObject hitObject)
: base(hitObject)
{
Origin = Anchor.Centre;
@@ -65,9 +63,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public abstract class DrawableCatchHitObject : DrawableHitObject
{
- public virtual bool CanBePlated => false;
-
- public virtual bool StaysOnPlate => CanBePlated;
+ public virtual bool StaysOnPlate => HitObject.CanBePlated;
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
index cad8892283..688240fd86 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
@@ -4,12 +4,11 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Utils;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableDroplet : PalpableCatchHitObject
+ public class DrawableDroplet : PalpableDrawableCatchHitObject
{
public override bool StaysOnPlate => false;
@@ -21,11 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new Pulp
- {
- Size = Size / 4,
- AccentColour = { BindTarget = AccentColour }
- });
+ ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece());
}
protected override void UpdateInitialTransforms()
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
index fae5a10d04..c1c34e4157 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -8,7 +8,7 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableFruit : PalpableCatchHitObject
+ public class DrawableFruit : PalpableDrawableCatchHitObject
{
public DrawableFruit(Fruit h)
: base(h)
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
new file mode 100644
index 0000000000..c2499446fa
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class DropletPiece : CompositeDrawable
+ {
+ public DropletPiece()
+ {
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject)
+ {
+ DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
+ var hitObject = drawableCatchObject.HitObject;
+
+ InternalChild = new Pulp
+ {
+ RelativeSizeAxes = Axes.Both,
+ AccentColour = { BindTarget = drawableObject.AccentColour }
+ };
+
+ if (hitObject.HyperDash)
+ {
+ AddInternal(new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(2f),
+ Depth = 1,
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
+ BorderThickness = 6,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0.3f,
+ Blending = BlendingParameters.Additive,
+ RelativeSizeAxes = Axes.Both,
+ Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
index 7ac9f11ad6..4bffdab3d8 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -21,11 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public const float RADIUS_ADJUST = 1.1f;
private Circle border;
-
private CatchHitObject hitObject;
- private readonly IBindable accentColour = new Bindable();
-
public FruitPiece()
{
RelativeSizeAxes = Axes.Both;
@@ -37,8 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
hitObject = drawableCatchObject.HitObject;
- accentColour.BindTo(drawableCatchObject.AccentColour);
-
AddRangeInternal(new[]
{
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
index 1e7506a257..d3e4945611 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
- Radius = Size.X / 2,
+ Radius = DrawWidth / 2,
Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f)
};
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
index 7b0bb3f0ae..9c1004a04b 100644
--- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
@@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Droplet : CatchHitObject
+ public class Droplet : PalpableCatchHitObject
{
public override Judgement CreateJudgement() => new CatchDropletJudgement();
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
index 6f0423b420..43486796ad 100644
--- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
@@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Fruit : CatchHitObject
+ public class Fruit : PalpableCatchHitObject
{
public override Judgement CreateJudgement() => new CatchJudgement();
}
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json
new file mode 100644
index 0000000000..3bde97070c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json
@@ -0,0 +1,17 @@
+{
+ "Mappings": [{
+ "StartTime": 3368,
+ "Objects": [{
+ "StartTime": 3368,
+ "Position": 374
+ }]
+ },
+ {
+ "StartTime": 3501,
+ "Objects": [{
+ "StartTime": 3501,
+ "Position": 446
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu
new file mode 100644
index 0000000000..6630f369d5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu
@@ -0,0 +1,20 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.7
+Mode: 2
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:9.6
+ApproachRate:9.6
+SliderMultiplier:1.9
+SliderTickRate:1
+
+[TimingPoints]
+2169,266.666666666667,4,2,1,70,1,0
+
+[HitObjects]
+374,60,3368,1,0,0:0:0:0:
+410,146,3501,1,2,0:1:0:0:
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
index 8ee23461ba..efc1b24ed5 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
@@ -10,15 +10,21 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
{
+ private const float playfield_size_adjust = 0.8f;
+
protected override Container Content => content;
private readonly Container content;
public CatchPlayfieldAdjustmentContainer()
{
- Anchor = Anchor.TopCentre;
- Origin = Anchor.TopCentre;
+ // because we are using centre anchor/origin, we will need to limit visibility in the future
+ // to ensure tall windows do not get a readability advantage.
+ // it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values
+ // which are compatible with TopCentre alignment.
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
- Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
+ Size = new Vector2(playfield_size_adjust);
InternalChild = new Container
{
@@ -27,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
FillAspectRatio = 4f / 3,
- Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
+ Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, }
};
}
@@ -40,8 +46,14 @@ namespace osu.Game.Rulesets.Catch.UI
{
base.Update();
+ // in stable, fruit fall vertically from -100 to 340.
+ // to emulate this, we want to make our playfield 440 gameplay pixels high.
+ // we then offset it -100 vertically in the position set below.
+ const float stable_v_offset_ratio = 440 / 384f;
+
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
- Size = Vector2.Divide(Vector2.One, Scale);
+ Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X);
+ Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 8820dff730..a30e1b7b47 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
///
- private const float allowed_catch_range = 0.8f;
+ public const float ALLOWED_CATCH_RANGE = 0.8f;
///
/// The drawable catcher for .
@@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// The scale of the catcher.
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;
///
/// Calculates the width of the area used for attempting catches in gameplay.
@@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Catch.UI
/// Whether the catch is possible.
public bool AttemptCatch(CatchHitObject fruit)
{
+ if (!fruit.CanBePlated)
+ return false;
+
var halfCatchWidth = catchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
@@ -226,9 +229,8 @@ namespace osu.Game.Rulesets.Catch.UI
catchObjectPosition >= catcherPosition - halfCatchWidth &&
catchObjectPosition <= catcherPosition + halfCatchWidth;
- // only update hyperdash state if we are catching a fruit.
- // exceptions are Droplets and JuiceStreams.
- if (!(fruit is Fruit)) return validCatch;
+ // only update hyperdash state if we are not catching a tiny droplet.
+ if (fruit is TinyDroplet) return validCatch;
if (validCatch && fruit.HyperDash)
{
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 4255c3b1af..03ebf01b9b 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI
lastPlateableFruit.OnLoadComplete += _ => action();
}
- if (result.IsHit && fruit.CanBePlated)
+ if (result.IsHit && fruit.HitObject.CanBePlated)
{
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs
new file mode 100644
index 0000000000..f2cc254e38
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public class TestSceneManiaModInvert : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [Test]
+ public void TestInversion() => CreateModTest(new ModTestData
+ {
+ Mod = new ManiaModInvert(),
+ PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
index efcfe11dad..5fa687298a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@@ -15,7 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
AccentColour.Value = colours.Yellow;
Background.Alpha = 0.5f;
- Foreground.Alpha = 0;
}
+
+ protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0);
}
}
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
index 294aab1e4e..28e5d2cc1b 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
@@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTickJudgement : ManiaJudgement
{
- protected override int NumericResultFor(HitResult result) => 20;
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0;
protected override double HealthIncreaseFor(HitResult result)
{
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 68dce8b139..2795868c97 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -220,6 +220,7 @@ namespace osu.Game.Rulesets.Mania
new ManiaModDualStages(),
new ManiaModMirror(),
new ManiaModDifficultyAdjust(),
+ new ManiaModInvert(),
};
case ModType.Automation:
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
new file mode 100644
index 0000000000..1ea45c295c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
@@ -0,0 +1,76 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion
+ {
+ public override string Name => "Invert";
+
+ public override string Acronym => "IN";
+ public override double ScoreMultiplier => 1;
+
+ public override string Description => "Hold the keys. To the beat.";
+
+ public override IconUsage? Icon => FontAwesome.Solid.YinYang;
+
+ public override ModType Type => ModType.Conversion;
+
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
+
+ var newObjects = new List();
+
+ foreach (var column in maniaBeatmap.HitObjects.GroupBy(h => h.Column))
+ {
+ var newColumnObjects = new List();
+
+ var locations = column.OfType().Select(n => (startTime: n.StartTime, samples: n.Samples))
+ .Concat(column.OfType().SelectMany(h => new[]
+ {
+ (startTime: h.StartTime, samples: h.GetNodeSamples(0)),
+ (startTime: h.EndTime, samples: h.GetNodeSamples(1))
+ }))
+ .OrderBy(h => h.startTime).ToList();
+
+ for (int i = 0; i < locations.Count - 1; i++)
+ {
+ // Full duration of the hold note.
+ double duration = locations[i + 1].startTime - locations[i].startTime;
+
+ // Beat length at the end of the hold note.
+ double beatLength = beatmap.ControlPointInfo.TimingPointAt(locations[i + 1].startTime).BeatLength;
+
+ // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes.
+ duration = Math.Max(duration / 2, duration - beatLength / 4);
+
+ newColumnObjects.Add(new HoldNote
+ {
+ Column = column.Key,
+ StartTime = locations[i].startTime,
+ Duration = duration,
+ NodeSamples = new List> { locations[i].samples, Array.Empty() }
+ });
+ }
+
+ newObjects.AddRange(newColumnObjects);
+ }
+
+ maniaBeatmap.HitObjects = newObjects.OrderBy(h => h.StartTime).ToList();
+
+ // No breaks
+ maniaBeatmap.Breaks.Clear();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 0c5289efe1..a44f8a8886 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container tailContainer;
private readonly Container tickContainer;
- private readonly Drawable bodyPiece;
+ private readonly SkinnableDrawable bodyPiece;
///
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
RelativeSizeAxes = Axes.X;
- AddRangeInternal(new[]
+ AddRangeInternal(new Drawable[]
{
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
{
@@ -135,6 +135,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// Samples are played by the head/tail notes.
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+ (bodyPiece.Drawable as IHoldNoteBody)?.Recycle();
+ }
+
protected override void Update()
{
base.Update();
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
index bc4a095395..9999983af5 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
@@ -19,24 +19,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
///
/// Represents length-wise portion of a hold note.
///
- public class DefaultBodyPiece : CompositeDrawable
+ public class DefaultBodyPiece : CompositeDrawable, IHoldNoteBody
{
protected readonly Bindable AccentColour = new Bindable();
-
- private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
- private readonly IBindable isHitting = new Bindable();
+ protected readonly IBindable IsHitting = new Bindable();
protected Drawable Background { get; private set; }
- protected BufferedContainer Foreground { get; private set; }
-
- private BufferedContainer subtractionContainer;
- private Container subtractionLayer;
+ private Container foregroundContainer;
public DefaultBodyPiece()
{
Blending = BlendingParameters.Additive;
-
- AddLayout(subtractionCache);
}
[BackgroundDependencyLoader(true)]
@@ -45,7 +38,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
InternalChildren = new[]
{
Background = new Box { RelativeSizeAxes = Axes.Both },
- Foreground = new BufferedContainer
+ foregroundContainer = new Container { RelativeSizeAxes = Axes.Both }
+ };
+
+ if (drawableObject != null)
+ {
+ var holdNote = (DrawableHoldNote)drawableObject;
+
+ AccentColour.BindTo(drawableObject.AccentColour);
+ IsHitting.BindTo(holdNote.IsHitting);
+ }
+
+ AccentColour.BindValueChanged(onAccentChanged, true);
+
+ Recycle();
+ }
+
+ public void Recycle() => foregroundContainer.Child = CreateForeground();
+
+ protected virtual Drawable CreateForeground() => new ForegroundPiece
+ {
+ AccentColour = { BindTarget = AccentColour },
+ IsHitting = { BindTarget = IsHitting }
+ };
+
+ private void onAccentChanged(ValueChangedEvent accent) => Background.Colour = accent.NewValue.Opacity(0.7f);
+
+ private class ForegroundPiece : CompositeDrawable
+ {
+ public readonly Bindable AccentColour = new Bindable();
+ public readonly IBindable IsHitting = new Bindable();
+
+ private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
+
+ private BufferedContainer foregroundBuffer;
+ private BufferedContainer subtractionBuffer;
+ private Container subtractionLayer;
+
+ public ForegroundPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ AddLayout(subtractionCache);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = foregroundBuffer = new BufferedContainer
{
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
@@ -53,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Children = new Drawable[]
{
new Box { RelativeSizeAxes = Axes.Both },
- subtractionContainer = new BufferedContainer
+ subtractionBuffer = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
// This is needed because we're blending with another object
@@ -77,60 +117,51 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
}
- }
- };
-
- if (drawableObject != null)
- {
- var holdNote = (DrawableHoldNote)drawableObject;
-
- AccentColour.BindTo(drawableObject.AccentColour);
- isHitting.BindTo(holdNote.IsHitting);
- }
-
- AccentColour.BindValueChanged(onAccentChanged, true);
- isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true);
- }
-
- private void onAccentChanged(ValueChangedEvent accent)
- {
- Foreground.Colour = accent.NewValue.Opacity(0.5f);
- Background.Colour = accent.NewValue.Opacity(0.7f);
-
- const float animation_length = 50;
-
- Foreground.ClearTransforms(false, nameof(Foreground.Colour));
-
- if (isHitting.Value)
- {
- // wait for the next sync point
- double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
- using (Foreground.BeginDelayedSequence(synchronisedOffset))
- Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
- }
-
- subtractionCache.Invalidate();
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (!subtractionCache.IsValid)
- {
- subtractionLayer.Width = 5;
- subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth);
- subtractionLayer.EdgeEffect = new EdgeEffectParameters
- {
- Colour = Color4.White,
- Type = EdgeEffectType.Glow,
- Radius = DrawWidth
};
- Foreground.ForceRedraw();
- subtractionContainer.ForceRedraw();
+ AccentColour.BindValueChanged(onAccentChanged, true);
+ IsHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true);
+ }
- subtractionCache.Validate();
+ private void onAccentChanged(ValueChangedEvent accent)
+ {
+ foregroundBuffer.Colour = accent.NewValue.Opacity(0.5f);
+
+ const float animation_length = 50;
+
+ foregroundBuffer.ClearTransforms(false, nameof(foregroundBuffer.Colour));
+
+ if (IsHitting.Value)
+ {
+ // wait for the next sync point
+ double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
+ using (foregroundBuffer.BeginDelayedSequence(synchronisedOffset))
+ foregroundBuffer.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(foregroundBuffer.Colour, animation_length).Loop();
+ }
+
+ subtractionCache.Invalidate();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!subtractionCache.IsValid)
+ {
+ subtractionLayer.Width = 5;
+ subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth);
+ subtractionLayer.EdgeEffect = new EdgeEffectParameters
+ {
+ Colour = Color4.White,
+ Type = EdgeEffectType.Glow,
+ Radius = DrawWidth
+ };
+
+ foregroundBuffer.ForceRedraw();
+ subtractionBuffer.ForceRedraw();
+
+ subtractionCache.Validate();
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs
new file mode 100644
index 0000000000..ac3792c01d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
+{
+ ///
+ /// Interface for mania hold note bodies.
+ ///
+ public interface IHoldNoteBody
+ {
+ ///
+ /// Recycles the contents of this to free used resources.
+ ///
+ void Recycle();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index a100c9a58e..6cc7ff92d3 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mania.Objects
{
StartTime = StartTime,
Column = Column,
- Samples = getNodeSamples(0),
+ Samples = GetNodeSamples(0),
});
AddNested(Tail = new TailNote
{
StartTime = EndTime,
Column = Column,
- Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1),
+ Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
});
}
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.Objects
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
- private IList getNodeSamples(int nodeIndex) =>
+ public IList GetNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
index 64a7641421..f9286b5095 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
@@ -20,9 +21,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
private readonly IBindable direction = new Bindable();
private readonly bool isLastColumn;
+ private Container borderLineContainer;
private Container lightContainer;
private Sprite light;
+ private float hitPosition;
+
public LegacyColumnBackground(bool isLastColumn)
{
this.isLastColumn = isLastColumn;
@@ -44,6 +48,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|| isLastColumn;
+ hitPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitPosition)?.Value
+ ?? Stage.HIT_TARGET_POSITION;
+
float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
?? 0;
@@ -63,23 +70,30 @@ namespace osu.Game.Rulesets.Mania.Skinning
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour
},
- new Box
+ borderLineContainer = new Container
{
- 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
+ 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
+ }
+ }
},
lightContainer = new Container
{
@@ -109,11 +123,15 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
lightContainer.Anchor = Anchor.TopCentre;
lightContainer.Scale = new Vector2(1, -1);
+
+ borderLineContainer.Padding = new MarginPadding { Top = hitPosition };
}
else
{
lightContainer.Anchor = Anchor.BottomCentre;
lightContainer.Scale = Vector2.One;
+
+ borderLineContainer.Padding = new MarginPadding { Bottom = hitPosition };
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
index 12747924de..7c5d41efcf 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
@@ -66,6 +67,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
public void Animate(JudgementResult result)
{
+ if (result.Judgement is HoldNoteTickJudgement)
+ return;
+
(explosion as IFramedAnimation)?.GotoFrame(0);
explosion?.FadeInFromZero(80)
diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs
index 9c4b6f774f..0b1232b8db 100644
--- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
{
- protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK;
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0;
protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
index de3ae27e55..f54e7a9a15 100644
--- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public override bool AffectsCombo => false;
- protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK;
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0;
protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0;
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
new file mode 100644
index 0000000000..b0baf0385e
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneScoreProcessor : OsuTestScene
+ {
+ [Test]
+ public void TestNoScoreIncreaseFromMiss()
+ {
+ var beatmap = new Beatmap { HitObjects = { new TestHitObject() } };
+
+ var scoreProcessor = new ScoreProcessor();
+ scoreProcessor.ApplyBeatmap(beatmap);
+
+ // Apply a miss judgement
+ scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss });
+
+ Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0));
+ }
+
+ private class TestHitObject : HitObject
+ {
+ public override Judgement CreateJudgement() => new TestJudgement();
+ }
+
+ private class TestJudgement : Judgement
+ {
+ protected override int NumericResultFor(HitResult result) => 100;
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index 7b2913b817..d15682b1eb 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
}
[Test]
- public void TestApplyDrainRateQueries()
+ public void TestApplyDrainRateQueriesByDrKeyword()
{
const string query = "dr>2 quite specific dr<:6";
var filterCriteria = new FilterCriteria();
@@ -73,6 +73,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
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]
public void TestApplyBPMQueries()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
index 030d420ec0..09b4f9b761 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
@@ -20,7 +20,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
- TextSize = 40,
Margin = new MarginPadding(20),
};
Add(score);
@@ -30,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Margin = new MarginPadding(10),
- TextSize = 40,
};
Add(comboCounter);
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index b4985cad9f..f819ae4682 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -4,8 +4,10 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
+using osu.Game.Overlays;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets;
using osuTK.Input;
@@ -15,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneToolbar : OsuManualInputManagerTestScene
{
- private Toolbar toolbar;
+ private TestToolbar toolbar;
[Resolved]
private RulesetStore rulesets { get; set; }
@@ -23,7 +25,7 @@ namespace osu.Game.Tests.Visual.Menus
[SetUp]
public void SetUp() => Schedule(() =>
{
- Child = toolbar = new Toolbar { State = { Value = Visibility.Visible } };
+ Child = toolbar = new TestToolbar { State = { Value = Visibility.Visible } };
});
[Test]
@@ -72,5 +74,24 @@ namespace osu.Game.Tests.Visual.Menus
AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected));
}
}
+
+ [TestCase(OverlayActivation.All)]
+ [TestCase(OverlayActivation.Disabled)]
+ public void TestRespectsOverlayActivation(OverlayActivation mode)
+ {
+ AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode);
+ AddStep("hide toolbar", () => toolbar.Hide());
+ AddStep("try to show toolbar", () => toolbar.Show());
+
+ if (mode == OverlayActivation.Disabled)
+ AddAssert("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden);
+ else
+ AddAssert("toolbar is visible", () => toolbar.State.Value == Visibility.Visible);
+ }
+
+ public class TestToolbar : Toolbar
+ {
+ public new Bindable OverlayActivationMode => base.OverlayActivationMode;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
index 61859c9da3..3924b0333f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -10,11 +12,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
protected override bool UseOnlineAPI => true;
+ [Cached]
+ private MusicController musicController { get; set; } = new MusicController();
+
public TestSceneMultiScreen()
{
Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer();
- AddStep(@"show", () => LoadScreen(multi));
+ AddStep("show", () => LoadScreen(multi));
+ AddUntilStep("wait for loaded", () => multi.IsLoaded);
}
}
}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index e06b3a8a7e..987a4a67fe 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -68,22 +68,22 @@ namespace osu.Game.Tests.Visual.Settings
[Test]
public void TestClearButtonOnBindings()
{
- KeyBindingRow backBindingRow = null;
+ KeyBindingRow multiBindingRow = null;
- AddStep("click back binding row", () =>
+ AddStep("click first row with two bindings", () =>
{
- backBindingRow = panel.ChildrenOfType().ElementAt(10);
- InputManager.MoveMouseTo(backBindingRow);
+ multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1);
+ InputManager.MoveMouseTo(multiBindingRow);
InputManager.Click(MouseButton.Left);
});
clickClearButton();
- AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType().First().Text.Text));
+ AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text));
AddStep("click second binding", () =>
{
- var target = backBindingRow.ChildrenOfType().ElementAt(1);
+ var target = multiBindingRow.ChildrenOfType().ElementAt(1);
InputManager.MoveMouseTo(target);
InputManager.Click(MouseButton.Left);
@@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual.Settings
clickClearButton();
- AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType().ElementAt(1).Text.Text));
+ AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text));
void clickClearButton()
{
AddStep("click clear button", () =>
{
- var clearButton = backBindingRow.ChildrenOfType().Single();
+ var clearButton = multiBindingRow.ChildrenOfType().Single();
InputManager.MoveMouseTo(clearButton);
InputManager.Click(MouseButton.Left);
@@ -108,20 +108,20 @@ namespace osu.Game.Tests.Visual.Settings
[Test]
public void TestClickRowSelectsFirstBinding()
{
- KeyBindingRow backBindingRow = null;
+ KeyBindingRow multiBindingRow = null;
- AddStep("click back binding row", () =>
+ AddStep("click first row with two bindings", () =>
{
- backBindingRow = panel.ChildrenOfType().ElementAt(10);
- InputManager.MoveMouseTo(backBindingRow);
+ multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1);
+ InputManager.MoveMouseTo(multiBindingRow);
InputManager.Click(MouseButton.Left);
});
- AddAssert("first binding selected", () => backBindingRow.ChildrenOfType().First().IsBinding);
+ AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding);
AddStep("click second binding", () =>
{
- var target = backBindingRow.ChildrenOfType().ElementAt(1);
+ var target = multiBindingRow.ChildrenOfType().ElementAt(1);
InputManager.MoveMouseTo(target);
InputManager.Click(MouseButton.Left);
@@ -129,12 +129,12 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("click back binding row", () =>
{
- backBindingRow = panel.ChildrenOfType().ElementAt(10);
- InputManager.MoveMouseTo(backBindingRow);
+ multiBindingRow = panel.ChildrenOfType().ElementAt(10);
+ InputManager.MoveMouseTo(multiBindingRow);
InputManager.Click(MouseButton.Left);
});
- AddAssert("first binding selected", () => backBindingRow.ChildrenOfType().First().IsBinding);
+ AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding);
}
}
}
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
index 2e7484542a..695c6d6f3e 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
@@ -127,21 +128,29 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
private class MatchScoreCounter : ScoreCounter
{
+ private OsuSpriteText displayedSpriteText;
+
public MatchScoreCounter()
{
Margin = new MarginPadding { Top = bar_height, Horizontal = 10 };
-
- Winning = false;
-
- DisplayedCountSpriteText.Spacing = new Vector2(-6);
}
public bool Winning
{
- set => DisplayedCountSpriteText.Font = value
+ set => updateFont(value);
+ }
+
+ protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
+ {
+ displayedSpriteText = s;
+ displayedSpriteText.Spacing = new Vector2(-6);
+ updateFont(false);
+ });
+
+ private void updateFont(bool winning)
+ => displayedSpriteText.Font = winning
? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true)
: OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true);
- }
}
}
}
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index 7b1a174c1e..307ee1c773 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -31,6 +31,7 @@ namespace osu.Game.Tournament
public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff");
private Drawable heightWarning;
private Bindable windowSize;
+ private Bindable windowMode;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig)
@@ -43,6 +44,12 @@ namespace osu.Game.Tournament
heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
}), true);
+ windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode);
+ windowMode.BindValueChanged(mode => ScheduleAfterChildren(() =>
+ {
+ windowMode.Value = WindowMode.Windowed;
+ }), true);
+
AddRange(new[]
{
new Container
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index ac399e37c4..b4bcf285b9 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -109,6 +109,15 @@ namespace osu.Game.Beatmaps
// Convert
IBeatmap converted = converter.Convert();
+ // Apply conversion mods to the result
+ foreach (var mod in mods.OfType())
+ {
+ if (cancellationSource.IsCancellationRequested)
+ throw new BeatmapLoadTimeoutException(BeatmapInfo);
+
+ mod.ApplyToBeatmap(converted);
+ }
+
// Apply difficulty mods
if (mods.Any(m => m is IApplicableToDifficulty))
{
diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index 0d173e2d3e..1ec4dfc91a 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -74,9 +74,9 @@ namespace osu.Game.Graphics.UserInterface
protected override Color4 SelectionColour => new Color4(249, 90, 255, 255);
- protected override void OnTextAdded(string added)
+ protected override void OnUserTextAdded(string added)
{
- base.OnTextAdded(added);
+ base.OnUserTextAdded(added);
if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples)
capsTextAddedSample?.Play();
@@ -84,9 +84,9 @@ namespace osu.Game.Graphics.UserInterface
textAddedSamples[RNG.Next(0, 3)]?.Play();
}
- protected override void OnTextRemoved(string removed)
+ protected override void OnUserTextRemoved(string removed)
{
- base.OnTextRemoved(removed);
+ base.OnUserTextRemoved(removed);
textRemovedSample?.Play();
}
diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs
index 940c9808ce..3ea9c1053c 100644
--- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs
+++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs
@@ -3,6 +3,8 @@
using System;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Utils;
namespace osu.Game.Graphics.UserInterface
@@ -23,12 +25,11 @@ namespace osu.Game.Graphics.UserInterface
public PercentageCounter()
{
- DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true);
Current.Value = DisplayedCount = 1.0f;
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours) => AccentColour = colours.BlueLighter;
+ private void load(OsuColour colours) => Colour = colours.BlueLighter;
protected override string FormatCount(double count) => count.FormatAccuracy();
@@ -37,6 +38,9 @@ namespace osu.Game.Graphics.UserInterface
return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f;
}
+ protected override OsuSpriteText CreateSpriteText()
+ => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f, fixedWidth: true));
+
public override void Increment(double amount)
{
Current.Value += amount;
diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs
index cd244ed7e6..6763198213 100644
--- a/osu.Game/Graphics/UserInterface/RollingCounter.cs
+++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs
@@ -7,12 +7,12 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using System;
using System.Collections.Generic;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface
{
- public abstract class RollingCounter : Container, IHasAccentColour
+ public abstract class RollingCounter : Container
where T : struct, IEquatable
{
///
@@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface
///
public Bindable Current = new Bindable();
- protected SpriteText DisplayedCountSpriteText;
+ private SpriteText displayedCountSpriteText;
///
/// If true, the roll-up duration will be proportional to change in value.
@@ -46,57 +46,39 @@ namespace osu.Game.Graphics.UserInterface
public virtual T DisplayedCount
{
get => displayedCount;
-
set
{
if (EqualityComparer.Default.Equals(displayedCount, value))
return;
displayedCount = value;
- DisplayedCountSpriteText.Text = FormatCount(value);
+ if (displayedCountSpriteText != null)
+ displayedCountSpriteText.Text = FormatCount(value);
}
}
public abstract void Increment(T amount);
- public float TextSize
- {
- get => DisplayedCountSpriteText.Font.Size;
- set => DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: value);
- }
-
- public Color4 AccentColour
- {
- get => DisplayedCountSpriteText.Colour;
- set => DisplayedCountSpriteText.Colour = value;
- }
-
///
/// Skeleton of a numeric counter which value rolls over time.
///
protected RollingCounter()
{
- Children = new Drawable[]
- {
- DisplayedCountSpriteText = new OsuSpriteText { Font = OsuFont.Numeric }
- };
-
- TextSize = 40;
AutoSizeAxes = Axes.Both;
- DisplayedCount = Current.Value;
-
Current.ValueChanged += val =>
{
- if (IsLoaded) TransformCount(displayedCount, val.NewValue);
+ if (IsLoaded)
+ TransformCount(DisplayedCount, val.NewValue);
};
}
- protected override void LoadComplete()
+ [BackgroundDependencyLoader]
+ private void load()
{
- base.LoadComplete();
-
- DisplayedCountSpriteText.Text = FormatCount(Current.Value);
+ displayedCountSpriteText = CreateSpriteText();
+ displayedCountSpriteText.Text = FormatCount(DisplayedCount);
+ Child = displayedCountSpriteText;
}
///
@@ -167,5 +149,10 @@ namespace osu.Game.Graphics.UserInterface
this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing);
}
+
+ protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText
+ {
+ Font = OsuFont.Numeric.With(size: 40f),
+ };
}
}
diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs
index 01d8edaecf..faabe69f87 100644
--- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs
+++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs
@@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
@@ -24,12 +25,11 @@ namespace osu.Game.Graphics.UserInterface
/// How many leading zeroes the counter will have.
public ScoreCounter(uint leading = 0)
{
- DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true);
LeadingZeroes = leading;
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours) => AccentColour = colours.BlueLighter;
+ private void load(OsuColour colours) => Colour = colours.BlueLighter;
protected override double GetProportionalDuration(double currentValue, double newValue)
{
@@ -49,6 +49,9 @@ namespace osu.Game.Graphics.UserInterface
return ((long)count).ToString(format);
}
+ protected override OsuSpriteText CreateSpriteText()
+ => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
+
public override void Increment(double amount)
{
Current.Value += amount;
diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs
index af03cbb63e..aac0166774 100644
--- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs
+++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs
@@ -3,6 +3,8 @@
using System;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
@@ -19,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours) => AccentColour = colours.BlueLighter;
+ private void load(OsuColour colours) => Colour = colours.BlueLighter;
protected override string FormatCount(int count)
{
@@ -35,5 +37,8 @@ namespace osu.Game.Graphics.UserInterface
{
Current.Value += amount;
}
+
+ protected override OsuSpriteText CreateSpriteText()
+ => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f));
}
}
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index beac6adc59..3bf9e85428 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar
private const double transition_time = 500;
- private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All);
+ protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All);
// Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden.
public override bool PropagateNonPositionalInputSubTree => true;
@@ -89,14 +89,8 @@ namespace osu.Game.Overlays.Toolbar
// Bound after the selector is added to the hierarchy to give it a chance to load the available rulesets
rulesetSelector.Current.BindTo(parentRuleset);
- State.ValueChanged += visibility =>
- {
- if (overlayActivationMode.Value == OverlayActivation.Disabled)
- Hide();
- };
-
if (osuGame != null)
- overlayActivationMode.BindTo(osuGame.OverlayActivationMode);
+ OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
}
public class ToolbarBackground : Container
@@ -137,6 +131,17 @@ namespace osu.Game.Overlays.Toolbar
}
}
+ protected override void UpdateState(ValueChangedEvent state)
+ {
+ if (state.NewValue == Visibility.Visible && OverlayActivationMode.Value == OverlayActivation.Disabled)
+ {
+ State.Value = Visibility.Hidden;
+ return;
+ }
+
+ base.UpdateState(state);
+ }
+
protected override void PopIn()
{
this.MoveToY(0, transition_time, Easing.OutQuint);
diff --git a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs
new file mode 100644
index 0000000000..d45311675d
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Rulesets.Mods
+{
+ ///
+ /// Interface for a that applies changes to the generated by the .
+ ///
+ public interface IApplicableAfterBeatmapConversion : IApplicableMod
+ {
+ ///
+ /// Applies this to the after conversion has taken place.
+ ///
+ /// The converted .
+ void ApplyToBeatmap(IBeatmap beatmap);
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index f1cdfd93c8..eac47aa089 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -133,17 +133,19 @@ namespace osu.Game.Rulesets.Scoring
}
}
+ double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result);
+
if (result.Judgement.IsBonus)
{
if (result.IsHit)
- bonusScore += result.Judgement.NumericResultFor(result);
+ bonusScore += scoreIncrease;
}
else
{
if (result.HasResult)
scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1;
- baseScore += result.Judgement.NumericResultFor(result);
+ baseScore += scoreIncrease;
rollingMaxBaseScore += result.Judgement.MaxNumericResult;
}
@@ -169,17 +171,19 @@ namespace osu.Game.Rulesets.Scoring
if (result.FailedAtJudgement)
return;
+ double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result);
+
if (result.Judgement.IsBonus)
{
if (result.IsHit)
- bonusScore -= result.Judgement.NumericResultFor(result);
+ bonusScore -= scoreIncrease;
}
else
{
if (result.HasResult)
scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1;
- baseScore -= result.Judgement.NumericResultFor(result);
+ baseScore -= scoreIncrease;
rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index f09745cf71..26aefa138b 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -232,7 +232,6 @@ namespace osu.Game.Screens.Play
protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter
{
- TextSize = 20,
BypassAutoSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopRight,
@@ -241,14 +240,12 @@ namespace osu.Game.Screens.Play
protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6)
{
- TextSize = 40,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
};
protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter
{
- TextSize = 20,
BypassAutoSizeAxes = Axes.X,
Anchor = Anchor.TopRight,
Origin = Anchor.TopLeft,
diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs
index 2a0e33aab7..6933456e7e 100644
--- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Utils;
@@ -43,16 +44,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
- public Counter()
- {
- DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
- DisplayedCountSpriteText.Spacing = new Vector2(-2, 0);
- }
-
protected override string FormatCount(double count) => count.FormatAccuracy();
public override void Increment(double amount)
=> Current.Value += amount;
+
+ protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
+ {
+ s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
+ s.Spacing = new Vector2(-2, 0);
+ });
}
}
}
diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs
index 817cc9b8c2..043a560d12 100644
--- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osuTK;
@@ -43,11 +44,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
- public Counter()
+ protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
{
- DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
- DisplayedCountSpriteText.Spacing = new Vector2(-2, 0);
- }
+ s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
+ s.Spacing = new Vector2(-2, 0);
+ });
public override void Increment(int amount)
=> Current.Value += amount;
diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs
index cab04edb8b..7f6fd1eabe 100644
--- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs
+++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osuTK;
@@ -23,15 +24,19 @@ namespace osu.Game.Screens.Ranking.Expanded
// Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
- DisplayedCountSpriteText.Anchor = Anchor.TopCentre;
- DisplayedCountSpriteText.Origin = Anchor.TopCentre;
-
- DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
- DisplayedCountSpriteText.Spacing = new Vector2(-5, 0);
}
protected override string FormatCount(long count) => count.ToString("N0");
+ protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
+ {
+ s.Anchor = Anchor.TopCentre;
+ s.Origin = Anchor.TopCentre;
+
+ s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
+ s.Spacing = new Vector2(-5, 0);
+ });
+
public override void Increment(long amount)
=> Current.Value += amount;
}
diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs
index 89afc729fe..39fa4f777d 100644
--- a/osu.Game/Screens/Select/FilterQueryParser.cs
+++ b/osu.Game/Screens/Select/FilterQueryParser.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select
internal static class FilterQueryParser
{
private static readonly Regex query_syntax_regex = new Regex(
- @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))",
+ @"\b(?stars|ar|dr|hp|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static void ApplyQueries(FilterCriteria criteria, string query)
@@ -43,6 +43,7 @@ namespace osu.Game.Screens.Select
break;
case "dr" when parseFloatWithPoint(value, out var dr):
+ case "hp" when parseFloatWithPoint(value, out dr):
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
break;
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
index 8eaf9ac652..119c48836b 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
@@ -76,6 +76,8 @@ namespace osu.Game.Storyboards.Drawables
protected override void Dispose(bool isDisposing)
{
Channel?.Stop();
+ Channel = null;
+
base.Dispose(isDisposing);
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index a12ce138bd..d1e2033596 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 0170e94140..9b25eaab41 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -80,7 +80,7 @@
-
+