diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs new file mode 100644 index 0000000000..7cb984b254 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs @@ -0,0 +1,44 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class DrawableTestStrongHit : DrawableHit + { + private readonly HitResult type; + private readonly bool hitBoth; + + public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true) + : base(new Hit + { + IsStrong = true, + StartTime = startTime, + }) + { + // in order to create nested strong hit + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + this.type = type; + this.hitBoth = hitBoth; + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + Result.Type = type; + + var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single(); + nestedStrongHit.Result.Type = hitBoth ? type : HitResult.Miss; + } + + public override bool OnPressed(TaikoAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 2b5efec7f9..48969e0f5a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Scoring; @@ -15,24 +14,29 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [TestFixture] public class TestSceneHitExplosion : TaikoSkinnableTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestNormalHit() { - AddStep("Great", () => SetContents(() => getContentFor(HitResult.Great))); - AddStep("Good", () => SetContents(() => getContentFor(HitResult.Good))); - AddStep("Miss", () => SetContents(() => getContentFor(HitResult.Miss))); + AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great)))); + AddStep("Good", () => SetContents(() => getContentFor(createHit(HitResult.Good)))); + AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); } - private Drawable getContentFor(HitResult type) + [Test] + public void TestStrongHit([Values(false, true)] bool hitBoth) { - DrawableTaikoHitObject hit; + AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); + AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); + } + private Drawable getContentFor(DrawableTaikoHitObject hit) + { return new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - hit = createHit(type), + hit, new HitExplosion(hit) { Anchor = Anchor.Centre, @@ -43,5 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning } private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); + + private DrawableTaikoHitObject createStrongHit(HitResult type, bool hitBoth) + => new DrawableTestStrongHit(Time.Current, type, hitBoth); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 44452d70c1..99d1b72ea4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -174,7 +174,9 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addMissJudgement() { - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); + DrawableTestHit h; + Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index f032c5f485..c222ccb51f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -75,7 +75,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionGoodStrong: case TaikoSkinComponents.TaikoExplosionGreat: + case TaikoSkinComponents.TaikoExplosionGreatStrong: case TaikoSkinComponents.TaikoExplosionMiss: var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); @@ -107,8 +109,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionGood: return "taiko-hit100"; + case TaikoSkinComponents.TaikoExplosionGoodStrong: + return "taiko-hit100k"; + case TaikoSkinComponents.TaikoExplosionGreat: return "taiko-hit300"; + + case TaikoSkinComponents.TaikoExplosionGreatStrong: + return "taiko-hit300k"; } throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index ac4fb51661..0d785adb4a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Taiko BarLine, TaikoExplosionMiss, TaikoExplosionGood, + TaikoExplosionGoodStrong, TaikoExplosionGreat, + TaikoExplosionGreatStrong, Scroller, Mascot, } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index f0585b9c50..e3eabbf88f 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.UI @@ -45,24 +47,41 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject.Result?.Type ?? HitResult.Great)), _ => new DefaultHitExplosion()); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion()); } - private TaikoSkinComponents getComponentName(HitResult resultType) + private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) { + var resultType = judgedObject.Result?.Type ?? HitResult.Great; + switch (resultType) { case HitResult.Miss: return TaikoSkinComponents.TaikoExplosionMiss; case HitResult.Good: - return TaikoSkinComponents.TaikoExplosionGood; + return useStrongExplosion(judgedObject) + ? TaikoSkinComponents.TaikoExplosionGoodStrong + : TaikoSkinComponents.TaikoExplosionGood; case HitResult.Great: - return TaikoSkinComponents.TaikoExplosionGreat; + return useStrongExplosion(judgedObject) + ? TaikoSkinComponents.TaikoExplosionGreatStrong + : TaikoSkinComponents.TaikoExplosionGreat; } - throw new ArgumentOutOfRangeException(nameof(resultType), "Invalid result type"); + throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type"); + } + + private bool useStrongExplosion(DrawableHitObject judgedObject) + { + if (!(judgedObject.HitObject is Hit)) + return false; + + if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit)) + return false; + + return judgedObject.Result.Type == nestedHit.Result.Type; } /// diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index dabdfe6f44..7976d5bc6d 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -205,9 +205,6 @@ namespace osu.Game.Rulesets.Taiko.UI X = result.IsHit ? judgedObject.Position.X : 0, }); - if (!result.IsHit) - break; - var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; addExplosion(judgedObject, type); @@ -218,12 +215,16 @@ namespace osu.Game.Rulesets.Taiko.UI private void addDrumRollHit(DrawableDrumRollTick drawableTick) => drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick)); - private void addExplosion(DrawableHitObject drawableObject, HitType type) + /// + /// As legacy skins have different explosions for singular and double strong hits, + /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame. + /// + private void addExplosion(DrawableHitObject drawableObject, HitType type) => Schedule(() => { hitExplosionContainer.Add(new HitExplosion(drawableObject)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); - } + }); private class ProxyContainer : LifetimeManagementContainer {