diff --git a/global.json b/global.json
index 2cb4c02970..f5aaffcd3d 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "5.0.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.2.3"
+ "Microsoft.Build.Traversal": "3.0.2"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 6dab6edc5e..9a3d42d6b7 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index dbbf6d048b..55e42b160e 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -60,6 +60,7 @@ namespace osu.Desktop
if (OperatingSystem.IsWindows())
{
stableInstallPath = getStableInstallPathFromRegistry();
+
if (checkExists(stableInstallPath))
return stableInstallPath;
}
@@ -141,7 +142,7 @@ namespace osu.Desktop
break;
// SDL2 DesktopWindow
- case DesktopWindow desktopWindow:
+ case SDL2DesktopWindow desktopWindow:
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name;
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 285a813d97..6ca7079654 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -22,9 +22,9 @@ namespace osu.Desktop
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
- bool useSdl = args.Contains("--sdl");
+ bool useOsuTK = args.Contains("--tk");
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl))
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 2052c4bc25..4554f8b83a 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
index b570f090ca..e70def7f8b 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.IO.Stores;
using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
index 1eb0975010..c01aff0aa0 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
@@ -38,17 +38,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
new Fruit
{
X = 0,
- StartTime = 250
+ StartTime = 1000
},
new Fruit
{
X = CatchPlayfield.WIDTH,
- StartTime = 500
+ StartTime = 2000
},
new JuiceStream
{
X = CatchPlayfield.CENTER_X,
- StartTime = 750,
+ StartTime = 3000,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index 3c636a5b97..f552c3c27b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests
NewCombo = i % 8 == 0,
Samples = new List(new[]
{
- new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 }
+ new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100)
})
});
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
new file mode 100644
index 0000000000..64695153b5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -0,0 +1,14 @@
+// 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.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 6eeda2c731..cf6011d721 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -1,26 +1,272 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Catch.Judgements;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneCatcher : CatchSkinnableTestScene
+ public class TestSceneCatcher : OsuTestScene
{
- [BackgroundDependencyLoader]
- private void load()
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ private Container droppedObjectContainer;
+
+ private TestCatcher catcher;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- SetContents(() => new Catcher(new Container())
+ var difficulty = new BeatmapDifficulty
+ {
+ CircleSize = 0,
+ };
+
+ var trailContainer = new Container();
+ droppedObjectContainer = new Container();
+ catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
+
+ Child = new Container
{
- RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ trailContainer,
+ droppedObjectContainer,
+ catcher
+ }
+ };
+ });
+
+ [Test]
+ public void TestCatcherHyperStateReverted()
+ {
+ DrawableCatchHitObject drawableObject1 = null;
+ DrawableCatchHitObject drawableObject2 = null;
+ JudgementResult result1 = null;
+ JudgementResult result2 = null;
+ AddStep("catch hyper fruit", () =>
+ {
+ drawableObject1 = createDrawableObject(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
+ result1 = attemptCatch(drawableObject1);
});
+ AddStep("catch normal fruit", () =>
+ {
+ drawableObject2 = createDrawableObject(new Fruit());
+ result2 = attemptCatch(drawableObject2);
+ });
+ AddStep("revert second result", () =>
+ {
+ catcher.OnRevertResult(drawableObject2, result2);
+ });
+ checkHyperDash(true);
+ AddStep("revert first result", () =>
+ {
+ catcher.OnRevertResult(drawableObject1, result1);
+ });
+ checkHyperDash(false);
+ }
+
+ [Test]
+ public void TestCatcherAnimationStateReverted()
+ {
+ DrawableCatchHitObject drawableObject = null;
+ JudgementResult result = null;
+ AddStep("catch kiai fruit", () =>
+ {
+ drawableObject = createDrawableObject(new TestKiaiFruit());
+ result = attemptCatch(drawableObject);
+ });
+ checkState(CatcherAnimationState.Kiai);
+ AddStep("revert result", () =>
+ {
+ catcher.OnRevertResult(drawableObject, result);
+ });
+ checkState(CatcherAnimationState.Idle);
+ }
+
+ [Test]
+ public void TestCatcherCatchWidth()
+ {
+ var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
+ AddStep("catch fruit", () =>
+ {
+ attemptCatch(new Fruit { X = -halfWidth + 1 });
+ attemptCatch(new Fruit { X = halfWidth - 1 });
+ });
+ checkPlate(2);
+ AddStep("miss fruit", () =>
+ {
+ attemptCatch(new Fruit { X = -halfWidth - 1 });
+ attemptCatch(new Fruit { X = halfWidth + 1 });
+ });
+ checkPlate(2);
+ }
+
+ [Test]
+ public void TestFruitChangesCatcherState()
+ {
+ AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 }));
+ checkState(CatcherAnimationState.Fail);
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ checkState(CatcherAnimationState.Idle);
+ AddStep("catch kiai fruit", () => attemptCatch(new TestKiaiFruit()));
+ checkState(CatcherAnimationState.Kiai);
+ }
+
+ [Test]
+ public void TestNormalFruitResetsHyperDashState()
+ {
+ AddStep("catch hyper fruit", () => attemptCatch(new Fruit
+ {
+ HyperDashTarget = new Fruit { X = 100 }
+ }));
+ checkHyperDash(true);
+ AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
+ checkHyperDash(false);
+ }
+
+ [Test]
+ public void TestTinyDropletMissPreservesCatcherState()
+ {
+ AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
+ {
+ HyperDashTarget = new Fruit { X = 100 }
+ }));
+ AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
+ AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 }));
+ // catcher state and hyper dash state is preserved
+ checkState(CatcherAnimationState.Kiai);
+ checkHyperDash(true);
+ }
+
+ [Test]
+ public void TestBananaMissPreservesCatcherState()
+ {
+ AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
+ {
+ HyperDashTarget = new Fruit { X = 100 }
+ }));
+ AddStep("miss banana", () => attemptCatch(new Banana { X = 100 }));
+ // catcher state is preserved but hyper dash state is reset
+ checkState(CatcherAnimationState.Kiai);
+ checkHyperDash(false);
+ }
+
+ [Test]
+ public void TestCatcherStacking()
+ {
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ checkPlate(1);
+ AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
+ checkPlate(10);
+ AddAssert("caught objects are stacked", () =>
+ catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
+ catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
+ catcher.CaughtObjects.Any(obj => obj.Y < -20));
+ }
+
+ [Test]
+ public void TestCatcherExplosionAndDropping()
+ {
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
+ AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1);
+ AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
+ AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
+ AddStep("explode", () => catcher.Explode());
+ AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
+ AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
+ AddStep("catch fruits", () => attemptCatch(new Fruit(), 10));
+ AddStep("drop", () => catcher.Drop());
+ AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestHitLighting(bool enabled)
+ {
+ AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled));
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddAssert("check hit lighting", () => catcher.ChildrenOfType().Any() == enabled);
+ }
+
+ private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
+
+ private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
+
+ private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
+
+ private void attemptCatch(CatchHitObject hitObject, int count = 1)
+ {
+ for (var i = 0; i < count; i++)
+ attemptCatch(createDrawableObject(hitObject));
+ }
+
+ private JudgementResult attemptCatch(DrawableCatchHitObject drawableObject)
+ {
+ drawableObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ var result = new CatchJudgementResult(drawableObject.HitObject, drawableObject.HitObject.CreateJudgement())
+ {
+ Type = catcher.CanCatch(drawableObject.HitObject) ? HitResult.Great : HitResult.Miss
+ };
+ catcher.OnNewResult(drawableObject, result);
+ return result;
+ }
+
+ private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case Banana banana:
+ return new DrawableBanana(banana);
+
+ case Droplet droplet:
+ return new DrawableDroplet(droplet);
+
+ case Fruit fruit:
+ return new DrawableFruit(fruit);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(hitObject));
+ }
+ }
+
+ public class TestCatcher : Catcher
+ {
+ public IEnumerable CaughtObjects => this.ChildrenOfType();
+
+ public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
+ : base(trailsTarget, droppedObjectTarget, difficulty)
+ {
+ }
+ }
+
+ public class TestKiaiFruit : Fruit
+ {
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ {
+ controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
+ base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index e055f08dc2..8602c7aad1 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -6,18 +6,16 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
-using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -29,82 +27,67 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved]
private OsuConfigManager config { get; set; }
- private Catcher catcher => this.ChildrenOfType().First().MovableCatcher;
+ private Catcher catcher => this.ChildrenOfType().First();
+
+ private float circleSize;
public TestSceneCatcherArea()
{
- AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
- AddToggleStep("Hyperdash", t =>
- CreatedDrawables.OfType().Select(i => i.Child)
- .OfType().ForEach(c => c.ToggleHyperDash(t)));
+ AddSliderStep("circle size", 0, 8, 5, createCatcher);
+ AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t)));
- AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
- {
- X = catcher.X
- }), 20);
- AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
- {
- X = catcher.X,
- LastInCombo = true,
- }), 20);
- AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
- {
- X = catcher.X
- }), 20);
- AddRepeatStep("miss fruit", () => catchFruit(new Fruit
- {
- X = catcher.X + 100,
- LastInCombo = true,
- }, true), 20);
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true }));
+ AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit()));
+ AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true }));
}
- [TestCase(true)]
- [TestCase(false)]
- public void TestHitLighting(bool enable)
+ private void attemptCatch(Fruit fruit)
{
- AddStep("create catcher", () => createCatcher(5));
-
- AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
- AddStep("catch fruit", () => catchFruit(new TestFruit(false)
+ fruit.X += catcher.X;
+ fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
{
- X = catcher.X
- }));
- AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
- {
- X = catcher.X,
- LastInCombo = true
- }));
- AddAssert("check hit explosion", () => catcher.ChildrenOfType().Any() == enable);
- }
+ CircleSize = circleSize
+ });
- private void catchFruit(Fruit fruit, bool miss = false)
- {
- this.ChildrenOfType().ForEach(area =>
+ foreach (var area in this.ChildrenOfType())
{
DrawableFruit drawable = new DrawableFruit(fruit);
area.Add(drawable);
Schedule(() =>
{
- area.AttemptCatch(fruit);
- area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
+ area.OnNewResult(drawable, new CatchJudgementResult(fruit, new CatchJudgement())
+ {
+ Type = area.MovableCatcher.CanCatch(fruit) ? HitResult.Great : HitResult.Miss
+ });
drawable.Expire();
});
- });
+ }
}
private void createCatcher(float size)
{
- SetContents(() => new CatchInputManager(catchRuleset)
+ circleSize = size;
+
+ SetContents(() =>
{
- RelativeSizeAxes = Axes.Both,
- Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
+ var droppedObjectContainer = new Container();
+
+ return new CatchInputManager(catchRuleset)
{
- Anchor = Anchor.Centre,
- Origin = Anchor.TopCentre,
- CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
- },
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ droppedObjectContainer,
+ new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size })
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ }
+ }
+ };
});
}
@@ -114,26 +97,13 @@ namespace osu.Game.Rulesets.Catch.Tests
catchRuleset = rulesets.GetRuleset(2);
}
- public class TestFruit : Fruit
- {
- public TestFruit(bool kiai)
- {
- var kiaiCpi = new ControlPointInfo();
- kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
-
- ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty());
- }
- }
-
private class TestCatcherArea : CatcherArea
{
- public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
- : base(beatmapDifficulty)
+ public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
+ : base(droppedObjectContainer, beatmapDifficulty)
{
}
- public new Catcher MovableCatcher => base.MovableCatcher;
-
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index d35f828e28..3e4995482d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Tests
if (juice.NestedHitObjects.Last() is CatchHitObject tail)
tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
- addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation));
+ addToPlayfield(new DrawableJuiceStream(juice));
}
private void spawnBananas(bool hit = false)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index 89063319d6..3a651605d3 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -1,12 +1,14 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -17,53 +19,69 @@ namespace osu.Game.Rulesets.Catch.Tests
{
base.LoadComplete();
- foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
- AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep)));
+ AddStep("show pear", () => SetContents(() => createDrawableFruit(0)));
+ AddStep("show grape", () => SetContents(() => createDrawableFruit(1)));
+ AddStep("show pineapple / apple", () => SetContents(() => createDrawableFruit(2)));
+ AddStep("show raspberry / orange", () => SetContents(() => createDrawableFruit(3)));
+
+ AddStep("show banana", () => SetContents(createDrawableBanana));
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
- foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
- AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true)));
+ AddStep("show hyperdash pear", () => SetContents(() => createDrawableFruit(0, true)));
+ AddStep("show hyperdash grape", () => SetContents(() => createDrawableFruit(1, true)));
+ AddStep("show hyperdash pineapple / apple", () => SetContents(() => createDrawableFruit(2, true)));
+ AddStep("show hyperdash raspberry / orange", () => SetContents(() => createDrawableFruit(3, true)));
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
}
- private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) =>
- setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash);
+ private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
+ new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
+ {
+ IndexInBeatmap = indexInBeatmap,
+ HyperDashBindable = { Value = hyperdash }
+ }));
- private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash);
+ private Drawable createDrawableBanana() =>
+ new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
- private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet()));
+ private Drawable createDrawableDroplet(bool hyperdash = false) =>
+ new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
+ {
+ HyperDashBindable = { Value = hyperdash }
+ }));
- private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false)
+ private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
+ }
+
+ public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable
+ {
+ public readonly ManualClock ManualClock;
+
+ public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d)
{
- var hitObject = d.HitObject;
- hitObject.StartTime = 1000000000000;
- hitObject.Scale = 1.5f;
+ AutoSizeAxes = Axes.Both;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
- if (hyperdash)
- hitObject.HyperDashTarget = new Banana();
+ ManualClock = new ManualClock();
+ Clock = new FramedClock(ManualClock);
+
+ var hitObject = d.HitObject;
+ hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ hitObject.Scale = 1.5f;
+ hitObject.StartTime = 500;
d.Anchor = Anchor.Centre;
- d.RelativePositionAxes = Axes.None;
- d.Position = Vector2.Zero;
d.HitObjectApplied += _ =>
{
d.LifetimeStart = double.NegativeInfinity;
d.LifetimeEnd = double.PositiveInfinity;
};
- return d;
- }
- public class TestCatchFruit : Fruit
- {
- public TestCatchFruit(FruitVisualRepresentation rep)
- {
- VisualRepresentation = rep;
- }
-
- public override FruitVisualRepresentation VisualRepresentation { get; }
+ InternalChild = d;
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
new file mode 100644
index 0000000000..2ffebb7de1
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
@@ -0,0 +1,96 @@
+// 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.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneFruitRandomness : OsuTestScene
+ {
+ private readonly TestDrawableFruit drawableFruit;
+ private readonly TestDrawableBanana drawableBanana;
+
+ public TestSceneFruitRandomness()
+ {
+ drawableFruit = new TestDrawableFruit(new Fruit());
+ drawableBanana = new TestDrawableBanana(new Banana());
+
+ Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
+ Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
+
+ AddSliderStep("start time", 500, 600, 0, x =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
+ });
+ }
+
+ [Test]
+ public void TestFruitRandomness()
+ {
+ // Use values such that the banana colour changes (2/3 of the integers are okay)
+ const int initial_start_time = 500;
+ const int another_start_time = 501;
+
+ float fruitRotation = 0;
+ float bananaRotation = 0;
+ float bananaScale = 0;
+ Color4 bananaColour = new Color4();
+
+ AddStep("Initialize start time", () =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
+
+ fruitRotation = drawableFruit.InnerRotation;
+ bananaRotation = drawableBanana.InnerRotation;
+ bananaScale = drawableBanana.InnerScale;
+ bananaColour = drawableBanana.AccentColour.Value;
+ });
+
+ AddStep("change start time", () =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
+ });
+
+ AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
+ AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
+ AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
+ AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
+
+ AddStep("reset start time", () =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
+ });
+
+ AddAssert("rotation and scale restored", () =>
+ drawableFruit.InnerRotation == fruitRotation &&
+ drawableBanana.InnerRotation == bananaRotation &&
+ drawableBanana.InnerScale == bananaScale &&
+ drawableBanana.AccentColour.Value == bananaColour);
+ }
+
+ private class TestDrawableFruit : DrawableFruit
+ {
+ public float InnerRotation => ScaleContainer.Rotation;
+
+ public TestDrawableFruit(Fruit h)
+ : base(h)
+ {
+ }
+ }
+
+ private class TestDrawableBanana : DrawableBanana
+ {
+ public float InnerRotation => ScaleContainer.Rotation;
+ public float InnerScale => ScaleContainer.Scale.X;
+
+ public TestDrawableBanana(Banana h)
+ : base(h)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
new file mode 100644
index 0000000000..125e0c674c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
@@ -0,0 +1,32 @@
+// 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.Bindables;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneFruitVisualChange : TestSceneFruitObjects
+ {
+ private readonly Bindable indexInBeatmap = new Bindable();
+ private readonly Bindable hyperDash = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
+ {
+ IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
+ HyperDashBindable = { BindTarget = hyperDash },
+ }))));
+
+ AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
+ {
+ HyperDashBindable = { BindTarget = hyperDash },
+ }))));
+
+ Scheduler.AddDelayed(() => indexInBeatmap.Value++, 250, true);
+ Scheduler.AddDelayed(() => hyperDash.Value = !hyperDash.Value, 1000, true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
index 1b8368794c..d78dc2d2b5 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
@@ -117,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("create hyper-dashing catcher", () =>
{
- Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index a51b9830be..5d5e9e2d9d 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index a08c5b6fb1..00ce9ea8c2 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -5,11 +5,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Catch.MathUtils;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private static void initialiseHyperDash(IBeatmap beatmap)
{
- List objectWithDroplets = new List();
+ List palpableObjects = new List();
foreach (var currentObject in beatmap.HitObjects)
{
if (currentObject is Fruit fruitObject)
- objectWithDroplets.Add(fruitObject);
+ palpableObjects.Add(fruitObject);
if (currentObject is JuiceStream)
{
- foreach (var currentJuiceElement in currentObject.NestedHitObjects)
+ foreach (var juice in currentObject.NestedHitObjects)
{
- if (!(currentJuiceElement is TinyDroplet))
- objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
+ if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
+ palpableObjects.Add(palpableObject);
}
}
}
- objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+ palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
@@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
- for (int i = 0; i < objectWithDroplets.Count - 1; i++)
+ for (int i = 0; i < palpableObjects.Count - 1; i++)
{
- CatchHitObject currentObject = objectWithDroplets[i];
- CatchHitObject nextObject = objectWithDroplets[i + 1];
+ var currentObject = palpableObjects[i];
+ var nextObject = palpableObjects[i + 1];
// Reset variables in-case values have changed (e.g. after applying HR)
currentObject.HyperDashTarget = null;
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index ad584d3f48..0a817eca0d 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -21,7 +21,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using System;
-using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
index 23d8428fec..668f7197be 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -5,11 +5,8 @@ namespace osu.Game.Rulesets.Catch
{
public enum CatchSkinComponents
{
- FruitBananas,
- FruitApple,
- FruitGrapes,
- FruitOrange,
- FruitPear,
+ Fruit,
+ Banana,
Droplet,
CatcherIdle,
CatcherFail,
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
index 3e21b8fbaf..dcd410e08f 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
@@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
{
private const float normalized_hitobject_radius = 41.0f;
- public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
+ public new PalpableCatchHitObject BaseObject => (PalpableCatchHitObject)base.BaseObject;
- public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
+ public new PalpableCatchHitObject LastObject => (PalpableCatchHitObject)base.LastObject;
public readonly float NormalizedPosition;
public readonly float LastNormalizedPosition;
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs
new file mode 100644
index 0000000000..c09355d59c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Catch.Judgements
+{
+ public class CatchJudgementResult : JudgementResult
+ {
+ ///
+ /// The catcher animation state prior to this judgement.
+ ///
+ public CatcherAnimationState CatcherAnimationState;
+
+ ///
+ /// Whether the catcher was hyper dashing prior to this judgement.
+ ///
+ public bool CatcherHyperDash;
+
+ public CatchJudgementResult([NotNull] HitObject hitObject, [NotNull] Judgement judgement)
+ : base(hitObject, judgement)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
index a82d0af102..16ef56d845 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModEasy : ModEasy
+ public class CatchModEasy : ModEasyWithExtraLives
{
public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs
index 4ecfb7b16d..178306b3bc 100644
--- a/osu.Game.Rulesets.Catch/Objects/Banana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs
@@ -1,22 +1,26 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
+using System;
using System.Collections.Generic;
using osu.Game.Audio;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Utils;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Banana : Fruit
+ public class Banana : PalpableCatchHitObject, IHasComboInformation
{
///
/// Index of banana in current shower.
///
public int BananaIndex;
- public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
-
public override Judgement CreateJudgement() => new CatchBananaJudgement();
private static readonly List samples = new List { new BananaHitSampleInfo() };
@@ -26,11 +30,45 @@ namespace osu.Game.Rulesets.Catch.Objects
Samples = samples;
}
- private class BananaHitSampleInfo : HitSampleInfo
- {
- private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
+ // override any external colour changes with banananana
+ Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => getBananaColour();
- public override IEnumerable LookupNames => lookupNames;
+ private Color4 getBananaColour()
+ {
+ switch (StatelessRNG.NextInt(3, RandomSeed))
+ {
+ default:
+ return new Color4(255, 240, 0, 255);
+
+ case 1:
+ return new Color4(255, 192, 0, 255);
+
+ case 2:
+ return new Color4(214, 221, 28, 255);
+ }
+ }
+
+ private class BananaHitSampleInfo : HitSampleInfo, IEquatable
+ {
+ private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
+
+ public override IEnumerable LookupNames => lookup_names;
+
+ public BananaHitSampleInfo(int volume = 0)
+ : base(string.Empty, volume: volume)
+ {
+ }
+
+ public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default)
+ => new BananaHitSampleInfo(newVolume.GetOr(Volume));
+
+ public bool Equals(BananaHitSampleInfo? other)
+ => other != null;
+
+ public override bool Equals(object? obj)
+ => obj is BananaHitSampleInfo other && Equals(other);
+
+ public override int GetHashCode() => lookup_names.GetHashCode();
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 89c51459a6..b45f95a8e6 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public class BananaShower : CatchHitObject, IHasDuration
{
- public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
-
public override bool LastInCombo => true;
public override Judgement CreateJudgement() => new IgnoreJudgement();
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 5985ec9b68..b86b3a7496 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -16,32 +16,47 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public const float OBJECT_RADIUS = 64;
- private float x;
+ // This value is after XOffset applied.
+ public readonly Bindable XBindable = new Bindable();
+
+ // This value is before XOffset applied.
+ private float originalX;
///
/// The horizontal position of the fruit between 0 and .
///
public float X
{
- get => x + XOffset;
- set => x = value;
+ // TODO: I don't like this asymmetry.
+ get => XBindable.Value;
+ // originalX is set by `XBindable.BindValueChanged`
+ set => XBindable.Value = value + xOffset;
}
- ///
- /// Whether this object can be placed on the catcher's plate.
- ///
- public virtual bool CanBePlated => false;
+ private float xOffset;
///
/// A random offset applied to , set by the .
///
- internal float XOffset { get; set; }
+ internal float XOffset
+ {
+ get => xOffset;
+ set
+ {
+ xOffset = value;
+ XBindable.Value = originalX + xOffset;
+ }
+ }
public double TimePreempt = 1000;
- public int IndexInBeatmap { get; set; }
+ public readonly Bindable IndexInBeatmapBindable = new Bindable();
- public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
+ public int IndexInBeatmap
+ {
+ get => IndexInBeatmapBindable.Value;
+ set => IndexInBeatmapBindable.Value = value;
+ }
public virtual bool NewCombo { get; set; }
@@ -63,13 +78,6 @@ namespace osu.Game.Rulesets.Catch.Objects
set => ComboIndexBindable.Value = value;
}
- ///
- /// Difference between the distance to the next object
- /// and the distance that would have triggered a hyper dash.
- /// A value close to 0 indicates a difficult jump (for difficulty calculation).
- ///
- public float DistanceToHyperDash { get; set; }
-
public Bindable LastInComboBindable { get; } = new Bindable();
///
@@ -81,17 +89,19 @@ namespace osu.Game.Rulesets.Catch.Objects
set => LastInComboBindable.Value = value;
}
- public float Scale { get; set; } = 1;
+ public readonly Bindable ScaleBindable = new Bindable(1);
+
+ public float Scale
+ {
+ get => ScaleBindable.Value;
+ set => ScaleBindable.Value = value;
+ }
///
- /// Whether this fruit can initiate a hyperdash.
+ /// The seed value used for visual randomness such as fruit rotation.
+ /// The value is truncated to an integer.
///
- public bool HyperDash => HyperDashTarget != null;
-
- ///
- /// The target fruit if we are to initiate a hyperdash.
- ///
- public CatchHitObject HyperDashTarget;
+ public int RandomSeed => (int)StartTime;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
@@ -103,22 +113,10 @@ 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,
- Grape,
- Pineapple,
- Raspberry,
- Banana // banananananannaanana
+ protected CatchHitObject()
+ {
+ XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs
deleted file mode 100644
index ebb0bf0f2c..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
-{
- public class BananaPiece : PulpFormation
- {
- public BananaPiece()
- {
- InternalChildren = new Drawable[]
- {
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(SMALL_PULP),
- Y = -0.3f
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4 * 0.8f, LARGE_PULP_4 * 2.5f),
- Y = 0.05f,
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
index a865984d45..2a543a0e04 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
@@ -1,26 +1,40 @@
// 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 JetBrains.Annotations;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Utils;
-using osuTK.Graphics;
+using osu.Game.Rulesets.Catch.Skinning.Default;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableBanana : DrawableFruit
+ public class DrawableBanana : DrawablePalpableCatchHitObject
{
- public DrawableBanana(Banana h)
+ public DrawableBanana()
+ : this(null)
+ {
+ }
+
+ public DrawableBanana([CanBeNull] Banana h)
: base(h)
{
}
- private Color4? colour;
-
- protected override Color4 GetComboColour(IReadOnlyList comboColours)
+ [BackgroundDependencyLoader]
+ private void load()
{
- // override any external colour changes with banananana
- return colour ??= getBananaColour();
+ ScaleContainer.Child = new SkinnableDrawable(
+ new CatchSkinComponent(CatchSkinComponents.Banana),
+ _ => new BananaPiece());
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // start time affects the random seed which is used to determine the banana colour
+ StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
}
protected override void UpdateInitialTransforms()
@@ -30,14 +44,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
const float end_scale = 0.6f;
const float random_scale_range = 1.6f;
- ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle()))
+ ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
- ScaleContainer.RotateTo(getRandomAngle())
+ ScaleContainer.RotateTo(getRandomAngle(1))
.Then()
- .RotateTo(getRandomAngle(), HitObject.TimePreempt);
+ .RotateTo(getRandomAngle(2), HitObject.TimePreempt);
- float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
+ float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
}
public override void PlaySamples()
@@ -46,20 +60,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
if (Samples != null)
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
}
-
- private Color4 getBananaColour()
- {
- switch (RNG.Next(0, 3))
- {
- default:
- return new Color4(255, 240, 0, 255);
-
- case 1:
- return new Color4(255, 192, 0, 255);
-
- case 2:
- return new Color4(214, 221, 28, 255);
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
index 4ce80aceb8..9b2f95e221 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
@@ -1,26 +1,27 @@
// 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 JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableBananaShower : DrawableCatchHitObject
+ public class DrawableBananaShower : DrawableCatchHitObject
{
- private readonly Func> createDrawableRepresentation;
private readonly Container bananaContainer;
- public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null)
+ public DrawableBananaShower()
+ : this(null)
+ {
+ }
+
+ public DrawableBananaShower([CanBeNull] BananaShower s)
: base(s)
{
- this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
- X = 0;
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
}
@@ -34,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
- bananaContainer.Clear();
- }
-
- protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
- {
- switch (hitObject)
- {
- case Banana banana:
- return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
- }
-
- return base.CreateNestedHitObject(hitObject);
+ bananaContainer.Clear(false);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index 7922510a49..70efe9cf29 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -2,77 +2,50 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
-using osu.Framework.Allocation;
+using JetBrains.Annotations;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
-using osuTK.Graphics;
+using osu.Game.Utils;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject
- where TObject : PalpableCatchHitObject
- {
- protected Container ScaleContainer { get; private set; }
-
- protected PalpableDrawableCatchHitObject(TObject hitObject)
- : base(hitObject)
- {
- Origin = Anchor.Centre;
- Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
- Masking = false;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- AddRangeInternal(new Drawable[]
- {
- ScaleContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- }
- });
-
- ScaleContainer.Scale = new Vector2(HitObject.Scale);
- }
-
- protected override Color4 GetComboColour(IReadOnlyList comboColours) =>
- comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count];
- }
-
- public abstract class DrawableCatchHitObject : DrawableCatchHitObject
- where TObject : CatchHitObject
- {
- public new TObject HitObject;
-
- protected DrawableCatchHitObject(TObject hitObject)
- : base(hitObject)
- {
- HitObject = hitObject;
- Anchor = Anchor.BottomLeft;
- }
- }
-
public abstract class DrawableCatchHitObject : DrawableHitObject
{
+ public readonly Bindable XBindable = new Bindable();
+
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
- public virtual bool StaysOnPlate => HitObject.CanBePlated;
-
- public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
-
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
- protected DrawableCatchHitObject(CatchHitObject hitObject)
+ public int RandomSeed => HitObject?.RandomSeed ?? 0;
+
+ protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
: base(hitObject)
{
- X = hitObject.X;
+ Anchor = Anchor.BottomLeft;
+ }
+
+ ///
+ /// Get a random number in range [0,1) based on seed .
+ ///
+ public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed, series);
+
+ protected override void OnApply()
+ {
+ base.OnApply();
+
+ XBindable.BindTo(HitObject.XBindable);
+ }
+
+ protected override void OnFree()
+ {
+ base.OnFree();
+
+ XBindable.UnbindFrom(HitObject.XBindable);
}
public Func CheckPosition;
@@ -81,6 +54,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public override bool RemoveWhenNotAlive => IsOnPlate;
+ protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (CheckPosition == null) return;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
index 688240fd86..81c8de2e59 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
@@ -1,18 +1,24 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableDroplet : PalpableDrawableCatchHitObject
+ public class DrawableDroplet : DrawablePalpableCatchHitObject
{
public override bool StaysOnPlate => false;
- public DrawableDroplet(Droplet h)
+ public DrawableDroplet()
+ : this(null)
+ {
+ }
+
+ public DrawableDroplet([CanBeNull] CatchHitObject h)
: base(h)
{
}
@@ -20,7 +26,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece());
+ ScaleContainer.Child = new SkinnableDrawable(
+ new CatchSkinComponent(CatchSkinComponents.Droplet),
+ _ => new DropletPiece());
}
protected override void UpdateInitialTransforms()
@@ -28,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
base.UpdateInitialTransforms();
// roughly matches osu-stable
- float startRotation = RNG.NextSingle() * 20;
+ float startRotation = RandomSingle(1) * 20;
double duration = HitObject.TimePreempt + 2000;
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
index c1c34e4157..0fcd319a93 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -1,16 +1,25 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
-using osu.Framework.Utils;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableFruit : PalpableDrawableCatchHitObject
+ public class DrawableFruit : DrawablePalpableCatchHitObject
{
- public DrawableFruit(Fruit h)
+ public readonly Bindable VisualRepresentation = new Bindable();
+
+ public DrawableFruit()
+ : this(null)
+ {
+ }
+
+ public DrawableFruit([CanBeNull] Fruit h)
: base(h)
{
}
@@ -18,34 +27,29 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- ScaleContainer.Child = new SkinnableDrawable(
- new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece());
+ IndexInBeatmap.BindValueChanged(change =>
+ {
+ VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
+ }, true);
- ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
+ ScaleContainer.Child = new SkinnableDrawable(
+ new CatchSkinComponent(CatchSkinComponents.Fruit),
+ _ => new FruitPiece());
}
- private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation)
+ protected override void UpdateInitialTransforms()
{
- switch (hitObjectVisualRepresentation)
- {
- case FruitVisualRepresentation.Pear:
- return CatchSkinComponents.FruitPear;
+ base.UpdateInitialTransforms();
- case FruitVisualRepresentation.Grape:
- return CatchSkinComponents.FruitGrapes;
-
- case FruitVisualRepresentation.Pineapple:
- return CatchSkinComponents.FruitApple;
-
- case FruitVisualRepresentation.Raspberry:
- return CatchSkinComponents.FruitOrange;
-
- case FruitVisualRepresentation.Banana:
- return CatchSkinComponents.FruitBananas;
-
- default:
- throw new ArgumentOutOfRangeException(nameof(hitObjectVisualRepresentation), hitObjectVisualRepresentation, null);
- }
+ ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
}
}
+
+ public enum FruitVisualRepresentation
+ {
+ Pear,
+ Grape,
+ Pineapple,
+ Raspberry,
+ }
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
index 7bc016d94f..a496a35842 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
@@ -1,37 +1,33 @@
// 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 JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableJuiceStream : DrawableCatchHitObject
+ public class DrawableJuiceStream : DrawableCatchHitObject
{
- private readonly Func> createDrawableRepresentation;
private readonly Container dropletContainer;
- public override Vector2 OriginPosition => base.OriginPosition - new Vector2(0, CatchHitObject.OBJECT_RADIUS);
+ public DrawableJuiceStream()
+ : this(null)
+ {
+ }
- public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null)
+ public DrawableJuiceStream([CanBeNull] JuiceStream s)
: base(s)
{
- this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
- X = 0;
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
- hitObject.Origin = Anchor.BottomCentre;
-
base.AddNestedHitObject(hitObject);
dropletContainer.Add(hitObject);
}
@@ -39,19 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
- dropletContainer.Clear();
- }
-
- protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
- {
- switch (hitObject)
- {
- case CatchHitObject catchObject:
- return createDrawableRepresentation?.Invoke(catchObject)?.With(o =>
- ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
- }
-
- throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
+ dropletContainer.Clear(false);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
new file mode 100644
index 0000000000..0877b5e248
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
+ {
+ public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
+
+ public readonly Bindable HyperDash = new Bindable();
+
+ public readonly Bindable ScaleBindable = new Bindable(1);
+
+ public readonly Bindable IndexInBeatmap = new Bindable();
+
+ ///
+ /// The multiplicative factor applied to scale relative to scale.
+ ///
+ protected virtual float ScaleFactor => 1;
+
+ ///
+ /// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
+ ///
+ public virtual bool StaysOnPlate => true;
+
+ public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor;
+
+ protected readonly Container ScaleContainer;
+
+ protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
+ : base(h)
+ {
+ Origin = Anchor.Centre;
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
+
+ AddInternal(ScaleContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ XBindable.BindValueChanged(x =>
+ {
+ if (!IsOnPlate) X = x.NewValue;
+ }, true);
+
+ ScaleBindable.BindValueChanged(scale =>
+ {
+ ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
+ }, true);
+
+ IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
+ }
+
+ protected override void OnApply()
+ {
+ base.OnApply();
+
+ HyperDash.BindTo(HitObject.HyperDashBindable);
+ ScaleBindable.BindTo(HitObject.ScaleBindable);
+ IndexInBeatmap.BindTo(HitObject.IndexInBeatmapBindable);
+ }
+
+ protected override void OnFree()
+ {
+ HyperDash.UnbindFrom(HitObject.HyperDashBindable);
+ ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
+ IndexInBeatmap.UnbindFrom(HitObject.IndexInBeatmapBindable);
+
+ base.OnFree();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
index ae775684d8..8f5a04dfda 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
@@ -1,21 +1,22 @@
// 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 JetBrains.Annotations;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableTinyDroplet : DrawableDroplet
{
- public DrawableTinyDroplet(TinyDroplet h)
- : base(h)
+ protected override float ScaleFactor => base.ScaleFactor / 2;
+
+ public DrawableTinyDroplet()
+ : this(null)
{
}
- [BackgroundDependencyLoader]
- private void load()
+ public DrawableTinyDroplet([CanBeNull] TinyDroplet h)
+ : base(h)
{
- ScaleContainer.Scale /= 2;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
deleted file mode 100644
index c2499446fa..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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
deleted file mode 100644
index 4bffdab3d8..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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 osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Objects.Drawables;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
-{
- internal class FruitPiece : CompositeDrawable
- {
- ///
- /// Because we're adding a border around the fruit, we need to scale down some.
- ///
- public const float RADIUS_ADJUST = 1.1f;
-
- private Circle border;
- private CatchHitObject hitObject;
-
- public FruitPiece()
- {
- RelativeSizeAxes = Axes.Both;
- }
-
- [BackgroundDependencyLoader]
- private void load(DrawableHitObject drawableObject)
- {
- DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
- hitObject = drawableCatchObject.HitObject;
-
- AddRangeInternal(new[]
- {
- getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
- border = new Circle
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- BorderColour = Color4.White,
- BorderThickness = 6f * RADIUS_ADJUST,
- Children = new Drawable[]
- {
- new Box
- {
- AlwaysPresent = true,
- Alpha = 0,
- RelativeSizeAxes = Axes.Both
- }
- }
- },
- });
-
- if (hitObject.HyperDash)
- {
- AddInternal(new Circle
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
- BorderThickness = 12f * RADIUS_ADJUST,
- Children = new Drawable[]
- {
- new Box
- {
- AlwaysPresent = true,
- Alpha = 0.3f,
- Blending = BlendingParameters.Additive,
- RelativeSizeAxes = Axes.Both,
- Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
- }
- }
- });
- }
- }
-
- protected override void Update()
- {
- base.Update();
- border.Alpha = (float)Math.Clamp((hitObject.StartTime - Time.Current) / 500, 0, 1);
- }
-
- private Drawable getFruitFor(FruitVisualRepresentation representation)
- {
- switch (representation)
- {
- case FruitVisualRepresentation.Pear:
- return new PearPiece();
-
- case FruitVisualRepresentation.Grape:
- return new GrapePiece();
-
- case FruitVisualRepresentation.Pineapple:
- return new PineapplePiece();
-
- case FruitVisualRepresentation.Banana:
- return new BananaPiece();
-
- case FruitVisualRepresentation.Raspberry:
- return new RaspberryPiece();
- }
-
- return Empty();
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs
deleted file mode 100644
index 1d1faf893b..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
-{
- public class GrapePiece : PulpFormation
- {
- public GrapePiece()
- {
- InternalChildren = new Drawable[]
- {
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(SMALL_PULP),
- Y = -0.25f,
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_3),
- Position = PositionAt(0, DISTANCE_FROM_CENTRE_3),
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_3),
- Position = PositionAt(120, DISTANCE_FROM_CENTRE_3),
- },
- new Pulp
- {
- Size = new Vector2(LARGE_PULP_3),
- AccentColour = { BindTarget = AccentColour },
- Position = PositionAt(240, DISTANCE_FROM_CENTRE_3),
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs
deleted file mode 100644
index 7f14217cda..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
-{
- public class PearPiece : PulpFormation
- {
- public PearPiece()
- {
- InternalChildren = new Drawable[]
- {
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(SMALL_PULP),
- Y = -0.33f,
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_3),
- Position = PositionAt(60, DISTANCE_FROM_CENTRE_3),
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_3),
- Position = PositionAt(180, DISTANCE_FROM_CENTRE_3),
- },
- new Pulp
- {
- Size = new Vector2(LARGE_PULP_3),
- AccentColour = { BindTarget = AccentColour },
- Position = PositionAt(300, DISTANCE_FROM_CENTRE_3),
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs
deleted file mode 100644
index c328ba1837..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
-{
- public class PineapplePiece : PulpFormation
- {
- public PineapplePiece()
- {
- InternalChildren = new Drawable[]
- {
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(SMALL_PULP),
- Y = -0.3f,
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4),
- Position = PositionAt(45, DISTANCE_FROM_CENTRE_4),
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4),
- Position = PositionAt(135, DISTANCE_FROM_CENTRE_4),
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4),
- Position = PositionAt(225, DISTANCE_FROM_CENTRE_4),
- },
- new Pulp
- {
- Size = new Vector2(LARGE_PULP_4),
- AccentColour = { BindTarget = AccentColour },
- Position = PositionAt(315, DISTANCE_FROM_CENTRE_4),
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs
deleted file mode 100644
index 22ce3ba5b3..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
-{
- public class RaspberryPiece : PulpFormation
- {
- public RaspberryPiece()
- {
- InternalChildren = new Drawable[]
- {
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(SMALL_PULP),
- Y = -0.34f,
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4),
- Position = PositionAt(0, DISTANCE_FROM_CENTRE_4),
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4),
- Position = PositionAt(90, DISTANCE_FROM_CENTRE_4),
- },
- new Pulp
- {
- AccentColour = { BindTarget = AccentColour },
- Size = new Vector2(LARGE_PULP_4),
- Position = PositionAt(180, DISTANCE_FROM_CENTRE_4),
- },
- new Pulp
- {
- Size = new Vector2(LARGE_PULP_4),
- AccentColour = { BindTarget = AccentColour },
- Position = PositionAt(270, DISTANCE_FROM_CENTRE_4),
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index e209d012fa..d5819935ad 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -50,12 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
base.CreateNestedHitObjects(cancellationToken);
- var dropletSamples = Samples.Select(s => new HitSampleInfo
- {
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }).ToList();
+ var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
int nodeIndex = 0;
SliderEventDescriptor? lastEvent = null;
diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
new file mode 100644
index 0000000000..0cd3af01df
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
@@ -0,0 +1,48 @@
+// 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 osu.Framework.Bindables;
+using osu.Game.Rulesets.Objects.Types;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects
+{
+ ///
+ /// Represents a single object that can be caught by the catcher.
+ /// This includes normal fruits, droplets, and bananas but excludes objects that act only as a container of nested hit objects.
+ ///
+ public abstract class PalpableCatchHitObject : CatchHitObject, IHasComboInformation
+ {
+ ///
+ /// Difference between the distance to the next object
+ /// and the distance that would have triggered a hyper dash.
+ /// A value close to 0 indicates a difficult jump (for difficulty calculation).
+ ///
+ public float DistanceToHyperDash { get; set; }
+
+ public readonly Bindable HyperDashBindable = new Bindable();
+
+ ///
+ /// Whether this fruit can initiate a hyperdash.
+ ///
+ public bool HyperDash => HyperDashBindable.Value;
+
+ private CatchHitObject hyperDashTarget;
+
+ ///
+ /// The target fruit if we are to initiate a hyperdash.
+ ///
+ public CatchHitObject HyperDashTarget
+ {
+ get => hyperDashTarget;
+ set
+ {
+ hyperDashTarget = value;
+ HyperDashBindable.Value = value != null;
+ }
+ }
+
+ Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count];
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index a4f54bfe82..dfc81ee8d9 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays
float lastPosition = CatchPlayfield.CENTER_X;
double lastTime = 0;
- void moveToNext(CatchHitObject h)
+ void moveToNext(PalpableCatchHitObject h)
{
float positionChange = Math.Abs(lastPosition - h.X);
double timeAvailable = h.StartTime - lastTime;
@@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays
foreach (var obj in Beatmap.HitObjects)
{
- switch (obj)
+ if (obj is PalpableCatchHitObject palpableObject)
{
- case Fruit _:
- moveToNext(obj);
- break;
+ moveToNext(palpableObject);
}
foreach (var nestedObj in obj.NestedHitObjects.Cast())
{
- switch (nestedObj)
+ if (nestedObj is PalpableCatchHitObject palpableNestedObject)
{
- case Banana _:
- case TinyDroplet _:
- case Droplet _:
- case Fruit _:
- moveToNext(nestedObj);
- break;
+ moveToNext(palpableNestedObject);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
new file mode 100644
index 0000000000..8da18a668a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
@@ -0,0 +1,26 @@
+// 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.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class BananaPiece : CatchHitObjectPiece
+ {
+ protected override BorderPiece BorderPiece { get; }
+
+ public BananaPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new BananaPulpFormation
+ {
+ AccentColour = { BindTarget = AccentColour },
+ },
+ BorderPiece = new BorderPiece(),
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/BananaPulpFormation.cs b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPulpFormation.cs
new file mode 100644
index 0000000000..ee1cc68f7d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPulpFormation.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.
+
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class BananaPulpFormation : PulpFormation
+ {
+ public BananaPulpFormation()
+ {
+ AddPulp(new Vector2(0, -0.3f), new Vector2(SMALL_PULP));
+ AddPulp(new Vector2(0, 0.05f), new Vector2(LARGE_PULP_4 * 0.8f, LARGE_PULP_4 * 2.5f));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs
new file mode 100644
index 0000000000..7308d6b499
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs
@@ -0,0 +1,32 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Catch.Objects;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class BorderPiece : Circle
+ {
+ public BorderPiece()
+ {
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ BorderColour = Color4.White;
+ BorderThickness = 6f * FruitPiece.RADIUS_ADJUST;
+
+ // Border is drawn only when there is a child drawable.
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ };
+ }
+ }
+}
+
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
new file mode 100644
index 0000000000..d59b6cc0de
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
@@ -0,0 +1,61 @@
+// 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 JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public abstract class CatchHitObjectPiece : CompositeDrawable
+ {
+ public readonly Bindable AccentColour = new Bindable();
+ public readonly Bindable HyperDash = new Bindable();
+
+ [Resolved(canBeNull: true)]
+ [CanBeNull]
+ protected DrawableHitObject DrawableHitObject { get; private set; }
+
+ ///
+ /// A part of this piece that will be faded out while falling in the playfield.
+ ///
+ [CanBeNull]
+ protected virtual BorderPiece BorderPiece => null;
+
+ ///
+ /// A part of this piece that will be only visible when is true.
+ ///
+ [CanBeNull]
+ protected virtual HyperBorderPiece HyperBorderPiece => null;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
+
+ if (hitObject != null)
+ {
+ AccentColour.BindTo(hitObject.AccentColour);
+ HyperDash.BindTo(hitObject.HyperDash);
+ }
+
+ HyperDash.BindValueChanged(hyper =>
+ {
+ if (HyperBorderPiece != null)
+ HyperBorderPiece.Alpha = hyper.NewValue ? 1 : 0;
+ }, true);
+ }
+
+ protected override void Update()
+ {
+ if (BorderPiece != null && DrawableHitObject?.HitObject != null)
+ BorderPiece.Alpha = (float)Math.Clamp((DrawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
new file mode 100644
index 0000000000..8b1052dfe2
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
@@ -0,0 +1,29 @@
+// 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.Graphics;
+using osu.Game.Rulesets.Catch.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class DropletPiece : CatchHitObjectPiece
+ {
+ protected override HyperBorderPiece HyperBorderPiece { get; }
+
+ public DropletPiece()
+ {
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
+
+ InternalChildren = new Drawable[]
+ {
+ new Pulp
+ {
+ RelativeSizeAxes = Axes.Both,
+ AccentColour = { BindTarget = AccentColour }
+ },
+ HyperBorderPiece = new HyperDropletBorderPiece()
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
new file mode 100644
index 0000000000..2e3803a31a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
@@ -0,0 +1,48 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ internal class FruitPiece : CatchHitObjectPiece
+ {
+ ///
+ /// Because we're adding a border around the fruit, we need to scale down some.
+ ///
+ public const float RADIUS_ADJUST = 1.1f;
+
+ public readonly Bindable VisualRepresentation = new Bindable();
+
+ protected override BorderPiece BorderPiece { get; }
+ protected override HyperBorderPiece HyperBorderPiece { get; }
+
+ public FruitPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new FruitPulpFormation
+ {
+ AccentColour = { BindTarget = AccentColour },
+ VisualRepresentation = { BindTarget = VisualRepresentation }
+ },
+ BorderPiece = new BorderPiece(),
+ HyperBorderPiece = new HyperBorderPiece()
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ var fruit = (DrawableFruit)DrawableHitObject;
+
+ if (fruit != null)
+ VisualRepresentation.BindTo(fruit.VisualRepresentation);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs
new file mode 100644
index 0000000000..88e0b5133a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs
@@ -0,0 +1,59 @@
+// 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.Bindables;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class FruitPulpFormation : PulpFormation
+ {
+ public readonly Bindable VisualRepresentation = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ VisualRepresentation.BindValueChanged(setFormation, true);
+ }
+
+ private void setFormation(ValueChangedEvent visualRepresentation)
+ {
+ Clear();
+
+ switch (visualRepresentation.NewValue)
+ {
+ case FruitVisualRepresentation.Pear:
+ AddPulp(new Vector2(0, -0.33f), new Vector2(SMALL_PULP));
+ AddPulp(PositionAt(60, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
+ AddPulp(PositionAt(180, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
+ AddPulp(PositionAt(300, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
+ break;
+
+ case FruitVisualRepresentation.Grape:
+ AddPulp(new Vector2(0, -0.25f), new Vector2(SMALL_PULP));
+ AddPulp(PositionAt(0, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
+ AddPulp(PositionAt(120, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
+ AddPulp(PositionAt(240, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
+ break;
+
+ case FruitVisualRepresentation.Pineapple:
+ AddPulp(new Vector2(0, -0.3f), new Vector2(SMALL_PULP));
+ AddPulp(PositionAt(45, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ AddPulp(PositionAt(135, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ AddPulp(PositionAt(225, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ AddPulp(PositionAt(315, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ break;
+
+ case FruitVisualRepresentation.Raspberry:
+ AddPulp(new Vector2(0, -0.34f), new Vector2(SMALL_PULP));
+ AddPulp(PositionAt(0, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ AddPulp(PositionAt(90, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ AddPulp(PositionAt(180, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ AddPulp(PositionAt(270, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs
new file mode 100644
index 0000000000..d160956a6e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs
@@ -0,0 +1,22 @@
+// 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.Graphics;
+using osu.Game.Rulesets.Catch.UI;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class HyperBorderPiece : BorderPiece
+ {
+ public HyperBorderPiece()
+ {
+ BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
+ BorderThickness = 12f * FruitPiece.RADIUS_ADJUST;
+
+ Child.Alpha = 0.3f;
+ Child.Blending = BlendingParameters.Additive;
+ Child.Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
+ }
+ }
+}
+
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/HyperDropletBorderPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/HyperDropletBorderPiece.cs
new file mode 100644
index 0000000000..53a487b97f
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/HyperDropletBorderPiece.cs
@@ -0,0 +1,14 @@
+// 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.Catch.Skinning.Default
+{
+ public class HyperDropletBorderPiece : HyperBorderPiece
+ {
+ public HyperDropletBorderPiece()
+ {
+ Size /= 2;
+ BorderThickness = 6f;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Skinning/Default/Pulp.cs
similarity index 95%
rename from osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
rename to osu.Game.Rulesets.Catch/Skinning/Default/Pulp.cs
index d3e4945611..96c6233b41 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/Pulp.cs
@@ -8,10 +8,12 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
+namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class Pulp : Circle
{
+ public readonly Bindable AccentColour = new Bindable();
+
public Pulp()
{
RelativePositionAxes = Axes.Both;
@@ -22,8 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.9f);
}
- public readonly Bindable AccentColour = new Bindable();
-
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs b/osu.Game.Rulesets.Catch/Skinning/Default/PulpFormation.cs
similarity index 59%
rename from osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs
rename to osu.Game.Rulesets.Catch/Skinning/Default/PulpFormation.cs
index be70c3400c..8753aa4077 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/PulpFormation.cs
@@ -2,19 +2,17 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Catch.Objects.Drawables
+namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public abstract class PulpFormation : CompositeDrawable
{
- protected readonly IBindable AccentColour = new Bindable();
+ public readonly Bindable AccentColour = new Bindable();
protected const float LARGE_PULP_3 = 16f * FruitPiece.RADIUS_ADJUST;
protected const float DISTANCE_FROM_CENTRE_3 = 0.15f;
@@ -24,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected const float SMALL_PULP = LARGE_PULP_3 / 2;
+ private int pulpsInUse;
+
protected PulpFormation()
{
RelativeSizeAxes = Axes.Both;
@@ -33,11 +33,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
distance * MathF.Sin(angle * MathF.PI / 180),
distance * MathF.Cos(angle * MathF.PI / 180));
- [BackgroundDependencyLoader]
- private void load(DrawableHitObject drawableObject)
+ protected void Clear()
{
- DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
- AccentColour.BindTo(drawableCatchObject.AccentColour);
+ for (int i = 0; i < pulpsInUse; i++)
+ InternalChildren[i].Alpha = 0;
+ pulpsInUse = 0;
+ }
+
+ protected void AddPulp(Vector2 position, Vector2 size)
+ {
+ if (pulpsInUse == InternalChildren.Count)
+ AddInternal(new Pulp { AccentColour = { BindTarget = AccentColour } });
+
+ var pulp = InternalChildren[pulpsInUse];
+ pulp.Position = position;
+ pulp.Size = size;
+ pulp.Alpha = 1;
+
+ pulpsInUse++;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
similarity index 83%
rename from osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
rename to osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 22db147e32..41fd0fe776 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Skinning;
-using osuTK;
using osuTK.Graphics;
using static osu.Game.Skinning.LegacySkinConfiguration;
-namespace osu.Game.Rulesets.Catch.Skinning
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public class CatchLegacySkinTransformer : LegacySkinTransformer
{
@@ -40,20 +38,21 @@ namespace osu.Game.Rulesets.Catch.Skinning
switch (catchSkinComponent.Component)
{
- case CatchSkinComponents.FruitApple:
- case CatchSkinComponents.FruitBananas:
- case CatchSkinComponents.FruitOrange:
- case CatchSkinComponents.FruitGrapes:
- case CatchSkinComponents.FruitPear:
- var lookupName = catchSkinComponent.Component.ToString().Kebaberize();
- if (GetTexture(lookupName) != null)
- return new LegacyFruitPiece(lookupName);
+ case CatchSkinComponents.Fruit:
+ if (GetTexture("fruit-pear") != null)
+ return new LegacyFruitPiece();
+
+ break;
+
+ case CatchSkinComponents.Banana:
+ if (GetTexture("fruit-bananas") != null)
+ return new LegacyBananaPiece();
break;
case CatchSkinComponents.Droplet:
if (GetTexture("fruit-drop") != null)
- return new LegacyFruitPiece("fruit-drop") { Scale = new Vector2(0.8f) };
+ return new LegacyDropletPiece();
break;
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
similarity index 98%
rename from osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs
rename to osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
index 34608b07ff..f797ae75c2 100644
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
@@ -9,7 +9,7 @@ using osuTK;
using osuTK.Graphics;
using static osu.Game.Skinning.LegacySkinConfiguration;
-namespace osu.Game.Rulesets.Catch.Skinning
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
///
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
new file mode 100644
index 0000000000..6f93e68594
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
@@ -0,0 +1,47 @@
+// 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.Bindables;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
+{
+ internal class LegacyFruitPiece : LegacyCatchHitObjectPiece
+ {
+ public readonly Bindable VisualRepresentation = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ var fruit = (DrawableFruit)DrawableHitObject;
+
+ if (fruit != null)
+ VisualRepresentation.BindTo(fruit.VisualRepresentation);
+
+ VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
+ }
+
+ private void setTexture(FruitVisualRepresentation visualRepresentation)
+ {
+ switch (visualRepresentation)
+ {
+ case FruitVisualRepresentation.Pear:
+ SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay"));
+ break;
+
+ case FruitVisualRepresentation.Grape:
+ SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay"));
+ break;
+
+ case FruitVisualRepresentation.Pineapple:
+ SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay"));
+ break;
+
+ case FruitVisualRepresentation.Raspberry:
+ SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay"));
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyBananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyBananaPiece.cs
new file mode 100644
index 0000000000..f80e50c8c0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyBananaPiece.cs
@@ -0,0 +1,20 @@
+// 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.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ public class LegacyBananaPiece : LegacyCatchHitObjectPiece
+ {
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Texture texture = Skin.GetTexture("fruit-bananas");
+ Texture overlayTexture = Skin.GetTexture("fruit-bananas-overlay");
+
+ SetTexture(texture, overlayTexture);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs
new file mode 100644
index 0000000000..1e68439402
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Pooling;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ public abstract class LegacyCatchHitObjectPiece : PoolableDrawable
+ {
+ public readonly Bindable AccentColour = new Bindable();
+ public readonly Bindable HyperDash = new Bindable();
+
+ private readonly Sprite colouredSprite;
+ private readonly Sprite overlaySprite;
+ private readonly Sprite hyperSprite;
+
+ [Resolved]
+ protected ISkinSource Skin { get; private set; }
+
+ [Resolved(canBeNull: true)]
+ [CanBeNull]
+ protected DrawableHitObject DrawableHitObject { get; private set; }
+
+ protected LegacyCatchHitObjectPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ colouredSprite = new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ overlaySprite = new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ hyperSprite = new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Depth = 1,
+ Alpha = 0,
+ Scale = new Vector2(1.2f),
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
+
+ if (hitObject != null)
+ {
+ AccentColour.BindTo(hitObject.AccentColour);
+ HyperDash.BindTo(hitObject.HyperDash);
+ }
+
+ hyperSprite.Colour = Skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ??
+ Skin.GetConfig(CatchSkinColour.HyperDash)?.Value ??
+ Catcher.DEFAULT_HYPER_DASH_COLOUR;
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue);
+ }, true);
+
+ HyperDash.BindValueChanged(hyper =>
+ {
+ hyperSprite.Alpha = hyper.NewValue ? 0.7f : 0;
+ }, true);
+ }
+
+ protected void SetTexture(Texture texture, Texture overlayTexture)
+ {
+ colouredSprite.Texture = texture;
+ overlaySprite.Texture = overlayTexture;
+ hyperSprite.Texture = texture;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyDropletPiece.cs
new file mode 100644
index 0000000000..8f4331d2a3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyDropletPiece.cs
@@ -0,0 +1,26 @@
+// 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.Graphics.Textures;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ public class LegacyDropletPiece : LegacyCatchHitObjectPiece
+ {
+ public LegacyDropletPiece()
+ {
+ Scale = new Vector2(0.8f);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Texture texture = Skin.GetTexture("fruit-drop");
+ Texture overlayTexture = Skin.GetTexture("fruit-drop-overlay");
+
+ SetTexture(texture, overlayTexture);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
deleted file mode 100644
index 381d066750..0000000000
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Rulesets.Catch.Objects.Drawables;
-using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Skinning;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Catch.Skinning
-{
- internal class LegacyFruitPiece : CompositeDrawable
- {
- private readonly string lookupName;
-
- private readonly IBindable accentColour = new Bindable();
- private Sprite colouredSprite;
-
- public LegacyFruitPiece(string lookupName)
- {
- this.lookupName = lookupName;
- RelativeSizeAxes = Axes.Both;
- }
-
- [BackgroundDependencyLoader]
- private void load(DrawableHitObject drawableObject, ISkinSource skin)
- {
- DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
-
- accentColour.BindTo(drawableCatchObject.AccentColour);
-
- InternalChildren = new Drawable[]
- {
- colouredSprite = new Sprite
- {
- Texture = skin.GetTexture(lookupName),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new Sprite
- {
- Texture = skin.GetTexture($"{lookupName}-overlay"),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- };
-
- if (drawableCatchObject.HitObject.HyperDash)
- {
- var hyperDash = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Blending = BlendingParameters.Additive,
- Depth = 1,
- Alpha = 0.7f,
- Scale = new Vector2(1.2f),
- Texture = skin.GetTexture(lookupName),
- Colour = skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ??
- skin.GetConfig(CatchSkinColour.HyperDash)?.Value ??
- Catcher.DEFAULT_HYPER_DASH_COLOUR,
- };
-
- AddInternal(hyperDash);
- }
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 735d7fc300..fdc12bf088 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
@@ -35,41 +36,53 @@ namespace osu.Game.Rulesets.Catch.UI
public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation)
{
- var explodingFruitContainer = new Container
+ var droppedObjectContainer = new Container
{
RelativeSizeAxes = Axes.Both,
};
- CatcherArea = new CatcherArea(difficulty)
+ CatcherArea = new CatcherArea(droppedObjectContainer, difficulty)
{
- CreateDrawableRepresentation = createDrawableRepresentation,
- ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
};
InternalChildren = new[]
{
- explodingFruitContainer,
+ droppedObjectContainer,
CatcherArea.MovableCatcher.CreateProxiedContent(),
HitObjectContainer,
CatcherArea,
};
}
- public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
-
- public override void Add(DrawableHitObject h)
+ [BackgroundDependencyLoader]
+ private void load()
{
- h.OnNewResult += onNewResult;
- h.OnRevertResult += onRevertResult;
-
- base.Add(h);
-
- var fruit = (DrawableCatchHitObject)h;
- fruit.CheckPosition = CheckIfWeCanCatch;
+ RegisterPool(50);
+ RegisterPool(50);
+ RegisterPool(100);
+ RegisterPool(100);
+ RegisterPool(10);
+ RegisterPool(2);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // these subscriptions need to be done post constructor to ensure externally bound components have a chance to populate required fields (ScoreProcessor / ComboAtJudgement in this case).
+ NewResult += onNewResult;
+ RevertResult += onRevertResult;
+ }
+
+ protected override void OnNewDrawableHitObject(DrawableHitObject d)
+ {
+ ((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
+ }
+
+ private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.MovableCatcher.CanCatch(obj);
+
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index a221ca7966..a806e623af 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -9,14 +9,16 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
-using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -46,19 +48,15 @@ namespace osu.Game.Rulesets.Catch.UI
///
public const double BASE_SPEED = 1.0;
- public Container ExplodingFruitTarget;
-
- private Container caughtFruitContainer { get; } = new Container
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
- };
-
[NotNull]
private readonly Container trailsTarget;
private CatcherTrailDisplay trails;
+ private readonly Container droppedObjectTarget;
+
+ private readonly Container caughtFruitContainer;
+
public CatcherAnimationState CurrentState { get; private set; }
///
@@ -91,9 +89,9 @@ namespace osu.Game.Rulesets.Catch.UI
///
private readonly float catchWidth;
- private CatcherSprite catcherIdle;
- private CatcherSprite catcherKiai;
- private CatcherSprite catcherFail;
+ private readonly CatcherSprite catcherIdle;
+ private readonly CatcherSprite catcherKiai;
+ private readonly CatcherSprite catcherFail;
private CatcherSprite currentCatcher;
@@ -107,9 +105,13 @@ namespace osu.Game.Rulesets.Catch.UI
private float hyperDashTargetPosition;
private Bindable hitLighting;
- public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
+ private readonly DrawablePool hitExplosionPool;
+ private readonly Container hitExplosionContainer;
+
+ public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
{
this.trailsTarget = trailsTarget;
+ this.droppedObjectTarget = droppedObjectTarget;
Origin = Anchor.TopCentre;
@@ -118,16 +120,15 @@ namespace osu.Game.Rulesets.Catch.UI
Scale = calculateScale(difficulty);
catchWidth = CalculateCatchWidth(Scale);
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
- {
- hitLighting = config.GetBindable(OsuSetting.HitLighting);
InternalChildren = new Drawable[]
{
- caughtFruitContainer,
+ hitExplosionPool = new DrawablePool(10),
+ caughtFruitContainer = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ },
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
{
Anchor = Anchor.TopCentre,
@@ -142,9 +143,19 @@ namespace osu.Game.Rulesets.Catch.UI
{
Anchor = Anchor.TopCentre,
Alpha = 0,
- }
+ },
+ hitExplosionContainer = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ },
};
+ }
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ hitLighting = config.GetBindable(OsuSetting.HitLighting);
trails = new CatcherTrailDisplay(this);
updateCatcher();
@@ -166,65 +177,26 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
///
- private static Vector2 calculateScale(BeatmapDifficulty difficulty)
- => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
///
/// Calculates the width of the area used for attempting catches in gameplay.
///
/// The scale of the catcher.
- internal static float CalculateCatchWidth(Vector2 scale)
- => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
+ internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
///
/// Calculates the width of the area used for attempting catches in gameplay.
///
/// The beatmap difficulty.
- internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
- => CalculateCatchWidth(calculateScale(difficulty));
+ internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty));
///
- /// Add a caught fruit to the catcher's stack.
+ /// Determine if this catcher can catch a in the current position.
///
- /// The fruit that was caught.
- public void PlaceOnPlate(DrawableCatchHitObject fruit)
+ public bool CanCatch(CatchHitObject hitObject)
{
- var ourRadius = fruit.DisplayRadius;
- float theirRadius = 0;
-
- const float allowance = 10;
-
- while (caughtFruitContainer.Any(f =>
- f.LifetimeEnd == double.MaxValue &&
- Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
- {
- var diff = (ourRadius + theirRadius) / allowance;
- fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
- fruit.Y -= RNG.NextSingle() * diff;
- }
-
- fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
-
- caughtFruitContainer.Add(fruit);
-
- if (hitLighting.Value)
- {
- AddInternal(new HitExplosion(fruit)
- {
- X = fruit.X,
- Scale = new Vector2(fruit.HitObject.Scale)
- });
- }
- }
-
- ///
- /// Let the catcher attempt to catch a fruit.
- ///
- /// The fruit to catch.
- /// Whether the catch is possible.
- public bool AttemptCatch(CatchHitObject fruit)
- {
- if (!fruit.CanBePlated)
+ if (!(hitObject is PalpableCatchHitObject fruit))
return false;
var halfCatchWidth = catchWidth * 0.5f;
@@ -233,18 +205,29 @@ namespace osu.Game.Rulesets.Catch.UI
var catchObjectPosition = fruit.X;
var catcherPosition = Position.X;
- var validCatch =
- catchObjectPosition >= catcherPosition - halfCatchWidth &&
- catchObjectPosition <= catcherPosition + halfCatchWidth;
+ return catchObjectPosition >= catcherPosition - halfCatchWidth &&
+ catchObjectPosition <= catcherPosition + halfCatchWidth;
+ }
- // only update hyperdash state if we are not catching a tiny droplet.
- if (fruit is TinyDroplet) return validCatch;
+ public void OnNewResult(DrawableCatchHitObject drawableObject, JudgementResult result)
+ {
+ var catchResult = (CatchJudgementResult)result;
+ catchResult.CatcherAnimationState = CurrentState;
+ catchResult.CatcherHyperDash = HyperDashing;
- if (validCatch && fruit.HyperDash)
+ if (!(drawableObject.HitObject is PalpableCatchHitObject hitObject)) return;
+
+ if (result.IsHit)
+ placeCaughtObject(hitObject);
+
+ // droplet doesn't affect the catcher state
+ if (hitObject is TinyDroplet) return;
+
+ if (result.IsHit && hitObject.HyperDash)
{
- var target = fruit.HyperDashTarget;
- var timeDifference = target.StartTime - fruit.StartTime;
- double positionDifference = target.X - catcherPosition;
+ var target = hitObject.HyperDashTarget;
+ var timeDifference = target.StartTime - hitObject.StartTime;
+ double positionDifference = target.X - X;
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperDashState(Math.Abs(velocity), target.X);
@@ -252,12 +235,30 @@ namespace osu.Game.Rulesets.Catch.UI
else
SetHyperDashState();
- if (validCatch)
- updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
- else if (!(fruit is Banana))
+ if (result.IsHit)
+ updateState(hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
+ else if (!(hitObject is Banana))
updateState(CatcherAnimationState.Fail);
+ }
- return validCatch;
+ public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
+ {
+ var catchResult = (CatchJudgementResult)result;
+
+ if (CurrentState != catchResult.CatcherAnimationState)
+ updateState(catchResult.CatcherAnimationState);
+
+ if (HyperDashing != catchResult.CatcherHyperDash)
+ {
+ if (catchResult.CatcherHyperDash)
+ SetHyperDashState(2);
+ else
+ SetHyperDashState();
+ }
+
+ caughtFruitContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
+ droppedObjectTarget.RemoveAll(d => (d as DrawableCatchHitObject)?.HitObject == drawableObject.HitObject);
+ hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
}
///
@@ -291,24 +292,17 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
- private void runHyperDashStateTransition(bool hyperDashing)
+ public void UpdatePosition(float position)
{
- updateTrailVisibility();
+ position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
- if (hyperDashing)
- {
- this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
- this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
- }
- else
- {
- this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
- this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
- }
+ if (position == X)
+ return;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
+ X = position;
}
- private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
-
public bool OnPressed(CatchAction action)
{
switch (action)
@@ -347,56 +341,34 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
- public void UpdatePosition(float position)
- {
- position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
-
- if (position == X)
- return;
-
- Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
- X = position;
- }
-
///
/// Drop any fruit off the plate.
///
- public void Drop()
- {
- foreach (var f in caughtFruitContainer.ToArray())
- Drop(f);
- }
+ public void Drop() => clearPlate(DroppedObjectAnimation.Drop);
///
- /// Explode any fruit off the plate.
+ /// Explode all fruit off the plate.
///
- public void Explode()
- {
- foreach (var f in caughtFruitContainer.ToArray())
- Explode(f);
- }
+ public void Explode() => clearPlate(DroppedObjectAnimation.Explode);
- public void Drop(DrawableHitObject fruit)
+ private void runHyperDashStateTransition(bool hyperDashing)
{
- removeFromPlateWithTransform(fruit, f =>
+ updateTrailVisibility();
+
+ if (hyperDashing)
{
- f.MoveToY(f.Y + 75, 750, Easing.InSine);
- f.FadeOut(750);
- });
- }
-
- public void Explode(DrawableHitObject fruit)
- {
- var originalX = fruit.X * Scale.X;
-
- removeFromPlateWithTransform(fruit, f =>
+ this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ }
+ else
{
- f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
- f.MoveToX(f.X + originalX * 6, 1000);
- f.FadeOut(750);
- });
+ this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ }
}
+ private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
+
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
@@ -469,33 +441,144 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher();
}
- private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
+ private void placeCaughtObject(PalpableCatchHitObject source)
{
- if (ExplodingFruitTarget != null)
+ var caughtObject = createCaughtObject(source);
+
+ if (caughtObject == null) return;
+
+ caughtObject.RelativePositionAxes = Axes.None;
+ caughtObject.X = source.X - X;
+ caughtObject.IsOnPlate = true;
+
+ caughtObject.Anchor = Anchor.TopCentre;
+ caughtObject.Origin = Anchor.Centre;
+ caughtObject.Scale *= 0.5f;
+ caughtObject.LifetimeStart = source.StartTime;
+ caughtObject.LifetimeEnd = double.MaxValue;
+
+ adjustPositionInStack(caughtObject);
+
+ caughtFruitContainer.Add(caughtObject);
+
+ addLighting(caughtObject);
+
+ if (!caughtObject.StaysOnPlate)
+ removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
+ }
+
+ private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject)
+ {
+ const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
+ const float allowance = 10;
+
+ float caughtObjectRadius = caughtObject.DisplayRadius;
+
+ while (caughtFruitContainer.Any(f => Vector2Extensions.Distance(f.Position, caughtObject.Position) < (caughtObjectRadius + radius_div_2) / (allowance / 2)))
{
- fruit.Anchor = Anchor.TopLeft;
- fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
+ float diff = (caughtObjectRadius + radius_div_2) / allowance;
- if (!caughtFruitContainer.Remove(fruit))
- // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
- // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
- return;
-
- ExplodingFruitTarget.Add(fruit);
+ caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2;
+ caughtObject.Y -= RNG.NextSingle() * diff;
}
- var actionTime = Clock.CurrentTime;
+ caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
+ }
- fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
- onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
+ private void addLighting(DrawablePalpableCatchHitObject caughtObject)
+ {
+ if (!hitLighting.Value) return;
- void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
+ HitExplosion hitExplosion = hitExplosionPool.Get();
+ hitExplosion.HitObject = caughtObject.HitObject;
+ hitExplosion.X = caughtObject.X;
+ hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale);
+ hitExplosion.ObjectColour = caughtObject.AccentColour.Value;
+ hitExplosionContainer.Add(hitExplosion);
+ }
+
+ private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source)
+ {
+ switch (source)
{
- using (fruit.BeginAbsoluteSequence(actionTime))
- action(fruit);
+ case Banana banana:
+ return new DrawableBanana(banana);
- fruit.Expire();
+ case Fruit fruit:
+ return new DrawableFruit(fruit);
+
+ case TinyDroplet tiny:
+ return new DrawableTinyDroplet(tiny);
+
+ case Droplet droplet:
+ return new DrawableDroplet(droplet);
+
+ default:
+ return null;
}
}
+
+ private void clearPlate(DroppedObjectAnimation animation)
+ {
+ var caughtObjects = caughtFruitContainer.Children.ToArray();
+ caughtFruitContainer.Clear(false);
+
+ droppedObjectTarget.AddRange(caughtObjects);
+
+ foreach (var caughtObject in caughtObjects)
+ drop(caughtObject, animation);
+ }
+
+ private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation)
+ {
+ if (!caughtFruitContainer.Remove(caughtObject))
+ throw new InvalidOperationException("Can only drop a caught object on the plate");
+
+ droppedObjectTarget.Add(caughtObject);
+
+ drop(caughtObject, animation);
+ }
+
+ private void drop(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation)
+ {
+ var originalX = d.X * Scale.X;
+ var startTime = Clock.CurrentTime;
+
+ d.Anchor = Anchor.TopLeft;
+ d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget);
+
+ // we cannot just apply the transforms because DHO clears transforms when state is updated
+ d.ApplyCustomUpdateState += (o, state) => animate(o, animation, originalX, startTime);
+ if (d.IsLoaded)
+ animate(d, animation, originalX, startTime);
+ }
+
+ private void animate(Drawable d, DroppedObjectAnimation animation, float originalX, double startTime)
+ {
+ using (d.BeginAbsoluteSequence(startTime))
+ {
+ switch (animation)
+ {
+ case DroppedObjectAnimation.Drop:
+ d.MoveToY(d.Y + 75, 750, Easing.InSine);
+ d.FadeOut(750);
+ break;
+
+ case DroppedObjectAnimation.Explode:
+ d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
+ d.MoveToX(d.X + originalX * 6, 1000);
+ d.FadeOut(750);
+ break;
+ }
+
+ d.Expire();
+ }
+ }
+
+ private enum DroppedObjectAnimation
+ {
+ Drop,
+ Explode
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 5e794a76aa..857d9141c9 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -1,16 +1,13 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
-using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -21,19 +18,10 @@ namespace osu.Game.Rulesets.Catch.UI
{
public const float CATCHER_SIZE = 106.75f;
- public Func> CreateDrawableRepresentation;
-
public readonly Catcher MovableCatcher;
private readonly CatchComboDisplay comboDisplay;
- public Container ExplodingFruitTarget
- {
- set => MovableCatcher.ExplodingFruitTarget = value;
- }
-
- private DrawableCatchHitObject lastPlateableFruit;
-
- public CatcherArea(BeatmapDifficulty difficulty = null)
+ public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
{
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
Children = new Drawable[]
@@ -47,73 +35,32 @@ namespace osu.Game.Rulesets.Catch.UI
Margin = new MarginPadding { Bottom = 350f },
X = CatchPlayfield.CENTER_X
},
- MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
+ MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X },
};
}
- public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
+ public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
{
+ MovableCatcher.OnNewResult(hitObject, result);
+
if (!result.Type.IsScorable())
return;
- void runAfterLoaded(Action action)
- {
- if (lastPlateableFruit == null)
- return;
-
- // this is required to make this run after the last caught fruit runs updateState() at least once.
- // TODO: find a better alternative
- if (lastPlateableFruit.IsLoaded)
- action();
- else
- lastPlateableFruit.OnLoadComplete += _ => action();
- }
-
- 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);
-
- if (caughtFruit == null) return;
-
- caughtFruit.RelativePositionAxes = Axes.None;
- caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
- caughtFruit.IsOnPlate = true;
-
- caughtFruit.Anchor = Anchor.TopCentre;
- caughtFruit.Origin = Anchor.Centre;
- caughtFruit.Scale *= 0.5f;
- caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
- caughtFruit.LifetimeEnd = double.MaxValue;
-
- MovableCatcher.PlaceOnPlate(caughtFruit);
- lastPlateableFruit = caughtFruit;
-
- if (!fruit.StaysOnPlate)
- runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
- }
-
- if (fruit.HitObject.LastInCombo)
+ if (hitObject.HitObject.LastInCombo)
{
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
- runAfterLoaded(() => MovableCatcher.Explode());
+ MovableCatcher.Explode();
else
MovableCatcher.Drop();
}
- comboDisplay.OnNewResult(fruit, result);
+ comboDisplay.OnNewResult(hitObject, result);
}
- public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
- => comboDisplay.OnRevertResult(fruit, result);
-
- public void OnReleased(CatchAction action)
+ public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
{
- }
-
- public bool AttemptCatch(CatchHitObject obj)
- {
- return MovableCatcher.AttemptCatch(obj);
+ comboDisplay.OnRevertResult(hitObject, result);
+ MovableCatcher.OnRevertResult(hitObject, result);
}
protected override void UpdateAfterChildren()
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
index f7e9fd19a7..fa65190032 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
@@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
private readonly Catcher catcher;
+ private readonly DrawablePool trailPool;
+
private readonly Container dashTrails;
private readonly Container hyperDashTrails;
private readonly Container endGlowSprites;
@@ -80,8 +83,9 @@ namespace osu.Game.Rulesets.Catch.UI
RelativeSizeAxes = Axes.Both;
- InternalChildren = new[]
+ InternalChildren = new Drawable[]
{
+ trailPool = new DrawablePool(30),
dashTrails = new Container { RelativeSizeAxes = Axes.Both },
hyperDashTrails = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
endGlowSprites = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
@@ -118,14 +122,14 @@ namespace osu.Game.Rulesets.Catch.UI
{
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
- var sprite = new CatcherTrailSprite(texture)
- {
- Anchor = catcher.Anchor,
- Scale = catcher.Scale,
- Blending = BlendingParameters.Additive,
- RelativePositionAxes = catcher.RelativePositionAxes,
- Position = catcher.Position
- };
+ CatcherTrailSprite sprite = trailPool.Get();
+
+ sprite.Texture = texture;
+ sprite.Anchor = catcher.Anchor;
+ sprite.Scale = catcher.Scale;
+ sprite.Blending = BlendingParameters.Additive;
+ sprite.RelativePositionAxes = catcher.RelativePositionAxes;
+ sprite.Position = catcher.Position;
target.Add(sprite);
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
index 56cb7dbfda..0e3e409fac 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
@@ -1,22 +1,40 @@
// 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.Graphics;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
- public class CatcherTrailSprite : Sprite
+ public class CatcherTrailSprite : PoolableDrawable
{
- public CatcherTrailSprite(Texture texture)
+ public Texture Texture
{
- Texture = texture;
+ set => sprite.Texture = value;
+ }
+
+ private readonly Sprite sprite;
+
+ public CatcherTrailSprite()
+ {
+ InternalChild = sprite = new Sprite
+ {
+ RelativeSizeAxes = Axes.Both
+ };
Size = new Vector2(CatcherArea.CATCHER_SIZE);
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
}
+
+ protected override void FreeAfterUse()
+ {
+ ClearTransforms();
+ base.FreeAfterUse();
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index ebe45aa3ab..46733181e3 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -8,7 +8,6 @@ using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
@@ -40,30 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI
protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
- public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h)
- {
- switch (h)
- {
- case Banana banana:
- return new DrawableBanana(banana);
-
- case Fruit fruit:
- return new DrawableFruit(fruit);
-
- case JuiceStream stream:
- return new DrawableJuiceStream(stream, CreateDrawableRepresentation);
-
- case BananaShower shower:
- return new DrawableBananaShower(shower, CreateDrawableRepresentation);
-
- case TinyDroplet tiny:
- return new DrawableTinyDroplet(tiny);
-
- case Droplet droplet:
- return new DrawableDroplet(droplet);
- }
-
- return null;
- }
+ public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) => null;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
index 04a86f83be..26627422e1 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -5,35 +5,45 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Utils;
-using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.Objects;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
- public class HitExplosion : CompositeDrawable
+ public class HitExplosion : PoolableDrawable
{
- private readonly CircularContainer largeFaint;
+ private Color4 objectColour;
+ public CatchHitObject HitObject;
- public HitExplosion(DrawableCatchHitObject fruit)
+ public Color4 ObjectColour
+ {
+ get => objectColour;
+ set
+ {
+ if (objectColour == value) return;
+
+ objectColour = value;
+ onColourChanged();
+ }
+ }
+
+ private readonly CircularContainer largeFaint;
+ private readonly CircularContainer smallFaint;
+ private readonly CircularContainer directionalGlow1;
+ private readonly CircularContainer directionalGlow2;
+
+ public HitExplosion()
{
Size = new Vector2(20);
Anchor = Anchor.TopCentre;
Origin = Anchor.BottomCentre;
- Color4 objectColour = fruit.AccentColour.Value;
-
// scale roughly in-line with visual appearance of notes
-
- const float angle_variangle = 15; // should be less than 45
-
- const float roundness = 100;
-
const float initial_height = 10;
- var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
-
InternalChildren = new Drawable[]
{
largeFaint = new CircularContainer
@@ -42,33 +52,17 @@ namespace osu.Game.Rulesets.Catch.UI
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.8f),
Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- },
},
- new CircularContainer
+ smallFaint = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- },
},
- new CircularContainer
+ directionalGlow1 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -76,16 +70,8 @@ namespace osu.Game.Rulesets.Catch.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
},
- new CircularContainer
+ directionalGlow2 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -93,30 +79,57 @@ namespace osu.Game.Rulesets.Catch.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
}
};
}
- protected override void LoadComplete()
+ protected override void PrepareForUse()
{
- base.LoadComplete();
+ base.PrepareForUse();
const double duration = 400;
+ // we want our size to be very small so the glow dominates it.
+ largeFaint.Size = new Vector2(0.8f);
largeFaint
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
.FadeOut(duration * 2);
+ const float angle_variangle = 15; // should be less than 45
+ directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
+ directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
+
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
Expire(true);
}
+
+ private void onColourChanged()
+ {
+ const float roundness = 100;
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ };
+
+ smallFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ };
+
+ directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
index 654b752001..538a51db5f 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -96,6 +96,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
throw new System.NotImplementedException();
}
+ public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
+ {
+ throw new System.NotImplementedException();
+ }
+
public override float GetBeatSnapDistanceAt(double referenceTime)
{
throw new System.NotImplementedException();
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
index c9551ee79e..aaf96c63a6 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
@@ -15,7 +15,7 @@ using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index a4d4ec50f8..dcb25f21ba 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -24,7 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
if (hitWindows.IsHitResultAllowed(result))
{
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
- new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
+ {
+ Type = result
+ }, null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
index 0c56f7bcf4..4dc6700786 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
@@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osuTK;
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index d314671bce..4860dc0109 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
index 5fa687298a..f5067ea366 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
@@ -4,7 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
index 8773a39939..9c9273de3a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
@@ -4,7 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index b5ec1e1a2a..1f92929392 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -78,9 +78,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private double originalStartTime;
- public override void UpdatePosition(SnapResult result)
+ public override void UpdateTimeAndPosition(SnapResult result)
{
- base.UpdatePosition(result);
+ base.UpdateTimeAndPosition(result);
if (PlacementActive)
{
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index 27a279e044..5e09054667 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return true;
}
- public override void UpdatePosition(SnapResult result)
+ public override void UpdateTimeAndPosition(SnapResult result)
{
- base.UpdatePosition(result);
+ base.UpdateTimeAndPosition(result);
if (!PlacementActive)
Column = result.Playfield as Column;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
index 684004b558..3db89c8ae6 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
@@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
}
- public override void UpdatePosition(SnapResult result)
+ public override void UpdateTimeAndPosition(SnapResult result)
{
- base.UpdatePosition(result);
+ base.UpdateTimeAndPosition(result);
if (result.Playfield != null)
{
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 01d572447b..324670c4b2 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -9,7 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 906f7382c5..59c766fd84 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -26,7 +26,7 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Scoring;
-using osu.Game.Rulesets.Mania.Skinning;
+using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Game.Scoring;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
index ff77df0ae0..4093aeb2a7 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModEasy : ModEasy
+ public class ManiaModEasy : ModEasyWithExtraLives
{
public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index a64cc6dc67..4f062753a6 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -4,9 +4,9 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
index a4029e7893..3a00933e4d 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true);
+ protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index b3402d13e4..b512986ccb 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -5,7 +5,7 @@ using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Default/DefaultBodyPiece.cs
similarity index 98%
rename from osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
rename to osu.Game.Rulesets.Mania/Skinning/Default/DefaultBodyPiece.cs
index 9999983af5..db1ac6da88 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Default/DefaultBodyPiece.cs
@@ -5,16 +5,17 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
+using osuTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
+namespace osu.Game.Rulesets.Mania.Skinning.Default
{
///
/// Represents length-wise portion of a hold note.
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Default/DefaultNotePiece.cs
similarity index 97%
rename from osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs
rename to osu.Game.Rulesets.Mania/Skinning/Default/DefaultNotePiece.cs
index 29f5217fd8..c9c3cff799 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Default/DefaultNotePiece.cs
@@ -4,7 +4,6 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,8 +11,9 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
+namespace osu.Game.Rulesets.Mania.Skinning.Default
{
///
/// Represents the static hit markers of notes.
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Skinning/Default/IHoldNoteBody.cs
similarity index 88%
rename from osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs
rename to osu.Game.Rulesets.Mania/Skinning/Default/IHoldNoteBody.cs
index ac3792c01d..1f290f1f1c 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Default/IHoldNoteBody.cs
@@ -1,7 +1,7 @@
// 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
+namespace osu.Game.Rulesets.Mania.Skinning.Default
{
///
/// Interface for mania hold note bodies.
diff --git a/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs
similarity index 96%
rename from osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs
index c8b05ed2f8..3c89e2c04a 100644
--- a/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class HitTargetInsetContainer : Container
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
similarity index 99%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
index 8902d82f33..31db08ce2f 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
@@ -14,7 +14,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyBodyPiece : LegacyManiaColumnElement
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs
similarity index 98%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs
index 3bf51b3073..661e7f66f4 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs
@@ -12,7 +12,7 @@ using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs
similarity index 98%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs
index 7c5d41efcf..73aece1ed4 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs
similarity index 98%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs
index 6eced571d2..490a03d11a 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs
@@ -12,7 +12,7 @@ using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyHitTarget : CompositeDrawable
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs
similarity index 92%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs
index c5aa062d0f..21e5bdd5d6 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs
@@ -4,7 +4,7 @@
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyHoldNoteHeadPiece : LegacyNotePiece
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs
similarity index 96%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs
index 2e8259f10a..232b47ae27 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs
@@ -6,7 +6,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyHoldNoteTailPiece : LegacyNotePiece
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
similarity index 98%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
index b269ea25d4..78ccb83a8c 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
@@ -12,7 +12,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyKeyArea : LegacyManiaColumnElement, IKeyBindingHandler
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs
similarity index 96%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs
index 3c0c632c14..eb5514ba43 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs
@@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
///
/// A which is placed somewhere within a .
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs
similarity index 98%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs
index 283b04373b..31279796ce 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs
@@ -12,7 +12,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyNotePiece : LegacyManiaColumnElement
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
similarity index 99%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
index b0bab8e760..fec3e9493e 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
@@ -12,7 +12,7 @@ using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyStageBackground : CompositeDrawable
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs
similarity index 97%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs
index 4609fcc849..4e1952a670 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyStageForeground : CompositeDrawable
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
similarity index 99%
rename from osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
rename to osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 3724269f4d..89f639e2fe 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -2,19 +2,19 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osu.Framework.Graphics;
-using osu.Framework.Bindables;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Skinning;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Legacy;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
-namespace osu.Game.Rulesets.Mania.Skinning
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class ManiaLegacySkinTransformer : LegacySkinTransformer
{
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 9aabcc6699..d2a9b69b60 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
maniaObject.CheckHittable = hitPolicy.IsHittable;
- HitObjectContainer.Add(hitObject);
+ base.Add(hitObject);
}
public override bool Remove(DrawableHitObject h)
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
index e0b099ab9b..ec6c377a2e 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
index 225269cf48..69b81d6d5c 100644
--- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
@@ -10,7 +10,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
index ebce40a785..a3dcd0e57f 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
+using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -19,22 +20,42 @@ namespace osu.Game.Rulesets.Mania.UI
{
}
- protected override double FadeInDuration => 50;
+ protected override void ApplyMissAnimations()
+ {
+ if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
+ {
+ // this is temporary logic until mania's skin transformer returns IAnimatableJudgements
+ JudgementBody.ScaleTo(1.6f);
+ JudgementBody.ScaleTo(1, 100, Easing.In);
+
+ JudgementBody.MoveTo(Vector2.Zero);
+ JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
+
+ JudgementBody.RotateTo(0);
+ JudgementBody.RotateTo(40, 800, Easing.InQuint);
+ JudgementBody.FadeOutFromOne(800);
+
+ LifetimeEnd = JudgementBody.LatestTransformEndTime;
+ }
+
+ base.ApplyMissAnimations();
+ }
protected override void ApplyHitAnimations()
{
JudgementBody.ScaleTo(0.8f);
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
- JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250);
- this.Delay(FadeInDuration).FadeOut(200);
+ JudgementBody.Delay(50)
+ .ScaleTo(0.75f, 250)
+ .FadeOut(200);
}
- protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result);
+ protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
- private class ManiaJudgementPiece : DefaultJudgementPiece
+ private class DefaultManiaJudgementPiece : DefaultJudgementPiece
{
- public ManiaJudgementPiece(HitResult result)
+ public DefaultManiaJudgementPiece(HitResult result)
: base(result)
{
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.cs
new file mode 100644
index 0000000000..a652fb32f4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.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 System.Linq;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Tests.Beatmaps;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Editor
+{
+ [TestFixture]
+ public class TestSceneObjectBeatSnap : TestSceneOsuEditor
+ {
+ private OsuPlayfield playfield;
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+ AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First());
+ }
+
+ [Test]
+ public void TestBeatSnapHitCircle()
+ {
+ double firstTimingPointTime() => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time;
+
+ AddStep("seek some milliseconds forward", () => EditorClock.Seek(firstTimingPointTime() + 10));
+
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
+ AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
+ AddStep("place first object", () => InputManager.Click(MouseButton.Left));
+
+ AddAssert("ensure object snapped back to correct time", () => EditorBeatmap.HitObjects.First().StartTime == firstTimingPointTime());
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs
index 1ca94df26b..7bdf131e0d 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
base.SetUpSteps();
AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First());
+ AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
}
[TestCase(true)]
@@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
var first = (OsuHitObject)objects.First();
var second = (OsuHitObject)objects.Last();
- return first.Position == second.Position;
+ return Precision.AlmostEquals(first.EndPosition, second.Position);
});
}
@@ -66,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("start slider placement", () => InputManager.Click(MouseButton.Left));
- AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.185f, 0)));
+ AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.225f, 0)));
AddStep("end slider placement", () => InputManager.Click(MouseButton.Right));
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
- AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0)));
+ AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.235f, 0)));
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
@@ -86,5 +87,64 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
return Precision.AlmostEquals(first.EndPosition, second.Position);
});
}
+
+ [Test]
+ public void TestSecondCircleInSelectionAlsoSnaps()
+ {
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
+
+ AddStep("disable distance snap", () => InputManager.Key(Key.Q));
+
+ AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
+
+ AddStep("place first object", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("increment time", () => EditorClock.SeekForward(true));
+
+ AddStep("move mouse right", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.2f, 0)));
+ AddStep("place second object", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("increment time", () => EditorClock.SeekForward(true));
+
+ AddStep("move mouse down", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Width * 0.2f)));
+ AddStep("place third object", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("enter selection mode", () => InputManager.Key(Key.Number1));
+
+ AddStep("select objects 2 and 3", () =>
+ {
+ // add selection backwards to test non-sequential time ordering
+ EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[2]);
+ EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]);
+ });
+
+ AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
+
+ AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
+
+ AddAssert("object 3 snapped to 1", () =>
+ {
+ var objects = EditorBeatmap.HitObjects;
+
+ var first = (OsuHitObject)objects.First();
+ var third = (OsuHitObject)objects.Last();
+
+ return Precision.AlmostEquals(first.EndPosition, third.Position);
+ });
+
+ AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f)));
+
+ AddAssert("object 2 snapped to 1", () =>
+ {
+ var objects = EditorBeatmap.HitObjects;
+
+ var first = (OsuHitObject)objects.First();
+ var second = (OsuHitObject)objects.ElementAt(1);
+
+ return Precision.AlmostEquals(first.EndPosition, second.Position);
+ });
+
+ AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
index 1232369a0b..9af2a99470 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
@@ -174,6 +174,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private class SnapProvider : IPositionSnapProvider
{
+ public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
+ new SnapResult(screenSpacePosition, null);
+
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
index 7697f46160..d3cb3bcf59 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
@@ -5,7 +5,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
- public class OsuModTestScene : ModTestScene
+ public abstract class OsuModTestScene : ModTestScene
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
index 40f1c4a52f..1ac3ad9194 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
@@ -2,12 +2,15 @@
// 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.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
@@ -17,15 +20,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
{
- Mod = new OsuModHidden(),
+ Mod = new TestOsuModHidden(),
Autoplay = true,
- PassCondition = checkSomeHit
+ PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(0)
});
[Test]
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
{
- Mod = new OsuModHidden(),
+ Mod = new TestOsuModHidden(),
Autoplay = true,
Beatmap = new Beatmap
{
@@ -54,13 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
}
}
},
- PassCondition = checkSomeHit
+ PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2)
});
[Test]
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
{
- Mod = new OsuModHidden(),
+ Mod = new TestOsuModHidden(),
Autoplay = true,
Beatmap = new Beatmap
{
@@ -89,12 +92,41 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
}
}
},
+ PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2)
+ });
+
+ [Test]
+ public void TestWithSliderReuse() => CreateModTest(new ModTestData
+ {
+ Mod = new TestOsuModHidden(),
+ Autoplay = true,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Slider
+ {
+ StartTime = 1000,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
+ },
+ new Slider
+ {
+ StartTime = 4000,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
+ },
+ }
+ },
PassCondition = checkSomeHit
});
- private bool checkSomeHit()
+ private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4;
+
+ private bool objectWithIncreasedVisibilityHasIndex(int index)
+ => Player.Mods.Value.OfType().Single().FirstObject == Player.ChildrenOfType().Single().HitObjects[index];
+
+ private class TestOsuModHidden : OsuModHidden
{
- return Player.ScoreProcessor.JudgedHits >= 4;
+ public new HitObject FirstObject => base.FirstObject;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
index 7b909d2907..7df5ca0f7c 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
index dde02e873b..fefe983f97 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -12,7 +12,7 @@ using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing.Input;
using osu.Game.Audio;
-using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index 646f12f710..e4158d8f07 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -43,10 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests
showResult(HitResult.Great);
AddUntilStep("judgements shown", () => this.ChildrenOfType().Any());
- AddAssert("judgement body immediately visible",
- () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha == 1));
- AddAssert("hit lighting hidden",
- () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0));
+ AddAssert("hit lighting has no transforms", () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any()));
+ AddAssert("hit lighting hidden", () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0));
}
[Test]
@@ -57,10 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests
showResult(HitResult.Great);
AddUntilStep("judgements shown", () => this.ChildrenOfType().Any());
- AddAssert("judgement body not immediately visible",
- () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1));
- AddAssert("hit lighting shown",
- () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha > 0));
+ AddUntilStep("hit lighting shown", () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Alpha > 0));
}
private void showResult(HitResult result)
@@ -89,7 +84,13 @@ namespace osu.Game.Rulesets.Osu.Tests
Children = new Drawable[]
{
pool,
- pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j =>
+ pool.Get(j => j.Apply(new JudgementResult(new HitObject
+ {
+ StartTime = Time.Current
+ }, new Judgement())
+ {
+ Type = result,
+ }, null)).With(j =>
{
j.Anchor = Anchor.Centre;
j.Origin = Anchor.Centre;
@@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private class TestDrawableOsuJudgement : DrawableOsuJudgement
{
public new SkinnableSprite Lighting => base.Lighting;
- public new Container JudgementBody => base.JudgementBody;
+ public new SkinnableDrawable JudgementBody => base.JudgementBody;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
index 6c077eb214..fe67b63252 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
+using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -94,9 +95,19 @@ namespace osu.Game.Rulesets.Osu.Tests
{
addMultipleObjectsStep();
- AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100));
+ AddStep("move hitobject", () =>
+ {
+ var manualClock = new ManualClock();
+ followPointRenderer.Clock = new FramedClock(manualClock);
+
+ manualClock.CurrentTime = getObject(1).HitObject.StartTime;
+ followPointRenderer.UpdateSubTree();
+
+ getObject(2).HitObject.Position = new Vector2(300, 100);
+ });
assertGroups();
+ assertDirections();
}
[TestCase(0, 0)] // Start -> Start
@@ -207,7 +218,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void assertGroups()
{
- AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count);
+ AddAssert("has correct group count", () => followPointRenderer.Entries.Count == hitObjectContainer.Count);
AddAssert("group endpoints are correct", () =>
{
for (int i = 0; i < hitObjectContainer.Count; i++)
@@ -215,10 +226,10 @@ namespace osu.Game.Rulesets.Osu.Tests
DrawableOsuHitObject expectedStart = getObject(i);
DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
- if (getGroup(i).Start != expectedStart.HitObject)
+ if (getEntry(i).Start != expectedStart.HitObject)
throw new AssertionException($"Object {i} expected to be the start of group {i}.");
- if (getGroup(i).End != expectedEnd?.HitObject)
+ if (getEntry(i).End != expectedEnd?.HitObject)
throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
}
@@ -238,6 +249,12 @@ namespace osu.Game.Rulesets.Osu.Tests
if (expectedEnd == null)
continue;
+ var manualClock = new ManualClock();
+ followPointRenderer.Clock = new FramedClock(manualClock);
+
+ manualClock.CurrentTime = expectedStart.HitObject.StartTime;
+ followPointRenderer.UpdateSubTree();
+
var points = getGroup(i).ChildrenOfType().ToArray();
if (points.Length == 0)
continue;
@@ -255,7 +272,9 @@ namespace osu.Game.Rulesets.Osu.Tests
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
- private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
+ private FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index];
+
+ private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType().Single(c => c.Entry == getEntry(index));
private class TestHitObjectContainer : Container
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index 596bc06c68..1278a0ff2d 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -1,17 +1,17 @@
// 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.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osuTK;
-using osu.Game.Rulesets.Mods;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -38,13 +38,37 @@ namespace osu.Game.Rulesets.Osu.Tests
}
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
+ {
+ var drawable = createSingle(circleSize, auto, timeOffset, positionOffset);
+
+ var playfield = new TestOsuPlayfield();
+ playfield.Add(drawable);
+ return playfield;
+ }
+
+ private Drawable testStream(float circleSize, bool auto = false)
+ {
+ var playfield = new TestOsuPlayfield();
+
+ Vector2 pos = new Vector2(-250, 0);
+
+ for (int i = 0; i <= 1000; i += 100)
+ {
+ playfield.Add(createSingle(circleSize, auto, i, pos));
+ pos.X += 50;
+ }
+
+ return playfield;
+ }
+
+ private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset)
{
positionOffset ??= Vector2.Zero;
var circle = new HitCircle
{
StartTime = Time.Current + 1000 + timeOffset,
- Position = positionOffset.Value,
+ Position = OsuPlayfield.BASE_SIZE / 4 + positionOffset.Value,
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
@@ -53,31 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests
foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
-
return drawable;
}
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
{
- Anchor = Anchor.Centre,
Depth = depthIndex++
};
- private Drawable testStream(float circleSize, bool auto = false)
- {
- var container = new Container { RelativeSizeAxes = Axes.Both };
-
- Vector2 pos = new Vector2(-250, 0);
-
- for (int i = 0; i <= 1000; i += 100)
- {
- container.Add(testSingle(circleSize, auto, i, pos));
- pos.X += 50;
- }
-
- return container;
- }
-
protected class TestDrawableHitCircle : DrawableHitCircle
{
private readonly bool auto;
@@ -101,5 +108,13 @@ namespace osu.Game.Rulesets.Osu.Tests
base.CheckForResult(userTriggered, timeOffset);
}
}
+
+ protected class TestOsuPlayfield : OsuPlayfield
+ {
+ public TestOsuPlayfield()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index d692be89b2..7e973d0971 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -1,8 +1,11 @@
// 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.Diagnostics;
+using osu.Framework.Threading;
using osu.Framework.Utils;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -10,6 +13,19 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneShaking : TestSceneHitCircle
{
+ private readonly List scheduledTasks = new List();
+
+ protected override IBeatmap CreateBeatmapForSkinProvider()
+ {
+ // best way to run cleanup before a new step is run
+ foreach (var task in scheduledTasks)
+ task.Cancel();
+
+ scheduledTasks.Clear();
+
+ return base.CreateBeatmapForSkinProvider();
+ }
+
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
{
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
@@ -17,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
- Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
+ scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay));
return drawableHitObject;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index c400e2f2ea..d40484f5ed 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("change samples", () => slider.HitObject.Samples = new[]
{
- new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
- new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
+ new HitSampleInfo(HitSampleInfo.HIT_CLAP),
+ new HitSampleInfo(HitSampleInfo.HIT_WHISTLE),
});
AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle));
@@ -136,15 +136,15 @@ namespace osu.Game.Rulesets.Osu.Tests
slider = (DrawableSlider)createSlider(repeats: 1);
for (int i = 0; i < 2; i++)
- slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
+ slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo(HitSampleInfo.HIT_FINISH) });
Add(slider);
});
AddStep("change samples", () => slider.HitObject.Samples = new[]
{
- new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
- new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
+ new HitSampleInfo(HitSampleInfo.HIT_CLAP),
+ new HitSampleInfo(HitSampleInfo.HIT_WHISTLE),
});
AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle));
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
index 084af7dafe..aac6db60fe 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
@@ -12,7 +12,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
index b71400b71d..e111bb1054 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -19,7 +19,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Storyboards;
using osuTK;
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index b0799bd3f5..3ba14f2387 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index fff033357d..e8ac60bc5e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -12,5 +12,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double ApproachRate;
public double OverallDifficulty;
public int HitCircleCount;
+ public int SpinnerCount;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 6027635b75..6a7d76151c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
+ int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
return new OsuDifficultyAttributes
{
@@ -59,6 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo,
HitCircleCount = hitCirclesCount,
+ SpinnerCount = spinnerCount,
Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 063cde8747..030b0cf6d1 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
multiplier *= 0.90;
if (mods.Any(m => m is OsuModSpunOut))
- multiplier *= 0.95;
+ multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
double aimValue = computeAimValue();
double speedValue = computeSpeedValue();
@@ -99,16 +99,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (Attributes.MaxCombo > 0)
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
- double approachRateFactor = 1.0;
-
+ double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
- approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33);
+ approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33);
else if (Attributes.ApproachRate < 8.0)
- {
- approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate);
- }
+ approachRateFactor += 0.1 * (8.0 - Attributes.ApproachRate);
- aimValue *= approachRateFactor;
+ aimValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0));
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
@@ -137,8 +134,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more
- speedValue *= 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
- (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
+ double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
+ speedValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
speedValue *= Math.Pow(0.97, countMiss);
@@ -147,11 +145,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (Attributes.MaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
- double approachRateFactor = 1.0;
+ double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
- approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33);
+ approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33);
- speedValue *= approachRateFactor;
+ speedValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0));
if (mods.Any(m => m is OsuModHidden))
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
index 2868ddeaa4..0cfc67cedb 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
@@ -5,7 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
index e14d6647d2..c45a04053f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
@@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
return base.OnMouseDown(e);
}
- public override void UpdatePosition(SnapResult result)
+ public override void UpdateTimeAndPosition(SnapResult result)
{
- base.UpdatePosition(result);
+ base.UpdateTimeAndPosition(result);
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index c06904c0c2..e9838de63d 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private OsuColour colours { get; set; }
private IBindable sliderPosition;
+ private IBindable sliderScale;
private IBindable controlPointPosition;
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
@@ -69,13 +70,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(10),
+ Size = new Vector2(20),
},
markerRing = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(14),
+ Size = new Vector2(28),
Masking = true,
BorderThickness = 2,
BorderColour = Color4.White,
@@ -102,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
controlPointPosition = ControlPoint.Position.GetBoundCopy();
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
+ sliderScale = slider.ScaleBindable.GetBoundCopy();
+ sliderScale.BindValueChanged(_ => updateMarkerDisplay());
+
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
updateMarkerDisplay();
@@ -143,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
+ private Vector2 dragStartPosition;
+
protected override bool OnDragStart(DragStartEvent e)
{
if (RequestSelection == null)
@@ -150,6 +156,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (e.Button == MouseButton.Left)
{
+ dragStartPosition = ControlPoint.Position.Value;
changeHandler?.BeginChange();
return true;
}
@@ -174,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
}
else
- ControlPoint.Position.Value += e.Delta;
+ ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
}
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
@@ -194,6 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
colour = colour.Lighten(1);
marker.Colour = colour;
+ marker.Scale = new Vector2(slider.Scale);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 7375c0e981..ce5dc4855e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -20,12 +20,15 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
+using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu
{
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
+
internal readonly Container Pieces;
internal readonly Container Connections;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index 5581ce4bfd..1c3d270c95 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 4b99cc23ed..b71e1914f7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -67,9 +67,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
inputManager = GetContainingInputManager();
}
- public override void UpdatePosition(SnapResult result)
+ public override void UpdateTimeAndPosition(SnapResult result)
{
- base.UpdatePosition(result);
+ base.UpdateTimeAndPosition(result);
switch (state)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 7ae4f387ca..d592e129d9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
+ private readonly BindableList controlPoints = new BindableList();
+ private readonly IBindable pathVersion = new Bindable();
+
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
{
@@ -61,13 +64,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
};
}
- private IBindable pathVersion;
-
protected override void LoadComplete()
{
base.LoadComplete();
- pathVersion = HitObject.Path.Version.GetBoundCopy();
+ controlPoints.BindTo(HitObject.Path.ControlPoints);
+
+ pathVersion.BindTo(HitObject.Path.Version);
pathVersion.BindValueChanged(_ => updatePath());
BodyPiece.UpdateFrom(HitObject);
@@ -164,8 +167,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
}
- private BindableList controlPoints => HitObject.Path.ControlPoints;
-
private int addControlPoint(Vector2 position)
{
position -= HitObject.Position;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
index 2347d8a34c..92961b40bc 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs
deleted file mode 100644
index 776aacd143..0000000000
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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.Linq;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-
-namespace osu.Game.Rulesets.Osu.Edit
-{
- public class DrawableOsuEditPool : DrawableOsuPool
- where T : DrawableHitObject, new()
- {
- ///
- /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
- /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
- ///
- private const double editor_hit_object_fade_out_extension = 700;
-
- public DrawableOsuEditPool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null)
- : base(checkHittable, onLoaded, initialSize, maximumSize)
- {
- }
-
- protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState);
-
- private void updateState(DrawableHitObject hitObject, ArmedState state)
- {
- if (state == ArmedState.Idle)
- return;
-
- // adjust the visuals of certain object types to make them stay on screen for longer than usual.
- switch (hitObject)
- {
- default:
- // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
- return;
-
- case DrawableSlider _:
- // no specifics to sliders but let them fade slower below.
- break;
-
- case DrawableHitCircle circle: // also handles slider heads
- circle.ApproachCircle
- .FadeOutFromOne(editor_hit_object_fade_out_extension)
- .Expire();
- break;
- }
-
- // Get the existing fade out transform
- var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
-
- if (existing == null)
- return;
-
- hitObject.RemoveTransform(existing);
-
- using (hitObject.BeginAbsoluteSequence(existing.StartTime))
- hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index 547dff88b5..5fdb79cbbd 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -2,9 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using osu.Framework.Graphics.Pooling;
+using System.Linq;
+using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -26,8 +29,51 @@ namespace osu.Game.Rulesets.Osu.Edit
{
protected override GameplayCursorContainer CreateCursor() => null;
- protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null)
- => new DrawableOsuEditPool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
+ protected override void OnNewDrawableHitObject(DrawableHitObject d)
+ {
+ d.ApplyCustomUpdateState += updateState;
+ }
+
+ ///
+ /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
+ /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
+ ///
+ private const double editor_hit_object_fade_out_extension = 700;
+
+ private void updateState(DrawableHitObject hitObject, ArmedState state)
+ {
+ if (state == ArmedState.Idle)
+ return;
+
+ // adjust the visuals of certain object types to make them stay on screen for longer than usual.
+ switch (hitObject)
+ {
+ default:
+ // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
+ return;
+
+ case DrawableSlider _:
+ // no specifics to sliders but let them fade slower below.
+ break;
+
+ case DrawableHitCircle circle: // also handles slider heads
+ circle.ApproachCircle
+ .FadeOutFromOne(editor_hit_object_fade_out_extension)
+ .Expire();
+ break;
+ }
+
+ // Get the existing fade out transform
+ var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
+
+ if (existing == null)
+ return;
+
+ hitObject.RemoveTransform(existing);
+
+ using (hitObject.BeginAbsoluteSequence(existing.StartTime))
+ hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index bfa8ab4431..0490e8b8ce 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -105,11 +105,20 @@ namespace osu.Game.Rulesets.Osu.Edit
}
}
- public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
+ public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
{
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
return snapResult;
+ return new SnapResult(screenSpacePosition, null);
+ }
+
+ public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
+ {
+ var positionSnap = SnapScreenSpacePositionToValidPosition(screenSpacePosition);
+ if (positionSnap.ScreenSpacePosition != screenSpacePosition)
+ return positionSnap;
+
// will be null if distance snap is disabled or not feasible for the current time value.
if (distanceSnapGrid == null)
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
index f13c7d2ff6..06b5b6cfb8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModEasy : ModEasy
+ public class OsuModEasy : ModEasyWithExtraLives
{
public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!";
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 7c1dd46c02..45f314af7b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -2,9 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -23,25 +24,21 @@ namespace osu.Game.Rulesets.Osu.Mods
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
- protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner);
+ protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
- public override void ApplyToDrawableHitObjects(IEnumerable drawables)
+ public override void ApplyToBeatmap(IBeatmap beatmap)
{
- foreach (var d in drawables)
- d.HitObjectApplied += applyFadeInAdjustment;
+ base.ApplyToBeatmap(beatmap);
- base.ApplyToDrawableHitObjects(drawables);
- }
+ foreach (var obj in beatmap.HitObjects.OfType())
+ applyFadeInAdjustment(obj);
- private void applyFadeInAdjustment(DrawableHitObject hitObject)
- {
- if (!(hitObject is DrawableOsuHitObject d))
- return;
-
- d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier;
-
- foreach (var nested in d.NestedHitObjects)
- applyFadeInAdjustment(nested);
+ static void applyFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = osuObject.TimePreempt * fade_in_duration_multiplier;
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ applyFadeInAdjustment(nested);
+ }
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
@@ -56,37 +53,27 @@ namespace osu.Game.Rulesets.Osu.Mods
applyState(hitObject, false);
}
- private void applyState(DrawableHitObject drawable, bool increaseVisibility)
+ private void applyState(DrawableHitObject drawableObject, bool increaseVisibility)
{
- if (!(drawable is DrawableOsuHitObject d))
+ if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
return;
- var h = d.HitObject;
+ OsuHitObject hitObject = drawableOsuObject.HitObject;
- var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn;
- var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
+ (double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject);
- // new duration from completed fade in to end (before fading out)
- var longFadeDuration = h.GetEndTime() - fadeOutStartTime;
-
- switch (drawable)
+ switch (drawableObject)
{
- case DrawableSliderTail sliderTail:
- // use stored values from head circle to achieve same fade sequence.
- var tailFadeOutParameters = getFadeOutParametersFromSliderHead(h);
-
- using (drawable.BeginAbsoluteSequence(tailFadeOutParameters.startTime, true))
- sliderTail.FadeOut(tailFadeOutParameters.duration);
+ case DrawableSliderTail _:
+ using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
+ drawableObject.FadeOut(fadeOut.duration);
break;
case DrawableSliderRepeat sliderRepeat:
- // use stored values from head circle to achieve same fade sequence.
- var repeatFadeOutParameters = getFadeOutParametersFromSliderHead(h);
-
- using (drawable.BeginAbsoluteSequence(repeatFadeOutParameters.startTime, true))
+ using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
// only apply to circle piece – reverse arrow is not affected by hidden.
- sliderRepeat.CirclePiece.FadeOut(repeatFadeOutParameters.duration);
+ sliderRepeat.CirclePiece.FadeOut(fadeOut.duration);
break;
@@ -101,29 +88,23 @@ namespace osu.Game.Rulesets.Osu.Mods
else
{
// we don't want to see the approach circle
- using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true))
circle.ApproachCircle.Hide();
}
- // fade out immediately after fade in.
- using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
- fadeTarget.FadeOut(fadeOutDuration);
+ using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
+ fadeTarget.FadeOut(fadeOut.duration);
break;
case DrawableSlider slider:
- associateNestedSliderCirclesWithHead(slider.HitObject);
-
- using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
- slider.Body.FadeOut(longFadeDuration, Easing.Out);
+ using (slider.BeginAbsoluteSequence(fadeOut.startTime, true))
+ slider.Body.FadeOut(fadeOut.duration, Easing.Out);
break;
case DrawableSliderTick sliderTick:
- // slider ticks fade out over up to one second
- var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
-
- using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true))
- sliderTick.FadeOut(tickFadeOutDuration);
+ using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true))
+ sliderTick.FadeOut(fadeOut.duration);
break;
@@ -131,30 +112,55 @@ namespace osu.Game.Rulesets.Osu.Mods
// hide elements we don't care about.
// todo: hide background
- using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
- spinner.FadeOut(fadeOutDuration);
+ using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true))
+ spinner.FadeOut(fadeOut.duration);
break;
}
}
- private readonly Dictionary correspondingSliderHeadForObject = new Dictionary();
-
- private void associateNestedSliderCirclesWithHead(Slider slider)
+ private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject)
{
- var sliderHead = slider.NestedHitObjects.Single(obj => obj is SliderHeadCircle);
-
- foreach (var nested in slider.NestedHitObjects)
+ switch (drawableObject)
{
- if ((nested is SliderRepeat || nested is SliderEndCircle) && !correspondingSliderHeadForObject.ContainsKey(nested))
- correspondingSliderHeadForObject[nested] = (SliderHeadCircle)sliderHead;
- }
- }
+ case DrawableSliderTail tail:
+ // Use the same fade sequence as the slider head.
+ Debug.Assert(tail.Slider != null);
+ return getParameters(tail.Slider.HeadCircle);
- private (double startTime, double duration) getFadeOutParametersFromSliderHead(OsuHitObject h)
- {
- var sliderHead = correspondingSliderHeadForObject[h];
- return (sliderHead.StartTime - sliderHead.TimePreempt + sliderHead.TimeFadeIn, sliderHead.TimePreempt * fade_out_duration_multiplier);
+ case DrawableSliderRepeat repeat:
+ // Use the same fade sequence as the slider head.
+ Debug.Assert(repeat.Slider != null);
+ return getParameters(repeat.Slider.HeadCircle);
+
+ default:
+ return getParameters(drawableObject.HitObject);
+ }
+
+ static (double startTime, double duration) getParameters(OsuHitObject hitObject)
+ {
+ var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn;
+ var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier;
+
+ // new duration from completed fade in to end (before fading out)
+ var longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime;
+
+ switch (hitObject)
+ {
+ case Slider _:
+ return (fadeOutStartTime, longFadeDuration);
+
+ case SliderTick _:
+ var tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
+ return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration);
+
+ case Spinner _:
+ return (fadeOutStartTime + longFadeDuration, fadeOutDuration);
+
+ default:
+ return (fadeOutStartTime, fadeOutDuration);
+ }
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index b7e60295cb..df0a41455f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
namespace osu.Game.Rulesets.Osu.Mods
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index a981648444..b989500066 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -1,12 +1,14 @@
// 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.Bindables;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Game.Skinning;
@@ -15,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
///
/// A single follow point positioned between two adjacent s.
///
- public class FollowPoint : Container, IAnimationTimeReference
+ public class FollowPoint : PoolableDrawable, IAnimationTimeReference
{
private const float width = 8;
@@ -25,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
Origin = Anchor.Centre;
- Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer
+ InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer
{
Masking = true,
AutoSizeAxes = Axes.Both,
@@ -46,6 +48,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
});
}
- public double AnimationStartTime { get; set; }
+ public Bindable AnimationStartTime { get; } = new BindableDouble();
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index 3a9e19b361..6e7b1050cb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -2,11 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Diagnostics;
-using JetBrains.Annotations;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects;
using osuTK;
@@ -15,150 +12,106 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
///
/// Visualises the s between two s.
///
- public class FollowPointConnection : CompositeDrawable
+ public class FollowPointConnection : PoolableDrawable
{
// Todo: These shouldn't be constants
- private const int spacing = 32;
- private const double preempt = 800;
+ public const int SPACING = 32;
+ public const double PREEMPT = 800;
- public override bool RemoveWhenNotAlive => false;
+ public FollowPointLifetimeEntry Entry;
+ public DrawablePool Pool;
- ///
- /// The start time of .
- ///
- public readonly Bindable StartTime = new BindableDouble();
-
- ///
- /// The which s will exit from.
- ///
- [NotNull]
- public readonly OsuHitObject Start;
-
- ///
- /// Creates a new .
- ///
- /// The which s will exit from.
- public FollowPointConnection([NotNull] OsuHitObject start)
+ protected override void PrepareForUse()
{
- Start = start;
+ base.PrepareForUse();
- RelativeSizeAxes = Axes.Both;
+ Entry.Invalidated += onEntryInvalidated;
- StartTime.BindTo(start.StartTimeBindable);
+ refreshPoints();
}
- protected override void LoadComplete()
+ protected override void FreeAfterUse()
{
- base.LoadComplete();
- bindEvents(Start);
+ base.FreeAfterUse();
+
+ Entry.Invalidated -= onEntryInvalidated;
+
+ // Return points to the pool.
+ ClearInternal(false);
+
+ Entry = null;
}
- private OsuHitObject end;
+ private void onEntryInvalidated() => refreshPoints();
- ///
- /// The which s will enter.
- ///
- [CanBeNull]
- public OsuHitObject End
+ private void refreshPoints()
{
- get => end;
- set
- {
- end = value;
+ ClearInternal(false);
- if (end != null)
- bindEvents(end);
+ OsuHitObject start = Entry.Start;
+ OsuHitObject end = Entry.End;
- if (IsLoaded)
- scheduleRefresh();
- else
- refresh();
- }
- }
+ double startTime = start.GetEndTime();
- private void bindEvents(OsuHitObject obj)
- {
- obj.PositionBindable.BindValueChanged(_ => scheduleRefresh());
- obj.DefaultsApplied += _ => scheduleRefresh();
- }
-
- private void scheduleRefresh()
- {
- Scheduler.AddOnce(refresh);
- }
-
- private void refresh()
- {
- double startTime = Start.GetEndTime();
-
- LifetimeStart = startTime;
-
- if (End == null || End.NewCombo || Start is Spinner || End is Spinner)
- {
- // ensure we always set a lifetime for full LifetimeManagementContainer benefits
- LifetimeEnd = LifetimeStart;
- return;
- }
-
- Vector2 startPosition = Start.StackedEndPosition;
- Vector2 endPosition = End.StackedPosition;
- double endTime = End.StartTime;
+ Vector2 startPosition = start.StackedEndPosition;
+ Vector2 endPosition = end.StackedPosition;
Vector2 distanceVector = endPosition - startPosition;
int distance = (int)distanceVector.Length;
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
- double duration = endTime - startTime;
- double? firstTransformStartTime = null;
double finalTransformEndTime = startTime;
- int point = 0;
-
- ClearInternal();
-
- for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
+ for (int d = (int)(SPACING * 1.5); d < distance - SPACING; d += SPACING)
{
float fraction = (float)d / distance;
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
- double fadeOutTime = startTime + fraction * duration;
- double fadeInTime = fadeOutTime - preempt;
+
+ GetFadeTimes(start, end, (float)d / distance, out var fadeInTime, out var fadeOutTime);
FollowPoint fp;
- AddInternal(fp = new FollowPoint());
-
- Debug.Assert(End != null);
+ AddInternal(fp = Pool.Get());
+ fp.ClearTransforms();
fp.Position = pointStartPosition;
fp.Rotation = rotation;
fp.Alpha = 0;
- fp.Scale = new Vector2(1.5f * End.Scale);
+ fp.Scale = new Vector2(1.5f * end.Scale);
- firstTransformStartTime ??= fadeInTime;
-
- fp.AnimationStartTime = fadeInTime;
+ fp.AnimationStartTime.Value = fadeInTime;
using (fp.BeginAbsoluteSequence(fadeInTime))
{
- fp.FadeIn(End.TimeFadeIn);
- fp.ScaleTo(End.Scale, End.TimeFadeIn, Easing.Out);
- fp.MoveTo(pointEndPosition, End.TimeFadeIn, Easing.Out);
- fp.Delay(fadeOutTime - fadeInTime).FadeOut(End.TimeFadeIn);
+ fp.FadeIn(end.TimeFadeIn);
+ fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
+ fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
+ fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn);
- finalTransformEndTime = fadeOutTime + End.TimeFadeIn;
+ finalTransformEndTime = fadeOutTime + end.TimeFadeIn;
}
-
- point++;
}
- int excessPoints = InternalChildren.Count - point;
- for (int i = 0; i < excessPoints; i++)
- RemoveInternal(InternalChildren[^1]);
-
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
- LifetimeStart = firstTransformStartTime ?? startTime;
- LifetimeEnd = finalTransformEndTime;
+ Entry.LifetimeEnd = finalTransformEndTime;
+ }
+
+ ///
+ /// Computes the fade time of follow point positioned between two hitobjects.
+ ///
+ /// The first , where follow points should originate from.
+ /// The second , which follow points should target.
+ /// The fractional distance along and at which the follow point is to be located.
+ /// The fade-in time of the follow point/
+ /// The fade-out time of the follow point.
+ public static void GetFadeTimes(OsuHitObject start, OsuHitObject end, float fraction, out double fadeInTime, out double fadeOutTime)
+ {
+ double startTime = start.GetEndTime();
+ double duration = end.StartTime - startTime;
+
+ fadeOutTime = startTime + fraction * duration;
+ fadeInTime = fadeOutTime - PREEMPT;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs
new file mode 100644
index 0000000000..a167cb2f0f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs
@@ -0,0 +1,98 @@
+// 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 osu.Framework.Bindables;
+using osu.Framework.Graphics.Performance;
+using osu.Game.Rulesets.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
+{
+ public class FollowPointLifetimeEntry : LifetimeEntry
+ {
+ public event Action Invalidated;
+ public readonly OsuHitObject Start;
+
+ public FollowPointLifetimeEntry(OsuHitObject start)
+ {
+ Start = start;
+ LifetimeStart = Start.StartTime;
+
+ bindEvents();
+ }
+
+ private OsuHitObject end;
+
+ public OsuHitObject End
+ {
+ get => end;
+ set
+ {
+ UnbindEvents();
+
+ end = value;
+
+ bindEvents();
+
+ refreshLifetimes();
+ }
+ }
+
+ private void bindEvents()
+ {
+ UnbindEvents();
+
+ // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects.
+ Start.DefaultsApplied += onDefaultsApplied;
+ Start.PositionBindable.ValueChanged += onPositionChanged;
+
+ if (End != null)
+ {
+ End.DefaultsApplied += onDefaultsApplied;
+ End.PositionBindable.ValueChanged += onPositionChanged;
+ }
+ }
+
+ public void UnbindEvents()
+ {
+ if (Start != null)
+ {
+ Start.DefaultsApplied -= onDefaultsApplied;
+ Start.PositionBindable.ValueChanged -= onPositionChanged;
+ }
+
+ if (End != null)
+ {
+ End.DefaultsApplied -= onDefaultsApplied;
+ End.PositionBindable.ValueChanged -= onPositionChanged;
+ }
+ }
+
+ private void onDefaultsApplied(HitObject obj) => refreshLifetimes();
+
+ private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes();
+
+ private void refreshLifetimes()
+ {
+ if (End == null || End.NewCombo || Start is Spinner || End is Spinner)
+ {
+ LifetimeEnd = LifetimeStart;
+ return;
+ }
+
+ Vector2 startPosition = Start.StackedEndPosition;
+ Vector2 endPosition = End.StackedPosition;
+ Vector2 distanceVector = endPosition - startPosition;
+
+ // The lifetime start will match the fade-in time of the first follow point.
+ float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length;
+ FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _);
+
+ LifetimeStart = fadeInTime;
+ LifetimeEnd = double.MaxValue; // This will be set by the connection.
+
+ Invalidated?.Invoke();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index be1392d7c3..3e85e528e8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -2,53 +2,74 @@
// 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.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Performance;
+using osu.Framework.Graphics.Pooling;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
///
/// Visualises connections between s.
///
- public class FollowPointRenderer : LifetimeManagementContainer
+ public class FollowPointRenderer : CompositeDrawable
{
- ///
- /// All the s contained by this .
- ///
- internal IReadOnlyList Connections => connections;
-
- private readonly List connections = new List();
-
public override bool RemoveCompletedTransforms => false;
- ///
- /// Adds the s around an .
- /// This includes s leading into , and s exiting .
- ///
- /// The to add s for.
- public void AddFollowPoints(OsuHitObject hitObject)
- => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g))));
+ public IReadOnlyList Entries => lifetimeEntries;
- ///
- /// Removes the s around an .
- /// This includes s leading into , and s exiting .
- ///
- /// The to remove s for.
- public void RemoveFollowPoints(OsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject));
+ private DrawablePool connectionPool;
+ private DrawablePool pointPool;
- ///
- /// Adds a to this .
- ///
- /// The to add.
- /// The index of in .
- private void addConnection(FollowPointConnection connection)
+ private readonly List lifetimeEntries = new List();
+ private readonly Dictionary connectionsInUse = new Dictionary();
+ private readonly Dictionary startTimeMap = new Dictionary();
+ private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
+
+ public FollowPointRenderer()
{
- // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
- int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) =>
+ lifetimeManager.EntryBecameAlive += onEntryBecameAlive;
+ lifetimeManager.EntryBecameDead += onEntryBecameDead;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
{
- int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value);
+ connectionPool = new DrawablePoolNoLifetime(1, 200),
+ pointPool = new DrawablePoolNoLifetime(50, 1000)
+ };
+ }
+
+ public void AddFollowPoints(OsuHitObject hitObject)
+ {
+ addEntry(hitObject);
+
+ var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy();
+ startTimeBindable.ValueChanged += _ => onStartTimeChanged(hitObject);
+ startTimeMap[hitObject] = startTimeBindable;
+ }
+
+ public void RemoveFollowPoints(OsuHitObject hitObject)
+ {
+ removeEntry(hitObject);
+
+ startTimeMap[hitObject].UnbindAll();
+ startTimeMap.Remove(hitObject);
+ }
+
+ private void addEntry(OsuHitObject hitObject)
+ {
+ var newEntry = new FollowPointLifetimeEntry(hitObject);
+
+ var index = lifetimeEntries.AddInPlace(newEntry, Comparer.Create((e1, e2) =>
+ {
+ int comp = e1.Start.StartTime.CompareTo(e2.Start.StartTime);
if (comp != 0)
return comp;
@@ -61,19 +82,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
return -1;
}));
- if (index < connections.Count - 1)
+ if (index < lifetimeEntries.Count - 1)
{
// Update the connection's end point to the next connection's start point
// h1 -> -> -> h2
// connection nextGroup
- FollowPointConnection nextConnection = connections[index + 1];
- connection.End = nextConnection.Start;
+ FollowPointLifetimeEntry nextEntry = lifetimeEntries[index + 1];
+ newEntry.End = nextEntry.Start;
}
else
{
// The end point may be non-null during re-ordering
- connection.End = null;
+ newEntry.End = null;
}
if (index > 0)
@@ -82,23 +103,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
// h1 -> -> -> h2
// prevGroup connection
- FollowPointConnection previousConnection = connections[index - 1];
- previousConnection.End = connection.Start;
+ FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1];
+ previousEntry.End = newEntry.Start;
}
- AddInternal(connection);
+ lifetimeManager.AddEntry(newEntry);
}
- ///
- /// Removes a from this .
- ///
- /// The to remove.
- /// Whether was removed.
- private void removeGroup(FollowPointConnection connection)
+ private void removeEntry(OsuHitObject hitObject)
{
- RemoveInternal(connection);
+ int index = lifetimeEntries.FindIndex(e => e.Start == hitObject);
- int index = connections.IndexOf(connection);
+ var entry = lifetimeEntries[index];
+ entry.UnbindEvents();
+
+ lifetimeEntries.RemoveAt(index);
+ lifetimeManager.RemoveEntry(entry);
if (index > 0)
{
@@ -106,18 +126,61 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
// h1 -> -> -> h2 -> -> -> h3
// prevGroup connection nextGroup
// The current connection's end point is used since there may not be a next connection
- FollowPointConnection previousConnection = connections[index - 1];
- previousConnection.End = connection.End;
+ FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1];
+ previousEntry.End = entry.End;
}
-
- connections.Remove(connection);
}
- private void onStartTimeChanged(FollowPointConnection connection)
+ protected override bool CheckChildrenLife()
{
- // Naive but can be improved if performance becomes an issue
- removeGroup(connection);
- addConnection(connection);
+ bool anyAliveChanged = base.CheckChildrenLife();
+ anyAliveChanged |= lifetimeManager.Update(Time.Current);
+ return anyAliveChanged;
+ }
+
+ private void onEntryBecameAlive(LifetimeEntry entry)
+ {
+ var connection = connectionPool.Get(c =>
+ {
+ c.Entry = (FollowPointLifetimeEntry)entry;
+ c.Pool = pointPool;
+ });
+
+ connectionsInUse[entry] = connection;
+
+ AddInternal(connection);
+ }
+
+ private void onEntryBecameDead(LifetimeEntry entry)
+ {
+ RemoveInternal(connectionsInUse[entry]);
+ connectionsInUse.Remove(entry);
+ }
+
+ private void onStartTimeChanged(OsuHitObject hitObject)
+ {
+ removeEntry(hitObject);
+ addEntry(hitObject);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ foreach (var entry in lifetimeEntries)
+ entry.UnbindEvents();
+ lifetimeEntries.Clear();
+ }
+
+ private class DrawablePoolNoLifetime : DrawablePool
+ where T : PoolableDrawable, new()
+ {
+ public override bool RemoveWhenNotAlive => false;
+
+ public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null)
+ : base(initialSize, maximumSize)
+ {
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index d1ceca6d8f..3c0260f5f5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -12,14 +12,14 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
+ public class DrawableHitCircle : DrawableOsuHitObject
{
public OsuAction? HitAction => HitArea.HitAction;
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index a26db06ede..628d95dff4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -9,9 +9,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Graphics.Containers;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.UI;
-using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -53,26 +51,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
});
}
- protected override void OnApply(HitObject hitObject)
+ protected override void OnApply()
{
- base.OnApply(hitObject);
+ base.OnApply();
IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable);
PositionBindable.BindTo(HitObject.PositionBindable);
StackHeightBindable.BindTo(HitObject.StackHeightBindable);
ScaleBindable.BindTo(HitObject.ScaleBindable);
-
- // Manually set to reduce the number of future alive objects to a bare minimum.
- LifetimeStart = HitObject.StartTime - HitObject.TimePreempt;
-
- // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts.
- // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry.
- LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000;
}
- protected override void OnFree(HitObject hitObject)
+ protected override void OnFree()
{
- base.OnFree(hitObject);
+ base.OnFree();
IndexInCurrentComboBindable.UnbindFrom(HitObject.IndexInCurrentComboBindable);
PositionBindable.UnbindFrom(HitObject.PositionBindable);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
index 47fb53379f..13f5960bd4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
@@ -44,26 +44,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
- private double fadeOutDelay;
- protected override double FadeOutDelay => fadeOutDelay;
-
protected override void ApplyHitAnimations()
{
bool hitLightingEnabled = config.Get(OsuSetting.HitLighting);
- if (hitLightingEnabled)
- {
- JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400);
+ Lighting.Alpha = 0;
+ if (hitLightingEnabled && Lighting.Drawable != null)
+ {
+ // todo: this animation changes slightly based on new/old legacy skin versions.
Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
- }
- else
- {
- JudgementBody.Alpha = 1;
- }
- fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay;
+ // extend the lifetime to cover lighting fade
+ LifetimeEnd = Lighting.LatestTransformEndTime;
+ }
base.ApplyHitAnimations();
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs
deleted file mode 100644
index 1b5fd50022..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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 osu.Framework.Graphics;
-using osu.Framework.Graphics.Pooling;
-using osu.Game.Rulesets.Objects.Drawables;
-
-namespace osu.Game.Rulesets.Osu.Objects.Drawables
-{
- public class DrawableOsuPool : DrawablePool
- where T : DrawableHitObject, new()
- {
- private readonly Func checkHittable;
- private readonly Action onLoaded;
-
- public DrawableOsuPool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null)
- : base(initialSize, maximumSize)
- {
- this.checkHittable = checkHittable;
- this.onLoaded = onLoaded;
- }
-
- protected override T CreateNewDrawable() => base.CreateNewDrawable().With(o =>
- {
- var osuObject = (DrawableOsuHitObject)(object)o;
-
- osuObject.CheckHittable = checkHittable;
- osuObject.OnLoadComplete += onLoaded;
- });
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 14c494d909..511cbc2347 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -7,19 +7,20 @@ using JetBrains.Annotations;
using osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
+using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Osu.UI;
using osuTK.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
+ public class DrawableSlider : DrawableOsuHitObject
{
public new Slider HitObject => (Slider)base.HitObject;
@@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Container tailContainer;
private Container tickContainer;
private Container repeatContainer;
- private Container samplesContainer;
+ private PausableSkinnableSound slidingSample;
public DrawableSlider()
: this(null)
@@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Alpha = 0
},
headContainer = new Container { RelativeSizeAxes = Axes.Both },
- samplesContainer = new Container { RelativeSizeAxes = Axes.Both }
+ slidingSample = new PausableSkinnableSound { Looping = true }
};
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
@@ -86,42 +87,35 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking.BindValueChanged(updateSlidingSample);
}
- protected override void OnApply(HitObject hitObject)
+ protected override void OnApply()
{
- base.OnApply(hitObject);
+ base.OnApply();
// Ensure that the version will change after the upcoming BindTo().
pathVersion.Value = int.MaxValue;
PathVersion.BindTo(HitObject.Path.Version);
}
- protected override void OnFree(HitObject hitObject)
+ protected override void OnFree()
{
- base.OnFree(hitObject);
+ base.OnFree();
PathVersion.UnbindFrom(HitObject.Path.Version);
- }
- private PausableSkinnableSound slidingSample;
+ slidingSample.Samples = null;
+ }
protected override void LoadSamples()
{
base.LoadSamples();
- samplesContainer.Clear();
- slidingSample = null;
-
var firstSample = HitObject.Samples.FirstOrDefault();
if (firstSample != null)
{
- var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
- clone.Name = "sliderslide";
+ var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
- samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone)
- {
- Looping = true
- });
+ slidingSample.Samples = new ISampleInfo[] { clone };
}
}
@@ -255,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (userTriggered || Time.Current < HitObject.EndTime)
return;
- ApplyResult(r => r.Type = r.Judgement.MaxResult);
+ ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
public override void PlaySamples()
@@ -294,14 +288,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
case ArmedState.Hit:
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
+ if (sliderBody?.SnakingOut.Value == true)
+ Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
break;
}
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
}
- public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
-
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
private class DefaultSliderBody : PlaySliderBody
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index fd0f35d20d..acc95ab036 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -2,24 +2,25 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSliderHead : DrawableHitCircle
{
+ [CanBeNull]
+ public Slider Slider => DrawableSlider?.HitObject;
+
+ protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
+
private readonly IBindable pathVersion = new Bindable();
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
- private DrawableSlider drawableSlider;
-
- private Slider slider => drawableSlider?.HitObject;
-
public DrawableSliderHead()
{
}
@@ -36,34 +37,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
pathVersion.BindValueChanged(_ => updatePosition());
}
- protected override void OnFree(HitObject hitObject)
+ protected override void OnFree()
{
- base.OnFree(hitObject);
+ base.OnFree();
- pathVersion.UnbindFrom(drawableSlider.PathVersion);
+ pathVersion.UnbindFrom(DrawableSlider.PathVersion);
}
- protected override void OnParentReceived(DrawableHitObject parent)
+ protected override void OnApply()
{
- base.OnParentReceived(parent);
+ base.OnApply();
- drawableSlider = (DrawableSlider)parent;
+ pathVersion.BindTo(DrawableSlider.PathVersion);
- pathVersion.BindTo(drawableSlider.PathVersion);
-
- OnShake = drawableSlider.Shake;
- CheckHittable = (d, t) => drawableSlider.CheckHittable?.Invoke(d, t) ?? true;
+ OnShake = DrawableSlider.Shake;
+ CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
}
protected override void Update()
{
base.Update();
- double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
+ Debug.Assert(Slider != null);
+
+ double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1);
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!IsHit)
- Position = slider.CurvePositionAt(completionProgress);
+ Position = Slider.CurvePositionAt(completionProgress);
}
public Action OnShake;
@@ -72,8 +73,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void updatePosition()
{
- if (slider != null)
- Position = HitObject.Position - slider.Position;
+ if (Slider != null)
+ Position = HitObject.Position - Slider.Position;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index 0735d48ae1..76490e0de1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -3,12 +3,13 @@
using System;
using System.Collections.Generic;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
@@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
+ [CanBeNull]
+ public Slider Slider => DrawableSlider?.HitObject;
+
+ protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
+
private double animDuration;
public Drawable CirclePiece { get; private set; }
@@ -26,8 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool DisplayResult => false;
- private DrawableSlider drawableSlider;
-
public DrawableSliderRepeat()
: base(null)
{
@@ -60,19 +64,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
}
- protected override void OnParentReceived(DrawableHitObject parent)
+ protected override void OnApply()
{
- base.OnParentReceived(parent);
+ base.OnApply();
- drawableSlider = (DrawableSlider)parent;
-
- Position = HitObject.Position - drawableSlider.Position;
+ Position = HitObject.Position - DrawableSlider.Position;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (HitObject.StartTime <= Time.Current)
- ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
+ ApplyResult(r => r.Type = DrawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
protected override void UpdateInitialTransforms()
@@ -114,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (IsHit) return;
bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0;
- List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
+ List curve = ((PlaySliderBody)DrawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index eff72168ee..6a8e02e886 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -15,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
+ [CanBeNull]
+ public Slider Slider => DrawableSlider?.HitObject;
+
+ protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
+
///
/// The judgement text is provided by the .
///
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index faccf5d4d1..c7bfdb02fb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool DisplayResult => false;
+ protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
+
private SkinnableDrawable scaleContainer;
public DrawableSliderTick()
@@ -62,11 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
}
- protected override void OnParentReceived(DrawableHitObject parent)
+ protected override void OnApply()
{
- base.OnParentReceived(parent);
+ base.OnApply();
- Position = HitObject.Position - ((DrawableSlider)parent).HitObject.Position;
+ Position = HitObject.Position - DrawableSlider.HitObject.Position;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 2a14a7c975..1f3bcece0c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -9,13 +9,14 @@ using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
@@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Container ticks;
private SpinnerBonusDisplay bonusDisplay;
- private Container samplesContainer;
+ private PausableSkinnableSound spinningSample;
private Bindable isSpinning;
private bool spinnerFrequencyModulate;
@@ -81,7 +82,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre,
Y = -120,
},
- samplesContainer = new Container { RelativeSizeAxes = Axes.Both }
+ spinningSample = new PausableSkinnableSound
+ {
+ Volume = { Value = 0 },
+ Looping = true,
+ Frequency = { Value = spinning_sample_initial_frequency }
+ }
};
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
@@ -95,30 +101,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
isSpinning.BindValueChanged(updateSpinningSample);
}
- private PausableSkinnableSound spinningSample;
private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f;
+ protected override void OnFree()
+ {
+ base.OnFree();
+
+ spinningSample.Samples = null;
+ }
+
protected override void LoadSamples()
{
base.LoadSamples();
- samplesContainer.Clear();
- spinningSample = null;
-
var firstSample = HitObject.Samples.FirstOrDefault();
if (firstSample != null)
{
- var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
- clone.Name = "spinnerspin";
+ var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin");
- samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone)
- {
- Volume = { Value = 0 },
- Looping = true,
- Frequency = { Value = spinning_sample_initial_frequency }
- });
+ spinningSample.Samples = new ISampleInfo[] { clone };
+ spinningSample.Frequency.Value = spinning_sample_initial_frequency;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
index fc9a7c00e6..726fbd3ea6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
@@ -7,6 +7,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public override bool DisplayResult => false;
+ protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
+
public DrawableSpinnerTick()
: base(null)
{
@@ -17,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
}
+ protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
+
///