diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index 31a1bc369e..6da47c5bc4 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -50,7 +50,7 @@ namespace osu.Desktop.VisualTests.Tests Anchor = Anchor.Centre, Origin = Anchor.Centre, Depth = -i, - State = ArmedState.Armed, + State = ArmedState.Hit, }; approachContainer.Add(d.ApproachCircle.CreateProxy()); diff --git a/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs index 8c73955ca9..6c11b328a9 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -39,7 +39,9 @@ namespace osu.Game.Modes.Osu.Objects.Drawables circle = new CirclePiece { Colour = osuObject.Colour, - Hit = Hit, + Hit = () => Hit(new JudgementInfo { + UserTriggered = true, + }), }, number = new NumberPiece(), ring = new RingPiece(), @@ -73,7 +75,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Flush(true); //move to DrawableHitObject ApproachCircle.Flush(true); - double t = HitTime ?? osuObject.StartTime; + double t = osuObject.EndTime + (Judgement?.TimeOffset ?? 0); Alpha = 0; @@ -103,14 +105,26 @@ namespace osu.Game.Modes.Osu.Objects.Drawables switch (state) { - case ArmedState.Disarmed: - Delay(osuObject.Duration + 200); - FadeOut(200); + case ArmedState.Idle: + Delay(osuObject.Duration + 500); + FadeOut(500); explosion?.Expire(); explosion = null; break; - case ArmedState.Armed: + case ArmedState.Miss: + ring.FadeOut(); + circle.FadeOut(); + number.FadeOut(); + + explosion?.Expire(); + explosion = null; + + Schedule(() => Add(explosion = new HitExplosion(HitResult.Miss))); + + FadeOut(800); + break; + case ArmedState.Hit: const double flash_in = 30; flash.FadeTo(0.8f, flash_in); @@ -119,7 +133,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables explode.FadeIn(flash_in); - Schedule(() => Add(explosion = new HitExplosion(Judgement.Hit300))); + Schedule(() => Add(explosion = new HitExplosion(Judgement.Result))); Delay(flash_in, true); diff --git a/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs index 693f05cd1f..50203d48ae 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs @@ -18,7 +18,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Add(new CirclePiece { Colour = h.Colour, - Hit = Hit, + Hit = () => Hit(new JudgementInfo()), Position = h.Curve.PositionAt(i) - h.Position //non-relative? }); } diff --git a/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs b/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs index a3067a1eab..fac9ab636b 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs @@ -12,7 +12,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables private SpriteText line1; private SpriteText line2; - public HitExplosion(Judgement judgement, ComboJudgement comboJudgement = ComboJudgement.None) + public HitExplosion(HitResult hitResult, ComboResult comboResult = ComboResult.None) { AutoSizeAxes = Axes.Both; Anchor = Anchor.Centre; @@ -27,13 +27,13 @@ namespace osu.Game.Modes.Osu.Objects.Drawables { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = judgement.GetDescription(), + Text = hitResult.GetDescription(), Font = @"Venera", TextSize = 20, }, line2 = new SpriteText { - Text = comboJudgement.GetDescription(), + Text = comboResult.GetDescription(), Font = @"Venera", TextSize = 14, } diff --git a/osu.Game.Mode.Osu/OsuHitJudgementResolver.cs b/osu.Game.Mode.Osu/OsuHitJudgementResolver.cs new file mode 100644 index 0000000000..6dc240c7af --- /dev/null +++ b/osu.Game.Mode.Osu/OsuHitJudgementResolver.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Osu.Objects.Drawables; + +namespace osu.Game.Modes.Osu +{ + class OsuHitJudgementResolver : HitJudgementResolver + { + double hit50 = 150; + double hit100 = 80; + double hit300 = 30; + public override void CheckJudgement(DrawableHitObject h, JudgementInfo info) + { + DrawableHitCircle circle = h as DrawableHitCircle; + if (circle != null) + { + if (!info.UserTriggered) + { + if (info.TimeOffset > hit50) + info.Result = HitResult.Miss; + return; + } + + double hitOffset = Math.Abs(info.TimeOffset); + + if (hitOffset < hit300) + info.Result = HitResult.Hit300; + else if (hitOffset < hit100) + info.Result = HitResult.Hit100; + else if (hitOffset < hit50) + info.Result = HitResult.Hit50; + else + info.Result = HitResult.Miss; + } + } + } +} diff --git a/osu.Game.Mode.Osu/OsuRuleset.cs b/osu.Game.Mode.Osu/OsuRuleset.cs index ac43e5501c..e79cbef748 100644 --- a/osu.Game.Mode.Osu/OsuRuleset.cs +++ b/osu.Game.Mode.Osu/OsuRuleset.cs @@ -13,6 +13,8 @@ namespace osu.Game.Modes.Osu { public override ScoreOverlay CreateScoreOverlay() => new OsuScoreOverlay(); + public override HitJudgementResolver CreateHitJudgement() => new OsuHitJudgementResolver(); + public override HitRenderer CreateHitRendererWith(List objects) => new OsuHitRenderer { Objects = objects }; public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser(); diff --git a/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj index e7efa413c4..d63cc2354a 100644 --- a/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj @@ -52,6 +52,7 @@ + diff --git a/osu.Game/Modes/HitJudgementResolver.cs b/osu.Game/Modes/HitJudgementResolver.cs index 5f2afec369..d9f3cc96c1 100644 --- a/osu.Game/Modes/HitJudgementResolver.cs +++ b/osu.Game/Modes/HitJudgementResolver.cs @@ -4,28 +4,34 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using osu.Game.Modes.Objects; +using osu.Game.Modes.Objects.Drawables; using OpenTK; namespace osu.Game.Modes { public class HitJudgementResolver { - public JudgementResult CheckJudgement(HitObject h) => new JudgementResult { Combo = ComboJudgement.None, Judgement = Judgement.Hit300 }; + public virtual void CheckJudgement(DrawableHitObject h, JudgementInfo info) + { + info.Result = HitResult.Hit300; + } } - public struct JudgementResult + public class JudgementInfo { - public ComboJudgement Combo; - public Judgement Judgement; - public float TimeOffset; + public bool UserTriggered; + public ComboResult Combo; + public HitResult Result; + public double TimeOffset; public Vector2 PositionOffset; } - public enum ComboJudgement + public enum ComboResult { [Description(@"")] None, @@ -35,8 +41,9 @@ namespace osu.Game.Modes Perfect } - public enum Judgement + public enum HitResult { + Ignore, [Description(@"Miss")] Miss, [Description(@"50")] diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs index 387d5d061a..0d8b124be2 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs @@ -2,6 +2,7 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Diagnostics; using osu.Framework; using osu.Framework.Graphics.Containers; @@ -13,11 +14,11 @@ namespace osu.Game.Modes.Objects.Drawables public Action OnHit; public Action OnMiss; - public Func AllowHit; + public Action CheckJudgement; public Container ChildObjects; - public JudgementResult Result; + public JudgementInfo Judgement; public HitObject HitObject; @@ -41,36 +42,47 @@ namespace osu.Game.Modes.Objects.Drawables } } - protected double? HitTime; - - protected virtual bool Hit() + /// + /// Process a hit of this hitobject. Carries out judgement. + /// + /// Preliminary judgement information provided by the hit source. + /// Whether a hit was processed. + protected bool Hit(JudgementInfo judgement) { - if (State != ArmedState.Disarmed) + if (State != ArmedState.Idle) return false; - if (AllowHit?.Invoke(this) == false) + judgement.TimeOffset = Time.Current - HitObject.EndTime; + + CheckJudgement?.Invoke(this, judgement); + + if (judgement.Result == HitResult.Ignore) return false; - HitTime = Time.Current; + Judgement = judgement; - State = ArmedState.Armed; + switch (judgement.Result) + { + default: + State = ArmedState.Hit; + OnHit?.Invoke(this); + break; + case HitResult.Miss: + State = ArmedState.Miss; + OnMiss?.Invoke(this); + break; + } + + return true; } - private bool counted; - protected override void Update() { base.Update(); - if (Time.Current >= HitObject.EndTime && !counted) - { - counted = true; - if (state == ArmedState.Armed) - OnHit?.Invoke(this); - else - OnMiss?.Invoke(this); - } + if (Time.Current >= HitObject.EndTime && Judgement == null) + Hit(new JudgementInfo()); } protected abstract void UpdateState(ArmedState state); @@ -78,7 +90,8 @@ namespace osu.Game.Modes.Objects.Drawables public enum ArmedState { - Disarmed, - Armed + Idle, + Hit, + Miss } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8691097ebf..a5bbf8c453 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -86,11 +86,16 @@ namespace osu.Game.Screens.Play var scoreOverlay = ruleset.CreateScoreOverlay(); var hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects); + var hitJudgement = ruleset.CreateHitJudgement(); + hitRenderer.OnHit += delegate (HitObject h) { scoreOverlay.OnHit(h); }; hitRenderer.OnMiss += delegate (HitObject h) { scoreOverlay.OnMiss(h); }; if (Autoplay) - hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Armed)); + hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Hit)); + + //bind DrawableHitObjects to HitJudgement + hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.CheckJudgement = hitJudgement.CheckJudgement)); Children = new Drawable[] {