mirror of
https://github.com/ppy/osu.git
synced 2025-01-21 08:52:54 +08:00
Merge branch 'ppy:master' into adjust_beatmap_length_according_to_rate
This commit is contained in:
commit
3487b5865e
@ -11,7 +11,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||||
{
|
{
|
||||||
public partial class TestSceneCatchModPerfect : ModPerfectTestScene
|
public partial class TestSceneCatchModPerfect : ModFailConditionTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
{
|
{
|
||||||
public partial class TestSceneManiaModPerfect : ModPerfectTestScene
|
public partial class TestSceneManiaModPerfect : ModFailConditionTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
@ -24,5 +29,52 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss);
|
public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGreatHit() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new ManiaModPerfect(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Note
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(1020, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(2000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new ManiaModPerfect(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
EndTime = 3000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(2000)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaModSuddenDeath : ModFailConditionTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
public TestSceneManiaModSuddenDeath()
|
||||||
|
: base(new ManiaModSuddenDeath())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGreatHit() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new ManiaModSuddenDeath(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Note
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(1020, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(2000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new ManiaModSuddenDeath(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
EndTime = 3000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(2000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,26 @@
|
|||||||
// 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 osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModPerfect : ModPerfect
|
public class ManiaModPerfect : ModPerfect
|
||||||
{
|
{
|
||||||
|
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
||||||
|
{
|
||||||
|
if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Mania allows imperfect "Great" hits without failing.
|
||||||
|
if (result.Judgement.MaxResult == HitResult.Perfect)
|
||||||
|
return result.Type < HitResult.Great;
|
||||||
|
|
||||||
|
return result.Type != result.Judgement.MaxResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
{
|
{
|
||||||
public partial class TestSceneOsuModPerfect : ModPerfectTestScene
|
public partial class TestSceneOsuModPerfect : ModFailConditionTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
@ -50,5 +54,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
|
|
||||||
CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss);
|
CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissSliderTail() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModPerfect(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneOsuModStrictTracking : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSliderInput() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModStrictTracking(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(0, 100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(500, new Vector2(200, 0), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(200, 0)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1751, new Vector2(0, 100)),
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneOsuModSuddenDeath : ModFailConditionTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
public TestSceneOsuModSuddenDeath()
|
||||||
|
: base(new OsuModSuddenDeath())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissTail() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSuddenDeath(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissTick() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSuddenDeath(),
|
||||||
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
if (!slider.HeadCircle.IsHit)
|
if (!slider.HeadCircle.IsHit)
|
||||||
handleHitCircle(slider.HeadCircle);
|
handleHitCircle(slider.HeadCircle);
|
||||||
|
|
||||||
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
|
requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSpinner spinner:
|
case DrawableSpinner spinner:
|
||||||
|
@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
if (e.NewValue || slider.Judged) return;
|
if (e.NewValue || slider.Judged) return;
|
||||||
|
|
||||||
|
if (slider.Time.Current < slider.HitObject.StartTime)
|
||||||
|
return;
|
||||||
|
|
||||||
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
|
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
|
||||||
|
|
||||||
if (!tail.Judged)
|
if (!tail.Judged)
|
||||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
updateTracking(isMouseInFollowArea(Tracking));
|
updateTracking(IsMouseInFollowArea(Tracking));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PostProcessHeadJudgement(DrawableSliderHead head)
|
public void PostProcessHeadJudgement(DrawableSliderHead head)
|
||||||
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (!head.Judged || !head.Result.IsHit)
|
if (!head.Judged || !head.Result.IsHit)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!isMouseInFollowArea(true))
|
if (!IsMouseInFollowArea(true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Debug.Assert(screenSpaceMousePosition != null);
|
Debug.Assert(screenSpaceMousePosition != null);
|
||||||
@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// If all ticks were hit so far, enable tracking the full extent.
|
// If all ticks were hit so far, enable tracking the full extent.
|
||||||
// If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball.
|
// If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball.
|
||||||
// For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update().
|
// For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update().
|
||||||
updateTracking(allTicksInRange || isMouseInFollowArea(false));
|
updateTracking(allTicksInRange || IsMouseInFollowArea(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset)
|
public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset)
|
||||||
@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
/// Whether the mouse is currently in the follow area.
|
/// Whether the mouse is currently in the follow area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="expanded">Whether to test against the maximum area of the follow circle.</param>
|
/// <param name="expanded">Whether to test against the maximum area of the follow circle.</param>
|
||||||
private bool isMouseInFollowArea(bool expanded)
|
public bool IsMouseInFollowArea(bool expanded)
|
||||||
{
|
{
|
||||||
if (screenSpaceMousePosition is not Vector2 pos)
|
if (screenSpaceMousePosition is not Vector2 pos)
|
||||||
return false;
|
return false;
|
||||||
|
@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
||||||
|
|
||||||
|
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime);
|
||||||
|
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
|
||||||
|
|
||||||
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
|
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
|
||||||
|
72
osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Normal file
72
osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
|
{
|
||||||
|
public partial class OsuHealthProcessor : DrainingHealthProcessor
|
||||||
|
{
|
||||||
|
public OsuHealthProcessor(double drainStartTime, double drainLenience = 0)
|
||||||
|
: base(drainStartTime, drainLenience)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex;
|
||||||
|
|
||||||
|
protected override double GetHealthIncreaseFor(JudgementResult result)
|
||||||
|
{
|
||||||
|
switch (result.Type)
|
||||||
|
{
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
|
||||||
|
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
|
||||||
|
|
||||||
|
case HitResult.Miss:
|
||||||
|
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2);
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
// When classic slider mechanics are enabled, this result comes from the tail.
|
||||||
|
return 0.02;
|
||||||
|
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
switch (result.HitObject)
|
||||||
|
{
|
||||||
|
case SliderTick:
|
||||||
|
return 0.015;
|
||||||
|
|
||||||
|
case SliderHeadCircle:
|
||||||
|
case SliderTailCircle:
|
||||||
|
case SliderRepeat:
|
||||||
|
return 0.02;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Meh:
|
||||||
|
return 0.002;
|
||||||
|
|
||||||
|
case HitResult.Ok:
|
||||||
|
return 0.011;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
return 0.03;
|
||||||
|
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
return 0.0085;
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetHealthIncreaseFor(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ using osu.Game.Tests.Visual;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||||
{
|
{
|
||||||
public partial class TestSceneTaikoModPerfect : ModPerfectTestScene
|
public partial class TestSceneTaikoModPerfect : ModFailConditionTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset();
|
||||||
|
|
||||||
|
@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
||||||
=> result.Type.AffectsAccuracy()
|
=> (isRelevantResult(result.Judgement.MinResult) || isRelevantResult(result.Judgement.MaxResult) || isRelevantResult(result.Type))
|
||||||
&& result.Type != result.Judgement.MaxResult;
|
&& result.Type != result.Judgement.MaxResult;
|
||||||
|
|
||||||
|
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly double DrainLenience;
|
protected readonly double DrainLenience;
|
||||||
|
|
||||||
private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>();
|
private readonly List<HealthIncrease> healthIncreases = new List<HealthIncrease>();
|
||||||
|
private readonly Dictionary<int, double> densityMultiplierByGroup = new Dictionary<int, double>();
|
||||||
|
|
||||||
private double gameplayEndTime;
|
private double gameplayEndTime;
|
||||||
private double targetMinimumHealth;
|
private double targetMinimumHealth;
|
||||||
|
|
||||||
@ -133,14 +135,33 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
{
|
{
|
||||||
base.ApplyResultInternal(result);
|
base.ApplyResultInternal(result);
|
||||||
|
|
||||||
if (!result.Type.IsBonus())
|
if (IsSimulating && !result.Type.IsBonus())
|
||||||
healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result)));
|
{
|
||||||
|
healthIncreases.Add(new HealthIncrease(
|
||||||
|
result.HitObject.GetEndTime() + result.TimeOffset,
|
||||||
|
GetHealthIncreaseFor(result),
|
||||||
|
GetDensityGroup(result.HitObject)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject));
|
||||||
|
|
||||||
|
private double getDensityMultiplier(int? group)
|
||||||
|
{
|
||||||
|
if (group == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual int? GetDensityGroup(HitObject hitObject) => null;
|
||||||
|
|
||||||
protected override void Reset(bool storeResults)
|
protected override void Reset(bool storeResults)
|
||||||
{
|
{
|
||||||
base.Reset(storeResults);
|
base.Reset(storeResults);
|
||||||
|
|
||||||
|
densityMultiplierByGroup.Clear();
|
||||||
|
|
||||||
if (storeResults)
|
if (storeResults)
|
||||||
DrainRate = ComputeDrainRate();
|
DrainRate = ComputeDrainRate();
|
||||||
|
|
||||||
@ -152,6 +173,24 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (healthIncreases.Count <= 1)
|
if (healthIncreases.Count <= 1)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
// Normalise the health gain during sections with higher densities.
|
||||||
|
(int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases
|
||||||
|
.Where(i => i.Group != null)
|
||||||
|
.GroupBy(i => i.Group)
|
||||||
|
.Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (avgIncreasesByGroup.Length > 1)
|
||||||
|
{
|
||||||
|
double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease);
|
||||||
|
|
||||||
|
foreach ((int group, double avgIncrease) in avgIncreasesByGroup)
|
||||||
|
{
|
||||||
|
// Reduce the health increase for groups that return more health than average.
|
||||||
|
densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int adjustment = 1;
|
int adjustment = 1;
|
||||||
double result = 1;
|
double result = 1;
|
||||||
|
|
||||||
@ -165,8 +204,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
for (int i = 0; i < healthIncreases.Count; i++)
|
for (int i = 0; i < healthIncreases.Count; i++)
|
||||||
{
|
{
|
||||||
double currentTime = healthIncreases[i].time;
|
double currentTime = healthIncreases[i].Time;
|
||||||
double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime;
|
double lastTime = i > 0 ? healthIncreases[i - 1].Time : DrainStartTime;
|
||||||
|
|
||||||
while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime)
|
while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime)
|
||||||
{
|
{
|
||||||
@ -177,10 +216,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
currentBreak++;
|
currentBreak++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double multiplier = getDensityMultiplier(healthIncreases[i].Group);
|
||||||
|
|
||||||
// Apply health adjustments
|
// Apply health adjustments
|
||||||
currentHealth -= (currentTime - lastTime) * result;
|
currentHealth -= (currentTime - lastTime) * result;
|
||||||
lowestHealth = Math.Min(lowestHealth, currentHealth);
|
lowestHealth = Math.Min(lowestHealth, currentHealth);
|
||||||
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health);
|
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier);
|
||||||
|
|
||||||
// Common scenario for when the drain rate is definitely too harsh
|
// Common scenario for when the drain rate is definitely too harsh
|
||||||
if (lowestHealth < 0)
|
if (lowestHealth < 0)
|
||||||
@ -198,5 +239,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record struct HealthIncrease(double Time, double Amount, int? Group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,11 @@ using osu.Game.Rulesets.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
public abstract partial class ModPerfectTestScene : ModTestScene
|
public abstract partial class ModFailConditionTestScene : ModTestScene
|
||||||
{
|
{
|
||||||
private readonly ModPerfect mod;
|
private readonly ModFailCondition mod;
|
||||||
|
|
||||||
protected ModPerfectTestScene(ModPerfect mod)
|
protected ModFailConditionTestScene(ModFailCondition mod)
|
||||||
{
|
{
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
}
|
}
|
||||||
@ -26,15 +26,15 @@ namespace osu.Game.Tests.Visual
|
|||||||
HitObjects = { testData.HitObject }
|
HitObjects = { testData.HitObject }
|
||||||
},
|
},
|
||||||
Autoplay = !shouldMiss,
|
Autoplay = !shouldMiss,
|
||||||
PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss)
|
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss)
|
||||||
});
|
});
|
||||||
|
|
||||||
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer();
|
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModFailConditionTestPlayer(CurrentTestData, AllowFail);
|
||||||
|
|
||||||
private partial class PerfectModTestPlayer : TestPlayer
|
protected partial class ModFailConditionTestPlayer : ModTestPlayer
|
||||||
{
|
{
|
||||||
public PerfectModTestPlayer()
|
public ModFailConditionTestPlayer(ModTestData data, bool allowFail)
|
||||||
: base(showResults: false)
|
: base(data, allowFail)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -20,35 +20,35 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
protected sealed override bool HasCustomSteps => true;
|
protected sealed override bool HasCustomSteps => true;
|
||||||
|
|
||||||
private ModTestData currentTestData;
|
protected ModTestData CurrentTestData { get; private set; }
|
||||||
|
|
||||||
protected void CreateModTest(ModTestData testData) => CreateTest(() =>
|
protected void CreateModTest(ModTestData testData) => CreateTest(() =>
|
||||||
{
|
{
|
||||||
AddStep("set test data", () => currentTestData = testData);
|
AddStep("set test data", () => CurrentTestData = testData);
|
||||||
});
|
});
|
||||||
|
|
||||||
public override void TearDownSteps()
|
public override void TearDownSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("test passed", () =>
|
AddUntilStep("test passed", () =>
|
||||||
{
|
{
|
||||||
if (currentTestData == null)
|
if (CurrentTestData == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return currentTestData.PassCondition?.Invoke() ?? false;
|
return CurrentTestData.PassCondition?.Invoke() ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
base.TearDownSteps();
|
base.TearDownSteps();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset);
|
protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap ?? base.CreateBeatmap(ruleset);
|
||||||
|
|
||||||
protected sealed override TestPlayer CreatePlayer(Ruleset ruleset)
|
protected sealed override TestPlayer CreatePlayer(Ruleset ruleset)
|
||||||
{
|
{
|
||||||
var mods = new List<Mod>(SelectedMods.Value);
|
var mods = new List<Mod>(SelectedMods.Value);
|
||||||
|
|
||||||
if (currentTestData.Mods != null)
|
if (CurrentTestData.Mods != null)
|
||||||
mods.AddRange(currentTestData.Mods);
|
mods.AddRange(CurrentTestData.Mods);
|
||||||
if (currentTestData.Autoplay)
|
if (CurrentTestData.Autoplay)
|
||||||
mods.Add(ruleset.GetAutoplayMod());
|
mods.Add(ruleset.GetAutoplayMod());
|
||||||
|
|
||||||
SelectedMods.Value = mods;
|
SelectedMods.Value = mods;
|
||||||
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
return CreateModPlayer(ruleset);
|
return CreateModPlayer(ruleset);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail);
|
protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(CurrentTestData, AllowFail);
|
||||||
|
|
||||||
protected partial class ModTestPlayer : TestPlayer
|
protected partial class ModTestPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user