Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the note sheet logic into being friendly with rewinding #33

Merged
merged 2 commits into from
Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 79 additions & 104 deletions osu.Game.Rulesets.Rush/Objects/Drawables/DrawableNoteSheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Rush.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Rush.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;

namespace osu.Game.Rulesets.Rush.Objects.Drawables
{
Expand All @@ -21,23 +22,31 @@ public class DrawableNoteSheet : DrawableLanedHit<NoteSheet>
public const float NOTE_SHEET_SIZE = RushPlayfield.HIT_TARGET_SIZE * 0.75f;
public const float REQUIRED_COMPLETION = 0.75f;

public Bindable<bool> HasBroken { get; } = new BindableBool();

public DrawableNoteSheetHead Head => headContainer.Child;
public DrawableNoteSheetTail Tail => tailContainer.Child;
public DrawableNoteSheetBody Body => bodyContainer.Child;

private readonly Container<DrawableNoteSheetBody> bodyContainer;
private readonly Container<DrawableNoteSheetHead> headContainer;
private readonly Container<DrawableNoteSheetTail> tailContainer;

public NoteSheetBodyPiece BodyPiece => bodyContainer.Child;

private readonly Container<NoteSheetBodyPiece> bodyContainer;

private readonly DrawableNoteSheetCapStar holdStar;

private int pressCount;
private double? holdStartTime => !Head.IsHit ? (double?)null : HitObject.StartTime;
private double? holdEndTime => !Judged ? (double?)null : (HitObject.EndTime + Result.TimeOffset);

public double Progress
{
get
{
if (IsHit)
return 1.0;

public double? HoldStartTime { get; private set; }
public double? HoldEndTime { get; private set; }
public double CompletionAmount => ((HoldEndTime ?? HoldStartTime ?? 0) - (HoldStartTime ?? 0)) / HitObject.Duration;
return Math.Clamp(((holdEndTime ?? Time.Current) - (holdStartTime ?? Time.Current)) / HitObject.Duration, 0.0, 1.0);
}
}

[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
Expand All @@ -52,34 +61,28 @@ public DrawableNoteSheet(NoteSheet hitObject)

Content.AddRange(new Drawable[]
{
bodyContainer = new Container<DrawableNoteSheetBody>
bodyContainer = new Container<NoteSheetBodyPiece>
{
RelativeSizeAxes = Axes.Both,
Masking = true,
RelativeSizeAxes = Axes.Both,
Child = new NoteSheetBodyPiece
{
RelativeSizeAxes = Axes.Y,
AccentColour = { BindTarget = AccentColour },
},
},
headContainer = new Container<DrawableNoteSheetHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableNoteSheetTail> { RelativeSizeAxes = Axes.Both },
holdStar = new DrawableNoteSheetCapStar
{
Alpha = 0f,
Origin = Anchor.Centre,
Size = new Vector2(NOTE_SHEET_SIZE),
Alpha = 0f,
AccentColour = { BindTarget = AccentColour },
}
});

AccentColour.ValueChanged += _ => updateHoldStar();

HasBroken.ValueChanged += _ =>
{
if (HasBroken.Value && HoldStartTime != null && HoldEndTime == null)
HoldEndTime = Time.Current;

updateHoldStar();
};
}

private void updateHoldStar() => holdStar.UpdateColour(HasBroken.Value ? Color4.Gray : AccentColour.Value);

protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
Expand All @@ -93,10 +96,6 @@ protected override void AddNestedHitObject(DrawableHitObject hitObject)
case DrawableNoteSheetTail tail:
tailContainer.Child = tail;
break;

case DrawableNoteSheetBody body:
bodyContainer.Child = body;
break;
}
}

Expand All @@ -105,7 +104,6 @@ protected override void ClearNestedHitObjects()
base.ClearNestedHitObjects();
headContainer.Clear();
tailContainer.Clear();
bodyContainer.Clear();
}

protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
Expand All @@ -117,26 +115,15 @@ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
Anchor = LeadingAnchor,
Origin = Anchor.Centre,
AccentColour = { BindTarget = AccentColour },
HasBroken = { BindTarget = HasBroken },
AccentColour = { BindTarget = AccentColour }
};

case NoteSheetTail _:
return new DrawableNoteSheetTail(this)
{
Anchor = TrailingAnchor,
Origin = Anchor.Centre,
AccentColour = { BindTarget = AccentColour },
HasBroken = { BindTarget = HasBroken },
};

case NoteSheetBody _:
return new DrawableNoteSheetBody(this)
{
Anchor = TrailingAnchor,
Origin = TrailingAnchor,
AccentColour = { BindTarget = AccentColour },
HasBroken = { BindTarget = HasBroken },
AccentColour = { BindTarget = AccentColour }
};
}

Expand All @@ -148,105 +135,93 @@ protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection>
base.OnDirectionChanged(e);

Origin = LeadingAnchor;
bodyContainer.Origin = bodyContainer.Anchor = TrailingAnchor;
tailContainer.Origin = tailContainer.Anchor = TrailingAnchor;
headContainer.Origin = headContainer.Anchor = LeadingAnchor;
}

protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (AllJudged || timeOffset < 0)
return;

Tail.UpdateResult(userTriggered);
tailContainer.Origin = tailContainer.Anchor = TrailingAnchor;

if (Tail.IsHit && Head.IsHit && !HasBroken.Value)
ApplyResult(r => r.Type = HitResult.Perfect);
else
ApplyResult(r => r.Type = HitResult.Miss);
BodyPiece.Origin = BodyPiece.Anchor = TrailingAnchor;
bodyContainer.Origin = bodyContainer.Anchor = TrailingAnchor;
}

public override bool OnPressed(RushAction action)
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (AllJudged || HasBroken.Value)
return false;

if (!LaneMatchesAction(action))
return false;
// Check if the head hasn't been hit. if so then triggered by a press, judge the head.
if (!Head.IsHit)
{
// Head missed, judge as overall missed.
if (Head.Result.Type == HitResult.Miss)
{
ApplyResult(r => r.Type = HitResult.Miss);
return;
}

if (pressCount > 1)
return true;
if (userTriggered)
Head.TriggerResult();

if (!Head.AllJudged && !Head.UpdateResult(true))
return false;
return;
}

if (Head.IsHit)
// Released before required progress for completion, judge as overall missed.
if (userTriggered && Progress < REQUIRED_COMPLETION)
{
pressCount++;
beginHoldAt(Time.Current - Head.HitObject.StartTime);
ApplyResult(r => r.Type = HitResult.Miss);
return;
}

return true;
}
// Judge the tail if triggered by user.
if (userTriggered)
Tail.TriggerResult();

private void beginHoldAt(double timeOffset)
{
if (HoldStartTime != null)
// Wait until the tail is judged.
if (!Tail.Judged)
return;

if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss))
return;
// Determine the overall judgement for the object when the tail is judged.
var minimumResult = (HitResult)Math.Min((int)Head.Result.Type, (int)Tail.Result.Type);
ApplyResult(r => r.Type = minimumResult);
}

HoldStartTime = Time.Current;
public override bool OnPressed(RushAction action)
{
if (!LaneMatchesAction(action) || Head.Judged)
return false;

UpdateResult(true);
return Head.Judged;
}

public override void OnReleased(RushAction action)
{
if (!LaneMatchesAction(action))
return;

pressCount--;

if (pressCount > 0)
// Check if there was also another action holding the same note sheet,
// and use it in replace to this released one if so. (support for switching keys during hold)
if (ActionInputManager.PressedActions.Count(LaneMatchesAction) > 1)
return;

if (HasBroken.Value || HoldStartTime == null || HoldEndTime != null)
// Note sheet not held yet (i.e. not our time yet) or already broken / finished.
if (!Head.IsHit || Judged)
return;

HoldEndTime = Time.Current;

var tailOffset = Time.Current - Tail.HitObject.StartTime;
tailOffset /= DrawableNoteSheetTail.RELEASE_WINDOW_LENIENCE;

if (CompletionAmount < REQUIRED_COMPLETION)
HasBroken.Value = true;
else if (Tail.HitObject.HitWindows.CanBeHit(tailOffset))
{
Tail.UpdateResult(true);
HasBroken.Value = !Tail.IsHit;
}
UpdateResult(true);
}

protected override void Update()
{
base.Update();

if (Head.IsHit && !Tail.AllJudged || HasBroken.Value)
if (Head.IsHit)
holdStar.Show();
else
holdStar.Hide();

float targetRatio = 0;

if (HoldStartTime != null)
{
var targetTime = HoldEndTime ?? Time.Current;
targetRatio = (float)Math.Clamp((targetTime - HitObject.StartTime) / HitObject.Duration, 0, 1);
}

bodyContainer.Width = 1 - targetRatio;
holdStar.X = DrawWidth * (float)Progress;
holdStar.Y = DrawHeight / 2f;
holdStar.X = DrawWidth * targetRatio;

// Keep the body piece width in-line with ours and
// start cutting its container's width as we hold it.
BodyPiece.Width = DrawWidth;
bodyContainer.Width = 1 - (float)Progress;
}

public override void PlaySamples()
Expand Down
Loading