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