1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-14 18:23:21 +08:00

Rewrite no release mod

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.
This commit is contained in:
Nathan Du 2024-07-01 19:34:33 +08:00
parent 463ab46fee
commit 1eb10e029c
4 changed files with 89 additions and 37 deletions

View File

@ -273,10 +273,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
/// <summary>
/// -----[ ]--------------
/// xo
/// xo
/// </summary>
[Test]
public void TestPressAndReleaseJustAfterTailWithCloseByHead()
public void TestPressAndReleaseAfterTailWithCloseByHead()
{
const int duration = 30;
@ -301,11 +301,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
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);
}

View File

@ -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";
@ -21,14 +27,80 @@ namespace osu.Game.Rulesets.Mania.Mods
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)
{
((DrawableHoldNote)dho).Tail.LateReleaseResult = HitResult.Perfect;
};
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
{
StartTime = StartTime,
Column = Column
});
}
}
}

View File

@ -3,7 +3,6 @@
#nullable disable
using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Scoring;
@ -19,11 +18,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
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)
{
@ -38,23 +32,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
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)
{

View File

@ -72,18 +72,18 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <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;