Skip to content

Commit

Permalink
Rewrite no release mod
Browse files Browse the repository at this point in the history
Per the request of spaceman_atlas, the No Release mod is rewritten to
avoid modifications to DrawableHoldNoteTail. The approach is based
on that of the Strict Tracking mod for the osu!(standard) ruleset,
injecting the mod behavior by replacing the normal hold note with
the mod's variant. The variant inherits most bevaior from the normal
hold note, but when creating nested hitobjects, it creates its own
hold note tail variant instead, which in turn is used to instantiate
the mod's variant of DrawableHoldNoteTail with a new behavior.

The time a judgement is awarded is changed from the end of its
Perfect window to the time of the tail itself.
  • Loading branch information
mcendu committed Jul 1, 2024
1 parent 463ab46 commit 1eb10e0
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 37 deletions.
10 changes: 5 additions & 5 deletions osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,10 @@ public void TestPressDuringNoteAndReleaseAtTail()

/// <summary>
/// -----[ ]--------------
/// xo
/// xo
/// </summary>
[Test]
public void TestPressAndReleaseJustAfterTailWithCloseByHead()
public void TestPressAndReleaseAfterTailWithCloseByHead()
{
const int duration = 30;

Expand All @@ -301,11 +301,11 @@ public void TestPressAndReleaseJustAfterTailWithCloseByHead()

performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_head + duration + 20, ManiaAction.Key1),
new ManiaReplayFrame(time_head + duration + 30),
new ManiaReplayFrame(time_head + duration + 60, ManiaAction.Key1),
new ManiaReplayFrame(time_head + duration + 70),
}, beatmap);

assertHeadJudgement(HitResult.Good);
assertHeadJudgement(HitResult.Ok);
assertTailJudgement(HitResult.Perfect);
}

Expand Down
86 changes: 79 additions & 7 deletions osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// 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.

using System.Linq;
using System.Threading;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;

namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNoRelease : Mod, IApplicableToDrawableHitObject
public partial class ManiaModNoRelease : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableRuleset<ManiaHitObject>
{
public override string Name => "No Release";

Expand All @@ -21,14 +27,80 @@ public class ManiaModNoRelease : Mod, IApplicableToDrawableHitObject

public override ModType Type => ModType.DifficultyReduction;

public void ApplyToDrawableHitObject(DrawableHitObject drawable)
public void ApplyToBeatmap(IBeatmap beatmap)
{
if (drawable is DrawableHoldNote hold)
var maniaBeatmap = (ManiaBeatmap)beatmap;
var hitObjects = maniaBeatmap.HitObjects.Select(obj =>
{
hold.HitObjectApplied += dho =>
if (obj is HoldNote hold)
return new NoReleaseHoldNote(hold);
return obj;
}).ToList();

maniaBeatmap.HitObjects = hitObjects;
}

public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
{
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;

foreach (var stage in maniaRuleset.Playfield.Stages)
{
foreach (var column in stage.Columns)
{
column.RegisterPool<NoReleaseTailNote, NoReleaseDrawableHoldNoteTail>(10, 50);
}
}
}

private partial class NoReleaseDrawableHoldNoteTail : DrawableHoldNoteTail
{
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
// apply perfect once the tail is reached
if (HoldNote.HoldStartTime != null && timeOffset >= 0)
ApplyResult(GetCappedResult(HitResult.Perfect));
else
base.CheckForResult(userTriggered, timeOffset);
}
}

private class NoReleaseTailNote : TailNote
{
}

private class NoReleaseHoldNote : HoldNote
{
public NoReleaseHoldNote(HoldNote hold)
{
StartTime = hold.StartTime;
Duration = hold.Duration;
Column = hold.Column;
NodeSamples = hold.NodeSamples;
}

protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
AddNested(Head = new HeadNote
{
StartTime = StartTime,
Column = Column,
Samples = GetNodeSamples(0),
});

AddNested(Tail = new NoReleaseTailNote
{
StartTime = EndTime,
Column = Column,
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
});

AddNested(Body = new HoldNoteBody
{
((DrawableHoldNote)dho).Tail.LateReleaseResult = HitResult.Perfect;
};
StartTime = StartTime,
Column = Column
});
}
}
}
Expand Down
24 changes: 2 additions & 22 deletions osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#nullable disable

using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Scoring;
Expand All @@ -19,11 +18,6 @@ public partial class DrawableHoldNoteTail : DrawableNote

protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;

/// <summary>
/// The minimum uncapped result for a late release.
/// </summary>
public HitResult LateReleaseResult { get; set; } = HitResult.Miss;

public DrawableHoldNoteTail()
: this(null)
{
Expand All @@ -38,23 +32,9 @@ public DrawableHoldNoteTail(TailNote tailNote)

public void UpdateResult() => base.UpdateResult(true);

protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);

protected override void CheckForResult(bool userTriggered, double timeOffset) =>
// Factor in the release lenience
double scaledTimeOffset = timeOffset / TailNote.RELEASE_WINDOW_LENIENCE;

// Check for late release
if (HoldNote.HoldStartTime != null && scaledTimeOffset > HitObject.HitWindows.WindowFor(LateReleaseResult))
{
ApplyResult(GetCappedResult(LateReleaseResult));
}
else
{
base.CheckForResult(userTriggered, scaledTimeOffset);
}
}
base.CheckForResult(userTriggered, timeOffset / TailNote.RELEASE_WINDOW_LENIENCE);

protected override HitResult GetCappedResult(HitResult result)
{
Expand Down
6 changes: 3 additions & 3 deletions osu.Game.Rulesets.Mania/Objects/HoldNote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,18 @@ public override int Column
/// <summary>
/// The head note of the hold.
/// </summary>
public HeadNote Head { get; private set; }
public HeadNote Head { get; protected set; }

/// <summary>
/// The tail note of the hold.
/// </summary>
public TailNote Tail { get; private set; }
public TailNote Tail { get; protected set; }

/// <summary>
/// The body of the hold.
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
/// </summary>
public HoldNoteBody Body { get; private set; }
public HoldNoteBody Body { get; protected set; }

public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;

Expand Down

0 comments on commit 1eb10e0

Please sign in to comment.