2019-01-24 16:43: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.
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
using System ;
2019-09-03 06:57:29 +08:00
using System.Collections.Generic ;
2019-10-30 16:05:15 +08:00
using System.Linq ;
2018-03-02 14:34:31 +08:00
using NUnit.Framework ;
2017-09-18 21:32:49 +08:00
using osu.Framework.Audio.Track ;
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
2021-07-15 12:51:28 +08:00
using osu.Framework.Testing ;
2021-07-15 13:36:37 +08:00
using osu.Framework.Utils ;
2017-09-18 21:32:49 +08:00
using osu.Game.Beatmaps.ControlPoints ;
2019-03-25 00:02:36 +08:00
using osu.Game.Graphics ;
2017-09-18 21:32:49 +08:00
using osu.Game.Graphics.Containers ;
using osu.Game.Graphics.Sprites ;
2021-07-15 12:51:28 +08:00
using osu.Game.Rulesets.Osu ;
2021-07-15 13:30:38 +08:00
using osu.Game.Screens.Play ;
2018-11-20 15:51:59 +08:00
using osuTK.Graphics ;
2018-04-13 17:19:50 +08:00
2019-03-25 00:02:36 +08:00
namespace osu.Game.Tests.Visual.UserInterface
2017-09-18 21:32:49 +08:00
{
2018-03-02 14:34:31 +08:00
[TestFixture]
2019-05-15 03:37:25 +08:00
public class TestSceneBeatSyncedContainer : OsuTestScene
2017-09-18 21:32:49 +08:00
{
2021-07-15 15:13:13 +08:00
private TestBeatSyncedContainer beatContainer ;
2021-07-15 13:30:38 +08:00
private MasterGameplayClockContainer gameplayClockContainer ;
2019-08-13 13:29:58 +08:00
2021-07-15 12:51:28 +08:00
[SetUpSteps]
public void SetUpSteps ( )
2017-09-18 21:32:49 +08:00
{
2021-07-15 12:51:28 +08:00
AddStep ( "Set beatmap" , ( ) = >
{
Beatmap . Value = CreateWorkingBeatmap ( new OsuRuleset ( ) . RulesetInfo ) ;
} ) ;
2018-04-13 17:19:50 +08:00
2021-07-15 12:51:28 +08:00
AddStep ( "Create beat sync container" , ( ) = >
2017-09-18 21:32:49 +08:00
{
2021-07-15 12:51:28 +08:00
Children = new Drawable [ ]
2019-08-13 13:29:58 +08:00
{
2021-07-15 13:30:38 +08:00
gameplayClockContainer = new MasterGameplayClockContainer ( Beatmap . Value , 0 )
2021-07-15 12:51:28 +08:00
{
2021-07-15 15:13:13 +08:00
Child = beatContainer = new TestBeatSyncedContainer
2021-07-15 12:51:28 +08:00
{
2021-07-15 13:30:38 +08:00
Anchor = Anchor . BottomCentre ,
Origin = Anchor . BottomCentre ,
2021-07-15 12:51:28 +08:00
} ,
2021-07-15 13:30:38 +08:00
}
2021-07-15 12:51:28 +08:00
} ;
2017-09-18 21:32:49 +08:00
} ) ;
2021-07-15 12:51:28 +08:00
2021-07-15 13:30:38 +08:00
AddStep ( "Start playback" , ( ) = > gameplayClockContainer . Start ( ) ) ;
2017-09-18 21:32:49 +08:00
}
2018-04-13 17:19:50 +08:00
2021-07-15 15:13:13 +08:00
[TestCase(false)]
[TestCase(true)]
public void TestDisallowMistimedEventFiring ( bool allowMistimed )
2021-07-15 15:01:46 +08:00
{
int? lastBeatIndex = null ;
double? lastActuationTime = null ;
TimingControlPoint lastTimingPoint = null ;
2021-07-15 18:08:12 +08:00
AddStep ( $"set mistimed to {(allowMistimed ? " allowed " : " disallowed ")}" , ( ) = > beatContainer . AllowMistimedEventFiring = allowMistimed ) ;
2021-07-15 15:13:13 +08:00
2021-07-17 13:33:02 +08:00
AddStep ( "Set time before zero" , ( ) = >
2021-07-15 15:01:46 +08:00
{
beatContainer . NewBeat = ( i , timingControlPoint , effectControlPoint , channelAmplitudes ) = >
{
lastActuationTime = gameplayClockContainer . CurrentTime ;
lastTimingPoint = timingControlPoint ;
lastBeatIndex = i ;
2021-07-17 13:33:02 +08:00
beatContainer . NewBeat = null ;
2021-07-15 15:01:46 +08:00
} ;
gameplayClockContainer . Seek ( - 1000 ) ;
} ) ;
AddUntilStep ( "wait for trigger" , ( ) = > lastBeatIndex ! = null ) ;
2021-07-15 15:13:13 +08:00
if ( ! allowMistimed )
{
AddAssert ( "trigger is near beat length" , ( ) = > lastActuationTime ! = null & & lastBeatIndex ! = null & & Precision . AlmostEquals ( lastTimingPoint . Time + lastBeatIndex . Value * lastTimingPoint . BeatLength , lastActuationTime . Value , BeatSyncedContainer . MISTIMED_ALLOWANCE ) ) ;
}
else
{
AddAssert ( "trigger is not near beat length" , ( ) = > lastActuationTime ! = null & & lastBeatIndex ! = null & & ! Precision . AlmostEquals ( lastTimingPoint . Time + lastBeatIndex . Value * lastTimingPoint . BeatLength , lastActuationTime . Value , BeatSyncedContainer . MISTIMED_ALLOWANCE ) ) ;
}
2021-07-15 15:01:46 +08:00
}
[Test]
public void TestNegativeBeatsStillUsingBeatmapTiming ( )
2017-09-18 21:32:49 +08:00
{
2021-07-15 13:36:37 +08:00
int? lastBeatIndex = null ;
double? lastBpm = null ;
2021-07-17 13:33:02 +08:00
AddStep ( "Set time before zero" , ( ) = >
2021-07-15 12:51:28 +08:00
{
2021-07-15 13:36:37 +08:00
beatContainer . NewBeat = ( i , timingControlPoint , effectControlPoint , channelAmplitudes ) = >
{
lastBeatIndex = i ;
lastBpm = timingControlPoint . BPM ;
} ;
2021-07-15 12:51:28 +08:00
2021-07-15 13:36:37 +08:00
gameplayClockContainer . Seek ( - 1000 ) ;
2021-07-15 12:51:28 +08:00
} ) ;
2021-07-15 13:36:37 +08:00
AddUntilStep ( "wait for trigger" , ( ) = > lastBpm ! = null ) ;
2021-07-15 13:59:57 +08:00
AddAssert ( "bpm is from beatmap" , ( ) = > lastBpm ! = null & & Precision . AlmostEquals ( lastBpm . Value , 128 ) ) ;
2021-07-15 13:36:37 +08:00
AddAssert ( "beat index is less than zero" , ( ) = > lastBeatIndex < 0 ) ;
2017-09-18 21:32:49 +08:00
}
2018-04-13 17:19:50 +08:00
2021-07-15 13:59:57 +08:00
[Test]
public void TestIdleBeatOnPausedClock ( )
{
double? lastBpm = null ;
AddStep ( "bind event" , ( ) = >
{
beatContainer . NewBeat = ( i , timingControlPoint , effectControlPoint , channelAmplitudes ) = > lastBpm = timingControlPoint . BPM ;
} ) ;
AddUntilStep ( "wait for trigger" , ( ) = > lastBpm ! = null ) ;
AddAssert ( "bpm is from beatmap" , ( ) = > lastBpm ! = null & & Precision . AlmostEquals ( lastBpm . Value , 128 ) ) ;
AddStep ( "pause gameplay clock" , ( ) = >
{
lastBpm = null ;
gameplayClockContainer . Stop ( ) ;
} ) ;
AddUntilStep ( "wait for trigger" , ( ) = > lastBpm ! = null ) ;
2021-07-17 13:29:18 +08:00
AddAssert ( "bpm is default" , ( ) = > lastBpm ! = null & & Precision . AlmostEquals ( lastBpm . Value , 60 ) ) ;
2021-07-15 13:59:57 +08:00
}
2021-12-05 17:53:36 +08:00
[TestCase(true)]
[TestCase(false)]
public void TestEarlyActivationEffectPoint ( bool earlyActivating )
{
double earlyActivationMilliseconds = earlyActivating ? 100 : 0 ;
ControlPoint actualEffectPoint = null ;
AddStep ( $"set early activation to {earlyActivationMilliseconds}" , ( ) = > beatContainer . EarlyActivationMilliseconds = earlyActivationMilliseconds ) ;
AddStep ( "seek before kiai effect point" , ( ) = >
{
ControlPoint expectedEffectPoint = Beatmap . Value . Beatmap . ControlPointInfo . EffectPoints . First ( ep = > ep . KiaiMode ) ;
actualEffectPoint = null ;
beatContainer . AllowMistimedEventFiring = false ;
beatContainer . NewBeat = ( i , timingControlPoint , effectControlPoint , channelAmplitudes ) = >
{
if ( Precision . AlmostEquals ( gameplayClockContainer . CurrentTime + earlyActivationMilliseconds , expectedEffectPoint . Time , BeatSyncedContainer . MISTIMED_ALLOWANCE ) )
actualEffectPoint = effectControlPoint ;
} ;
gameplayClockContainer . Seek ( expectedEffectPoint . Time - earlyActivationMilliseconds ) ;
} ) ;
AddUntilStep ( "wait for effect point" , ( ) = > actualEffectPoint ! = null ) ;
AddAssert ( "effect has kiai" , ( ) = > actualEffectPoint ! = null & & ( ( EffectControlPoint ) actualEffectPoint ) . KiaiMode ) ;
}
2021-07-15 15:13:13 +08:00
private class TestBeatSyncedContainer : BeatSyncedContainer
2017-09-18 21:32:49 +08:00
{
2021-07-15 12:51:28 +08:00
private const int flash_layer_height = 150 ;
2018-04-13 17:19:50 +08:00
2021-07-15 15:13:13 +08:00
public new bool AllowMistimedEventFiring
{
get = > base . AllowMistimedEventFiring ;
set = > base . AllowMistimedEventFiring = value ;
}
2021-12-05 17:53:36 +08:00
public new double EarlyActivationMilliseconds
{
get = > base . EarlyActivationMilliseconds ;
set = > base . EarlyActivationMilliseconds = value ;
}
2017-09-18 21:32:49 +08:00
private readonly InfoString timingPointCount ;
private readonly InfoString currentTimingPoint ;
private readonly InfoString beatCount ;
private readonly InfoString currentBeat ;
private readonly InfoString beatsPerMinute ;
private readonly InfoString adjustedBeatLength ;
private readonly InfoString timeUntilNextBeat ;
private readonly InfoString timeSinceLastBeat ;
2021-07-15 12:51:28 +08:00
private readonly InfoString currentTime ;
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
private readonly Box flashLayer ;
2018-04-13 17:19:50 +08:00
2021-07-15 15:13:13 +08:00
public TestBeatSyncedContainer ( )
2017-09-18 21:32:49 +08:00
{
RelativeSizeAxes = Axes . X ;
AutoSizeAxes = Axes . Y ;
Children = new Drawable [ ]
{
new Container
{
Name = @"Info Layer" ,
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
AutoSizeAxes = Axes . Both ,
2021-07-15 12:51:28 +08:00
Margin = new MarginPadding { Bottom = flash_layer_height } ,
2017-09-18 21:32:49 +08:00
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4 . Black . Opacity ( 150 ) ,
} ,
new FillFlowContainer
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
AutoSizeAxes = Axes . Both ,
Direction = FillDirection . Vertical ,
Children = new Drawable [ ]
{
2021-07-15 12:51:28 +08:00
currentTime = new InfoString ( @"Current time" ) ,
2017-09-18 21:32:49 +08:00
timingPointCount = new InfoString ( @"Timing points amount" ) ,
currentTimingPoint = new InfoString ( @"Current timing point" ) ,
beatCount = new InfoString ( @"Beats amount (in the current timing point)" ) ,
currentBeat = new InfoString ( @"Current beat" ) ,
beatsPerMinute = new InfoString ( @"BPM" ) ,
adjustedBeatLength = new InfoString ( @"Adjusted beat length" ) ,
timeUntilNextBeat = new InfoString ( @"Time until next beat" ) ,
timeSinceLastBeat = new InfoString ( @"Time since last beat" ) ,
}
}
}
} ,
new Container
{
Name = @"Color indicator" ,
Anchor = Anchor . BottomCentre ,
Origin = Anchor . BottomCentre ,
RelativeSizeAxes = Axes . X ,
2021-07-15 12:51:28 +08:00
Height = flash_layer_height ,
2017-09-18 21:32:49 +08:00
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4 . Black ,
} ,
flashLayer = new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4 . White ,
Alpha = 0 ,
}
}
}
} ;
2021-07-15 13:30:38 +08:00
}
2018-04-13 17:19:50 +08:00
2021-07-15 13:30:38 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
Beatmap . BindValueChanged ( _ = >
2017-09-18 21:32:49 +08:00
{
timingPointCount . Value = 0 ;
currentTimingPoint . Value = 0 ;
beatCount . Value = 0 ;
currentBeat . Value = 0 ;
beatsPerMinute . Value = 0 ;
adjustedBeatLength . Value = 0 ;
timeUntilNextBeat . Value = 0 ;
timeSinceLastBeat . Value = 0 ;
2021-07-15 13:30:38 +08:00
} , true ) ;
2017-09-18 21:32:49 +08:00
}
2018-04-13 17:19:50 +08:00
2019-10-30 16:05:15 +08:00
private List < TimingControlPoint > timingPoints = > Beatmap . Value . Beatmap . ControlPointInfo . TimingPoints . ToList ( ) ;
2019-02-28 12:31:40 +08:00
2017-09-18 21:32:49 +08:00
private TimingControlPoint getNextTimingPoint ( TimingControlPoint current )
{
2019-12-14 20:54:22 +08:00
if ( timingPoints [ ^ 1 ] = = current )
2017-09-18 21:32:49 +08:00
return current ;
2018-04-13 17:19:50 +08:00
2019-09-03 06:57:29 +08:00
int index = timingPoints . IndexOf ( current ) ; // -1 means that this is a "default beat"
return index = = - 1 ? current : timingPoints [ index + 1 ] ;
2017-09-18 21:32:49 +08:00
}
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
private int calculateBeatCount ( TimingControlPoint current )
{
if ( timingPoints . Count = = 0 ) return 0 ;
2018-04-13 17:19:50 +08:00
2019-12-14 20:54:22 +08:00
if ( timingPoints [ ^ 1 ] = = current )
2021-07-15 13:30:38 +08:00
return ( int ) Math . Ceiling ( ( BeatSyncClock . CurrentTime - current . Time ) / current . BeatLength ) ;
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
return ( int ) Math . Ceiling ( ( getNextTimingPoint ( current ) . Time - current . Time ) / current . BeatLength ) ;
}
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
protected override void Update ( )
{
base . Update ( ) ;
timeUntilNextBeat . Value = TimeUntilNextBeat ;
timeSinceLastBeat . Value = TimeSinceLastBeat ;
2021-07-15 13:30:38 +08:00
currentTime . Value = BeatSyncClock . CurrentTime ;
2017-09-18 21:32:49 +08:00
}
2018-04-13 17:19:50 +08:00
2021-07-15 12:51:28 +08:00
public Action < int , TimingControlPoint , EffectControlPoint , ChannelAmplitudes > NewBeat ;
2020-06-23 12:49:18 +08:00
protected override void OnNewBeat ( int beatIndex , TimingControlPoint timingPoint , EffectControlPoint effectPoint , ChannelAmplitudes amplitudes )
2017-09-18 21:32:49 +08:00
{
base . OnNewBeat ( beatIndex , timingPoint , effectPoint , amplitudes ) ;
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
timingPointCount . Value = timingPoints . Count ;
currentTimingPoint . Value = timingPoints . IndexOf ( timingPoint ) ;
beatCount . Value = calculateBeatCount ( timingPoint ) ;
currentBeat . Value = beatIndex ;
beatsPerMinute . Value = 60000 / timingPoint . BeatLength ;
adjustedBeatLength . Value = timingPoint . BeatLength ;
2018-04-13 17:19:50 +08:00
2021-07-15 12:51:28 +08:00
flashLayer . FadeOutFromOne ( timingPoint . BeatLength / 4 ) ;
NewBeat ? . Invoke ( beatIndex , timingPoint , effectPoint , amplitudes ) ;
2017-09-18 21:32:49 +08:00
}
}
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
private class InfoString : FillFlowContainer
{
private const int text_size = 20 ;
private const int margin = 7 ;
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
private readonly OsuSpriteText valueText ;
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
public double Value
{
2021-07-15 12:51:28 +08:00
set = > valueText . Text = $"{value:0.##}" ;
2017-09-18 21:32:49 +08:00
}
2018-04-13 17:19:50 +08:00
2017-09-18 21:32:49 +08:00
public InfoString ( string header )
{
AutoSizeAxes = Axes . Both ;
Direction = FillDirection . Horizontal ;
2019-02-12 12:04:46 +08:00
Add ( new OsuSpriteText { Text = header + @": " , Font = OsuFont . GetFont ( size : text_size ) } ) ;
Add ( valueText = new OsuSpriteText { Font = OsuFont . GetFont ( size : text_size ) } ) ;
2017-09-18 21:32:49 +08:00
Margin = new MarginPadding ( margin ) ;
}
}
}
}