1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 14:27:25 +08:00

Make swells and drumrolls optional by default

This commit is contained in:
sw1tchbl4d3 2022-08-05 16:30:07 +02:00
parent e6761ef6b1
commit e0426836c1
11 changed files with 24 additions and 308 deletions

View File

@ -1,80 +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 System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays;
namespace osu.Game.Rulesets.Taiko.Tests.Mods
{
public class TestSceneTaikoModClassic : TaikoModTestScene
{
[Test]
public void TestHittingDrumRollsIsOptional() => CreateModTest(new ModTestData
{
Mod = new TaikoModClassic(),
Autoplay = false,
Beatmap = new TaikoBeatmap
{
BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo },
HitObjects = new List<TaikoHitObject>
{
new Hit
{
StartTime = 1000,
Type = HitType.Centre
},
new DrumRoll
{
StartTime = 3000,
EndTime = 6000
}
}
},
ReplayFrames = new List<ReplayFrame>
{
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001)
},
PassCondition = () => Player.ScoreProcessor.HasCompleted.Value
&& Player.ScoreProcessor.Combo.Value == 1
&& Player.ScoreProcessor.Accuracy.Value == 1
});
[Test]
public void TestHittingSwellsIsOptional() => CreateModTest(new ModTestData
{
Mod = new TaikoModClassic(),
Autoplay = false,
Beatmap = new TaikoBeatmap
{
BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo },
HitObjects = new List<TaikoHitObject>
{
new Hit
{
StartTime = 1000,
Type = HitType.Centre
},
new Swell
{
StartTime = 3000,
EndTime = 6000
}
}
},
ReplayFrames = new List<ReplayFrame>
{
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001)
},
PassCondition = () => Player.ScoreProcessor.HasCompleted.Value
&& Player.ScoreProcessor.Combo.Value == 1
&& Player.ScoreProcessor.Accuracy.Value == 1
});
}
}

View File

@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void TestDrumRoll(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new DrumRoll { StartTime = 1000, EndTime = 3000 }), shouldMiss); public void TestDrumRoll(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new DrumRoll { StartTime = 1000, EndTime = 3000 }, false), shouldMiss);
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }), shouldMiss); public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }, false), shouldMiss);
private class TestTaikoRuleset : TaikoRuleset private class TestTaikoRuleset : TaikoRuleset
{ {

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
}; };
[Test] [Test]
public void TestSpinnerDoesFail() public void TestSwellDoesNotFail()
{ {
bool judged = false; bool judged = false;
AddStep("Setup judgements", () => AddStep("Setup judgements", () =>
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Player.ScoreProcessor.NewJudgement += _ => judged = true; Player.ScoreProcessor.NewJudgement += _ => judged = true;
}); });
AddUntilStep("swell judged", () => judged); AddUntilStep("swell judged", () => judged);
AddAssert("failed", () => Player.GameplayState.HasFailed); AddAssert("not failed", () => !Player.GameplayState.HasFailed);
} }
} }
} }

View File

@ -9,17 +9,8 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoDrumRollJudgement : TaikoJudgement public class TaikoDrumRollJudgement : TaikoJudgement
{ {
protected override double HealthIncreaseFor(HitResult result) public override HitResult MaxResult => HitResult.IgnoreHit;
{
// Drum rolls can be ignored with no health penalty
switch (result)
{
case HitResult.Miss:
return 0;
default: protected override double HealthIncreaseFor(HitResult result) => 0;
return base.HealthIncreaseFor(result);
}
}
} }
} }

View File

@ -9,18 +9,8 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoDrumRollTickJudgement : TaikoJudgement public class TaikoDrumRollTickJudgement : TaikoJudgement
{ {
public override HitResult MaxResult => HitResult.SmallTickHit; public override HitResult MaxResult => HitResult.SmallBonus;
protected override double HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result) => 0;
{
switch (result)
{
case HitResult.SmallTickHit:
return 0.15;
default:
return 0;
}
}
} }
} }

View File

@ -9,16 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoSwellJudgement : TaikoJudgement public class TaikoSwellJudgement : TaikoJudgement
{ {
/// <summary> public override HitResult MaxResult => HitResult.LargeBonus;
/// The <see cref="HitResult"/> to grant when the player has hit more than half of swell ticks.
/// </summary>
public virtual HitResult PartialCompletionResult => HitResult.Ok;
protected override double HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {
case HitResult.Miss: case HitResult.IgnoreMiss:
return -0.65; return -0.65;
default: default:

View File

@ -1,170 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Mods namespace osu.Game.Rulesets.Taiko.Mods
{ {
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield, IApplicableAfterBeatmapConversion public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
{ {
private DrawableTaikoRuleset? drawableTaikoRuleset; private DrawableTaikoRuleset? drawableTaikoRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset) public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
{ {
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
playfield.ClassicHitTargetPosition.Value = true;
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
drawableTaikoRuleset.LockPlayfieldAspect.Value = false; drawableTaikoRuleset.LockPlayfieldAspect.Value = false;
drawableTaikoRuleset.Playfield.RegisterPool<ClassicDrumRoll, ClassicDrawableDrumRoll>(5); var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
drawableTaikoRuleset.Playfield.RegisterPool<ClassicDrumRollTick, DrawableDrumRollTick>(100); playfield.ClassicHitTargetPosition.Value = true;
drawableTaikoRuleset.Playfield.RegisterPool<ClassicSwell, ClassicDrawableSwell>(5);
} }
public void ApplyToBeatmap(IBeatmap beatmap)
{
var taikoBeatmap = (TaikoBeatmap)beatmap;
if (taikoBeatmap.HitObjects.Count == 0) return;
var hitObjects = taikoBeatmap.HitObjects.Select(ho =>
{
switch (ho)
{
case DrumRoll drumRoll:
return new ClassicDrumRoll(drumRoll);
case Swell swell:
return new ClassicSwell(swell);
default:
return ho;
}
}).ToList();
taikoBeatmap.HitObjects = hitObjects;
}
#region Classic drum roll
private class TaikoClassicDrumRollJudgement : TaikoDrumRollJudgement
{
public override HitResult MaxResult => HitResult.IgnoreHit;
}
private class ClassicDrumRoll : DrumRoll
{
public ClassicDrumRoll(DrumRoll original)
{
StartTime = original.StartTime;
Samples = original.Samples;
EndTime = original.EndTime;
Duration = original.Duration;
TickRate = original.TickRate;
RequiredGoodHits = original.RequiredGoodHits;
RequiredGreatHits = original.RequiredGreatHits;
}
public override Judgement CreateJudgement() => new TaikoClassicDrumRollJudgement();
protected override List<TaikoHitObject> CreateTicks(CancellationToken cancellationToken)
{
List<TaikoHitObject> oldTicks = base.CreateTicks(cancellationToken);
List<TaikoHitObject> newTicks = oldTicks.Select(oldTick =>
{
if (oldTick is DrumRollTick drumRollTick)
{
return new ClassicDrumRollTick(drumRollTick);
}
return oldTick;
}).ToList();
return newTicks;
}
}
private class TaikoClassicDrumRollTickJudgement : TaikoDrumRollTickJudgement
{
public override HitResult MaxResult => HitResult.SmallBonus;
}
private class ClassicDrumRollTick : DrumRollTick
{
public override Judgement CreateJudgement() => new TaikoClassicDrumRollTickJudgement();
public ClassicDrumRollTick(DrumRollTick original)
{
StartTime = original.StartTime;
Samples = original.Samples;
FirstTick = original.FirstTick;
TickSpacing = original.TickSpacing;
}
}
private class ClassicDrawableDrumRoll : DrawableDrumRoll
{
public override bool DisplayResult => false;
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered)
return;
if (timeOffset < 0)
return;
ApplyResult(r => r.Type = HitResult.IgnoreHit);
}
}
#endregion
#region Classic swell
private class TaikoClassicSwellJudgement : TaikoSwellJudgement
{
public override HitResult MaxResult => HitResult.LargeBonus;
public override HitResult PartialCompletionResult => HitResult.SmallBonus;
}
private class ClassicSwell : Swell
{
public ClassicSwell(Swell original)
{
StartTime = original.StartTime;
Samples = original.Samples;
EndTime = original.EndTime;
Duration = original.Duration;
RequiredHits = original.RequiredHits;
}
public override Judgement CreateJudgement() => new TaikoClassicSwellJudgement();
}
private class ClassicDrawableSwell : DrawableSwell
{
public override bool DisplayResult => false;
}
#endregion
public void Update(Playfield playfield) public void Update(Playfield playfield)
{ {
Debug.Assert(drawableTaikoRuleset != null); Debug.Assert(drawableTaikoRuleset != null);
@ -172,8 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
// Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
const float scroll_rate = 10; const float scroll_rate = 10;
Debug.Assert(drawableTaikoRuleset != null);
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space. // Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
float ratio = drawableTaikoRuleset.DrawHeight / 480; float ratio = drawableTaikoRuleset.DrawHeight / 480;

View File

@ -4,7 +4,6 @@
#nullable disable #nullable disable
using System; using System;
using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -16,7 +15,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -40,6 +38,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private Color4 colourIdle; private Color4 colourIdle;
private Color4 colourEngaged; private Color4 colourEngaged;
public override bool DisplayResult => false;
public DrawableDrumRoll() public DrawableDrumRoll()
: this(null) : this(null)
{ {
@ -140,13 +140,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (timeOffset < 0) if (timeOffset < 0)
return; return;
int countHit = NestedHitObjects.Count(o => o.IsHit);
if (countHit >= HitObject.RequiredGoodHits)
{
ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok);
}
else
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(r => r.Type = r.Judgement.MinResult);
} }

View File

@ -16,7 +16,7 @@ using osuTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing; private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing; private readonly CircularContainer expandingRing;
public override bool DisplayResult => false;
public DrawableSwell() public DrawableSwell()
: this(null) : this(null)
{ {
@ -222,11 +224,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
tick.TriggerResult(false); tick.TriggerResult(false);
} }
ApplyResult(r => ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.SmallBonus : r.Judgement.MinResult);
{
var swellJudgement = (TaikoSwellJudgement)r.Judgement;
r.Type = numHits > HitObject.RequiredHits / 2 ? swellJudgement.PartialCompletionResult : swellJudgement.MinResult;
});
} }
} }

View File

@ -4,8 +4,6 @@
#nullable disable #nullable disable
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -43,24 +41,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary> /// </summary>
public int TickRate = 1; public int TickRate = 1;
/// <summary>
/// Number of drum roll ticks required for a "Good" hit.
/// </summary>
public double RequiredGoodHits { get; protected set; }
/// <summary>
/// Number of drum roll ticks required for a "Great" hit.
/// </summary>
public double RequiredGreatHits { get; protected set; }
/// <summary> /// <summary>
/// The length (in milliseconds) between ticks of this drumroll. /// The length (in milliseconds) between ticks of this drumroll.
/// <para>Half of this value is the hit window of the ticks.</para> /// <para>Half of this value is the hit window of the ticks.</para>
/// </summary> /// </summary>
private double tickSpacing = 100; private double tickSpacing = 100;
private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@ -71,28 +57,19 @@ namespace osu.Game.Rulesets.Taiko.Objects
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
tickSpacing = timingPoint.BeatLength / TickRate; tickSpacing = timingPoint.BeatLength / TickRate;
overallDifficulty = difficulty.OverallDifficulty;
} }
protected override void CreateNestedHitObjects(CancellationToken cancellationToken) protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{ {
foreach (TaikoHitObject tick in CreateTicks(cancellationToken)) createTicks(cancellationToken);
{
AddNested(tick);
}
RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty);
RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty);
base.CreateNestedHitObjects(cancellationToken); base.CreateNestedHitObjects(cancellationToken);
} }
protected virtual List<TaikoHitObject> CreateTicks(CancellationToken cancellationToken) private void createTicks(CancellationToken cancellationToken)
{ {
List<TaikoHitObject> ticks = new List<TaikoHitObject>();
if (tickSpacing == 0) if (tickSpacing == 0)
return ticks; return;
bool first = true; bool first = true;
@ -100,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ticks.Add(new DrumRollTick AddNested(new DrumRollTick
{ {
FirstTick = first, FirstTick = first,
TickSpacing = tickSpacing, TickSpacing = tickSpacing,
@ -110,8 +87,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
first = false; first = false;
} }
return ticks;
} }
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();

View File

@ -196,9 +196,6 @@ namespace osu.Game.Rulesets.Taiko
{ {
switch (result) switch (result)
{ {
case HitResult.SmallTickHit:
return "drum tick";
case HitResult.SmallBonus: case HitResult.SmallBonus:
return "bonus"; return "bonus";
} }