using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using ButtplugSong; using ButtplugSong.GUI; using ButtplugSong.GUI.CustomUI; using ButtplugSong.GUI.Network; using ButtplugSong.GUI.VibeSettings; using ButtplugSong.GUI.VibeSettings.LimitSettings; using ButtplugSong.GUI.VibeSettings.Presets; using ButtplugSong.GUI.VibeSettings.VibeSources; using ButtplugSong.Helper; using ButtplugSong.Network; using GlobalEnums; using GlobalSettings; using GoodVibes; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.Scripting; using UnityEngine.UIElements; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ButtplugSong")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.2.0")] [assembly: AssemblyInformationalVersion("1.2.2+1a342453a3a9f4fa7c6a0b2a045a02a6e1ff9d44")] [assembly: AssemblyProduct("ButtplugSong")] [assembly: AssemblyTitle("Buttplug_Song")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/danatron1/ButtplugSong")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public static class FloatHelper { public static float Clamp(this float value, float min, float max) { if (value > max) { return max; } if (value < min) { return min; } return value; } } namespace BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Microsoft.CodeAnalysis.Embedded] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Microsoft.CodeAnalysis.Embedded] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace Microsoft.CodeAnalysis { [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace GoodVibes { internal enum WaveType { ConstantLinear, ConstantExponental, SineSmall, SineBig, Square, PulseWidth, Triangle, InverseTriangle, Bounce, ZigZag } public enum AfterZeroMode { Subtract, Multiply } internal class VibeLogic { private class ActivityLogEntry { internal string Identifier; internal string Details; internal ActivityLogEntry? TriggeredLog; public ActivityLogEntry(string identifier, string details) { Identifier = identifier; Details = details; base..ctor(); } } public static bool Armed; private Action? _logger; private (string identifier, string details)? triggeredActivityLog; internal bool timerCountdownPaused = true; internal bool vibeWhileTimerPaused = true; internal const float _actualHardTimerMaximum = 5999f; private float _maxTimer = 600f; private float _timeRemaining; internal AfterZeroMode afterZeroMode; internal float afterZeroPowerChange = 1f; internal float afterZeroTimer; internal float afterZeroPunctuate; private float punctuatingTimer; private float _maxPower = 1f; private float _minPower; private float _targetPower; public WaveType waveType; public float wavePeriod = 1f; public float ComboMultiplier = 1f; private float _timeSinceLastActivityLog; public float MaxTimer { get { return _maxTimer; } set { _maxTimer = value.Clamp(0f, 5999f); if (Time > _maxTimer) { Time = _maxTimer; } } } public bool TimerZero => _timeRemaining <= 0f; public bool IsVibing => ActualPower > 0f; public float Time { get { return _timeRemaining; } set { bool flag = !TimerZero; _timeRemaining = value.Clamp(0f, MaxTimer); if (TimerZero && flag) { TimerBecameZero(); } } } public bool Punctuating => punctuatingTimer > 0f; public float MaxPower { get { return _maxPower; } set { float maxPower = _maxPower; _maxPower = value.Clamp(0f, 1f); if (MinPower > MaxPower) { MinPower = MaxPower; } if (_maxPower != maxPower) { UpdatePowerClamped(TargetPower); } } } public float MinPower { get { return _minPower; } set { float minPower = _minPower; _minPower = value.Clamp(0f, MaxPower); if (_minPower != minPower) { UpdatePowerClamped(TimerZero ? _minPower : TargetPower); } } } public float TargetPower { get { return _targetPower; } private set { UpdatePowerClamped(value); } } public float ActualPower { get { if (timerCountdownPaused && !vibeWhileTimerPaused) { return GetWavePower(MinPower); } if (Punctuating) { return MaxPower; } if (TimerZero) { return GetWavePower(MinPower); } return GetWavePower(TargetPower); } } public event Action? PowerChanged; public event Action? PunctuateChanged; public event Action? VibeSourceActivated; public event Action? TimerHitZero; public event Action? AddActivityLog; private void TimerBecameZero() { if (TargetPower == MinPower && TimerZero) { return; } string text = "Timer Hit Zero"; string text2; if (TargetPower == MinPower) { text += " : Power Min"; text2 = "Power hit " + ((MinPower == 0f) ? "zero" : "minimum") + ".\nTimer reset to 0 seconds."; Time = 0f; } else { float num = (afterZeroMode switch { AfterZeroMode.Subtract => TargetPower - afterZeroPowerChange, AfterZeroMode.Multiply => TargetPower * afterZeroPowerChange, _ => throw new NotImplementedException(), }).Clamp(MinPower, MaxPower); text2 = $"Power: {TargetPower * 100f:0.#}% {AfterZeroModeString()} {afterZeroPowerChange * 100f:0.#}% = {num * 100f:0.#}%"; if (MinPower != 0f) { text2 += $" (minimum {MinPower * 100f:0.#}%)"; } if (num - MinPower < 0.02f) { if (num - MinPower > Mathf.Epsilon) { text2 = text2 + "\n(New power almost " + ((MinPower == 0f) ? "zero" : "minimum") + "; cutting off)"; } num = MinPower; } else if (afterZeroTimer <= 0f) { text2 = text2 + "\nNo timer increase, so setting power to " + ((MinPower == 0f) ? "0" : "minimum") + "."; num = MinPower; } else { text2 += string.Format("\nTimer set to {0} second{1}.", afterZeroTimer, (afterZeroTimer != 1f) ? "s" : ""); Time += afterZeroTimer; } TargetPower = num; } if (afterZeroPunctuate > 0f) { AddPunctuateHit(afterZeroPunctuate); text2 += $"\nPunctuate: +{afterZeroPunctuate}s of max power"; } triggeredActivityLog = (text, text2); this.TimerHitZero?.Invoke(); string AfterZeroModeString() { return afterZeroMode switch { AfterZeroMode.Subtract => "-", AfterZeroMode.Multiply => "*", _ => throw new NotImplementedException(), }; } } public void DecreaseTimer(float realTimeAmount, float timerAmount) { TickActivityLogs(realTimeAmount); if (Punctuating) { punctuatingTimer -= realTimeAmount; if (punctuatingTimer <= 0f) { Time += punctuatingTimer; punctuatingTimer = 0f; this.PunctuateChanged?.Invoke(obj: false); } } else { Time -= timerAmount; } timerCountdownPaused = timerAmount <= float.Epsilon; } private void UpdatePowerClamped(float value) { float targetPower = _targetPower; _targetPower = value.Clamp(MinPower, MaxPower); if (_targetPower != targetPower) { PowerUpdated(targetPower, _targetPower); } } private void PowerUpdated(float before, float after) { if (after == MinPower) { TimerBecameZero(); } this.PowerChanged?.Invoke(); } private float GetWavePower(float power) { if (waveType == WaveType.ConstantLinear || power == 0f) { return power; } float num = Time / wavePeriod; return (waveType switch { WaveType.ConstantExponental => power * power, WaveType.SineSmall => power * ((Mathf.Sin(num * MathF.PI * 2f) + 2f) / 3f), WaveType.SineBig => Mathf.Sin(num * MathF.PI * 2f) * 0.8f * power + 0.8f * power, WaveType.Square => (num % 1f > 0.5f) ? 0f : power, WaveType.PulseWidth => (num % 1f > power) ? 0f : 1f, WaveType.Triangle => num % 1f * power, WaveType.InverseTriangle => power * (1f - num % 1f), WaveType.Bounce => num % 1f * (1f - num % 1f) * power * 4f, WaveType.ZigZag => Mathf.Max(num % 1f, 1f - num % 1f) * power + power / 4f, _ => power, }).Clamp(MinPower, MaxPower); } public VibeLogic(Action? logger = null) { _logger = logger; } public void VibeSourceActivation(string identifier, float power, string powerMode, float time, string timeMode, float punctuateTime, float? basePower = null, float? baseTime = null) { string powerMode2 = powerMode; string timeMode2 = timeMode; string identifier2 = identifier; float powerBefore; float timeBefore; float newPower; float newTime; if (Armed) { powerBefore = TargetPower; timeBefore = Time; if (timeMode2 == "+" && !TimerZero) { time *= ComboMultiplier; } newPower = ChangeByMode(TargetPower, power, powerMode2).Clamp(MinPower, MaxPower); newTime = ChangeByMode(Time, time, timeMode2).Clamp(0f, MaxTimer); if (newTime != 0f) { TargetPower = newPower; } if (TargetPower != MinPower) { Time = newTime; } if (punctuateTime != 0f) { AddPunctuateHit(punctuateTime); } SendDetailedActivityLog(); this.VibeSourceActivated?.Invoke(); } static float ChangeByMode(float original, float modifier, string mode) { return mode switch { "+" => original + modifier, "-" => original - modifier, "=" => modifier, "≥" => Mathf.Max(modifier, original), "≤" => Mathf.Min(modifier, original), "x" => original * modifier, _ => original, }; } void SendDetailedActivityLog() { string text = ""; if (!PowerFieldMeaningless(power, powerMode2)) { text += $"Power: {powerMode2}{power * 100f:0.#}%"; if (basePower.HasValue && basePower.Value != power) { text += $" ({basePower.Value * 100f:0.#}% x {power / basePower.Value:0.###})"; } text = ((newPower != powerBefore) ? (text + $", {powerBefore * 100f:0.#}% -> {newPower * 100f:0.#}%") : (text + $", remain at {powerBefore * 100f:0.#}%")); if (MinPower != 0f && newPower == MinPower) { text += " (min)"; } else if (MaxPower != 1f && newPower == MaxPower) { text += " (max)"; } } if (!TimeFieldMeaningless(time, timeMode2)) { text = text + "\nTime: " + timeMode2 + DisplayTime(time); if (baseTime.HasValue && baseTime.Value != time) { text += $" ({DisplayTime(baseTime.Value, includeS: false)} x {time / baseTime.Value:0.###})"; } text = ((newTime != timeBefore) ? (text + ", " + DisplayTime(timeBefore, includeS: false) + " -> " + DisplayTime(newTime, includeS: false)) : (text + ", remain at " + DisplayTime(timeBefore))); if (MaxTimer == newTime) { text += " (max)"; } } if (punctuateTime != 0f) { text += $"\nPunctuate: +{punctuateTime:0.##}s (now at {punctuatingTimer:0.##}s)"; } text = text.Trim('\n'); if (!string.IsNullOrWhiteSpace(text)) { ProcessActivityLog(identifier2, text); } } } internal static string DisplayTime(float time, bool includeS = true) { int num = (int)(time / 60f); int num2 = num / 60; if (num2 >= 1) { return $"{num2:f0}:{num:00}:{time % 60f:00}"; } if (num >= 1) { return $"{num:f0}:{time % 60f:00}"; } if (includeS) { return $"{time:0.#}s"; } return $"{time:0.#}"; } private void AddPunctuateHit(float punctuateTime) { bool punctuating = Punctuating; punctuatingTimer += punctuateTime; if (Punctuating != punctuating) { this.PunctuateChanged?.Invoke(Punctuating); } } internal static bool PowerFieldMeaningless(float power, string powerMode) { switch (powerMode) { case "+": case "-": case "≥": return power <= 0f; case "≤": return power >= 1f; case "x": return power == 1f; default: return false; } } internal static bool TimeFieldMeaningless(float time, string timeMode) { switch (timeMode) { case "+": case "-": case "≥": return time <= 0f; case "x": return time == 1f; default: return false; } } private void Log(string s) { _logger?.Invoke(s); } private void TickActivityLogs(float deltaTime) { _timeSinceLastActivityLog += deltaTime; ProcessTriggeredActivityLog(); } private void ProcessActivityLog(string identifier, string details) { this.AddActivityLog?.Invoke(identifier, details, _timeSinceLastActivityLog); _timeSinceLastActivityLog = 0f; ProcessTriggeredActivityLog(); } private void ProcessTriggeredActivityLog() { (string, string)? tuple = triggeredActivityLog; if (tuple.HasValue) { string item = triggeredActivityLog.Value.identifier; string item2 = triggeredActivityLog.Value.details; triggeredActivityLog = null; ProcessActivityLog(item, item2); } } internal void MultiplyTimer(float multiplier, string? subID = null) { if (multiplier != 1f && Time != 0f) { float time = Time; Time *= multiplier; string text = "Multiply Timer"; if (!string.IsNullOrWhiteSpace(subID)) { text = text + " : " + subID; } ProcessActivityLog(text, $"Timer {multiplier}x multiplier: {DisplayTime(time, includeS: false)} -> {DisplayTime(Time, includeS: false)}"); } } } public class VibeManager { private static VibeManager? _instance; internal GUIManager UI; internal VibeLogic Logic; internal PlugManager plug; public float PlugUpdateFrequency = 0.125f; private float timeSinceLastPlugUpdate; public static VibeManager Instance { get { if (_instance == null) { throw new NotImplementedException("Instantiate properly before referencing! >:("); } return _instance; } private set { _instance = value; } } public bool HasDevice => GetDevices().Any(); public event Action? PlugReconnectEstablished; public event Action? NeedsUpdate; public event Action? LogMessage; public VibeManager(string modPath, Action? logger = null) { Instance = this; if (logger != null) { LogMessage += logger; } Logic = new VibeLogic(Log); UI = GUIManager.CreateAndInitialize(); Logic.PowerChanged += TargetPowerChanged; Logic.PunctuateChanged += PunctuateChanged; NeedsUpdate += Logic.DecreaseTimer; ModHooks.OnFinishedLoadingModsHook += FinishedLoading; ReconnectPlug(); } private void FinishedLoading() { VibeLogic.Armed = true; UI.FinishedLoading(); } public void Update(float realTime, float timerTime) { this.NeedsUpdate?.Invoke(realTime, timerTime); timeSinceLastPlugUpdate += realTime; if (timeSinceLastPlugUpdate > NetworkSettings.UpdateFrequency) { ForcePlugUpdate(routineUpdate: true); } } public void TargetPowerChanged() { ForcePlugUpdate(routineUpdate: false); } public void PunctuateChanged(bool _) { ForcePlugUpdate(routineUpdate: false); } public void ForcePlugUpdate(bool routineUpdate) { timeSinceLastPlugUpdate = 0f; plug.SetPowerLevel(Logic.ActualPower, routineUpdate); } internal IEnumerable GetDevices() { return plug.GetDevices(); } internal void ReconnectPlug() { DisconnectPlug(); plug = new PlugManager(Log, NetworkSettings.ServerAddress, NetworkSettings.Port, NetworkSettings.RetryAttempts); this.PlugReconnectEstablished?.Invoke(); } internal void DisconnectPlug() { plug?.ShutDown(); } internal void Log(string message) { LogAsync(message, 5); } internal async void LogAsync(string message, int attempts) { message = $"[@{DateTime.UtcNow.Minute:D2}:{DateTime.UtcNow.Second:D2}] {message}"; for (int logAttempts = 0; logAttempts < attempts; logAttempts++) { try { this.LogMessage?.Invoke(message); break; } catch (IOException) { await Task.Delay(50); } } } } } namespace ButtplugSong { [BepInPlugin("danatron1-ButtplugSongMod-Silksong", "ButtplugSong", "1.2.2")] public class ButtplugSongPlugin : BaseUnityPlugin { public static string ModPath = "Not yet loaded"; private const string ModId = "danatron1-ButtplugSongMod-Silksong"; private const string ModName = "ButtplugSong"; private const string ModVersion = "1.2.2"; private readonly Harmony harmony = new Harmony("danatron1-ButtplugSongMod-Silksong"); private VibeManager vibe; public const string Id = "danatron1-ButtplugSongMod-Silksong"; public static string Name => "ButtplugSong"; public static string Version => "1.2.2"; private void Awake() { ModPath = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); vibe = new VibeManager(ModPath, (Action?)((BaseUnityPlugin)this).Logger.LogInfo); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin " + Name + " (danatron1-ButtplugSongMod-Silksong) has loaded!")); } private void OnDestroy() { vibe?.DisconnectPlug(); harmony.UnpatchSelf(); } } [HarmonyPatch] internal static class ModHooks { [HarmonyPatch] private static class SavedItemGetPatch { private static IEnumerable TargetMethods() { List list = new List(); MethodInfo method = typeof(SavedItem).GetMethod("Get", new Type[2] { typeof(int), typeof(bool) }); if (method != null && !method.IsAbstract) { list.Add(method); } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } catch { continue; } Type[] array2 = array; foreach (Type type in array2) { if (!typeof(SavedItem).IsAssignableFrom(type) || type == typeof(SavedItem)) { continue; } MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "Get" && !methodInfo.IsAbstract) { list.Add(methodInfo); } } } } return list; } [HarmonyPrefix] private static void Prefix(SavedItem __instance) { ModHooks.OnItemPickupHook?.Invoke(__instance); } } public static event Func? OnTakeDamageHook; public static event Action? OnBindInterruptedHook; public static event Func? OnAddHealthHook; public static event Func? OnDealDamageHook; public static event Action? OnBeforeDeathHook; public static event Action? OnAfterDeathHook; public static event Action? OnGetCocoonHook; public static event Action? OnSetBoolHook; public static event Action? OnSetIntHook; public static event Action? OnRumbleHook; public static event Func? OnAddRosariesHook; public static event Func? OnTakeRosariesHook; public static event Func? OnAddShardsHook; public static event Func? OnTakeShardsHook; public static event Action? OnHardLandingHook; public static event Action? OnCourierBreakItemHook; public static event Action? OnFinishedLoadingModsHook; public static event Action? OnMaxHealthUpHook; public static event Action? OnMaxSilkUpHook; public static event Action? OnToolUnlockHook; public static event Action? OnItemPickupHook; [HarmonyPatch(typeof(PlayerData), "TakeHealth")] [HarmonyPrefix] private static void PlayerData_TakeHealth(PlayerData __instance, ref int amount, ref bool hasBlueHealth, ref bool allowFracturedMaskBreak) { if (ModHooks.OnTakeDamageHook != null) { amount = ModHooks.OnTakeDamageHook(__instance, amount); } } [HarmonyPatch(typeof(HeroController), "BindInterrupted")] [HarmonyPostfix] private static void OnBindInterrupted(HeroController __instance) { ModHooks.OnBindInterruptedHook?.Invoke(__instance); } [HarmonyPatch(typeof(PlayerData), "AddHealth")] [HarmonyPrefix] private static void PlayerData_AddHealth(PlayerData __instance, ref int amount) { if (ModHooks.OnAddHealthHook != null) { amount = ModHooks.OnAddHealthHook(__instance, amount); } } [HarmonyPatch(typeof(HealthManager), "TakeDamage")] [HarmonyPrefix] private static void OnDealDamage(HealthManager __instance, ref HitInstance hitInstance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (ModHooks.OnDealDamageHook != null) { hitInstance.DamageDealt = ModHooks.OnDealDamageHook(__instance, hitInstance); } } [HarmonyPatch(typeof(HeroController), "Die")] [HarmonyPrefix] private static void OnBeforePlayerDead(HeroController __instance, bool nonLethal, bool frostDeath) { ModHooks.OnBeforeDeathHook?.Invoke(__instance, nonLethal, frostDeath); } [HarmonyPatch(typeof(GameManager), "PlayerDead")] [HarmonyPrefix] private static void OnAfterPlayerDead(GameManager __instance, float waitTime) { ModHooks.OnAfterDeathHook?.Invoke(__instance); } [HarmonyPatch(typeof(HeroController), "CocoonBroken", new Type[] { typeof(bool), typeof(bool) })] [HarmonyPrefix] private static void OnGetCocoon(HeroController __instance, bool doAirPause, bool forceCanBind) { ModHooks.OnGetCocoonHook?.Invoke(__instance); } [HarmonyPatch(typeof(PlayerData), "SetBool")] [HarmonyPostfix] private static void OnSetBool(string boolName, bool value) { ModHooks.OnSetBoolHook?.Invoke(boolName, value); } [HarmonyPatch(typeof(PlayerData), "SetInt")] [HarmonyPostfix] private static void OnSetInt(string intName, int value) { ModHooks.OnSetIntHook?.Invoke(intName, value); } [HarmonyPatch(typeof(VibrationManager), "PlayVibrationClipOneShot", new Type[] { typeof(VibrationData), typeof(VibrationTarget?), typeof(bool), typeof(string), typeof(bool) })] [HarmonyPrefix] private static void OnPlayVibrationClipOneShot(ref VibrationData vibrationData, ref VibrationTarget? vibrationTarget, ref bool isLooping, ref string tag, ref bool isRealtime) { ModHooks.OnRumbleHook?.Invoke(tag); } [HarmonyPatch(typeof(PlayerData), "AddGeo")] [HarmonyPrefix] private static void OnAddRosaries(PlayerData __instance, ref int amount) { if (ModHooks.OnAddRosariesHook != null) { amount = ModHooks.OnAddRosariesHook(__instance, amount); } } [HarmonyPatch(typeof(PlayerData), "TakeGeo")] [HarmonyPrefix] private static void OnTakeRosaries(PlayerData __instance, ref int amount) { if (ModHooks.OnTakeRosariesHook != null) { amount = ModHooks.OnTakeRosariesHook(__instance, amount); } } [HarmonyPatch(typeof(PlayerData), "AddShards")] [HarmonyPrefix] private static void OnAddShards(PlayerData __instance, ref int amount) { if (ModHooks.OnAddShardsHook != null) { amount = ModHooks.OnAddShardsHook(__instance, amount); } } [HarmonyPatch(typeof(PlayerData), "TakeShards")] [HarmonyPrefix] private static void OnTakeShards(PlayerData __instance, ref int amount) { if (ModHooks.OnTakeShardsHook != null) { amount = ModHooks.OnTakeShardsHook(__instance, amount); } } [HarmonyPatch(typeof(HeroController), "DoHardLandingEffectNoHit")] [HarmonyPrefix] private static void OnHardLanding(HeroController __instance) { ModHooks.OnHardLandingHook?.Invoke(__instance); } [HarmonyPatch(typeof(DeliveryQuestItem), "BreakEffect")] [HarmonyPrefix] private static void CourierBreakItem(DeliveryQuestItem __instance, Vector2 heroPos) { ModHooks.OnCourierBreakItemHook?.Invoke(__instance); } [HarmonyPatch(typeof(OnScreenDebugInfo), "Awake")] [HarmonyPrefix] private static void OnFinishedLoadingMods() { ModHooks.OnFinishedLoadingModsHook?.Invoke(); } [HarmonyPatch(typeof(PlayerData), "AddToMaxHealth")] [HarmonyPostfix] private static void OnMaxHealthUp() { ModHooks.OnMaxHealthUpHook?.Invoke(); } [HarmonyPatch(typeof(HeroController), "AddToMaxSilk")] [HarmonyPostfix] private static void OnMaxSilkUp() { ModHooks.OnMaxSilkUpHook?.Invoke(); } [HarmonyPatch(typeof(ToolItem), "Unlock")] [HarmonyPostfix] private static void OnToolUnlock(ToolItem __instance) { ModHooks.OnToolUnlockHook?.Invoke(__instance); } } } namespace ButtplugSong.Helper { public static class ExtHelper { public delegate void DropdownChanged(string newValue, bool isEnum, T type) where T : Enum; public static Random rng = new Random(); public static void RunTask(this Task t) { Task t2 = t; Task.Run(() => t2); } public static async void FireAndForget(this Task t, Action logging) { try { await t; } catch (Exception ex) { logging?.Invoke($"FAF Exception: {ex.GetType()}; {ex.Message}"); if (ex.InnerException != null) { logging?.Invoke($" Inner: {ex.InnerException.GetType()}; {ex.InnerException.Message}"); } } } public static T ChooseRandom() where T : Enum { Array values = Enum.GetValues(typeof(T)); return (T)values.GetValue(rng.Next(values.Length)); } public static T ChooseRandom(this IEnumerable collection) { return collection.ElementAt(rng.Next(collection.Count())); } public static void SetClassListIf(this T field, string classList, Func predicate) where T : VisualElement { if (predicate(field)) { if (!((VisualElement)field).ClassListContains(classList)) { ((VisualElement)field).AddToClassList(classList); } } else if (((VisualElement)field).ClassListContains(classList)) { ((VisualElement)field).RemoveFromClassList(classList); } } public static bool OutsideRange(this T value, T lowerBound, T upperBound, out T inRangeValue) where T : IComparable { if (value.CompareTo(lowerBound) < 0) { inRangeValue = lowerBound; return true; } if (value.CompareTo(upperBound) > 0) { inRangeValue = upperBound; return true; } inRangeValue = value; return false; } public static BaseField SetupValueClamping(this BaseField notifier, T lowerBound, T upperBound, bool delay = true) where T : IComparable { T lowerBound2 = lowerBound; T upperBound2 = upperBound; BaseField notifier2 = notifier; if (delay && notifier2 is TextInputBaseField val) { val.isDelayed = true; } INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)notifier2, (EventCallback>)delegate(ChangeEvent evt) { if (evt.newValue.OutsideRange(lowerBound2, upperBound2, out var inRangeValue)) { notifier2.value = inRangeValue; } }); return notifier2; } public static BaseField SetupGreyout(this BaseField notifier, Func predicate) where T : IComparable { BaseField notifier2 = notifier; Func predicate2 = predicate; INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)notifier2, (EventCallback>)delegate { notifier2.SetClassListIf>("grey-value", (Func, bool>)((BaseField n) => predicate2(n.value))); }); return notifier2; } public static BaseField SetupSaving(this BaseField element, string? defaultValue = null, string? settingName = null) { string settingName2 = settingName; if (settingName2 == null) { settingName2 = ((VisualElement)element).name; } Preset.BindElementToSetting((VisualElement)(object)element, settingName2); INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)element, (EventCallback>)delegate(ChangeEvent evt) { Preset.Custom.SaveSettingChange(settingName2, evt.newValue.Replace(" ", "")); }); if (defaultValue != null) { PresetDefault.SaveDefaultSetting(settingName2, defaultValue.Replace(" ", "")); } return element; } public static BaseField SetupSaving(this BaseField element, T? defaultValue = null, string? settingName = null) where T : struct { string settingName2 = settingName; if (settingName2 == null) { settingName2 = ((VisualElement)element).name; } Preset.BindElementToSetting((VisualElement)(object)element, settingName2); INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)element, (EventCallback>)delegate(ChangeEvent evt) { Preset.Custom.SaveSettingChange(settingName2, evt.newValue); }); if (defaultValue.HasValue) { PresetDefault.SaveDefaultSetting(settingName2, defaultValue); } return element; } public static void Load(this BaseField element, Preset preset) where T : struct, IComparable { if (Preset.TryGetBoundSetting((VisualElement)(object)element, out string settingName)) { T val = preset.Get(settingName) ?? element.value; ChangeEvent pooled = ChangeEvent.GetPooled(element.value, val); ((EventBase)pooled).target = (IEventHandler)(object)element; element.SetValueWithoutNotify(val); ((CallbackEventHandler)element).SendEvent((EventBase)(object)pooled); return; } throw new ArgumentException("Saving not setup! Couldn't load setting from preset " + preset.Identifier + " for element " + ((VisualElement)element).name + " (" + ((VisualElement)element).typeName + ")"); } public static void Load(this BaseField element, Preset preset, bool friendlyName = true) { if (Preset.TryGetBoundSetting((VisualElement)(object)element, out string settingName)) { string text = preset.GetString(settingName) ?? element.value; if (friendlyName) { text = text.FriendlyName(); } ChangeEvent pooled = ChangeEvent.GetPooled(element.value, text); ((EventBase)pooled).target = (IEventHandler)(object)element; element.SetValueWithoutNotify(text); ((CallbackEventHandler)element).SendEvent((EventBase)(object)pooled); return; } throw new ArgumentException("Saving not setup! Couldn't load setting from preset " + preset.Identifier + " for element " + ((VisualElement)element).name + " (" + ((VisualElement)element).typeName + ")"); } public static BaseField DependsOn(this BaseField element, Toggle other, Func predicate) { BaseField element2 = element; Func predicate2 = predicate; Toggle other2 = other; INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)other2, (EventCallback>)delegate(ChangeEvent evt) { ((VisualElement)element2).SetEnabled(evt.newValue && predicate2(other2)); }); return element2; } public static BaseField DependsOn(this BaseField element, BaseField other, Func, bool> predicate) { BaseField element2 = element; Func, bool> predicate2 = predicate; BaseField other2 = other; INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)other2, (EventCallback>)delegate { ((VisualElement)element2).SetEnabled(predicate2(other2)); }); return element2; } public static BaseField DependsOn(this BaseField element, params Toggle[] others) { BaseField element2 = element; Toggle[] others2 = others; if (others2.Length == 0) { return element2; } if (others2.Length == 1) { INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)others2[0], (EventCallback>)delegate(ChangeEvent evt) { ((VisualElement)element2).SetEnabled(evt.newValue); }); } else { Toggle[] array = others2; for (int i = 0; i < array.Length; i++) { INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)array[i], (EventCallback>)delegate(ChangeEvent evt) { ((VisualElement)element2).SetEnabled(evt.newValue && others2.All((Toggle x) => ((BaseField)(object)x).value)); }); } } return element2; } public static string FriendlyName(this string s) { string text = $"{char.ToUpper(s[0])}"; bool flag = true; for (int i = 1; i < s.Length; i++) { if (char.IsUpper(s[i])) { if (!char.IsWhiteSpace(s[i - 1]) && (!flag || i + 1 >= s.Length || char.IsLower(s[i + 1]))) { text += " "; } flag = true; } else { flag = false; } text += s[i]; } return text; } public static string[] DisplayFriendlyEnumNames() where T : Enum { return Enum.GetNames(typeof(T)).Select(FriendlyName).ToArray(); } public static DropdownField PopulateDropdown(this DropdownField dropdown, params string[] additionalSettings) where T : Enum { string[] array = DisplayFriendlyEnumNames(); int num = 0; string[] array2 = new string[array.Length + additionalSettings.Length]; ReadOnlySpan readOnlySpan = new ReadOnlySpan(array); readOnlySpan.CopyTo(new Span(array2).Slice(num, readOnlySpan.Length)); num += readOnlySpan.Length; ReadOnlySpan readOnlySpan2 = new ReadOnlySpan(additionalSettings); readOnlySpan2.CopyTo(new Span(array2).Slice(num, readOnlySpan2.Length)); num += readOnlySpan2.Length; return dropdown.PopulateDropdown(array2); } public static DropdownField PopulateDropdown(this DropdownField dropdown, params string[] options) { ((BasePopupField)(object)dropdown).choices = options.ToList(); return dropdown; } public static DropdownField RegisterDropdownChangedCallback(this DropdownField dropdown, DropdownChanged callWhenChanged) where T : struct, Enum { DropdownChanged callWhenChanged2 = callWhenChanged; INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)dropdown, (EventCallback>)delegate(ChangeEvent evt) { if (Enum.TryParse(evt.newValue.Replace(" ", ""), ignoreCase: true, out var result)) { callWhenChanged2(evt.newValue, isEnum: true, result); } else { callWhenChanged2(evt.newValue, isEnum: false, default(T)); } }); return dropdown; } public static T Next(this T current) where T : Enum { T[] array = (T[])Enum.GetValues(typeof(T)); int num = Array.IndexOf(array, current) + 1; if (array.Length != num) { return array[num]; } return array[0]; } } } namespace ButtplugSong.GUI { internal class GUIManager : MonoBehaviour { private static GUIManager? _instance; public GameObject GUI_GameObject; public UIDocument UIDoc; public VisualElement Root; internal VisualTreeAsset LogRow; internal VisualTreeAsset Device; public VibeManager Vibe; public VisualTreeAsset TreeAsset; public TemplateContainer TreeContainer; public StyleSheet Style; public PanelSettings Settings; private const float _displayUpdateFrequency = 0.0625f; private float _timeSinceLastUpdate = 0.1f; public WaveDisplay VibeDisplay; public VisualElement WaveDisplayHolder; public Label DebugInfo; public VisualElement SettingsPanel; public TabView MainTabView; public Label TimerReadout; public Label PowerReadout; public Label PowerReadoutActual; public Label DeathRoll; public List SettingsFoldouts; public List Sources; public LimitsSettings Limits; public WaveSettings Wave; public TimerSettings Timer; public PresetSettings Presets; public StopTheVibes StopVibes; public NetworkSettings Network; public UISettings UISettings; public GroupBox VibeLog; private const int MAX_VIBELOG_ENTRIES = 100; private Queue vibeLogEntries = new Queue(); private DateTime? _lastLogEntry; private float deathRollTimeRemaining; private bool _settingsCurrentlyLocked; public static GUIManager Instance { get { if ((Object)(object)_instance == (Object)null) { throw new NotImplementedException("Instantiate properly before referencing! >:("); } return _instance; } private set { _instance = value; } } public bool UIHidden { get; private set; } public static GUIManager CreateAndInitialize() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown GameObject val = new GameObject("ButtplugSong GUI Manager"); Instance = val.AddComponent(); Object.DontDestroyOnLoad((Object)val); Instance.Vibe = VibeManager.Instance; Instance.Vibe.UI = Instance; Instance.LoadAssetBundle(); Instance.CreateGUI(); Instance.CreateUIHandles(); Instance.AddHooks(); return Instance; } public void LoadAssetBundle() { AssetBundle val = AssetBundle.LoadFromFile(Path.Combine(ButtplugSongPlugin.ModPath, "buttplugsong.gui")); TreeAsset = val.LoadAsset("ButtplugSong-UXML"); Style = val.LoadAsset("ButtplugSong-USS"); Settings = val.LoadAsset("ButtplugSong-PanelSettings"); LogRow = val.LoadAsset("ButtplugSong-LogRow"); Device = val.LoadAsset("ButtplugSong-Device"); val.Unload(false); } public void CreateGUI() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown GUI_GameObject = new GameObject("ButtplugSong GUI"); UIDoc = GUI_GameObject.AddComponent(); UIDoc.visualTreeAsset = TreeAsset; UIDoc.panelSettings = Settings; Root = UIDoc.rootVisualElement; CreateWaveDisplay(); GUI_GameObject.transform.SetParent(((Component)this).transform); Root.AddToClassList("hide"); void CreateWaveDisplay() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) try { WaveDisplayHolder = UQueryExtensions.Q(Root, "WaveDisplayHolder", (string)null); VibeDisplay = new WaveDisplay { LineColor = new Color(1f, 0.72156864f, 82f / 85f, 0.8f), GlowColor = new Color(0.59607846f, 0.07450981f, 0.56078434f, 0.5f), LineWidth = 1f, GlowWidth = 1.2f, MinimumValue = 0f, MaximumValue = 1f, DefaultValue = 0f, LineBufferTop = 0.2f, LineBufferBottom = 0.8f, RecordSteps = 128 }; ((VisualElement)VibeDisplay).SetSize(new Vector2(320f, 35f)); ((VisualElement)VibeDisplay).AddToClassList("wave-display"); WaveDisplayHolder.Add((VisualElement)(object)VibeDisplay); } catch (Exception ex) { Vibe.Log($"Failed to create WaveDisplay: {ex.Message} - {ex.InnerException} - {ex.StackTrace}"); } } } private void CreateUIHandles() { DebugInfo = UQueryExtensions.Q