diff --git a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs index efe4a2002f..6b8f30eaec 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs @@ -11,9 +11,10 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using OpenTK; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -24,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModEasy : ModEasy { - } public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects @@ -32,9 +32,54 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override double ScoreMultiplier => 1.06; + private const double fade_in_speed_multiplier = 0.6; + private const double fade_out_speed_multiplier = 0.3; + + private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT; + public void ApplyToDrawableHitObjects(IEnumerable drawables) { + foreach (var d in drawables.OfType()) + d.ApplyCustomUpdateState += customSequence; + } + private void customSequence(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + + //var duration = (d.HitObject as IHasEndTime)?.Duration ?? 0; + var fadeInTime = d.HitObject.StartTime - preEmpt; + var fadeIn = d.HitObject.StartTime - preEmpt * fade_in_speed_multiplier - fadeInTime; + var fadeOutTime = fadeInTime + fadeIn; + var fadeOut = d.HitObject.StartTime - preEmpt * fade_out_speed_multiplier - fadeOutTime; + + d.FadeIn = fadeIn; + + using (drawable.BeginAbsoluteSequence(fadeInTime)) + { + switch (drawable) + { + case DrawableHitCircle circle: + circle.ApproachCircle.FadeOut(); + circle.LifetimeEnd = circle.HitObject.StartTime + Math.Max(fadeOut, circle.HitObject.HitWindowFor(HitResult.Miss)); + + using (circle.BeginDelayedSequence(fadeIn)) + circle.FadeOut(fadeOut); + break; + case DrawableSlider slider: + slider.InitialCircle.ApplyCustomUpdateState += customSequence; + + //using (slider.BeginDelayedSequence(fadeIn)) + // slider.Body.FadeOut(duration, Easing.Out); + break; + case DrawableSpinner spinner: + spinner.Disc.FadeOut(); + spinner.Ticks.FadeOut(); + spinner.Background.FadeOut(); + break; + } + } } } @@ -57,11 +102,6 @@ namespace osu.Game.Rulesets.Osu.Mods slider.ControlPoints = newControlPoints; slider.Curve?.Calculate(); // Recalculate the slider curve } - - public void ApplyToHitObjects(RulesetContainer rulesetContainer) - { - - } } public class OsuModSuddenDeath : ModSuddenDeath @@ -102,7 +142,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModPerfect : ModPerfect { - } public class OsuModSpunOut : Mod diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a973d2b580..84ae568a1d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using OpenTK; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -48,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, number = new NumberPiece { - Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(), + Text = (HitObject.ComboIndex + 1).ToString(), }, ring = new RingPiece(), flash = new FlashPiece(), @@ -88,25 +87,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdatePreemptState(); - ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT)); + ApproachCircle.FadeIn(Math.Min(FadeIn * 2, TIME_PREEMPT)); ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT); } protected override void UpdateCurrentState(ArmedState state) { - double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime; - - glow.Delay(duration).FadeOut(400); + glow.FadeOut(400); switch (state) { case ArmedState.Idle: - this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT); + this.Delay(TIME_PREEMPT).FadeOut(500); Expire(true); break; case ArmedState.Miss: ApproachCircle.FadeOut(50); - this.FadeOut(TIME_FADEOUT / 5); + this.FadeOut(100); Expire(); break; case ArmedState.Hit: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 3e0c23a1ab..a5b5fc481a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -12,7 +12,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public const float TIME_PREEMPT = 600; public const float TIME_FADEIN = 400; - public const float TIME_FADEOUT = 500; + + /// + /// The number of milliseconds used to fade in. + /// + public double FadeIn = TIME_FADEIN; + + public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - TIME_PREEMPT; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) @@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected virtual void UpdatePreemptState() - { - this.FadeIn(TIME_FADEIN); - } + protected virtual void UpdatePreemptState() => this.FadeIn(FadeIn); protected virtual void UpdateCurrentState(ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index befe84e3e9..e5387a1ce8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -15,25 +15,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { - private readonly Slider slider; + public readonly Slider Slider; - private readonly DrawableHitCircle initialCircle; + public readonly DrawableHitCircle InitialCircle; private readonly List components = new List(); private readonly Container ticks; private readonly Container repeatPoints; - private readonly SliderBody body; - private readonly SliderBall ball; + public readonly SliderBody Body; + public readonly SliderBall Ball; public DrawableSlider(Slider s) : base(s) { - slider = s; + Slider = s; Children = new Drawable[] { - body = new SliderBody(s) + Body = new SliderBody(s) { AccentColour = AccentColour, Position = s.StackedPosition, @@ -41,14 +41,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, ticks = new Container(), repeatPoints = new Container(), - ball = new SliderBall(s) + Ball = new SliderBall(s) { Scale = new Vector2(s.Scale), AccentColour = AccentColour, AlwaysPresent = true, Alpha = 0 }, - initialCircle = new DrawableHitCircle(new HitCircle + InitialCircle = new DrawableHitCircle(new HitCircle { //todo: avoid creating this temporary HitCircle. StartTime = s.StartTime, @@ -61,16 +61,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }) }; - components.Add(body); - components.Add(ball); + components.Add(Body); + components.Add(Ball); - AddNested(initialCircle); + AddNested(InitialCircle); var repeatDuration = s.Curve.Distance / s.Velocity; foreach (var tick in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); + var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeIn : FadeIn / 2); var fadeOutTime = repeatStartTime + repeatDuration; var drawableTick = new DrawableSliderTick(tick) @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var repeatPoint in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); + var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeIn : FadeIn / 2); var fadeOutTime = repeatStartTime + repeatDuration; var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) @@ -109,41 +109,41 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - Tracking = ball.Tracking; + Tracking = Ball.Tracking; - double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double progress = MathHelper.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); - int repeat = slider.RepeatAt(progress); - progress = slider.ProgressAt(progress); + int repeat = Slider.RepeatAt(progress); + progress = Slider.ProgressAt(progress); if (repeat > currentRepeat) { - if (repeat < slider.RepeatCount && ball.Tracking) + if (repeat < Slider.RepeatCount && Ball.Tracking) PlaySamples(); currentRepeat = repeat; } //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!initialCircle.Judgements.Any(j => j.IsHit)) - initialCircle.Position = slider.Curve.PositionAt(progress); + if (!InitialCircle.Judgements.Any(j => j.IsHit)) + InitialCircle.Position = Slider.Curve.PositionAt(progress); foreach (var c in components) c.UpdateProgress(progress, repeat); - foreach (var t in ticks.Children) t.Tracking = ball.Tracking; + foreach (var t in ticks.Children) t.Tracking = Ball.Tracking; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) { - if (!userTriggered && Time.Current >= slider.EndTime) + if (!userTriggered && Time.Current >= Slider.EndTime) { var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1; var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit)); - if (initialCircle.Judgements.Any(j => j.IsHit)) + if (InitialCircle.Judgements.Any(j => j.IsHit)) judgementsHit++; var hitFraction = (double)judgementsHit / judgementsCount; - if (hitFraction == 1 && initialCircle.Judgements.Any(j => j.Result == HitResult.Great)) + if (hitFraction == 1 && InitialCircle.Judgements.Any(j => j.Result == HitResult.Great)) AddJudgement(new OsuJudgement { Result = HitResult.Great }); - else if (hitFraction >= 0.5 && initialCircle.Judgements.Any(j => j.Result >= HitResult.Good)) + else if (hitFraction >= 0.5 && InitialCircle.Judgements.Any(j => j.Result >= HitResult.Good)) AddJudgement(new OsuJudgement { Result = HitResult.Good }); else if (hitFraction > 0) AddJudgement(new OsuJudgement { Result = HitResult.Meh }); @@ -154,22 +154,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateCurrentState(ArmedState state) { - ball.FadeIn(); + Ball.FadeIn(); - using (BeginDelayedSequence(slider.Duration, true)) + using (BeginDelayedSequence(Slider.Duration, true)) { - body.FadeOut(160); - ball.FadeOut(160); + Body.FadeOut(160); + Ball.FadeOut(160); this.FadeOut(800) .Expire(); } } - public Drawable ProxiedLayer => initialCircle.ApproachCircle; + public Drawable ProxiedLayer => InitialCircle.ApproachCircle; - public override Vector2 SelectionPoint => ToScreenSpace(body.Position); - public override Quad SelectionQuad => body.PathDrawQuad; + public override Vector2 SelectionPoint => ToScreenSpace(Body.Position); + public override Quad SelectionQuad => Body.PathDrawQuad; } internal interface ISliderProgress diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 054a2067ec..9e80e3ccd1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -20,13 +20,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { private readonly Spinner spinner; - private readonly SpinnerDisc disc; - private readonly SpinnerTicks ticks; + public readonly SpinnerDisc Disc; + public readonly SpinnerTicks Ticks; private readonly SpinnerSpmCounter spmCounter; private readonly Container mainContainer; - private readonly SpinnerBackground background; + public readonly SpinnerBackground Background; private readonly Container circleContainer; private readonly CirclePiece circle; private readonly GlowPiece glow; @@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - background = new SpinnerBackground + Background = new SpinnerBackground { Alpha = 0.6f, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - disc = new SpinnerDisc(spinner) + Disc = new SpinnerDisc(spinner) { Scale = Vector2.Zero, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, circleContainer.CreateProxy(), - ticks = new SpinnerTicks + Ticks = new SpinnerTicks { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -114,22 +114,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; } - public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); + public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); protected override void CheckForJudgements(bool userTriggered, double timeOffset) { if (Time.Current < HitObject.StartTime) return; - if (Progress >= 1 && !disc.Complete) + if (Progress >= 1 && !Disc.Complete) { - disc.Complete = true; + Disc.Complete = true; const float duration = 200; - disc.FadeAccent(completeColour, duration); + Disc.FadeAccent(completeColour, duration); - background.FadeAccent(completeColour, duration); - background.FadeOut(duration); + Background.FadeAccent(completeColour, duration); + Background.FadeOut(duration); circle.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration); @@ -153,20 +153,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { normalColour = baseColour; - background.AccentColour = normalColour; + Background.AccentColour = normalColour; completeColour = colours.YellowLight.Opacity(0.75f); - disc.AccentColour = fillColour; + Disc.AccentColour = fillColour; circle.Colour = colours.BlueDark; glow.Colour = colours.BlueDark; } protected override void Update() { - disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); - if (!spmCounter.IsPresent && disc.Tracking) - spmCounter.FadeIn(TIME_FADEIN); + Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); + if (!spmCounter.IsPresent && Disc.Tracking) + spmCounter.FadeIn(FadeIn); base.Update(); } @@ -175,14 +175,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateAfterChildren(); - circle.Rotation = disc.Rotation; - ticks.Rotation = disc.Rotation; - spmCounter.SetRotation(disc.RotationAbsolute); + circle.Rotation = Disc.Rotation; + Ticks.Rotation = Disc.Rotation; + spmCounter.SetRotation(Disc.RotationAbsolute); float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; - disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); + Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); - symbol.RotateTo(disc.Rotation / 2, 500, Easing.OutQuint); + symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); } protected override void UpdatePreemptState() @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleContainer.ScaleTo(spinner.Scale * 0.3f); circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); - disc.RotateTo(-720); + Disc.RotateTo(-720); symbol.RotateTo(-720); mainContainer diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index ca75a61738..9f54ce3fa3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; + public override bool IsPresent => true; // handle input when hidden + public SpinnerDisc(Spinner s) { spinner = s; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index 2751e36a25..1024d5686d 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -1,11 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ab4c04be3f..ca5326f35e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -119,6 +119,9 @@ namespace osu.Game.Rulesets.Objects.Drawables { UpdateState(state); + // apply any custom state overrides + ApplyCustomUpdateState?.Invoke(this, state); + if (State == ArmedState.Hit) PlaySamples(); }; @@ -243,6 +246,11 @@ namespace osu.Game.Rulesets.Objects.Drawables nestedHitObjects.Add(h); } + /// + /// Bind to apply a custom state which can override the default implementation. + /// + public event Action ApplyCustomUpdateState; + protected abstract void UpdateState(ArmedState state); } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b5f93123fd..a5d60f80f6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -310,6 +310,7 @@ +