2019-08-30 14:10:11 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-06-23 00:14:29 +08:00
// See the LICENCE file in the repository root for full licence text.
2022-06-17 15:37:17 +08:00
#nullable disable
2020-12-08 13:28:30 +08:00
using System ;
2020-12-03 15:40:14 +08:00
using System.Collections.Generic ;
using System.Linq ;
2019-06-23 00:14:29 +08:00
using NUnit.Framework ;
using osu.Framework.Allocation ;
2020-02-17 13:40:07 +08:00
using osu.Framework.Graphics ;
2021-06-24 14:56:53 +08:00
using osu.Game.Rulesets.Catch.UI ;
2020-04-22 10:04:07 +08:00
using osu.Framework.Graphics.Containers ;
2020-12-03 15:40:14 +08:00
using osu.Framework.Testing ;
2021-04-22 15:56:23 +08:00
using osu.Framework.Utils ;
2020-12-03 15:40:14 +08:00
using osu.Game.Beatmaps ;
using osu.Game.Beatmaps.ControlPoints ;
using osu.Game.Configuration ;
2020-12-08 13:28:30 +08:00
using osu.Game.Rulesets.Catch.Judgements ;
2020-12-03 15:40:14 +08:00
using osu.Game.Rulesets.Catch.Objects ;
using osu.Game.Rulesets.Catch.Objects.Drawables ;
2020-12-08 13:28:30 +08:00
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Scoring ;
2020-12-03 15:40:14 +08:00
using osu.Game.Tests.Visual ;
2021-04-22 16:32:24 +08:00
using osuTK ;
2019-06-23 00:14:29 +08:00
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
2022-11-24 13:32:20 +08:00
public partial class TestSceneCatcher : OsuTestScene
2019-06-23 00:14:29 +08:00
{
2020-12-03 15:40:14 +08:00
[Resolved]
private OsuConfigManager config { get ; set ; }
2021-07-19 19:11:49 +08:00
private DroppedObjectContainer droppedObjectContainer ;
2020-12-03 15:40:14 +08:00
private TestCatcher catcher ;
[SetUp]
public void SetUp ( ) = > Schedule ( ( ) = >
2019-06-23 00:14:29 +08:00
{
2020-12-03 15:40:14 +08:00
var difficulty = new BeatmapDifficulty
{
CircleSize = 0 ,
} ;
2021-07-19 19:11:49 +08:00
droppedObjectContainer = new DroppedObjectContainer ( ) ;
2020-12-03 15:40:14 +08:00
2021-07-19 19:11:49 +08:00
Child = new Container
2019-06-23 00:14:29 +08:00
{
2021-07-19 19:11:49 +08:00
Anchor = Anchor . Centre ,
Children = new Drawable [ ]
{
droppedObjectContainer ,
2021-07-27 17:59:55 +08:00
catcher = new TestCatcher ( droppedObjectContainer , difficulty ) ,
2021-07-19 19:11:49 +08:00
}
} ;
2020-12-03 15:40:14 +08:00
} ) ;
2020-12-08 14:02:55 +08:00
[Test]
2020-12-08 14:21:47 +08:00
public void TestCatcherHyperStateReverted ( )
{
DrawableCatchHitObject drawableObject1 = null ;
DrawableCatchHitObject drawableObject2 = null ;
JudgementResult result1 = null ;
JudgementResult result2 = null ;
AddStep ( "catch hyper fruit" , ( ) = >
{
2020-12-08 20:07:12 +08:00
attemptCatch ( new Fruit { HyperDashTarget = new Fruit { X = 100 } } , out drawableObject1 , out result1 ) ;
2020-12-08 14:21:47 +08:00
} ) ;
AddStep ( "catch normal fruit" , ( ) = >
{
2020-12-08 20:07:12 +08:00
attemptCatch ( new Fruit ( ) , out drawableObject2 , out result2 ) ;
2020-12-08 14:21:47 +08:00
} ) ;
AddStep ( "revert second result" , ( ) = >
{
catcher . OnRevertResult ( drawableObject2 , result2 ) ;
} ) ;
checkHyperDash ( true ) ;
AddStep ( "revert first result" , ( ) = >
{
catcher . OnRevertResult ( drawableObject1 , result1 ) ;
} ) ;
checkHyperDash ( false ) ;
}
[Test]
public void TestCatcherAnimationStateReverted ( )
2020-12-08 14:02:55 +08:00
{
DrawableCatchHitObject drawableObject = null ;
JudgementResult result = null ;
AddStep ( "catch kiai fruit" , ( ) = >
{
2020-12-08 20:07:12 +08:00
attemptCatch ( new TestKiaiFruit ( ) , out drawableObject , out result ) ;
2020-12-08 14:02:55 +08:00
} ) ;
checkState ( CatcherAnimationState . Kiai ) ;
AddStep ( "revert result" , ( ) = >
{
catcher . OnRevertResult ( drawableObject , result ) ;
} ) ;
checkState ( CatcherAnimationState . Idle ) ;
}
2020-12-03 15:40:14 +08:00
[Test]
public void TestCatcherCatchWidth ( )
{
2021-10-27 12:04:41 +08:00
float halfWidth = Catcher . CalculateCatchWidth ( new BeatmapDifficulty { CircleSize = 0 } ) / 2 ;
2022-09-21 00:29:12 +08:00
AddStep ( "move catcher to center" , ( ) = > catcher . X = CatchPlayfield . CENTER_X ) ;
float leftPlateBounds = CatchPlayfield . CENTER_X - halfWidth ;
float rightPlateBounds = CatchPlayfield . CENTER_X + halfWidth ;
2020-12-03 15:40:14 +08:00
AddStep ( "catch fruit" , ( ) = >
{
2022-09-21 00:29:12 +08:00
attemptCatch ( new Fruit { X = leftPlateBounds + 1 } ) ;
attemptCatch ( new Fruit { X = rightPlateBounds - 1 } ) ;
2020-12-03 15:40:14 +08:00
} ) ;
checkPlate ( 2 ) ;
2022-09-21 00:29:12 +08:00
2020-12-03 15:40:14 +08:00
AddStep ( "miss fruit" , ( ) = >
{
2022-09-21 00:29:12 +08:00
attemptCatch ( new Fruit { X = leftPlateBounds - 1 } ) ;
attemptCatch ( new Fruit { X = rightPlateBounds + 1 } ) ;
2019-06-23 00:14:29 +08:00
} ) ;
2020-12-03 15:40:14 +08:00
checkPlate ( 2 ) ;
}
2022-09-20 18:41:06 +08:00
[Test]
public void TestFruitClampedToCatchableRegion ( )
{
AddStep ( "catch fruit left" , ( ) = > attemptCatch ( new Fruit { X = - CatchPlayfield . WIDTH } ) ) ;
checkPlate ( 1 ) ;
AddStep ( "move catcher to right" , ( ) = > catcher . X = CatchPlayfield . WIDTH ) ;
AddStep ( "catch fruit right" , ( ) = > attemptCatch ( new Fruit { X = CatchPlayfield . WIDTH * 2 } ) ) ;
checkPlate ( 2 ) ;
}
2020-12-03 15:40:14 +08:00
[Test]
2020-12-04 09:09:07 +08:00
public void TestFruitChangesCatcherState ( )
2020-12-03 15:40:14 +08:00
{
AddStep ( "miss fruit" , ( ) = > attemptCatch ( new Fruit { X = 100 } ) ) ;
checkState ( CatcherAnimationState . Fail ) ;
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
checkState ( CatcherAnimationState . Idle ) ;
AddStep ( "catch kiai fruit" , ( ) = > attemptCatch ( new TestKiaiFruit ( ) ) ) ;
checkState ( CatcherAnimationState . Kiai ) ;
}
[Test]
2020-12-04 09:09:07 +08:00
public void TestNormalFruitResetsHyperDashState ( )
{
AddStep ( "catch hyper fruit" , ( ) = > attemptCatch ( new Fruit
{
HyperDashTarget = new Fruit { X = 100 }
} ) ) ;
checkHyperDash ( true ) ;
AddStep ( "catch normal fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
checkHyperDash ( false ) ;
}
[Test]
public void TestTinyDropletMissPreservesCatcherState ( )
2020-12-03 15:40:14 +08:00
{
AddStep ( "catch hyper kiai fruit" , ( ) = > attemptCatch ( new TestKiaiFruit
{
HyperDashTarget = new Fruit { X = 100 }
} ) ) ;
AddStep ( "catch tiny droplet" , ( ) = > attemptCatch ( new TinyDroplet ( ) ) ) ;
AddStep ( "miss tiny droplet" , ( ) = > attemptCatch ( new TinyDroplet { X = 100 } ) ) ;
2020-12-04 09:09:07 +08:00
// catcher state and hyper dash state is preserved
2020-12-03 15:40:14 +08:00
checkState ( CatcherAnimationState . Kiai ) ;
checkHyperDash ( true ) ;
}
[Test]
2020-12-04 09:09:07 +08:00
public void TestBananaMissPreservesCatcherState ( )
2020-12-03 15:40:14 +08:00
{
AddStep ( "catch hyper kiai fruit" , ( ) = > attemptCatch ( new TestKiaiFruit
{
HyperDashTarget = new Fruit { X = 100 }
} ) ) ;
2020-12-04 09:09:07 +08:00
AddStep ( "miss banana" , ( ) = > attemptCatch ( new Banana { X = 100 } ) ) ;
// catcher state is preserved but hyper dash state is reset
checkState ( CatcherAnimationState . Kiai ) ;
2020-12-03 15:40:14 +08:00
checkHyperDash ( false ) ;
}
2021-11-19 03:07:03 +08:00
[Test]
2021-11-19 03:23:42 +08:00
public void TestLastBananaShouldClearPlateOnMiss ( )
2021-11-19 03:07:03 +08:00
{
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
checkPlate ( 1 ) ;
AddStep ( "miss banana" , ( ) = > attemptCatch ( new Banana { X = 100 } ) ) ;
checkPlate ( 1 ) ;
AddStep ( "miss last banana" , ( ) = > attemptCatch ( new Banana { LastInCombo = true , X = 100 } ) ) ;
checkPlate ( 0 ) ;
}
2021-11-19 03:23:42 +08:00
[Test]
public void TestLastBananaShouldClearPlateOnCatch ( )
{
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
checkPlate ( 1 ) ;
AddStep ( "catch banana" , ( ) = > attemptCatch ( new Banana ( ) ) ) ;
checkPlate ( 2 ) ;
AddStep ( "catch last banana" , ( ) = > attemptCatch ( new Banana { LastInCombo = true } ) ) ;
checkPlate ( 0 ) ;
}
2020-12-03 15:40:14 +08:00
[Test]
2021-04-22 15:56:23 +08:00
public void TestCatcherRandomStacking ( )
{
AddStep ( "catch more fruits" , ( ) = > attemptCatch ( ( ) = > new Fruit
{
2021-04-22 16:32:24 +08:00
X = ( RNG . NextSingle ( ) - 0.5f ) * Catcher . CalculateCatchWidth ( Vector2 . One )
2021-04-22 15:56:23 +08:00
} , 50 ) ) ;
}
[Test]
public void TestCatcherStackingSameCaughtPosition ( )
2020-12-03 15:40:14 +08:00
{
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
checkPlate ( 1 ) ;
2021-04-22 15:56:23 +08:00
AddStep ( "catch more fruits" , ( ) = > attemptCatch ( ( ) = > new Fruit ( ) , 9 ) ) ;
2020-12-03 15:40:14 +08:00
checkPlate ( 10 ) ;
AddAssert ( "caught objects are stacked" , ( ) = >
2021-06-21 18:08:43 +08:00
catcher . CaughtObjects . All ( obj = > obj . Y < = 0 ) & &
catcher . CaughtObjects . Any ( obj = > obj . Y = = 0 ) & &
2021-07-04 09:23:49 +08:00
catcher . CaughtObjects . Any ( obj = > obj . Y < 0 ) ) ;
2020-12-03 15:40:14 +08:00
}
[Test]
public void TestCatcherExplosionAndDropping ( )
{
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
AddStep ( "catch tiny droplet" , ( ) = > attemptCatch ( new TinyDroplet ( ) ) ) ;
AddAssert ( "tiny droplet is exploded" , ( ) = > catcher . CaughtObjects . Count ( ) = = 1 & & droppedObjectContainer . Count = = 1 ) ;
AddUntilStep ( "wait explosion" , ( ) = > ! droppedObjectContainer . Any ( ) ) ;
2021-04-22 15:56:23 +08:00
AddStep ( "catch more fruits" , ( ) = > attemptCatch ( ( ) = > new Fruit ( ) , 9 ) ) ;
2020-12-03 15:40:14 +08:00
AddStep ( "explode" , ( ) = > catcher . Explode ( ) ) ;
AddAssert ( "fruits are exploded" , ( ) = > ! catcher . CaughtObjects . Any ( ) & & droppedObjectContainer . Count = = 10 ) ;
AddUntilStep ( "wait explosion" , ( ) = > ! droppedObjectContainer . Any ( ) ) ;
2021-04-22 15:56:23 +08:00
AddStep ( "catch fruits" , ( ) = > attemptCatch ( ( ) = > new Fruit ( ) , 10 ) ) ;
2020-12-03 15:40:14 +08:00
AddStep ( "drop" , ( ) = > catcher . Drop ( ) ) ;
AddAssert ( "fruits are dropped" , ( ) = > ! catcher . CaughtObjects . Any ( ) & & droppedObjectContainer . Count = = 10 ) ;
}
2020-12-08 20:07:12 +08:00
[Test]
public void TestHitLightingColour ( )
2020-12-03 15:40:14 +08:00
{
2021-03-17 15:10:16 +08:00
AddStep ( "enable hit lighting" , ( ) = > config . SetValue ( OsuSetting . HitLighting , true ) ) ;
2020-12-03 15:40:14 +08:00
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
2022-09-22 19:43:38 +08:00
AddAssert ( "correct hit lighting colour" , ( ) = > catcher . ChildrenOfType < HitExplosion > ( ) . First ( ) ? . Entry ? . ObjectColour = = this . ChildrenOfType < DrawableCatchHitObject > ( ) . First ( ) . AccentColour . Value ) ;
2020-12-08 20:07:12 +08:00
}
[Test]
public void TestHitLightingDisabled ( )
{
2021-03-17 15:10:16 +08:00
AddStep ( "disable hit lighting" , ( ) = > config . SetValue ( OsuSetting . HitLighting , false ) ) ;
2020-12-08 20:07:12 +08:00
AddStep ( "catch fruit" , ( ) = > attemptCatch ( new Fruit ( ) ) ) ;
AddAssert ( "no hit lighting" , ( ) = > ! catcher . ChildrenOfType < HitExplosion > ( ) . Any ( ) ) ;
2020-12-03 15:40:14 +08:00
}
private void checkPlate ( int count ) = > AddAssert ( $"{count} objects on the plate" , ( ) = > catcher . CaughtObjects . Count ( ) = = count ) ;
private void checkState ( CatcherAnimationState state ) = > AddAssert ( $"catcher state is {state}" , ( ) = > catcher . CurrentState = = state ) ;
private void checkHyperDash ( bool state ) = > AddAssert ( $"catcher is {(state ? "" : " not ")}hyper dashing" , ( ) = > catcher . HyperDashing = = state ) ;
2021-04-22 15:56:23 +08:00
private void attemptCatch ( CatchHitObject hitObject )
{
attemptCatch ( ( ) = > hitObject , 1 ) ;
}
private void attemptCatch ( Func < CatchHitObject > hitObject , int count )
2020-12-03 15:40:14 +08:00
{
2021-10-27 12:04:41 +08:00
for ( int i = 0 ; i < count ; i + + )
2021-04-22 15:56:23 +08:00
attemptCatch ( hitObject ( ) , out _ , out _ ) ;
2020-12-08 20:07:12 +08:00
}
private void attemptCatch ( CatchHitObject hitObject , out DrawableCatchHitObject drawableObject , out JudgementResult result )
{
hitObject . ApplyDefaults ( new ControlPointInfo ( ) , new BeatmapDifficulty ( ) ) ;
drawableObject = createDrawableObject ( hitObject ) ;
result = createResult ( hitObject ) ;
applyResult ( drawableObject , result ) ;
}
private void applyResult ( DrawableCatchHitObject drawableObject , JudgementResult result )
{
// Load DHO to set colour of hit explosion correctly
Add ( drawableObject ) ;
drawableObject . OnLoadComplete + = _ = >
{
catcher . OnNewResult ( drawableObject , result ) ;
drawableObject . Expire ( ) ;
} ;
2020-12-08 14:02:55 +08:00
}
2020-12-08 20:07:12 +08:00
private JudgementResult createResult ( CatchHitObject hitObject )
2020-12-08 14:02:55 +08:00
{
2020-12-08 20:07:12 +08:00
return new CatchJudgementResult ( hitObject , hitObject . CreateJudgement ( ) )
2020-12-08 13:28:30 +08:00
{
2020-12-08 20:07:12 +08:00
Type = catcher . CanCatch ( hitObject ) ? HitResult . Great : HitResult . Miss
2020-12-08 14:02:55 +08:00
} ;
2020-12-08 13:28:30 +08:00
}
private DrawableCatchHitObject createDrawableObject ( CatchHitObject hitObject )
{
switch ( hitObject )
{
case Banana banana :
return new DrawableBanana ( banana ) ;
case Droplet droplet :
return new DrawableDroplet ( droplet ) ;
case Fruit fruit :
return new DrawableFruit ( fruit ) ;
default :
throw new ArgumentOutOfRangeException ( nameof ( hitObject ) ) ;
}
2020-12-03 15:40:14 +08:00
}
2022-11-24 13:32:20 +08:00
public partial class TestCatcher : Catcher
2020-12-03 15:40:14 +08:00
{
2020-12-08 19:34:08 +08:00
public IEnumerable < CaughtObject > CaughtObjects = > this . ChildrenOfType < CaughtObject > ( ) ;
2020-12-03 15:40:14 +08:00
2021-10-01 13:56:42 +08:00
public TestCatcher ( DroppedObjectContainer droppedObjectTarget , IBeatmapDifficultyInfo difficulty )
2021-07-27 17:59:55 +08:00
: base ( droppedObjectTarget , difficulty )
2020-12-03 15:40:14 +08:00
{
}
}
public class TestKiaiFruit : Fruit
{
2021-10-01 13:56:42 +08:00
protected override void ApplyDefaultsToSelf ( ControlPointInfo controlPointInfo , IBeatmapDifficultyInfo difficulty )
2020-12-03 15:40:14 +08:00
{
controlPointInfo . Add ( 0 , new EffectControlPoint { KiaiMode = true } ) ;
base . ApplyDefaultsToSelf ( controlPointInfo , difficulty ) ;
}
2019-06-23 00:14:29 +08:00
}
}
}