// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; using OpenTK.Graphics; using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; 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; namespace osu.Game.Modes.Taiko.Objects.Drawable { public class DrawableSwell : DrawableTaikoHitObject { /// /// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime. /// This is only ever invoked once. /// public event Action OnStart; private const float target_ring_thick_border = 4f; private const float target_ring_thin_border = 1f; private const float target_ring_scale = 5f; private const float inner_ring_alpha = 0.35f; private readonly Swell swell; private readonly Container bodyContainer; private readonly CircularContainer targetRing; private readonly CircularContainer innerRing; private readonly CirclePiece circlePiece; private readonly Key[] rimKeys = { Key.D, Key.K }; private readonly Key[] centreKeys = { Key.F, Key.J }; private Key[] lastKeySet; /// /// The amount of times the user has hit this swell. /// private int userHits; private bool hasStarted; public DrawableSwell(Swell swell) : base(swell) { this.swell = swell; Children = new Framework.Graphics.Drawable[] { bodyContainer = new Container { Children = new Framework.Graphics.Drawable[] { innerRing = new CircularContainer { Name = "Inner ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), Masking = true, Children = new [] { new Box { RelativeSizeAxes = Axes.Both, Alpha = inner_ring_alpha, } } }, targetRing = new CircularContainer { Name = "Target ring (thick border)", Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), Masking = true, BorderThickness = target_ring_thick_border, Children = new Framework.Graphics.Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true }, new CircularContainer { Name = "Target ring (thin border)", Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, BorderThickness = target_ring_thin_border, BorderColour = Color4.White, Children = new[] { new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } } } } }, circlePiece = new CirclePiece { Children = new [] { new SwellSymbolPiece() } } } } }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { circlePiece.AccentColour = colours.YellowDark; innerRing.Colour = colours.YellowDark; targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); } protected override void CheckJudgement(bool userTriggered) { if (userTriggered) { if (Time.Current < HitObject.StartTime) return; userHits++; innerRing.FadeTo(1); innerRing.FadeTo(inner_ring_alpha, 500, EasingTypes.OutQuint); innerRing.ScaleTo(1f + (target_ring_scale - 1) * userHits / swell.RequiredHits, 1200, EasingTypes.OutElastic); if (userHits == swell.RequiredHits) { Judgement.Result = HitResult.Hit; Judgement.TaikoResult = TaikoHitResult.Great; } } else { if (Judgement.TimeOffset < 0) return; //TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP if (userHits > swell.RequiredHits / 2) { Judgement.Result = HitResult.Hit; Judgement.TaikoResult = TaikoHitResult.Good; } else Judgement.Result = HitResult.Miss; } } protected override void UpdateState(ArmedState state) { const float preempt = 300; Delay(HitObject.StartTime - Time.Current - preempt, true); targetRing.ScaleTo(target_ring_scale, preempt, EasingTypes.Out); Delay(preempt, true); Delay(Judgement.TimeOffset + swell.Duration, true); switch (state) { case ArmedState.Hit: bodyContainer.ScaleTo(1.2f, 400, EasingTypes.OutQuad); break; } FadeOut(600); Expire(); } protected override void UpdateScrollPosition(double time) { // Make the swell stop at the hit target double t = Math.Min(HitObject.StartTime, time); if (t == HitObject.StartTime && !hasStarted) { OnStart?.Invoke(); hasStarted = true; } base.UpdateScrollPosition(t); } protected override bool HandleKeyPress(Key key) { if (Judgement.Result.HasValue) return false; // Find the keyset which this key corresponds to var keySet = rimKeys.Contains(key) ? rimKeys : centreKeys; // Ensure alternating keysets if (keySet == lastKeySet) return false; lastKeySet = keySet; UpdateJudgement(true); return true; } } }