// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Catch.UI { /// /// The horizontal band at the bottom of the playfield the catcher is moving on. /// It holds a as a child and translates input to the catcher movement. /// It also holds a combo display that is above the catcher, and judgment results are translated to the catcher and the combo display. /// public class CatcherArea : Container, IKeyBindingHandler { public Catcher Catcher { get => catcher; set => catcherContainer.Child = catcher = value; } private readonly Container catcherContainer; private readonly CatchComboDisplay comboDisplay; private readonly CatcherTrailDisplay catcherTrails; private Catcher catcher; /// /// -1 when only left button is pressed. /// 1 when only right button is pressed. /// 0 when none or both left and right buttons are pressed. /// private int currentDirection; // TODO: support replay rewind private bool lastHyperDashState; /// /// must be set before loading. /// public CatcherArea() { Size = new Vector2(CatchPlayfield.WIDTH, Catcher.BASE_SIZE); Children = new Drawable[] { catcherContainer = new Container { RelativeSizeAxes = Axes.Both }, catcherTrails = new CatcherTrailDisplay(), comboDisplay = new CatchComboDisplay { RelativeSizeAxes = Axes.None, AutoSizeAxes = Axes.Both, Anchor = Anchor.TopLeft, Origin = Anchor.Centre, Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X } }; } public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result) { Catcher.OnNewResult(hitObject, result); // Ignore JuiceStreams and BananaShowers if (!(hitObject is DrawablePalpableCatchHitObject)) return; if (hitObject.HitObject.LastInCombo) { if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result)) Catcher.Explode(); else Catcher.Drop(); } comboDisplay.OnNewResult(hitObject, result); } public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result) { comboDisplay.OnRevertResult(hitObject, result); Catcher.OnRevertResult(hitObject, result); } protected override void Update() { base.Update(); var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState; SetCatcherPosition( replayState?.CatcherX ?? (float)(Catcher.X + Catcher.Speed * currentDirection * Clock.ElapsedFrameTime)); } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); comboDisplay.X = Catcher.X; if (Time.Elapsed <= 0) { // This is probably a wrong value, but currently the true value is not recorded. // Setting `true` will prevent generation of false-positive after-images (with more false-negatives). lastHyperDashState = true; return; } if (!lastHyperDashState && Catcher.HyperDashing) displayCatcherTrail(CatcherTrailAnimation.HyperDashAfterImage); if (Catcher.Dashing || Catcher.HyperDashing) { double generationInterval = Catcher.HyperDashing ? 25 : 50; if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval) displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing); } lastHyperDashState = Catcher.HyperDashing; } public void SetCatcherPosition(float X) { float lastPosition = Catcher.X; float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH); Catcher.X = newPosition; if (lastPosition < newPosition) Catcher.VisualDirection = Direction.Right; else if (lastPosition > newPosition) Catcher.VisualDirection = Direction.Left; } public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) { case CatchAction.MoveLeft: currentDirection--; return true; case CatchAction.MoveRight: currentDirection++; return true; case CatchAction.Dash: Catcher.Dashing = true; return true; } return false; } public void OnReleased(KeyBindingReleaseEvent e) { switch (e.Action) { case CatchAction.MoveLeft: currentDirection++; break; case CatchAction.MoveRight: currentDirection--; break; case CatchAction.Dash: Catcher.Dashing = false; break; } } private void displayCatcherTrail(CatcherTrailAnimation animation) => catcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation)); } }