1+ using Il2CppFormulaBase ;
2+ using MelonLoader ;
3+ using UnityEngine ;
4+
5+ namespace Cinema
6+ {
7+ internal static class DriftCorrector
8+ {
9+ // higher == more precise, but may have lower performance
10+ private const float CorrectionPrecision = 1f ;
11+ private const int ForceSetThreshold = 20 ;
12+ private const float DisableCoefficient = 1.1f ;
13+ private static StageBattleComponent _battleComponent ;
14+ private static bool _isInit ;
15+ private static readonly float [ ] DeltaSamples = new float [ 50 ] ;
16+
17+ private static int _deltaSampleIndex ;
18+ private static SpeedState _currentSpeed = SpeedState . Normal ;
19+
20+ /// <summary>
21+ /// In case another mod wants to change the playback speed (such as PracticeMod),
22+ /// <br />
23+ /// we store the original playback speed and use it in our calculations.
24+ /// </summary>
25+ private static float _originalPlaybackSpeed = 1f ;
26+
27+ private static double _lastUpdateTime ;
28+
29+ internal static void Init ( )
30+ {
31+ if ( _isInit ) return ;
32+ InitAverageDelta ( ) ;
33+ _isInit = true ;
34+ }
35+
36+ internal static void Stop ( )
37+ {
38+ var del = LateUpdate ;
39+ if ( MelonEvents . OnLateUpdate . CheckIfSubscribed ( del . Method ) )
40+ {
41+ MelonEvents . OnLateUpdate . Unsubscribe ( LateUpdate ) ;
42+ }
43+
44+ if ( Main . Player is not null ) Main . Player . playbackSpeed = _originalPlaybackSpeed ;
45+ }
46+
47+ internal static void Run ( )
48+ {
49+ _originalPlaybackSpeed = Main . Player . playbackSpeed ;
50+ _lastUpdateTime = 0 ;
51+ _battleComponent = StageBattleComponent . instance ;
52+ _currentSpeed = SpeedState . Normal ;
53+ MelonEvents . OnLateUpdate . Subscribe ( LateUpdate ) ;
54+ }
55+
56+ private static void CalculateAverageDelta ( )
57+ {
58+ DeltaSamples [ _deltaSampleIndex ++ ] = Time . deltaTime ;
59+ if ( _deltaSampleIndex == DeltaSamples . Length ) _deltaSampleIndex = 0 ;
60+ }
61+
62+ private static void InitAverageDelta ( )
63+ {
64+ CalculateAverageDelta ( ) ;
65+ for ( var i = 1 ; i < DeltaSamples . Length ; i ++ ) DeltaSamples [ i ] = DeltaSamples [ 0 ] ;
66+ }
67+
68+ /// <summary>
69+ /// Calculates playback speed using the given parameters
70+ /// </summary>
71+ private static float CalculateSpeed ( float deltaTime , float drift )
72+ {
73+ var averageScaled = deltaTime / 10 ;
74+ var relativeDrift = Math . Abs ( drift ) + 1 ;
75+ var speed = Math . Min ( ( 1 + averageScaled ) * relativeDrift , 1.05f ) ;
76+ if ( drift > 0 ) speed = 2 - speed ;
77+ ;
78+ return speed * _originalPlaybackSpeed ;
79+ }
80+
81+ private static void LateUpdate ( )
82+ {
83+ if ( Main . Player is null || Math . Abs ( Main . Player . time - _lastUpdateTime ) < 0.000001 ) return ;
84+ if ( Main . Player . time >= Main . Player . length )
85+ {
86+ Stop ( ) ;
87+ return ;
88+ }
89+
90+ CalculateAverageDelta ( ) ;
91+ _lastUpdateTime = Main . Player . time ;
92+
93+ var averageDelta = DeltaSamples . Average ( ) ;
94+ var drift = ( float ) ( _lastUpdateTime - _battleComponent . timeFromMusicStart ) ;
95+ if ( Math . Abs ( drift ) > averageDelta * ForceSetThreshold )
96+ {
97+ // If the game freezes, (e.g. extreme drift) instantly set the player's time,
98+ // instead of slightly speeding up, like when we correct small drifts.
99+ // This correction may cause a few Update loops worth of drift,
100+ // but is preferable to a multiple second drift.
101+ var correct = _battleComponent . timeFromMusicStart + averageDelta * ForceSetThreshold ;
102+ Main . Player . time = correct ;
103+ return ;
104+ }
105+
106+ var maxDifference = averageDelta / CorrectionPrecision ;
107+ var disableThreshold = maxDifference / DisableCoefficient ;
108+ switch ( _currentSpeed )
109+ {
110+ case SpeedState . Slow :
111+ if ( drift > disableThreshold ) return ;
112+ break ;
113+ case SpeedState . Fast :
114+ if ( drift < - disableThreshold ) return ;
115+ break ;
116+ case SpeedState . Normal :
117+ break ;
118+ default :
119+ throw new ArgumentOutOfRangeException ( ) ;
120+ }
121+
122+ if ( drift < - maxDifference )
123+ {
124+ _currentSpeed = SpeedState . Fast ;
125+ var speed = CalculateSpeed ( averageDelta , drift ) ;
126+ Main . Player . playbackSpeed = speed ;
127+ return ;
128+ }
129+
130+ if ( drift > maxDifference )
131+ {
132+ _currentSpeed = SpeedState . Slow ;
133+ var speed = CalculateSpeed ( averageDelta , drift ) ;
134+ Main . Player . playbackSpeed = speed ;
135+ return ;
136+ }
137+
138+ if ( _currentSpeed is SpeedState . Normal ) return ;
139+
140+ Main . Player . playbackSpeed = _originalPlaybackSpeed ;
141+ _currentSpeed = SpeedState . Normal ;
142+ }
143+
144+ private enum SpeedState
145+ {
146+ Slow ,
147+ Normal ,
148+ Fast
149+ }
150+ }
151+ }
0 commit comments