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

Change osu!mania "perfect" judgements to only award bonus score #25111

Merged
merged 18 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 27 additions & 16 deletions osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ public partial class TestSceneManiaModDoubleTime : ModTestScene
{
private const double offset = 18;

protected override bool AllowFail => true;

protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();

[Test]
public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData
{
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value != 1,
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
&& Player.ScoreProcessor.Accuracy.Value == 1
&& Player.ScoreProcessor.TotalScore.Value == 1_000_000,
Autoplay = false,
Beatmap = new Beatmap
{
Expand All @@ -40,24 +44,31 @@ public partial class TestSceneManiaModDoubleTime : ModTestScene
});

[Test]
public void TestHitWindowWithDoubleTime() => CreateModTest(new ModTestData
public void TestHitWindowWithDoubleTime()
{
Mod = new ManiaModDoubleTime(),
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value == 1,
Autoplay = false,
Beatmap = new Beatmap
var doubleTime = new ManiaModDoubleTime();

CreateModTest(new ModTestData
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
Difficulty = { OverallDifficulty = 10 },
HitObjects = new List<HitObject>
Mod = doubleTime,
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
&& Player.ScoreProcessor.Accuracy.Value == 1
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_010 * doubleTime.ScoreMultiplier),
Autoplay = false,
Beatmap = new Beatmap
{
new Note { StartTime = 1000 }
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
Difficulty = { OverallDifficulty = 10 },
HitObjects = new List<HitObject>
{
new Note { StartTime = 1000 }
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new ManiaReplayFrame(1000 + offset, ManiaAction.Key1)
}
});
ReplayFrames = new List<ReplayFrame>
{
new ManiaReplayFrame(1000 + offset, ManiaAction.Key1)
}
});
}
}
}
19 changes: 8 additions & 11 deletions osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,12 @@ public void TestPressAtStartThenReleaseAndImmediatelyRepress()
});

assertHeadJudgement(HitResult.Perfect);
assertComboAtJudgement(0, 1);
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
assertComboAtJudgement(1, 1);
assertTailJudgement(HitResult.Meh);
assertComboAtJudgement(1, 0);
assertComboAtJudgement(2, 1);
assertComboAtJudgement(2, 0);
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
assertComboAtJudgement(4, 1);
}

/// <summary>
Expand Down Expand Up @@ -380,7 +382,8 @@ public void TestPressAndReleaseJustBeforeTailWithNearbyNote()
[Test]
public void TestPressAndReleaseJustAfterTailWithNearbyNote()
{
Note note;
// Next note within tail lenience
Note note = new Note { StartTime = time_tail + 50 };

var beatmap = new Beatmap<ManiaHitObject>
{
Expand All @@ -392,13 +395,7 @@ public void TestPressAndReleaseJustAfterTailWithNearbyNote()
Duration = time_tail - time_head,
Column = 0,
},
{
// Next note within tail lenience
note = new Note
{
StartTime = time_tail + 50
}
}
note
},
BeatmapInfo =
{
Expand Down
4 changes: 2 additions & 2 deletions osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void TestSimultaneousTickAndNote()
AddAssert("all objects perfectly judged",
() => judgementResults.Select(result => result.Type),
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
AddAssert("score is 1 million", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_030));
}

[Test]
Expand Down Expand Up @@ -87,7 +87,7 @@ public void TestSimultaneousLongNotes()
AddAssert("all objects perfectly judged",
() => judgementResults.Select(result => result.Type),
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
AddAssert("score is 1 million", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_040));
}

private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
Expand Down
3 changes: 3 additions & 0 deletions osu.Game.Rulesets.Mania/ManiaRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,9 @@ protected override IEnumerable<HitResult> GetValidHitResults()
HitResult.Good,
HitResult.Ok,
HitResult.Meh,

// HitResult.SmallBonus is used for awarding perfect bonus score but is not included here as
// it would be a bit redundant to show this to the user.
};
}

Expand Down
35 changes: 9 additions & 26 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 Down Expand Up @@ -33,35 +32,19 @@ 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
timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE;

if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = r.Judgement.MinResult);
base.CheckForResult(userTriggered, timeOffset / TailNote.RELEASE_WINDOW_LENIENCE);

return;
}

var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
return;

ApplyResult(r =>
{
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
bool hasComboBreak = !HoldNote.Head.IsHit || HoldNote.Body.HasHoldBreak;
protected override HitResult GetCappedResult(HitResult result)
{
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
bool hasComboBreak = !HoldNote.Head.IsHit || HoldNote.Body.HasHoldBreak;

if (result > HitResult.Meh && hasComboBreak)
result = HitResult.Meh;
if (result > HitResult.Meh && hasComboBreak)
return HitResult.Meh;

r.Type = result;
});
return result;
}

public override bool OnPressed(KeyBindingPressEvent<ManiaAction> e) => false; // Handled by the hold note
Expand Down
48 changes: 48 additions & 0 deletions osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
Expand All @@ -38,6 +40,8 @@ public partial class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHan

private Drawable headPiece;

private DrawableNotePerfectBonus perfectBonus;

public DrawableNote()
: this(null)
{
Expand Down Expand Up @@ -89,17 +93,35 @@ protected override void CheckForResult(bool userTriggered, double timeOffset)
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
{
perfectBonus.TriggerResult(false);
ApplyResult(r => r.Type = r.Judgement.MinResult);
}

return;
}

var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
return;

result = GetCappedResult(result);

perfectBonus.TriggerResult(result == HitResult.Perfect);
ApplyResult(r => r.Type = result);
}

public override void MissForcefully()
{
perfectBonus.TriggerResult(false);
base.MissForcefully();
}

/// <summary>
/// Some objects in mania may want to limit the max result.
/// </summary>
protected virtual HitResult GetCappedResult(HitResult result) => result;

public virtual bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{
if (e.Action != Action.Value)
Expand All @@ -115,6 +137,32 @@ public virtual void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{
}

protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableNotePerfectBonus bonus:
AddInternal(perfectBonus = bonus);
break;
}
}

protected override void ClearNestedHitObjects()
{
RemoveInternal(perfectBonus, false);
}

protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case NotePerfectBonus bonus:
return new DrawableNotePerfectBonus(bonus);
}

return base.CreateNestedHitObject(hitObject);
}

private void updateSnapColour()
{
if (beatmap == null || HitObject == null) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.

namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public partial class DrawableNotePerfectBonus : DrawableManiaHitObject<NotePerfectBonus>
{
public override bool DisplayResult => false;

public DrawableNotePerfectBonus()
: this(null!)
{
}

public DrawableNotePerfectBonus(NotePerfectBonus hitObject)
: base(hitObject)
{
}

/// <summary>
/// Apply a judgement result.
/// </summary>
/// <param name="hit">Whether this tick was reached.</param>
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
}
8 changes: 8 additions & 0 deletions osu.Game.Rulesets.Mania/Objects/Note.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;

Expand All @@ -12,5 +13,12 @@ namespace osu.Game.Rulesets.Mania.Objects
public class Note : ManiaHitObject
{
public override Judgement CreateJudgement() => new ManiaJudgement();

protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
base.CreateNestedHitObjects(cancellationToken);

AddNested(new NotePerfectBonus { StartTime = StartTime });
}
}
}
20 changes: 20 additions & 0 deletions osu.Game.Rulesets.Mania/Objects/NotePerfectBonus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Scoring;

namespace osu.Game.Rulesets.Mania.Objects
{
public class NotePerfectBonus : ManiaHitObject
{
public override Judgement CreateJudgement() => new NotePerfectBonusJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;

public class NotePerfectBonusJudgement : ManiaJudgement
{
public override HitResult MaxResult => HitResult.SmallBonus;
}
}
}
15 changes: 12 additions & 3 deletions osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,18 @@ protected override void LoadComplete()

private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state)
{
// ensure that the hold note is also faded out when the head/tail/any tick is missed.
if (state == ArmedState.Miss)
missFadeTime.Value ??= hitObject.HitStateUpdateTime;
switch (hitObject)
{
// Ensure that the hold note is also faded out when the head/tail/body is missed.
// Importantly, we filter out unrelated objects like DrawableNotePerfectBonus.
case DrawableHoldNoteTail:
case DrawableHoldNoteHead:
case DrawableHoldNoteBody:
if (state == ArmedState.Miss)
missFadeTime.Value ??= hitObject.HitStateUpdateTime;

break;
}
}

private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
Expand Down
1 change: 1 addition & 0 deletions osu.Game.Rulesets.Mania/UI/Column.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ private void load(GameHost host)
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());

RegisterPool<Note, DrawableNote>(10, 50);
RegisterPool<NotePerfectBonus, DrawableNotePerfectBonus>(10, 50);
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
Expand Down
4 changes: 2 additions & 2 deletions osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int e
[TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)]
[TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 79_333)]
[TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 158_667)]
[TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 302_402)]
[TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 317_626)]
[TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 492_894)]
[TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 492_894)]
[TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)]
Expand All @@ -86,7 +86,7 @@ public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int e
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)]
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 7_975)]
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 15_949)]
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 30_398)]
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 31_928)]
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 49_546)]
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 49_546)]
[TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)]
Expand Down