mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 06:13:03 +08:00
Merge pull request #25062 from smoogipoo/remove-hold-note-ticks
Remove osu!mania hold note ticks
This commit is contained in:
commit
f26e3afd29
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
c.Add(hitExplosionPools[poolIndex].Get(e =>
|
c.Add(hitExplosionPools[poolIndex].Get(e =>
|
||||||
{
|
{
|
||||||
e.Apply(new JudgementResult(new HitObject(), runCount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
|
e.Apply(new JudgementResult(new HitObject(), new ManiaJudgement()));
|
||||||
|
|
||||||
e.Anchor = Anchor.Centre;
|
e.Anchor = Anchor.Centre;
|
||||||
e.Origin = Anchor.Centre;
|
e.Origin = Anchor.Centre;
|
||||||
|
@ -54,7 +54,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||||
}
|
}
|
||||||
@ -73,7 +72,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Perfect);
|
assertTailJudgement(HitResult.Perfect);
|
||||||
assertNoteJudgement(HitResult.IgnoreHit);
|
assertNoteJudgement(HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
@ -92,7 +90,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||||
}
|
}
|
||||||
@ -111,7 +108,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +125,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +144,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +163,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Perfect);
|
assertTailJudgement(HitResult.Perfect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,10 +181,31 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xox o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAtStartThenReleaseAndImmediatelyRepress()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + 1),
|
||||||
|
new ManiaReplayFrame(time_head + 2, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertComboAtJudgement(0, 1);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
assertComboAtJudgement(1, 0);
|
||||||
|
assertComboAtJudgement(2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
/// xo x o
|
/// xo x o
|
||||||
@ -208,7 +222,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +241,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +258,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +275,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickHit);
|
|
||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +368,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
}, beatmap);
|
}, beatmap);
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
|
||||||
assertHitObjectJudgement(note, HitResult.Good);
|
assertHitObjectJudgement(note, HitResult.Good);
|
||||||
@ -405,7 +414,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
}, beatmap);
|
}, beatmap);
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
|
||||||
assertHitObjectJudgement(note, HitResult.Great);
|
assertHitObjectJudgement(note, HitResult.Great);
|
||||||
@ -425,7 +433,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,42 +483,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
.All(j => j.Type.IsHit()));
|
.All(j => j.Type.IsHit()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestHitTailBeforeLastTick()
|
|
||||||
{
|
|
||||||
const int tick_rate = 8;
|
|
||||||
const double tick_spacing = TimingControlPoint.DEFAULT_BEAT_LENGTH / tick_rate;
|
|
||||||
const double time_last_tick = time_head + tick_spacing * (int)((time_tail - time_head) / tick_spacing - 1);
|
|
||||||
|
|
||||||
var beatmap = new Beatmap<ManiaHitObject>
|
|
||||||
{
|
|
||||||
HitObjects =
|
|
||||||
{
|
|
||||||
new HoldNote
|
|
||||||
{
|
|
||||||
StartTime = time_head,
|
|
||||||
Duration = time_tail - time_head,
|
|
||||||
Column = 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
BeatmapInfo =
|
|
||||||
{
|
|
||||||
Difficulty = new BeatmapDifficulty { SliderTickRate = tick_rate },
|
|
||||||
Ruleset = new ManiaRuleset().RulesetInfo
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
performTest(new List<ReplayFrame>
|
|
||||||
{
|
|
||||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
|
||||||
new ManiaReplayFrame(time_last_tick - 5)
|
|
||||||
}, beatmap);
|
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
|
||||||
assertLastTickJudgement(HitResult.LargeTickMiss);
|
|
||||||
assertTailJudgement(HitResult.Ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestZeroLength()
|
public void TestZeroLength()
|
||||||
{
|
{
|
||||||
@ -551,11 +522,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
private void assertNoteJudgement(HitResult result)
|
private void assertNoteJudgement(HitResult result)
|
||||||
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
|
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
private void assertTickJudgement(HitResult result)
|
private void assertComboAtJudgement(int judgementIndex, int combo)
|
||||||
=> AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Select(j => j.Type), () => Does.Contain(result));
|
=> AddAssert($"combo at judgement {judgementIndex} is {combo}", () => judgementResults.ElementAt(judgementIndex).ComboAfterJudgement, () => Is.EqualTo(combo));
|
||||||
|
|
||||||
private void assertLastTickJudgement(HitResult result)
|
|
||||||
=> AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(result));
|
|
||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||||
|
|
||||||
|
13
osu.Game.Rulesets.Mania/Judgements/HoldNoteBodyJudgement.cs
Normal file
13
osu.Game.Rulesets.Mania/Judgements/HoldNoteBodyJudgement.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Judgements
|
||||||
|
{
|
||||||
|
public class HoldNoteBodyJudgement : ManiaJudgement
|
||||||
|
{
|
||||||
|
public override HitResult MaxResult => HitResult.IgnoreHit;
|
||||||
|
public override HitResult MinResult => HitResult.ComboBreak;
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
// 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.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Judgements
|
|
||||||
{
|
|
||||||
public class HoldNoteTickJudgement : ManiaJudgement
|
|
||||||
{
|
|
||||||
public override HitResult MaxResult => HitResult.LargeTickHit;
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,10 +35,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public DrawableHoldNoteHead Head => headContainer.Child;
|
public DrawableHoldNoteHead Head => headContainer.Child;
|
||||||
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
||||||
|
public DrawableHoldNoteBody Body => bodyContainer.Child;
|
||||||
|
|
||||||
private Container<DrawableHoldNoteHead> headContainer;
|
private Container<DrawableHoldNoteHead> headContainer;
|
||||||
private Container<DrawableHoldNoteTail> tailContainer;
|
private Container<DrawableHoldNoteTail> tailContainer;
|
||||||
private Container<DrawableHoldNoteTick> tickContainer;
|
private Container<DrawableHoldNoteBody> bodyContainer;
|
||||||
|
|
||||||
private PausableSkinnableSound slidingSample;
|
private PausableSkinnableSound slidingSample;
|
||||||
|
|
||||||
@ -60,12 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public double? HoldStartTime { get; private set; }
|
public double? HoldStartTime { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time at which the hold note has been broken, i.e. released too early, resulting in a reduced score.
|
/// Used to decide whether to visually clamp the hold note to the judgement line.
|
||||||
/// </summary>
|
|
||||||
public double? HoldBrokenTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the hold note has been released potentially without having caused a break.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private double? releaseTime;
|
private double? releaseTime;
|
||||||
|
|
||||||
@ -103,6 +99,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
bodyContainer = new Container<DrawableHoldNoteBody> { RelativeSizeAxes = Axes.Both },
|
||||||
bodyPiece = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece
|
bodyPiece = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -110,7 +107,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X
|
RelativeSizeAxes = Axes.X
|
||||||
},
|
},
|
||||||
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
|
||||||
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
||||||
slidingSample = new PausableSkinnableSound { Looping = true }
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||||
});
|
});
|
||||||
@ -118,7 +114,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
maskedContents.AddRange(new[]
|
maskedContents.AddRange(new[]
|
||||||
{
|
{
|
||||||
bodyPiece.CreateProxy(),
|
bodyPiece.CreateProxy(),
|
||||||
tickContainer.CreateProxy(),
|
|
||||||
tailContainer.CreateProxy(),
|
tailContainer.CreateProxy(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -136,7 +131,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
sizingContainer.Size = Vector2.One;
|
sizingContainer.Size = Vector2.One;
|
||||||
HoldStartTime = null;
|
HoldStartTime = null;
|
||||||
HoldBrokenTime = null;
|
|
||||||
releaseTime = null;
|
releaseTime = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,8 +148,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
tailContainer.Child = tail;
|
tailContainer.Child = tail;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableHoldNoteTick tick:
|
case DrawableHoldNoteBody body:
|
||||||
tickContainer.Add(tick);
|
bodyContainer.Child = body;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,7 +159,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
base.ClearNestedHitObjects();
|
base.ClearNestedHitObjects();
|
||||||
headContainer.Clear(false);
|
headContainer.Clear(false);
|
||||||
tailContainer.Clear(false);
|
tailContainer.Clear(false);
|
||||||
tickContainer.Clear(false);
|
bodyContainer.Clear(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
@ -178,8 +172,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
case HeadNote head:
|
case HeadNote head:
|
||||||
return new DrawableHoldNoteHead(head);
|
return new DrawableHoldNoteHead(head);
|
||||||
|
|
||||||
case HoldNoteTick tick:
|
case HoldNoteBody body:
|
||||||
return new DrawableHoldNoteTick(tick);
|
return new DrawableHoldNoteBody(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
@ -266,20 +260,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
if (Tail.AllJudged)
|
if (Tail.AllJudged)
|
||||||
{
|
{
|
||||||
foreach (var tick in tickContainer)
|
|
||||||
{
|
|
||||||
if (!tick.Judged)
|
|
||||||
tick.MissForcefully();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Tail.IsHit)
|
if (Tail.IsHit)
|
||||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
else
|
else
|
||||||
MissForcefully();
|
MissForcefully();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Tail.Judged && !Tail.IsHit)
|
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||||
HoldBrokenTime = Time.Current;
|
if (Tail.AllJudged && !Body.AllJudged)
|
||||||
|
Body.TriggerResult(Tail.IsHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void MissForcefully()
|
public override void MissForcefully()
|
||||||
@ -333,22 +322,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (e.Action != Action.Value)
|
if (e.Action != Action.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Make sure a hold was started
|
|
||||||
if (HoldStartTime == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||||
if ((Clock as IGameplayClock)?.IsRewinding == true)
|
if ((Clock as IGameplayClock)?.IsRewinding == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Tail.UpdateResult();
|
// When our action is released and we are in the middle of a hold, there's a chance that
|
||||||
endHold();
|
// the user has released too early (before the tail).
|
||||||
|
//
|
||||||
|
// In such a case, we want to record this against the DrawableHoldNoteBody.
|
||||||
|
if (HoldStartTime != null)
|
||||||
|
{
|
||||||
|
Tail.UpdateResult();
|
||||||
|
Body.TriggerResult(Tail.IsHit);
|
||||||
|
|
||||||
// If the key has been released too early, the user should not receive full score for the release
|
endHold();
|
||||||
if (!Tail.IsHit)
|
releaseTime = Time.Current;
|
||||||
HoldBrokenTime = Time.Current;
|
}
|
||||||
|
|
||||||
releaseTime = Time.Current;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endHold()
|
private void endHold()
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
|
{
|
||||||
|
public partial class DrawableHoldNoteBody : DrawableManiaHitObject<HoldNoteBody>
|
||||||
|
{
|
||||||
|
public bool HasHoldBreak => AllJudged && !IsHit;
|
||||||
|
|
||||||
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
|
public DrawableHoldNoteBody()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteBody(HoldNoteBody hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TriggerResult(bool hit)
|
||||||
|
{
|
||||||
|
if (AllJudged) return;
|
||||||
|
|
||||||
|
ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||||
if (result > HitResult.Meh && (!HoldNote.Head.IsHit || HoldNote.HoldBrokenTime != null))
|
bool hasComboBreak = !HoldNote.Head.IsHit || HoldNote.Body.HasHoldBreak;
|
||||||
|
|
||||||
|
if (result > HitResult.Meh && hasComboBreak)
|
||||||
result = HitResult.Meh;
|
result = HitResult.Meh;
|
||||||
|
|
||||||
r.Type = result;
|
r.Type = result;
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Visualises a <see cref="HoldNoteTick"/> hit object.
|
|
||||||
/// </summary>
|
|
||||||
public partial class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// References the time at which the user started holding the hold note.
|
|
||||||
/// </summary>
|
|
||||||
private Func<double?> holdStartTime;
|
|
||||||
|
|
||||||
private Container glowContainer;
|
|
||||||
|
|
||||||
public DrawableHoldNoteTick()
|
|
||||||
: this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
|
||||||
: base(hitObject)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre;
|
|
||||||
Origin = Anchor.TopCentre;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
AddInternal(glowContainer = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
AlwaysPresent = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
|
||||||
{
|
|
||||||
glowContainer.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Radius = 2f,
|
|
||||||
Roundness = 15f,
|
|
||||||
Colour = colour.NewValue.Opacity(0.3f)
|
|
||||||
};
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApply()
|
|
||||||
{
|
|
||||||
base.OnApply();
|
|
||||||
|
|
||||||
Debug.Assert(ParentHitObject != null);
|
|
||||||
|
|
||||||
var holdNote = (DrawableHoldNote)ParentHitObject;
|
|
||||||
holdStartTime = () => holdNote.HoldStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnFree()
|
|
||||||
{
|
|
||||||
base.OnFree();
|
|
||||||
|
|
||||||
holdStartTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
|
||||||
{
|
|
||||||
if (Time.Current < HitObject.StartTime)
|
|
||||||
return;
|
|
||||||
|
|
||||||
double? startTime = holdStartTime?.Invoke();
|
|
||||||
|
|
||||||
if (startTime == null || startTime > HitObject.StartTime)
|
|
||||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
|
||||||
else
|
|
||||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The head note of a <see cref="HoldNote"/>.
|
||||||
|
/// </summary>
|
||||||
public class HeadNote : Note
|
public class HeadNote : Note
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -81,27 +79,18 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public TailNote Tail { get; private set; }
|
public TailNote Tail { get; private set; }
|
||||||
|
|
||||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time between ticks of this hold.
|
/// The body of the hold.
|
||||||
|
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private double tickSpacing = 50;
|
public HoldNoteBody Body { get; private set; }
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||||
{
|
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
|
||||||
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
createTicks(cancellationToken);
|
|
||||||
|
|
||||||
AddNested(Head = new HeadNote
|
AddNested(Head = new HeadNote
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
@ -115,23 +104,12 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
Column = Column,
|
Column = Column,
|
||||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private void createTicks(CancellationToken cancellationToken)
|
AddNested(Body = new HoldNoteBody
|
||||||
{
|
|
||||||
if (tickSpacing == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
|
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
StartTime = StartTime,
|
||||||
|
Column = Column
|
||||||
AddNested(new HoldNoteTick
|
});
|
||||||
{
|
|
||||||
StartTime = t,
|
|
||||||
Column = Column
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||||
|
21
osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs
Normal file
21
osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs
Normal file
@ -0,0 +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 osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The body of a <see cref="HoldNote"/>.
|
||||||
|
/// Mostly a dummy hitobject that provides the judgement for the "holding" state.<br />
|
||||||
|
/// On hit - the hold note was held correctly for the full duration.<br />
|
||||||
|
/// On miss - the hold note was released at some point during its judgement period.
|
||||||
|
/// </summary>
|
||||||
|
public class HoldNoteBody : ManiaHitObject
|
||||||
|
{
|
||||||
|
public override Judgement CreateJudgement() => new HoldNoteBodyJudgement();
|
||||||
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
// 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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A scoring tick of a hold note.
|
|
||||||
/// </summary>
|
|
||||||
public class HoldNoteTick : ManiaHitObject
|
|
||||||
{
|
|
||||||
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
|
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,6 +6,9 @@ using osu.Game.Rulesets.Mania.Judgements;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The tail note of a <see cref="HoldNote"/>.
|
||||||
|
/// </summary>
|
||||||
public class TailNote : Note
|
public class TailNote : Note
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -209,7 +209,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
|
||||||
|
if (holdNote.Body.HasHoldBreak)
|
||||||
|
missFadeTime.Value = holdNote.Body.Result.TimeAbsolute;
|
||||||
|
|
||||||
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
|
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Judgements;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -69,9 +68,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
|
|
||||||
public void Animate(JudgementResult result)
|
public void Animate(JudgementResult result)
|
||||||
{
|
{
|
||||||
if (result.Judgement is HoldNoteTickJudgement)
|
|
||||||
return;
|
|
||||||
|
|
||||||
(explosion as IFramedAnimation)?.GotoFrame(0);
|
(explosion as IFramedAnimation)?.GotoFrame(0);
|
||||||
|
|
||||||
explosion?.FadeInFromZero(FADE_IN_DURATION)
|
explosion?.FadeInFromZero(FADE_IN_DURATION)
|
||||||
|
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||||
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||||
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||||
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
|
RegisterPool<HoldNoteBody, DrawableHoldNoteBody>(10, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSourceChanged()
|
private void onSourceChanged()
|
||||||
|
@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Judgements;
|
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -150,9 +149,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
// scale roughly in-line with visual appearance of notes
|
// scale roughly in-line with visual appearance of notes
|
||||||
Vector2 scale = new Vector2(1, 0.6f);
|
Vector2 scale = new Vector2(1, 0.6f);
|
||||||
|
|
||||||
if (result.Judgement is HoldNoteTickJudgement)
|
|
||||||
scale *= 0.5f;
|
|
||||||
|
|
||||||
this.ScaleTo(scale);
|
this.ScaleTo(scale);
|
||||||
|
|
||||||
largeFaint
|
largeFaint
|
||||||
|
@ -195,10 +195,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Tick judgements should not display text.
|
|
||||||
if (judgedObject is DrawableHoldNoteTick)
|
|
||||||
return;
|
|
||||||
|
|
||||||
judgements.Clear(false);
|
judgements.Clear(false);
|
||||||
judgements.Add(judgementPool.Get(j =>
|
judgements.Add(judgementPool.Get(j =>
|
||||||
{
|
{
|
||||||
|
36
osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs
Normal file
36
osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Rulesets.Scoring
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class HitResultTest
|
||||||
|
{
|
||||||
|
[TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss })]
|
||||||
|
[TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss })]
|
||||||
|
[TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss })]
|
||||||
|
[TestCase(new[] { HitResult.LargeBonus, HitResult.SmallBonus }, new[] { HitResult.IgnoreMiss })]
|
||||||
|
[TestCase(new[] { HitResult.IgnoreHit }, new[] { HitResult.IgnoreMiss, HitResult.ComboBreak })]
|
||||||
|
public void TestValidResultPairs(HitResult[] maxResults, HitResult[] minResults)
|
||||||
|
{
|
||||||
|
HitResult[] unsupportedResults = HitResultExtensions.ALL_TYPES.Where(t => !minResults.Contains(t)).ToArray();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
foreach (var max in maxResults)
|
||||||
|
{
|
||||||
|
foreach (var min in minResults)
|
||||||
|
Assert.DoesNotThrow(() => HitResultExtensions.ValidateHitResultPair(max, min), $"{max} + {min} should be supported.");
|
||||||
|
|
||||||
|
foreach (var unsupported in unsupportedResults)
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => HitResultExtensions.ValidateHitResultPair(max, unsupported), $"{max} + {unsupported} should not be supported.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -107,7 +107,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], new TestJudgement(maxResult))
|
var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].CreateJudgement())
|
||||||
{
|
{
|
||||||
Type = i == 2 ? minResult : hitResult
|
Type = i == 2 ? minResult : hitResult
|
||||||
};
|
};
|
||||||
@ -259,6 +259,41 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestComboBreak()
|
||||||
|
{
|
||||||
|
Assert.That(HitResult.ComboBreak.IncreasesCombo(), Is.False);
|
||||||
|
Assert.That(HitResult.ComboBreak.BreaksCombo(), Is.True);
|
||||||
|
Assert.That(HitResult.ComboBreak.AffectsCombo(), Is.True);
|
||||||
|
Assert.That(HitResult.ComboBreak.AffectsAccuracy(), Is.False);
|
||||||
|
Assert.That(HitResult.ComboBreak.IsBasic(), Is.False);
|
||||||
|
Assert.That(HitResult.ComboBreak.IsTick(), Is.False);
|
||||||
|
Assert.That(HitResult.ComboBreak.IsBonus(), Is.False);
|
||||||
|
Assert.That(HitResult.ComboBreak.IsHit(), Is.False);
|
||||||
|
Assert.That(HitResult.ComboBreak.IsScorable(), Is.True);
|
||||||
|
Assert.That(HitResultExtensions.ALL_TYPES, Does.Contain(HitResult.ComboBreak));
|
||||||
|
|
||||||
|
beatmap = new TestBeatmap(new RulesetInfo())
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new TestHitObject(HitResult.Great),
|
||||||
|
new TestHitObject(HitResult.IgnoreHit, HitResult.ComboBreak),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreProcessor = new TestScoreProcessor();
|
||||||
|
scoreProcessor.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Great });
|
||||||
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||||
|
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||||
|
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.ComboBreak });
|
||||||
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||||
|
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAccuracyWhenNearPerfect()
|
public void TestAccuracyWhenNearPerfect()
|
||||||
{
|
{
|
||||||
@ -275,7 +310,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
|
|
||||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], new TestJudgement(HitResult.Great))
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].CreateJudgement())
|
||||||
{
|
{
|
||||||
Type = i == 0 ? HitResult.Miss : HitResult.Great
|
Type = i == 0 ? HitResult.Miss : HitResult.Great
|
||||||
});
|
});
|
||||||
@ -293,24 +328,31 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
{
|
{
|
||||||
public override HitResult MaxResult { get; }
|
public override HitResult MaxResult { get; }
|
||||||
|
|
||||||
public TestJudgement(HitResult maxResult)
|
public override HitResult MinResult => minResult ?? base.MinResult;
|
||||||
|
|
||||||
|
private readonly HitResult? minResult;
|
||||||
|
|
||||||
|
public TestJudgement(HitResult maxResult, HitResult? minResult = null)
|
||||||
{
|
{
|
||||||
MaxResult = maxResult;
|
MaxResult = maxResult;
|
||||||
|
this.minResult = minResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestHitObject : HitObject
|
private class TestHitObject : HitObject
|
||||||
{
|
{
|
||||||
private readonly HitResult maxResult;
|
private readonly HitResult maxResult;
|
||||||
|
private readonly HitResult? minResult;
|
||||||
|
|
||||||
public override Judgement CreateJudgement()
|
public override Judgement CreateJudgement()
|
||||||
{
|
{
|
||||||
return new TestJudgement(maxResult);
|
return new TestJudgement(maxResult, minResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestHitObject(HitResult maxResult)
|
public TestHitObject(HitResult maxResult, HitResult? minResult = null)
|
||||||
{
|
{
|
||||||
this.maxResult = maxResult;
|
this.maxResult = maxResult;
|
||||||
|
this.minResult = minResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ namespace osu.Game.Graphics
|
|||||||
case HitResult.SmallTickMiss:
|
case HitResult.SmallTickMiss:
|
||||||
case HitResult.LargeTickMiss:
|
case HitResult.LargeTickMiss:
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
|
case HitResult.ComboBreak:
|
||||||
return Red;
|
return Red;
|
||||||
|
|
||||||
case HitResult.Meh:
|
case HitResult.Meh:
|
||||||
|
@ -35,7 +35,40 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum <see cref="HitResult"/> that can be achieved - the inverse of <see cref="MaxResult"/>.
|
/// The minimum <see cref="HitResult"/> that can be achieved - the inverse of <see cref="MaxResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HitResult MinResult
|
/// <remarks>
|
||||||
|
/// Defaults to a sane value for the given <see cref="MaxResult"/>. May be overridden to provide a supported custom value:
|
||||||
|
/// <list type="table">
|
||||||
|
/// <listheader>
|
||||||
|
/// <term><see cref="MaxResult"/>s</term>
|
||||||
|
/// <description>Valid <see cref="MinResult"/>s</description>
|
||||||
|
/// </listheader>
|
||||||
|
/// <item>
|
||||||
|
/// <term><see cref="HitResult.Perfect"/>, <see cref="HitResult.Great"/>, <see cref="HitResult.Good"/>, <see cref="HitResult.Ok"/>, <see cref="HitResult.Meh"/></term>
|
||||||
|
/// <description><see cref="HitResult.Miss"/></description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term><see cref="HitResult.LargeBonus"/></term>
|
||||||
|
/// <description><see cref="HitResult.IgnoreMiss"/></description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term><see cref="HitResult.SmallBonus"/></term>
|
||||||
|
/// <description><see cref="HitResult.IgnoreMiss"/></description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term><see cref="HitResult.SmallTickHit"/></term>
|
||||||
|
/// <description><see cref="HitResult.SmallTickMiss"/></description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term><see cref="HitResult.LargeTickHit"/></term>
|
||||||
|
/// <description><see cref="HitResult.LargeTickMiss"/></description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term><see cref="HitResult.IgnoreHit"/></term>
|
||||||
|
/// <description><see cref="HitResult.IgnoreMiss"/>, <see cref="HitResult.ComboBreak"/></description>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
public virtual HitResult MinResult
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
@ -672,6 +672,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
if (!Result.HasResult)
|
if (!Result.HasResult)
|
||||||
throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}.");
|
throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}.");
|
||||||
|
|
||||||
|
HitResultExtensions.ValidateHitResultPair(Result.Judgement.MaxResult, Result.Judgement.MinResult);
|
||||||
|
|
||||||
if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult))
|
if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
@ -120,6 +120,16 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
[Order(12)]
|
[Order(12)]
|
||||||
IgnoreHit,
|
IgnoreHit,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a combo break should occur, but does not otherwise affect score.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// May be paired with <see cref="IgnoreHit"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[EnumMember(Value = "combo_break")]
|
||||||
|
[Order(15)]
|
||||||
|
ComboBreak,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy).
|
/// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -165,6 +175,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
case HitResult.LargeTickHit:
|
case HitResult.LargeTickHit:
|
||||||
case HitResult.LargeTickMiss:
|
case HitResult.LargeTickMiss:
|
||||||
case HitResult.LegacyComboIncrease:
|
case HitResult.LegacyComboIncrease:
|
||||||
|
case HitResult.ComboBreak:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -177,11 +188,19 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool AffectsAccuracy(this HitResult result)
|
public static bool AffectsAccuracy(this HitResult result)
|
||||||
{
|
{
|
||||||
// LegacyComboIncrease is a special type which is neither a basic, tick, bonus, or accuracy-affecting result.
|
switch (result)
|
||||||
if (result == HitResult.LegacyComboIncrease)
|
{
|
||||||
return false;
|
// LegacyComboIncrease is a special non-gameplay type which is neither a basic, tick, bonus, or accuracy-affecting result.
|
||||||
|
case HitResult.LegacyComboIncrease:
|
||||||
|
return false;
|
||||||
|
|
||||||
return IsScorable(result) && !IsBonus(result);
|
// ComboBreak is a special type that only affects combo. It cannot be considered as basic, tick, bonus, or accuracy-affecting.
|
||||||
|
case HitResult.ComboBreak:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return IsScorable(result) && !IsBonus(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -189,11 +208,19 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsBasic(this HitResult result)
|
public static bool IsBasic(this HitResult result)
|
||||||
{
|
{
|
||||||
// LegacyComboIncrease is a special type which is neither a basic, tick, bonus, or accuracy-affecting result.
|
switch (result)
|
||||||
if (result == HitResult.LegacyComboIncrease)
|
{
|
||||||
return false;
|
// LegacyComboIncrease is a special non-gameplay type which is neither a basic, tick, bonus, or accuracy-affecting result.
|
||||||
|
case HitResult.LegacyComboIncrease:
|
||||||
|
return false;
|
||||||
|
|
||||||
return IsScorable(result) && !IsTick(result) && !IsBonus(result);
|
// ComboBreak is a special type that only affects combo. It cannot be considered as basic, tick, bonus, or accuracy-affecting.
|
||||||
|
case HitResult.ComboBreak:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return IsScorable(result) && !IsTick(result) && !IsBonus(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -242,6 +269,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
case HitResult.SmallTickMiss:
|
case HitResult.SmallTickMiss:
|
||||||
case HitResult.LargeTickMiss:
|
case HitResult.LargeTickMiss:
|
||||||
|
case HitResult.ComboBreak:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -254,11 +282,20 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsScorable(this HitResult result)
|
public static bool IsScorable(this HitResult result)
|
||||||
{
|
{
|
||||||
// LegacyComboIncrease is not actually scorable (in terms of usable by rulesets for that purpose), but needs to be defined as such to be correctly included in statistics output.
|
switch (result)
|
||||||
if (result == HitResult.LegacyComboIncrease)
|
{
|
||||||
return true;
|
// LegacyComboIncrease is not actually scorable (in terms of usable by rulesets for that purpose), but needs to be defined as such to be correctly included in statistics output.
|
||||||
|
case HitResult.LegacyComboIncrease:
|
||||||
|
return true;
|
||||||
|
|
||||||
return result >= HitResult.Miss && result < HitResult.IgnoreMiss;
|
// ComboBreak is its own type that affects score via combo.
|
||||||
|
case HitResult.ComboBreak:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Note that IgnoreHit and IgnoreMiss are excluded as they do not affect score.
|
||||||
|
return result >= HitResult.Miss && result < HitResult.IgnoreMiss;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -291,6 +328,30 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="result">The <see cref="HitResult"/> to get the index of.</param>
|
/// <param name="result">The <see cref="HitResult"/> to get the index of.</param>
|
||||||
/// <returns>The index of <paramref name="result"/>.</returns>
|
/// <returns>The index of <paramref name="result"/>.</returns>
|
||||||
public static int GetIndexForOrderedDisplay(this HitResult result) => order.IndexOf(result);
|
public static int GetIndexForOrderedDisplay(this HitResult result) => order.IndexOf(result);
|
||||||
|
|
||||||
|
public static void ValidateHitResultPair(HitResult maxResult, HitResult minResult)
|
||||||
|
{
|
||||||
|
if (maxResult == HitResult.None || !IsHit(maxResult))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxResult), $"{maxResult} is not a valid maximum judgement result.");
|
||||||
|
|
||||||
|
if (minResult == HitResult.None || IsHit(minResult))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minResult), $"{minResult} is not a valid minimum judgement result.");
|
||||||
|
|
||||||
|
if (maxResult == HitResult.IgnoreHit && minResult is not (HitResult.IgnoreMiss or HitResult.ComboBreak))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minResult), $"{minResult} is not a valid minimum result for a {maxResult} judgement.");
|
||||||
|
|
||||||
|
if (maxResult.IsBonus() && minResult != HitResult.IgnoreMiss)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.IgnoreMiss} is the only valid minimum result for a {maxResult} judgement.");
|
||||||
|
|
||||||
|
if (maxResult == HitResult.LargeTickHit && minResult != HitResult.LargeTickMiss)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement.");
|
||||||
|
|
||||||
|
if (maxResult == HitResult.SmallTickHit && minResult != HitResult.SmallTickMiss)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.SmallTickMiss} is the only valid minimum result for a {maxResult} judgement.");
|
||||||
|
|
||||||
|
if (maxResult.IsBasic() && minResult != HitResult.Miss)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.Miss} is the only valid minimum result for a {maxResult} judgement.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user