2019-12-26 16:48:46 +08:00
// 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.
2022-06-17 15:37:17 +08:00
#nullable disable
2020-09-25 22:29:40 +08:00
using System.Threading ;
2019-12-26 16:48:46 +08:00
using NUnit.Framework ;
using osu.Framework.Graphics ;
2020-01-09 12:43:44 +08:00
using osu.Framework.Utils ;
2019-12-26 16:48:46 +08:00
using osu.Framework.Testing ;
using osu.Framework.Timing ;
using osu.Game.Beatmaps ;
2020-09-25 22:29:40 +08:00
using osu.Game.Beatmaps.ControlPoints ;
2020-04-06 02:46:07 +08:00
using osu.Game.Beatmaps.Timing ;
2019-12-26 16:48:46 +08:00
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Objects ;
2020-09-25 22:29:40 +08:00
using osu.Game.Rulesets.Objects.Types ;
2019-12-26 16:48:46 +08:00
using osu.Game.Rulesets.Scoring ;
using osu.Game.Tests.Visual ;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
2022-11-24 13:32:20 +08:00
public partial class TestSceneDrainingHealthProcessor : OsuTestScene
2019-12-26 16:48:46 +08:00
{
private HealthProcessor processor ;
private ManualClock clock ;
[Test]
public void TestInitialHealthStartsAtOne ( )
{
createProcessor ( createBeatmap ( 1000 , 2000 ) ) ;
assertHealthEqualTo ( 1 ) ;
}
[Test]
public void TestHealthNotDrainedBeforeGameplayStart ( )
{
createProcessor ( createBeatmap ( 1000 , 2000 ) ) ;
setTime ( 100 ) ;
assertHealthEqualTo ( 1 ) ;
setTime ( 900 ) ;
assertHealthEqualTo ( 1 ) ;
}
[Test]
2020-05-12 01:03:13 +08:00
public void TestHealthDrainBetweenBreakAndObjects ( )
2019-12-26 16:48:46 +08:00
{
2020-05-12 01:03:13 +08:00
createProcessor ( createBeatmap ( 0 , 2000 , new BreakPeriod ( 325 , 375 ) ) ) ;
2019-12-26 16:48:46 +08:00
2020-05-12 01:03:13 +08:00
// 275 300 325 350 375 400 425
// hitobjects o o
// break [-------------]
// no drain [---------------------------]
setTime ( 285 ) ;
2020-04-06 02:46:07 +08:00
setHealth ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 295 ) ;
assertHealthNotEqualTo ( 1 ) ;
2020-04-06 02:46:07 +08:00
2020-05-12 01:03:13 +08:00
setTime ( 305 ) ;
2020-04-06 02:46:07 +08:00
setHealth ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 315 ) ;
2019-12-26 16:48:46 +08:00
assertHealthEqualTo ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 365 ) ;
2019-12-26 16:48:46 +08:00
assertHealthEqualTo ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 395 ) ;
2019-12-26 16:48:46 +08:00
assertHealthEqualTo ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 425 ) ;
assertHealthNotEqualTo ( 1 ) ;
2019-12-26 16:48:46 +08:00
}
2020-04-06 02:46:07 +08:00
[Test]
2020-05-12 01:03:13 +08:00
public void TestHealthDrainDuringMaximalBreak ( )
2020-04-06 02:46:07 +08:00
{
2020-05-12 01:03:13 +08:00
createProcessor ( createBeatmap ( 0 , 2000 , new BreakPeriod ( 300 , 400 ) ) ) ;
// 275 300 325 350 375 400 425
// hitobjects o o
// break [---------------------------]
// no drain [---------------------------]
2020-04-06 02:46:07 +08:00
2020-05-12 01:03:13 +08:00
setTime ( 285 ) ;
2020-04-06 02:46:07 +08:00
setHealth ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 295 ) ;
assertHealthNotEqualTo ( 1 ) ;
2020-04-06 02:46:07 +08:00
2020-05-12 01:03:13 +08:00
setTime ( 305 ) ;
2020-04-06 02:46:07 +08:00
setHealth ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 395 ) ;
2020-04-06 02:46:07 +08:00
assertHealthEqualTo ( 1 ) ;
2020-05-12 01:03:13 +08:00
setTime ( 425 ) ;
assertHealthNotEqualTo ( 1 ) ;
2020-04-06 02:46:07 +08:00
}
[Test]
public void TestHealthNotDrainedAfterGameplayEnd ( )
{
createProcessor ( createBeatmap ( 1000 , 2000 ) ) ;
setTime ( 2001 ) ; // After the hitobjects
setHealth ( 1 ) ; // Reset the current health for assertions to take place
setTime ( 2100 ) ;
assertHealthEqualTo ( 1 ) ;
setTime ( 3000 ) ;
assertHealthEqualTo ( 1 ) ;
}
2019-12-26 16:48:46 +08:00
[Test]
public void TestHealthDrainedDuringGameplay ( )
{
createProcessor ( createBeatmap ( 0 , 1000 ) ) ;
setTime ( 500 ) ;
assertHealthNotEqualTo ( 1 ) ;
}
2019-12-27 15:55:46 +08:00
[Test]
public void TestHealthGainedAfterRewind ( )
{
createProcessor ( createBeatmap ( 0 , 1000 ) ) ;
setTime ( 500 ) ;
setTime ( 0 ) ;
assertHealthEqualTo ( 1 ) ;
}
2019-12-26 16:48:46 +08:00
[Test]
public void TestHealthGainedOnHit ( )
{
Beatmap beatmap = createBeatmap ( 0 , 1000 ) ;
createProcessor ( beatmap ) ;
setTime ( 10 ) ; // Decrease health slightly
assertHealthNotEqualTo ( 1 ) ;
AddStep ( "apply hit result" , ( ) = > processor . ApplyResult ( new JudgementResult ( beatmap . HitObjects [ 0 ] , new Judgement ( ) ) { Type = HitResult . Perfect } ) ) ;
assertHealthEqualTo ( 1 ) ;
}
[Test]
public void TestHealthRemovedOnRevert ( )
{
var beatmap = createBeatmap ( 0 , 1000 ) ;
JudgementResult result = null ;
createProcessor ( beatmap ) ;
setTime ( 10 ) ; // Decrease health slightly
AddStep ( "apply hit result" , ( ) = > processor . ApplyResult ( result = new JudgementResult ( beatmap . HitObjects [ 0 ] , new Judgement ( ) ) { Type = HitResult . Perfect } ) ) ;
AddStep ( "revert hit result" , ( ) = > processor . RevertResult ( result ) ) ;
assertHealthNotEqualTo ( 1 ) ;
}
2022-04-27 03:19:20 +08:00
[Test]
public void TestFailConditions ( )
{
var beatmap = createBeatmap ( 0 , 1000 ) ;
createProcessor ( beatmap ) ;
AddStep ( "setup fail conditions" , ( ) = > processor . FailConditions + = ( ( _ , result ) = > result . Type = = HitResult . Miss ) ) ;
AddStep ( "apply perfect hit result" , ( ) = > processor . ApplyResult ( new JudgementResult ( beatmap . HitObjects [ 0 ] , new Judgement ( ) ) { Type = HitResult . Perfect } ) ) ;
AddAssert ( "not failed" , ( ) = > ! processor . HasFailed ) ;
AddStep ( "apply miss hit result" , ( ) = > processor . ApplyResult ( new JudgementResult ( beatmap . HitObjects [ 0 ] , new Judgement ( ) ) { Type = HitResult . Miss } ) ) ;
AddAssert ( "failed" , ( ) = > processor . HasFailed ) ;
}
2022-04-27 05:05:15 +08:00
[TestCase(HitResult.Miss)]
[TestCase(HitResult.Meh)]
public void TestMultipleFailConditions ( HitResult resultApplied )
2022-04-27 03:19:20 +08:00
{
var beatmap = createBeatmap ( 0 , 1000 ) ;
createProcessor ( beatmap ) ;
AddStep ( "setup multiple fail conditions" , ( ) = >
{
processor . FailConditions + = ( ( _ , result ) = > result . Type = = HitResult . Miss ) ;
processor . FailConditions + = ( ( _ , result ) = > result . Type = = HitResult . Meh ) ;
} ) ;
AddStep ( "apply perfect hit result" , ( ) = > processor . ApplyResult ( new JudgementResult ( beatmap . HitObjects [ 0 ] , new Judgement ( ) ) { Type = HitResult . Perfect } ) ) ;
AddAssert ( "not failed" , ( ) = > ! processor . HasFailed ) ;
2022-06-20 20:39:47 +08:00
AddStep ( $"apply {resultApplied.ToString().ToLowerInvariant()} hit result" , ( ) = > processor . ApplyResult ( new JudgementResult ( beatmap . HitObjects [ 0 ] , new Judgement ( ) ) { Type = resultApplied } ) ) ;
2022-04-27 03:19:20 +08:00
AddAssert ( "failed" , ( ) = > processor . HasFailed ) ;
}
2020-07-11 19:17:40 +08:00
[Test]
public void TestBonusObjectsExcludedFromDrain ( )
{
var beatmap = new Beatmap
{
2021-10-02 11:34:29 +08:00
Difficulty = { DrainRate = 10 }
2020-07-11 19:17:40 +08:00
} ;
beatmap . HitObjects . Add ( new JudgeableHitObject { StartTime = 0 } ) ;
for ( double time = 0 ; time < 5000 ; time + = 100 )
2020-09-29 14:25:31 +08:00
beatmap . HitObjects . Add ( new JudgeableHitObject ( HitResult . LargeBonus ) { StartTime = time } ) ;
2020-07-11 19:17:40 +08:00
beatmap . HitObjects . Add ( new JudgeableHitObject { StartTime = 5000 } ) ;
createProcessor ( beatmap ) ;
setTime ( 4900 ) ; // Get close to the second combo-affecting object
assertHealthNotEqualTo ( 0 ) ;
}
2020-09-25 22:29:40 +08:00
[Test]
public void TestSingleLongObjectDoesNotDrain ( )
{
var beatmap = new Beatmap
{
HitObjects = { new JudgeableLongHitObject ( ) }
} ;
beatmap . HitObjects [ 0 ] . ApplyDefaults ( new ControlPointInfo ( ) , new BeatmapDifficulty ( ) ) ;
createProcessor ( beatmap ) ;
setTime ( 0 ) ;
assertHealthEqualTo ( 1 ) ;
setTime ( 5000 ) ;
assertHealthEqualTo ( 1 ) ;
}
2020-04-06 02:46:07 +08:00
private Beatmap createBeatmap ( double startTime , double endTime , params BreakPeriod [ ] breaks )
2019-12-26 16:48:46 +08:00
{
var beatmap = new Beatmap
{
2021-10-02 11:34:29 +08:00
Difficulty = { DrainRate = 10 }
2019-12-26 16:48:46 +08:00
} ;
2020-05-12 01:03:13 +08:00
for ( double time = startTime ; time < = endTime ; time + = 100 )
2020-04-06 02:46:07 +08:00
{
2019-12-26 16:48:46 +08:00
beatmap . HitObjects . Add ( new JudgeableHitObject { StartTime = time } ) ;
2020-04-06 02:46:07 +08:00
}
beatmap . Breaks . AddRange ( breaks ) ;
2019-12-26 16:48:46 +08:00
return beatmap ;
}
private void createProcessor ( Beatmap beatmap ) = > AddStep ( "create processor" , ( ) = >
{
Child = processor = new DrainingHealthProcessor ( beatmap . HitObjects [ 0 ] . StartTime ) . With ( d = >
{
d . RelativeSizeAxes = Axes . Both ;
d . Clock = new FramedClock ( clock = new ManualClock ( ) ) ;
} ) ;
processor . ApplyBeatmap ( beatmap ) ;
} ) ;
private void setTime ( double time ) = > AddStep ( $"set time = {time}" , ( ) = > clock . CurrentTime = time ) ;
private void setHealth ( double health ) = > AddStep ( $"set health = {health}" , ( ) = > processor . Health . Value = health ) ;
private void assertHealthEqualTo ( double value )
= > AddAssert ( $"health = {value}" , ( ) = > Precision . AlmostEquals ( value , processor . Health . Value , 0.0001f ) ) ;
private void assertHealthNotEqualTo ( double value )
= > AddAssert ( $"health != {value}" , ( ) = > ! Precision . AlmostEquals ( value , processor . Health . Value , 0.0001f ) ) ;
private class JudgeableHitObject : HitObject
{
2020-09-29 14:25:31 +08:00
private readonly HitResult maxResult ;
2020-07-11 19:17:40 +08:00
2020-09-29 14:25:31 +08:00
public JudgeableHitObject ( HitResult maxResult = HitResult . Perfect )
2020-07-11 19:17:40 +08:00
{
2020-09-29 14:25:31 +08:00
this . maxResult = maxResult ;
2020-07-11 19:17:40 +08:00
}
2020-09-29 14:25:31 +08:00
public override Judgement CreateJudgement ( ) = > new TestJudgement ( maxResult ) ;
2020-02-23 12:01:30 +08:00
protected override HitWindows CreateHitWindows ( ) = > new HitWindows ( ) ;
2020-07-11 19:17:40 +08:00
private class TestJudgement : Judgement
{
2020-09-29 14:25:31 +08:00
public override HitResult MaxResult { get ; }
2020-07-11 19:17:40 +08:00
2020-09-29 14:25:31 +08:00
public TestJudgement ( HitResult maxResult )
2020-07-11 19:17:40 +08:00
{
2020-09-29 14:25:31 +08:00
MaxResult = maxResult ;
2020-07-11 19:17:40 +08:00
}
}
2019-12-26 16:48:46 +08:00
}
2020-09-25 22:29:40 +08:00
private class JudgeableLongHitObject : JudgeableHitObject , IHasDuration
{
public double EndTime = > StartTime + Duration ;
public double Duration { get ; set ; } = 5000 ;
public JudgeableLongHitObject ( )
2020-09-29 14:36:08 +08:00
: base ( HitResult . LargeBonus )
2020-09-25 22:29:40 +08:00
{
}
protected override void CreateNestedHitObjects ( CancellationToken cancellationToken )
{
base . CreateNestedHitObjects ( cancellationToken ) ;
AddNested ( new JudgeableHitObject ( ) ) ;
}
}
2019-12-26 16:48:46 +08:00
}
}