diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index cc361628a3..97acdeb3e6 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -7,8 +7,10 @@ using osu.Game.Beatmaps.Samples; using osu.Game.Modes.Objects; using osu.Game.Modes.Objects.Types; using osu.Game.Modes.Taiko.Objects; +using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Database; namespace osu.Game.Modes.Taiko.Beatmaps { @@ -24,74 +26,100 @@ namespace osu.Game.Modes.Taiko.Beatmaps return new Beatmap(original) { - HitObjects = convertHitObjects(original.HitObjects) + HitObjects = original.HitObjects.SelectMany(h => convertHitObject(h, original)).ToList() }; } - private List convertHitObjects(List hitObjects) - { - return hitObjects.Select(convertHitObject).ToList(); - } - - private TaikoHitObject convertHitObject(HitObject original) + private IEnumerable convertHitObject(HitObject obj, Beatmap beatmap) { // Check if this HitObject is already a TaikoHitObject, and return it if so - TaikoHitObject originalTaiko = original as TaikoHitObject; + var originalTaiko = obj as TaikoHitObject; if (originalTaiko != null) - return originalTaiko; + yield return originalTaiko; - IHasDistance distanceData = original as IHasDistance; - IHasRepeats repeatsData = original as IHasRepeats; - IHasEndTime endTimeData = original as IHasEndTime; + var distanceData = obj as IHasDistance; + var repeatsData = obj as IHasRepeats; + var endTimeData = obj as IHasEndTime; // Old osu! used hit sounding to determine various hit type information - SampleType sample = original.Sample?.Type ?? SampleType.None; + SampleType sample = obj.Sample?.Type ?? SampleType.None; bool strong = (sample & SampleType.Finish) > 0; if (distanceData != null) { - return new DrumRoll + double sv = beatmap.TimingInfo.SliderVelocityAt(obj.StartTime) * beatmap.BeatmapInfo.Difficulty.SliderMultiplier; + + double l = distanceData.Distance * legacy_velocity_scale; + double v = sv * legacy_velocity_scale; + double bl = beatmap.TimingInfo.BeatLengthAt(obj.StartTime); + + int repeats = repeatsData?.RepeatCount ?? 1; + + double skipPeriod = Math.Min(bl / beatmap.BeatmapInfo.Difficulty.SliderTickRate, distanceData.Duration / repeats); + + if (skipPeriod > 0 && l / v * 1000 < 2 * bl) { - StartTime = original.StartTime, - Sample = original.Sample, + for (double j = obj.StartTime; j <= distanceData.EndTime + skipPeriod / 8; j += skipPeriod) + { + // Todo: This should generate different type of hits (including strongs) + // depending on hitobject sound additions (not implemented fully yet) + yield return new CentreHit + { + StartTime = obj.StartTime, + Sample = obj.Sample, + IsStrong = strong + }; + } + } + else + { + yield return new DrumRoll + { + StartTime = obj.StartTime, + Sample = obj.Sample, + IsStrong = strong, + Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) * legacy_velocity_scale + }; + } + } + else if (endTimeData != null) + { + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * bash_convert_factor; + + yield return new Swell + { + StartTime = obj.StartTime, + Sample = obj.Sample, IsStrong = strong, - Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) + EndTime = endTimeData.EndTime, + RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) }; } - - if (endTimeData != null) + else { - // We compute the end time manually to add in the Bash convert factor - return new Swell + bool isCentre = (sample & ~(SampleType.Finish | SampleType.Normal)) == 0; + + if (isCentre) { - StartTime = original.StartTime, - Sample = original.Sample, - IsStrong = strong, - - EndTime = original.StartTime + endTimeData.Duration * bash_convert_factor - }; - } - - bool isCentre = (sample & ~(SampleType.Finish | SampleType.Normal)) == 0; - - if (isCentre) - { - return new CentreHit + yield return new CentreHit + { + StartTime = obj.StartTime, + Sample = obj.Sample, + IsStrong = strong + }; + } + else { - StartTime = original.StartTime, - Sample = original.Sample, - IsStrong = strong - }; + yield return new RimHit + { + StartTime = obj.StartTime, + Sample = obj.Sample, + IsStrong = strong, + }; + } } - - return new RimHit - { - StartTime = original.StartTime, - Sample = original.Sample, - IsStrong = strong, - }; } } } diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs index f4745730db..c2c4093b1b 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs @@ -43,7 +43,7 @@ namespace osu.Game.Modes.Taiko.Judgements public override string MaxResultString => MAX_HIT_RESULT.GetDescription(); /// - /// Whether this Judgement has a secondary hit in the case of finishers. + /// Whether this Judgement has a secondary hit in the case of strong hits. /// public virtual bool SecondHit { get; set; } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableCentreHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableCentreHit.cs index 683d48df10..76eb6bb77d 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableCentreHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableCentreHit.cs @@ -12,24 +12,20 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { protected override Key[] HitKeys { get; } = { Key.F, Key.J }; - private readonly CirclePiece circlePiece; - public DrawableCentreHit(Hit hit) : base(hit) { - Add(circlePiece = new CirclePiece - { - Children = new[] - { - new CentreHitSymbolPiece() - } - }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - circlePiece.AccentColour = colours.PinkDarker; + Circle.AccentColour = colours.PinkDarker; } + + protected override CirclePiece CreateCirclePiece() => new CirclePiece + { + Children = new[] { new CentreHitSymbolPiece() } + }; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs index 4697625c5e..ea6b0043d7 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs @@ -41,6 +41,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable Width = (float)(drumRoll.Duration / drumRoll.PreEmpt); Add(circle = CreateCirclePiece()); + circle.KiaiMode = HitObject.Kiai; foreach (var tick in drumRoll.Ticks) { diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs index c8a7355e3c..87f0e76f60 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; using System; using System.Linq; @@ -20,15 +21,17 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override Container Content => bodyContainer; + protected readonly CirclePiece Circle; + private readonly Hit hit; + private readonly Container bodyContainer; + /// /// Whether the last key pressed is a valid hit key. /// private bool validKeyPressed; - private readonly Container bodyContainer; - protected DrawableHit(Hit hit) : base(hit) { @@ -38,7 +41,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Children = new[] + { + Circle = CreateCirclePiece() + } }); + + Circle.KiaiMode = HitObject.Kiai; } protected override void CheckJudgement(bool userTriggered) @@ -100,5 +109,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable Expire(); } + + protected abstract CirclePiece CreateCirclePiece(); } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs index cab6819300..893a6fbb4d 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs @@ -12,24 +12,20 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { protected override Key[] HitKeys { get; } = { Key.D, Key.K }; - private readonly CirclePiece circlePiece; - public DrawableRimHit(Hit hit) : base(hit) { - Add(circlePiece = new CirclePiece - { - Children = new[] - { - new RimHitSymbolPiece() - } - }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - circlePiece.AccentColour = colours.BlueDarker; + Circle.AccentColour = colours.BlueDarker; } + + protected override CirclePiece CreateCirclePiece() => new CirclePiece + { + Children = new[] { new RimHitSymbolPiece() } + }; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs index b4ec0b108a..bac44f48fb 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs @@ -12,24 +12,20 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { protected override Key[] HitKeys { get; } = { Key.F, Key.J }; - private readonly CirclePiece circlePiece; - public DrawableStrongCentreHit(Hit hit) : base(hit) { - Add(circlePiece = new StrongCirclePiece - { - Children = new [] - { - new CentreHitSymbolPiece() - } - }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - circlePiece.AccentColour = colours.PinkDarker; + Circle.AccentColour = colours.PinkDarker; } + + protected override CirclePiece CreateCirclePiece() => new StrongCirclePiece + { + Children = new[] { new CentreHitSymbolPiece() } + }; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs index 1f77ad0409..985ba1c2df 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs @@ -12,24 +12,20 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { protected override Key[] HitKeys { get; } = { Key.D, Key.K }; - private readonly CirclePiece circlePiece; - public DrawableStrongRimHit(Hit hit) : base(hit) { - Add(circlePiece = new StrongCirclePiece - { - Children = new[] - { - new RimHitSymbolPiece() - } - }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - circlePiece.AccentColour = colours.BlueDarker; + Circle.AccentColour = colours.BlueDarker; } + + protected override CirclePiece CreateCirclePiece() => new StrongCirclePiece + { + Children = new[] { new RimHitSymbolPiece() } + }; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs index ccd6380542..ec1ab42bfc 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs @@ -128,6 +128,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable } } }; + + circlePiece.KiaiMode = HitObject.Kiai; } [BackgroundDependencyLoader] diff --git a/osu.Game.Modes.Taiko/Objects/Swell.cs b/osu.Game.Modes.Taiko/Objects/Swell.cs index 4cbb5904c7..f55416509a 100644 --- a/osu.Game.Modes.Taiko/Objects/Swell.cs +++ b/osu.Game.Modes.Taiko/Objects/Swell.cs @@ -1,9 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using osu.Game.Beatmaps.Timing; -using osu.Game.Database; using osu.Game.Modes.Objects.Types; namespace osu.Game.Modes.Taiko.Objects @@ -17,14 +14,6 @@ namespace osu.Game.Modes.Taiko.Objects /// /// The number of hits required to complete the swell successfully. /// - public int RequiredHits { get; protected set; } = 10; - - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) - { - base.ApplyDefaults(timing, difficulty); - - double spinnerRotationRatio = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); - RequiredHits = (int)Math.Max(1, Duration / 1000f * spinnerRotationRatio); - } + public int RequiredHits = 10; } } \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Modes.Taiko/UI/HitExplosion.cs index 94938a700d..eb43c1a5d0 100644 --- a/osu.Game.Modes.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Modes.Taiko/UI/HitExplosion.cs @@ -18,12 +18,21 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitExplosion : CircularContainer { - private readonly TaikoJudgement judgement; + /// + /// The size multiplier of a hit explosion if a hit object has been hit with the second key. + /// + private const float secondhit_size_multiplier = 1.5f; + + /// + /// The judgement this hit explosion visualises. + /// + public readonly TaikoJudgement Judgement; + private readonly Box innerFill; public HitExplosion(TaikoJudgement judgement) { - this.judgement = judgement; + Judgement = judgement; Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); @@ -50,10 +59,7 @@ namespace osu.Game.Modes.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (judgement.SecondHit) - Size *= 1.5f; - - switch (judgement.TaikoResult) + switch (Judgement.TaikoResult) { case TaikoHitResult.Good: innerFill.Colour = colours.Green; @@ -73,5 +79,13 @@ namespace osu.Game.Modes.Taiko.UI Expire(); } + + /// + /// Transforms this hit explosion to visualise a secondary hit. + /// + public void VisualiseSecondHit() + { + ResizeTo(Size * secondhit_size_multiplier, 50); + } } } diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Modes.Taiko/UI/HitTarget.cs index 32b2877545..27562cd916 100644 --- a/osu.Game.Modes.Taiko/UI/HitTarget.cs +++ b/osu.Game.Modes.Taiko/UI/HitTarget.cs @@ -21,9 +21,9 @@ namespace osu.Game.Modes.Taiko.UI private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2; /// - /// Diameter of finisher hit object circles. + /// Diameter of strong hit object circles. /// - private const float finisher_diameter = normal_diameter * 1.5f; + private const float strong_hit_diameter = normal_diameter * 1.5f; /// /// The 1px inner border of the taiko playfield. @@ -47,15 +47,15 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - finisher_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), Alpha = 0.1f }, new CircularContainer { - Name = "Finisher Ring", + Name = "Strong Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(finisher_diameter), + Size = new Vector2(strong_hit_diameter), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -72,7 +72,7 @@ namespace osu.Game.Modes.Taiko.UI }, new CircularContainer { - Name = "Normal Ring", + Name = "Normal Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(normal_diameter), @@ -96,7 +96,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Y = -border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - finisher_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), Alpha = 0.1f }, }; diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs index b6165b785b..c23d920eda 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Primitives; using osu.Game.Modes.Taiko.Objects.Drawable; +using System.Linq; namespace osu.Game.Modes.Taiko.UI { @@ -22,7 +23,7 @@ namespace osu.Game.Modes.Taiko.UI { /// /// The play field height. This is relative to the size of hit objects - /// such that the playfield is just a bit larger than finishers. + /// such that the playfield is just a bit larger than strong hits. /// public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; @@ -176,9 +177,7 @@ namespace osu.Game.Modes.Taiko.UI public override void OnJudgement(DrawableHitObject judgedObject) { bool wasHit = judgedObject.Judgement.Result == HitResult.Hit; - - if (wasHit) - hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement)); + bool secondHit = judgedObject.Judgement.SecondHit; judgementContainer.Add(new DrawableTaikoJudgement(judgedObject.Judgement) { @@ -187,6 +186,22 @@ namespace osu.Game.Modes.Taiko.UI RelativePositionAxes = Axes.X, X = wasHit ? judgedObject.Position.X : 0, }); + + if (!wasHit) + return; + + if (!secondHit) + { + if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell)) + { + // If we're far enough away from the left stage, we should bring outselves in front of it + topLevelHitContainer.Add(judgedObject.CreateProxy()); + } + + hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement)); + } + else + hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); } } } \ No newline at end of file