diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index cb0f4eab96..89bd73f8fb 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Catch.Objects { public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; + public override bool LastInCombo => true; + protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index b5d9163a50..7b0370ef88 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -1,10 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -12,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container bananaContainer; - public DrawableBananaShower(BananaShower s) + public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) : base(s) { RelativeSizeAxes = Axes.X; @@ -22,7 +25,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Child = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; foreach (var b in s.NestedHitObjects.Cast()) - AddNested(new DrawableFruit(b)); + AddNested(getVisualRepresentation?.Invoke(b)); + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss }); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 554fb1323e..fce43137d1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using OpenTK; using osu.Game.Rulesets.Scoring; @@ -13,6 +14,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable public abstract class PalpableCatchHitObject : DrawableCatchHitObject where TObject : CatchHitObject { + public override bool CanBePlated => true; + protected PalpableCatchHitObject(TObject hitObject) : base(hitObject) { @@ -36,6 +39,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable public abstract class DrawableCatchHitObject : DrawableHitObject { + public virtual bool CanBePlated => false; + protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { @@ -47,8 +52,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable protected override void CheckForJudgements(bool userTriggered, double timeOffset) { + if (CheckPosition == null) return; + if (timeOffset > 0) - AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss }); + AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); } private const float preempt = 1000; @@ -56,17 +63,21 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable protected override void UpdateState(ArmedState state) { using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) - { - // animation this.FadeIn(200); - } - switch (state) + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + + using (BeginAbsoluteSequence(endTime, true)) { - case ArmedState.Miss: - using (BeginAbsoluteSequence(HitObject.StartTime, true)) - this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out); - break; + switch (state) + { + case ArmedState.Miss: + this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); + break; + case ArmedState.Hit: + this.FadeOut().Expire(); + break; + } } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index e66999229c..c8b15aec61 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -1,9 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawable @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container dropletContainer; - public DrawableJuiceStream(JuiceStream s) + public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) : base(s) { RelativeSizeAxes = Axes.Both; @@ -21,21 +22,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Child = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; - foreach (var tick in s.NestedHitObjects) - { - switch (tick) - { - case TinyDroplet tiny: - AddNested(new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }); - break; - case Droplet droplet: - AddNested(new DrawableDroplet(droplet)); - break; - case Fruit fruit: - AddNested(new DrawableFruit(fruit)); - break; - } - } + foreach (var o in s.NestedHitObjects.Cast()) + AddNested(getVisualRepresentation?.Invoke(o)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index b1c83f5964..188af58e6a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -125,10 +125,8 @@ namespace osu.Game.Rulesets.Catch.Objects X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH }); } - } - public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs index 6bf5b6beca..e23e7633ca 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests } }; - beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 500, NewCombo = true }); + beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true }); return beatmap; } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 7fdabd46c2..8ea30a2899 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly CatcherArea catcherArea; - public CatchPlayfield(BeatmapDifficulty difficulty) + public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) : base(ScrollingDirection.Down, BASE_WIDTH) { Container explodingFruitContainer; @@ -44,6 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI }, catcherArea = new CatcherArea(difficulty) { + GetVisualRepresentation = getVisualRepresentation, ExplodingFruitTarget = explodingFruitContainer, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index ae0fe8189e..956a524121 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; namespace osu.Game.Rulesets.Catch.UI { @@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); @@ -42,9 +43,13 @@ namespace osu.Game.Rulesets.Catch.UI case Fruit fruit: return new DrawableFruit(fruit); case JuiceStream stream: - return new DrawableJuiceStream(stream); + return new DrawableJuiceStream(stream, GetVisualRepresentation); case BananaShower banana: - return new DrawableBananaShower(banana); + return new DrawableBananaShower(banana, GetVisualRepresentation); + case TinyDroplet tiny: + return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; + case Droplet droplet: + return new DrawableDroplet(droplet); } return null; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 1837086d9c..17c78f3aa0 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; using OpenTK; using OpenTK.Graphics; @@ -28,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.UI protected readonly Catcher MovableCatcher; + public Func> GetVisualRepresentation; + public Container ExplodingFruitTarget { set { MovableCatcher.ExplodingFruitTarget = value; } @@ -43,31 +44,41 @@ namespace osu.Game.Rulesets.Catch.UI }; } + private DrawableCatchHitObject lastPlateableFruit; + public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) { - if (judgement.IsHit) + if (judgement.IsHit && fruit.CanBePlated) { - var screenSpacePosition = fruit.ScreenSpaceDrawQuad.Centre; + var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); - // todo: make this less ugly, somehow. - (fruit.Parent as HitObjectContainer)?.Remove(fruit); - (fruit.Parent as Container)?.Remove(fruit); + if (caughtFruit == null) return; - fruit.RelativePositionAxes = Axes.None; - fruit.Position = new Vector2(MovableCatcher.ToLocalSpace(screenSpacePosition).X - MovableCatcher.DrawSize.X / 2, 0); + caughtFruit.AccentColour = fruit.AccentColour; + caughtFruit.RelativePositionAxes = Axes.None; + caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); - fruit.Anchor = Anchor.TopCentre; - fruit.Origin = Anchor.Centre; - fruit.Scale *= 0.7f; - fruit.LifetimeEnd = double.MaxValue; + caughtFruit.Anchor = Anchor.TopCentre; + caughtFruit.Origin = Anchor.Centre; + caughtFruit.Scale *= 0.7f; + caughtFruit.LifetimeEnd = double.MaxValue; - MovableCatcher.Add(fruit); + MovableCatcher.Add(caughtFruit); + + lastPlateableFruit = caughtFruit; } if (fruit.HitObject.LastInCombo) { if (judgement.IsHit) - MovableCatcher.Explode(); + { + // this is required to make this run after the last caught fruit runs UpdateState at least once. + // TODO: find a better alternative + if (lastPlateableFruit.IsLoaded) + MovableCatcher.Explode(); + else + lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); }; + } else MovableCatcher.Drop(); } @@ -209,7 +220,7 @@ namespace osu.Game.Rulesets.Catch.UI while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && - Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) + Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) { float diff = (ourRadius + theirRadius) / allowance; fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;