2021-08-25 15:55:03 +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.
2023-02-16 16:58:32 +08:00
using System.Diagnostics ;
2021-08-25 15:55:03 +08:00
using System.Linq ;
using NUnit.Framework ;
2023-02-16 16:58:32 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Audio ;
using osu.Framework.Timing ;
2021-08-25 15:55:03 +08:00
using osu.Game.Audio ;
using osu.Game.Beatmaps ;
using osu.Game.Beatmaps.ControlPoints ;
2023-02-16 17:13:11 +08:00
using osu.Game.Beatmaps.Legacy ;
2021-08-25 15:55:03 +08:00
using osu.Game.Rulesets ;
using osu.Game.Rulesets.Objects ;
using osu.Game.Rulesets.Objects.Drawables ;
2023-02-16 17:13:11 +08:00
using osu.Game.Rulesets.Objects.Types ;
2021-08-25 15:55:03 +08:00
using osu.Game.Rulesets.Osu ;
using osu.Game.Rulesets.Osu.Objects ;
using osu.Game.Rulesets.UI ;
2023-02-16 16:58:32 +08:00
using osu.Game.Storyboards ;
2023-02-16 17:13:11 +08:00
using osuTK ;
2021-08-25 15:55:03 +08:00
using osuTK.Input ;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene
{
2023-02-16 16:37:46 +08:00
private TestGameplaySampleTriggerSource sampleTriggerSource = null ! ;
2021-08-25 15:55:03 +08:00
protected override Ruleset CreatePlayerRuleset ( ) = > new OsuRuleset ( ) ;
2023-02-16 16:37:46 +08:00
private Beatmap beatmap = null ! ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
[Resolved]
private AudioManager audio { get ; set ; } = null ! ;
protected override WorkingBeatmap CreateWorkingBeatmap ( IBeatmap beatmap , Storyboard ? storyboard = null )
= > new ClockBackedTestWorkingBeatmap ( beatmap , storyboard , new FramedClock ( new ManualClock { Rate = 1 } ) , audio ) ;
2021-08-25 15:55:03 +08:00
protected override IBeatmap CreateBeatmap ( RulesetInfo ruleset )
{
2023-02-16 17:13:11 +08:00
ControlPointInfo controlPointInfo = new LegacyControlPointInfo ( ) ;
2021-08-25 15:55:03 +08:00
beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
2022-01-18 21:57:39 +08:00
Difficulty = new BeatmapDifficulty { CircleSize = 6 , SliderMultiplier = 3 } ,
2021-08-25 15:55:03 +08:00
Ruleset = ruleset
2023-02-16 17:13:11 +08:00
} ,
ControlPointInfo = controlPointInfo
2021-08-25 15:55:03 +08:00
} ;
const double start_offset = 8000 ;
const double spacing = 2000 ;
2023-02-16 16:58:32 +08:00
// intentionally start objects a bit late so we can test the case of no alive objects.
2021-08-25 15:55:03 +08:00
double t = start_offset ;
2023-02-16 16:58:32 +08:00
2023-02-16 17:13:11 +08:00
beatmap . HitObjects . AddRange ( new HitObject [ ]
2021-08-25 15:55:03 +08:00
{
new HitCircle
{
StartTime = t + = spacing ,
Samples = new [ ] { new HitSampleInfo ( HitSampleInfo . HIT_NORMAL ) }
} ,
new HitCircle
{
StartTime = t + = spacing ,
Samples = new [ ] { new HitSampleInfo ( HitSampleInfo . HIT_WHISTLE ) }
} ,
new HitCircle
{
StartTime = t + = spacing ,
Samples = new [ ] { new HitSampleInfo ( HitSampleInfo . HIT_NORMAL ) } ,
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" } ,
} ,
new HitCircle
{
2023-02-16 17:13:11 +08:00
StartTime = t + = spacing ,
} ,
new Slider
{
StartTime = t + = spacing ,
Path = new SliderPath ( PathType . Linear , new [ ] { Vector2 . Zero , Vector2 . UnitY * 200 } ) ,
2021-08-25 15:55:03 +08:00
Samples = new [ ] { new HitSampleInfo ( HitSampleInfo . HIT_WHISTLE ) } ,
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" } ,
} ,
} ) ;
2023-02-16 17:13:11 +08:00
// Add a change in volume halfway through final slider.
controlPointInfo . Add ( t , new SampleControlPoint
{
SampleBank = "normal" ,
SampleVolume = 20 ,
} ) ;
2021-08-25 15:55:03 +08:00
return beatmap ;
}
public override void SetUpSteps ( )
{
base . SetUpSteps ( ) ;
AddStep ( "Add trigger source" , ( ) = > Player . HUDOverlay . Add ( sampleTriggerSource = new TestGameplaySampleTriggerSource ( Player . DrawableRuleset . Playfield . HitObjectContainer ) ) ) ;
}
[Test]
public void TestCorrectHitObject ( )
{
2023-02-16 16:58:32 +08:00
waitForAliveObjectIndex ( null ) ;
2023-02-17 02:02:51 +08:00
checkValidObjectIndex ( 0 ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
seekBeforeIndex ( 0 ) ;
waitForAliveObjectIndex ( 0 ) ;
checkValidObjectIndex ( 0 ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
AddAssert ( "first object not hit" , ( ) = > getNextAliveObject ( ) ? . Entry ? . Result ? . HasResult ! = true ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
AddStep ( "hit first object" , ( ) = >
2021-08-25 15:55:03 +08:00
{
2023-02-16 16:58:32 +08:00
var next = getNextAliveObject ( ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
if ( next ! = null )
2021-08-25 15:55:03 +08:00
{
2023-02-16 16:58:32 +08:00
Debug . Assert ( next . Entry ? . Result ? . HasResult ! = true ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
InputManager . MoveMouseTo ( next . ScreenSpaceDrawQuad . Centre ) ;
InputManager . Click ( MouseButton . Left ) ;
}
2021-08-25 15:55:03 +08:00
} ) ;
2023-02-16 16:58:32 +08:00
AddAssert ( "first object hit" , ( ) = > getNextAliveObject ( ) ? . Entry ? . Result ? . HasResult = = true ) ;
checkValidObjectIndex ( 1 ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
// Still object 1 as it's not hit yet.
seekBeforeIndex ( 1 ) ;
waitForAliveObjectIndex ( 1 ) ;
checkValidObjectIndex ( 1 ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
seekBeforeIndex ( 2 ) ;
waitForAliveObjectIndex ( 2 ) ;
checkValidObjectIndex ( 2 ) ;
2021-08-25 15:55:03 +08:00
2023-02-16 16:58:32 +08:00
seekBeforeIndex ( 3 ) ;
waitForAliveObjectIndex ( 3 ) ;
checkValidObjectIndex ( 3 ) ;
2023-02-16 17:13:11 +08:00
seekBeforeIndex ( 4 ) ;
waitForAliveObjectIndex ( 4 ) ;
// Even before the object, we should prefer the first nested object's sample.
// This is because the (parent) object will only play its sample at the final EndTime.
AddAssert ( "check valid object is slider's first nested" , ( ) = > sampleTriggerSource . GetMostValidObject ( ) , ( ) = > Is . EqualTo ( beatmap . HitObjects [ 4 ] . NestedHitObjects . First ( ) ) ) ;
2023-02-16 16:58:32 +08:00
2023-02-16 17:13:11 +08:00
AddStep ( "seek to just after slider" , ( ) = > Beatmap . Value . Track . Seek ( beatmap . HitObjects [ 4 ] . GetEndTime ( ) + 100 ) ) ;
AddUntilStep ( "wait until valid object is slider's last nested" , ( ) = > sampleTriggerSource . GetMostValidObject ( ) , ( ) = > Is . EqualTo ( beatmap . HitObjects [ 4 ] . NestedHitObjects . Last ( ) ) ) ;
// After we get far enough away, the samples of the object itself should be used, not any nested object.
AddStep ( "seek to further after slider" , ( ) = > Beatmap . Value . Track . Seek ( beatmap . HitObjects [ 4 ] . GetEndTime ( ) + 1000 ) ) ;
AddUntilStep ( "wait until valid object is slider itself" , ( ) = > sampleTriggerSource . GetMostValidObject ( ) , ( ) = > Is . EqualTo ( beatmap . HitObjects [ 4 ] ) ) ;
AddStep ( "Seek into future" , ( ) = > Beatmap . Value . Track . Seek ( beatmap . HitObjects . Last ( ) . GetEndTime ( ) + 10000 ) ) ;
2023-02-16 16:58:32 +08:00
waitForAliveObjectIndex ( null ) ;
2023-02-16 17:13:11 +08:00
checkValidObjectIndex ( 4 ) ;
2021-08-25 15:55:03 +08:00
}
2023-02-16 16:58:32 +08:00
private void seekBeforeIndex ( int index ) = >
AddStep ( $"seek to just before object {index}" , ( ) = > Beatmap . Value . Track . Seek ( beatmap . HitObjects [ index ] . StartTime - 100 ) ) ;
private void waitForAliveObjectIndex ( int? index )
{
if ( index = = null )
AddUntilStep ( "wait for no alive objects" , getNextAliveObject , ( ) = > Is . Null ) ;
else
AddUntilStep ( $"wait for next alive to be {index}" , ( ) = > getNextAliveObject ( ) ? . HitObject , ( ) = > Is . EqualTo ( beatmap . HitObjects [ index . Value ] ) ) ;
}
private void checkValidObjectIndex ( int index ) = >
AddAssert ( $"check valid object is {index}" , ( ) = > sampleTriggerSource . GetMostValidObject ( ) , ( ) = > Is . EqualTo ( beatmap . HitObjects [ index ] ) ) ;
2023-02-16 16:37:46 +08:00
private DrawableHitObject ? getNextAliveObject ( ) = >
2021-08-25 15:55:03 +08:00
Player . DrawableRuleset . Playfield . HitObjectContainer . AliveObjects . FirstOrDefault ( ) ;
[Test]
public void TestSampleTriggering ( )
{
AddRepeatStep ( "trigger sample" , ( ) = > sampleTriggerSource . Play ( ) , 10 ) ;
}
public partial class TestGameplaySampleTriggerSource : GameplaySampleTriggerSource
{
public TestGameplaySampleTriggerSource ( HitObjectContainer hitObjectContainer )
: base ( hitObjectContainer )
{
}
public new HitObject GetMostValidObject ( ) = > base . GetMostValidObject ( ) ;
}
}
}