1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-27 21:02:55 +08:00
osu-lazer/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs

255 lines
8.7 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
using System.Linq;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
/// Visualises a <see cref="HoldNote"/> hit object.
/// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction>
{
public override bool DisplayResult => false;
2018-05-20 18:22:42 +08:00
public readonly DrawableNote Head;
public readonly DrawableNote Tail;
2018-04-13 17:19:50 +08:00
private readonly BodyPiece bodyPiece;
/// <summary>
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
/// </summary>
private double? holdStartTime;
/// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary>
private bool hasBroken;
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
2018-04-13 17:19:50 +08:00
{
2019-07-22 13:45:25 +08:00
Container<DrawableHoldNoteTick> tickContainer;
2018-04-13 17:19:50 +08:00
RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[]
2018-04-13 17:19:50 +08:00
{
bodyPiece = new BodyPiece
{
RelativeSizeAxes = Axes.X,
},
tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = HitObject.NestedHitObjects.OfType<HoldNoteTick>().Select(tick => new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
})
},
Head = new DrawableHeadNote(this)
2018-04-13 17:19:50 +08:00
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
Tail = new DrawableTailNote(this)
2018-04-13 17:19:50 +08:00
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
});
2018-04-13 17:19:50 +08:00
foreach (var tick in tickContainer)
AddNested(tick);
2018-05-20 18:22:42 +08:00
AddNested(Head);
AddNested(Tail);
2019-07-22 13:45:25 +08:00
AccentColour.BindValueChanged(colour =>
{
bodyPiece.AccentColour = colour.NewValue;
Head.AccentColour.Value = colour.NewValue;
Tail.AccentColour.Value = colour.NewValue;
tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue);
}, true);
2018-04-13 17:19:50 +08:00
}
2019-02-21 17:56:34 +08:00
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{
2019-02-21 17:56:34 +08:00
base.OnDirectionChanged(e);
2019-02-21 17:56:34 +08:00
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
2018-05-16 18:43:01 +08:00
{
2018-05-20 18:22:42 +08:00
if (Tail.AllJudged)
ApplyResult(r => r.Type = HitResult.Perfect);
2018-04-13 17:19:50 +08:00
}
protected override void Update()
{
base.Update();
// Make the body piece not lie under the head note
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
2018-04-13 17:19:50 +08:00
}
protected override void UpdateStateTransforms(ArmedState state)
{
using (BeginDelayedSequence(HitObject.Duration, true))
base.UpdateStateTransforms(state);
}
protected void BeginHold()
{
holdStartTime = Time.Current;
bodyPiece.Hitting = true;
}
protected void EndHold()
{
holdStartTime = null;
bodyPiece.Hitting = false;
}
2018-04-13 17:19:50 +08:00
public bool OnPressed(ManiaAction action)
{
// Make sure the action happened within the body of the hold note
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false;
if (action != Action.Value)
2018-04-13 17:19:50 +08:00
return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
// and within the limited range of the above if-statement. This state will be managed by the head note if the
// user has pressed during the hit windows of the head note.
BeginHold();
2018-04-13 17:19:50 +08:00
return true;
}
public bool OnReleased(ManiaAction action)
{
// Make sure that the user started holding the key during the hold note
if (!holdStartTime.HasValue)
return false;
if (action != Action.Value)
2018-04-13 17:19:50 +08:00
return false;
EndHold();
2018-04-13 17:19:50 +08:00
// If the key has been released too early, the user should not receive full score for the release
2018-05-20 18:22:42 +08:00
if (!Tail.IsHit)
2018-04-13 17:19:50 +08:00
hasBroken = true;
return true;
}
/// <summary>
/// The head note of a hold.
/// </summary>
private class DrawableHeadNote : DrawableNote
{
private readonly DrawableHoldNote holdNote;
public DrawableHeadNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head)
2018-04-13 17:19:50 +08:00
{
this.holdNote = holdNote;
}
public override bool OnPressed(ManiaAction action)
{
if (!base.OnPressed(action))
return false;
// If the key has been released too early, the user should not receive full score for the release
if (Result.Type == HitResult.Miss)
2018-04-13 17:19:50 +08:00
holdNote.hasBroken = true;
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
// The body doesn't handle these early early hits, so we have to explicitly set the holding state here
holdNote.BeginHold();
2018-04-13 17:19:50 +08:00
return true;
}
}
/// <summary>
/// The tail note of a hold.
/// </summary>
private class DrawableTailNote : DrawableNote
{
2018-05-17 12:59:04 +08:00
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
2018-04-13 17:19:50 +08:00
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Tail)
2018-04-13 17:19:50 +08:00
{
this.holdNote = holdNote;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
2018-04-13 17:19:50 +08:00
{
2018-05-17 12:59:04 +08:00
// Factor in the release lenience
timeOffset /= release_window_lenience;
2018-04-13 17:19:50 +08:00
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
2018-04-13 17:19:50 +08:00
return;
}
var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
return;
ApplyResult(r =>
2018-04-13 17:19:50 +08:00
{
if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect))
result = HitResult.Good;
r.Type = result;
2018-04-13 17:19:50 +08:00
});
}
public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
public override bool OnReleased(ManiaAction action)
{
// Make sure that the user started holding the key during the hold note
if (!holdNote.holdStartTime.HasValue)
return false;
if (action != Action.Value)
2018-04-13 17:19:50 +08:00
return false;
UpdateResult(true);
2018-04-13 17:19:50 +08:00
// Handled by the hold note, which will set holding = false
return false;
}
}
}
}