// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; namespace osu.Game.Rulesets.Catch.UI { public class CatcherArea : Container { public const float CATCHER_SIZE = 172; private readonly Catcher catcher; public Container ExplodingFruitTarget { set { catcher.ExplodingFruitTarget = value; } } public CatcherArea(BeatmapDifficulty difficulty = null) { RelativeSizeAxes = Axes.X; Height = CATCHER_SIZE; Child = catcher = new Catcher(difficulty) { AdditiveTarget = this, }; } public void Add(DrawableHitObject fruit, Vector2 absolutePosition) { fruit.RelativePositionAxes = Axes.None; fruit.Position = new Vector2(catcher.ToLocalSpace(absolutePosition).X - catcher.DrawSize.X / 2, 0); fruit.Anchor = Anchor.TopCentre; fruit.Origin = Anchor.BottomCentre; fruit.Scale *= 0.7f; fruit.LifetimeEnd = double.MaxValue; catcher.Add(fruit); } public bool CanCatch(CatchHitObject obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X * Math.Abs(catcher.Scale.X) / DrawSize.X / 2; public class Catcher : Container, IKeyBindingHandler { private Texture texture; private Container caughtFruit; public Container ExplodingFruitTarget; public Container AdditiveTarget; public Catcher(BeatmapDifficulty difficulty = null) { RelativePositionAxes = Axes.X; X = 0.5f; Origin = Anchor.TopCentre; Anchor = Anchor.TopLeft; Size = new Vector2(CATCHER_SIZE); if (difficulty != null) Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); } [BackgroundDependencyLoader] private void load(TextureStore textures) { texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); Children = new Drawable[] { createCatcherSprite(), caughtFruit = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, } }; } private int currentDirection; private bool dashing; protected bool Dashing { get { return dashing; } set { if (value == dashing) return; dashing = value; if (dashing) Schedule(addAdditiveSprite); } } private void addAdditiveSprite() { if (!dashing || AdditiveTarget == null) return; var additive = createCatcherSprite(); additive.Anchor = Anchor; additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. additive.Position = Position; additive.Scale = Scale; additive.RelativePositionAxes = RelativePositionAxes; additive.Blending = BlendingMode.Additive; AdditiveTarget.Add(additive); additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); Scheduler.AddDelayed(addAdditiveSprite, 50); } private Sprite createCatcherSprite() => new Sprite { Size = new Vector2(CATCHER_SIZE), FillMode = FillMode.Fill, Texture = texture, OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly. }; public void Add(DrawableHitObject fruit) { float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance)) { fruit.X += RNG.Next(-5, 5); fruit.Y -= RNG.Next(0, 5); } caughtFruit.Add(fruit); if (((CatchHitObject)fruit.HitObject).LastInCombo) explode(); } public bool OnPressed(CatchAction action) { switch (action) { case CatchAction.MoveLeft: currentDirection--; return true; case CatchAction.MoveRight: currentDirection++; return true; case CatchAction.Dash: Dashing = true; return true; } return false; } public bool OnReleased(CatchAction action) { switch (action) { case CatchAction.MoveLeft: currentDirection++; return true; case CatchAction.MoveRight: currentDirection--; return true; case CatchAction.Dash: Dashing = false; return true; } return false; } /// /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. /// public const double BASE_SPEED = 1.0 / 512; protected override void Update() { base.Update(); if (currentDirection == 0) return; double dashModifier = Dashing ? 1 : 0.5; Scale = new Vector2(Math.Abs(Scale.X) * Math.Sign(currentDirection), Scale.Y); X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); } private void explode() { var fruit = caughtFruit.ToArray(); foreach (var f in fruit) { var originalX = f.X * Scale.X; if (ExplodingFruitTarget != null) { f.Anchor = Anchor.TopLeft; f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); caughtFruit.Remove(f); ExplodingFruitTarget.Add(f); } f.MoveToY(f.Y - 50, 250, Easing.OutSine) .Then() .MoveToY(f.Y + 50, 500, Easing.InSine); f.MoveToX(f.X + originalX * 6, 1000); f.FadeOut(750); f.Expire(); } } } } }