-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathButtplugManager.cs
300 lines (254 loc) · 12.3 KB
/
ButtplugManager.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BepInEx;
using ButtplugManaged;
using UnityEngine;
namespace UKButt
{
[BepInPlugin(PluginInfo.GUID, PluginInfo.NAME, PluginInfo.VERSION)]
public class ButtplugManager : BaseUnityPlugin
{
public static ButtplugManager Instance;
public static readonly plog.Logger Log = new plog.Logger("UKButt");
public bool emergencyStop = false;
private ButtplugClient buttplugClient;
private readonly List<ButtplugClientDevice> connectedDevices = new List<ButtplugClientDevice>();
public float currentSpeed = 0;
public float currentRank = 0;
public float currentLinearTime = 0;
private UnscaledTimeSince _unscaledTimeSinceVibes;
private UnscaledTimeSince _timeSinceVibes;
private UnscaledTimeSince _timeSinceVibeUpdate;
// Guh guh
private readonly Queue<string> _logQueue = new Queue<string>();
private readonly Queue<string> _errorLogQueue = new Queue<string>();
private float TimeSinceVibes => PrefsManager.Instance.GetBoolLocal(UKButtProperties.UseUnscaledTime, true) ? (float)_unscaledTimeSinceVibes : (float)_timeSinceVibes;
// TODO single class to store the defaults for this and the UKButt command prefs editor
private float StickForNormal => PrefsManager.Instance == null ? 2.0f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.StickForSeconds, 2.0f);
private float SoftStickFor => PrefsManager.Instance == null ? 0.2f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.TapStickForSeconds, 0.2f);
private float StrengthMultiplier => PrefsManager.Instance == null ? 0.8f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.Strength, 0.8f);
public InputMode InputMode => (InputMode)PrefsManager.Instance.GetIntLocal(UKButtProperties.InputMode, (int)InputMode.Varied);
public static bool ForwardPatchedEvents => Instance != null && Instance.InputMode == InputMode.Varied;
private float LinearPosMin => PrefsManager.Instance == null ? 0.1f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.LinearPosMin, 0.1f);
private float LinearPosMax => PrefsManager.Instance == null ? 0.9f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.LinearPosMax, 0.9f);
private float LinearTimeMin => PrefsManager.Instance == null ? 0.3f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.LinearTimeMin, 0.3f);
private float LinearTimeMax => PrefsManager.Instance == null ? 1.5f : PrefsManager.Instance.GetFloatLocal(UKButtProperties.LinearTimeMax, 1.5f);
private bool StrokeWhileIdle => PrefsManager.Instance != null && PrefsManager.Instance.GetBoolLocal(UKButtProperties.StrokeWhileIdle, false);
// Toggle for movement direction state
private bool _moveMax = true;
// Current setting for stroke time, recalculated based on rank at the end of each stroke
private float _moveTime;
// Timer for calculating stroke command frequency
private TimeSince _timeSinceLastMove;
private void Awake()
{
gameObject.hideFlags = HideFlags.HideAndDontSave;
}
private void Start()
{
Log.Info("Initializing UKButt");
Instance = this;
// Start at the slowest Linear time.
_moveTime = LinearTimeMax;
Log.Info("Patching Game...");
var harmony = HarmonyLib.Harmony.CreateAndPatchAll(typeof(CameraPatch));
harmony.PatchAll(typeof(CameraPatch)); // Intercepts shake calls
harmony.PatchAll(typeof(CheatsPatch)); // Adds the bindable emergency stop cheat
harmony.PatchAll(typeof(RevolverPatch)); // Intercepts the revolver's firing
harmony.PatchAll(typeof(DashPatch)); // Intercepts the player dash/dodge
harmony.PatchAll(typeof(ButtonPatch)); // Intercepts the button press
// Rank based input mode
harmony.PatchAll(typeof(StyleAscendRank));
harmony.PatchAll(typeof(StyleDescendRank));
// Register the command to the ULTRAKILL console
Log.Info("Registering \"UKButt\" command");
GameConsole.Console.Instance.RegisterCommand(new Commands.UKButt(GameConsole.Console.Instance));
// Connect the buttplug.io client
Task.Run(ReconnectClient);
}
public void TryRestartClient()
{
Log.Info("Restarting Buttplug client...");
Task.Run(ReconnectClient);
}
private Uri GetConnectionUri()
{
return new Uri($"{PrefsManager.Instance.GetStringLocal(UKButtProperties.SocketUri, "ws://localhost:12345")}/buttplug");
}
private async Task TryKillClient()
{
if (buttplugClient == null) return;
_logQueue.Enqueue("Disconnecting from Buttplug server...");
buttplugClient.DeviceAdded -= AddDevice;
buttplugClient.DeviceRemoved -= RemoveDevice;
buttplugClient.ScanningFinished -= ScanningFinished;
buttplugClient.ErrorReceived -= ErrorReceived;
buttplugClient.ServerDisconnect -= ServerDisconnect;
if (buttplugClient.IsScanning) await buttplugClient.StopScanningAsync();
if (buttplugClient.Connected) await buttplugClient.DisconnectAsync();
buttplugClient = null;
}
private async Task ReconnectClient()
{
var uri = GetConnectionUri();
await TryKillClient();
buttplugClient = new ButtplugClient("ULTRAKILL");
buttplugClient.DeviceAdded += AddDevice;
buttplugClient.DeviceRemoved += RemoveDevice;
buttplugClient.ScanningFinished += ScanningFinished;
buttplugClient.ErrorReceived += ErrorReceived;
buttplugClient.ServerDisconnect += ServerDisconnect;
_logQueue.Enqueue("Connecting to Buttplug server...");
try
{
await buttplugClient.ConnectAsync(new ButtplugWebsocketConnectorOptions(uri));
Task.Run(buttplugClient.StartScanningAsync);
}
catch (Exception ex)
{
_errorLogQueue.Enqueue(ex.ToString());
}
}
public static void Vibrate(float originalAmount)
{
if (!Instance || Instance.emergencyStop) return;
ResetVibeTimes();
var amount = Mathf.Clamp(originalAmount, 0, 1);
Instance.currentSpeed = amount;
}
// Used for very subtle vibrations (menu button clicks and dashes)
public static void Tap(bool isMenu = false)
{
if (!Instance || Instance.emergencyStop) return;
if (isMenu && !PrefsManager.Instance.GetBoolLocal(UKButtProperties.EnableMenuHaptics, true)) return;
ResetVibeTimes();
if (Instance.currentSpeed < 0.1f)
{
Instance.currentSpeed = 0.1f;
}
}
private void Update()
{
if (_logQueue.Count > 0 || _errorLogQueue.Count > 0)
{
while (_logQueue.Count > 0)
{
var message = _logQueue.Dequeue();
Log.Info(message);
}
while (_errorLogQueue.Count > 0)
{
var message = _errorLogQueue.Dequeue();
Log.Error(message);
}
}
if (buttplugClient == null) return;
if (emergencyStop) currentSpeed = 0;
if (InputMode == InputMode.Varied) UpdateHookArm();
else if (InputMode == InputMode.ContinuousRank)
{
if (StyleHUD.Instance == null) currentRank = 0;
currentSpeed = currentRank / 8f;
}
else if (InputMode == InputMode.Passthrough)
{
if (OptionsManager.Instance && OptionsManager.Instance.paused)
{
currentSpeed = 0;
}
else
{
currentSpeed = RumbleManager.Instance.currentIntensity;
}
}
// This shouldn't be run at more than 10hz, bluetooth can't keep up. Repeated commands will be
// ignored in Buttplug, but quick updates can still cause lag.
if (_timeSinceVibeUpdate > 0.10)
{
foreach (var buttplugClientDevice in connectedDevices)
{
if (buttplugClientDevice.AllowedMessages.ContainsKey("VibrateCmd"))
{
buttplugClientDevice.SendVibrateCmd(Math.Min(currentSpeed * StrengthMultiplier, 1.0));
}
// Only trigger stroker movement if we're using continuous rank mode. Variable mode doesn't make sense for this.
if (InputMode == InputMode.ContinuousRank && (StrokeWhileIdle || currentSpeed > 0.00001)) {
if (buttplugClientDevice.AllowedMessages.ContainsKey("LinearCmd"))
{
if (_timeSinceLastMove > _moveTime)
{
_moveTime = Math.Max(LinearTimeMax - ((LinearTimeMax - LinearTimeMin) * (currentSpeed * StrengthMultiplier)), LinearTimeMin);
buttplugClientDevice.SendLinearCmd((uint)(1000 * _moveTime), _moveMax ? LinearPosMin : LinearPosMax);
}
}
}
else
{
// This resets our move time so that once MoveWhileIdle or speed goes > 1, we'll be sure to actually trigger a command.
_moveTime = 0;
}
}
// On the extremely rare chance someone has multiple linear devices, don't reset values
// until after commands have been sent to all of them.
//
// Also, don't run this if we're not stroking already.
if (_moveTime > 0 && _timeSinceLastMove > _moveTime)
{
_timeSinceLastMove = 0;
_moveMax = !_moveMax;
}
_timeSinceVibeUpdate = 0;
}
if (TimeSinceVibes > StickForNormal && InputMode == InputMode.Varied) currentSpeed = 0;
else if (InputMode == InputMode.None) currentSpeed = 0;
}
private static void ResetVibeTimes()
{
Instance._timeSinceVibes = Instance.StickForNormal - Instance.SoftStickFor;
Instance._unscaledTimeSinceVibes = Instance.StickForNormal - Instance.SoftStickFor;
}
private void UpdateHookArm()
{
if (!HookArm.Instance) return;
switch (HookArm.Instance.state)
{
case HookState.Pulling:
Vibrate(0.5f);
ResetVibeTimes();
break;
case HookState.Throwing:
Vibrate(0.2f);
ResetVibeTimes();
break;
}
}
private void AddDevice(object sender, DeviceAddedEventArgs args)
{
_logQueue.Enqueue("Device Added: " + args.Device.Name);
connectedDevices.Add(args.Device);
}
private void RemoveDevice(object sender, DeviceRemovedEventArgs args)
{
_logQueue.Enqueue("Device Removed: " + args.Device.Name);
connectedDevices.Remove(args.Device);
}
private void ScanningFinished(object sender, EventArgs args)
{
_logQueue.Enqueue("Scanning Finished");
}
private void ErrorReceived(object sender, ButtplugExceptionEventArgs args)
{
_errorLogQueue.Enqueue("Error: " + args.Exception.Message);
}
private void ServerDisconnect(object sender, EventArgs args)
{
_logQueue.Enqueue("Server Disconnected");
Task.Run(TryKillClient);
}
private void OnDestroy()
{
buttplugClient?.DisconnectAsync().Wait();
}
}
}