Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Buttplug Song v1.2.2
ButtplugSong.dll
Decompiled 4 months ago
The result has been truncated due to the large size, download it to view full contents!
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<string>? _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<bool>? PunctuateChanged; public event Action? VibeSourceActivated; public event Action? TimerHitZero; public event Action<string, string, float>? 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<string>? 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<float, float>? NeedsUpdate; public event Action<string>? LogMessage; public VibeManager(string modPath, Action<string>? 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<ButtplugDevice> 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<string>?)((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<MethodBase> TargetMethods() { List<MethodBase> list = new List<MethodBase>(); 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<PlayerData, int, int>? OnTakeDamageHook; public static event Action<HeroController>? OnBindInterruptedHook; public static event Func<PlayerData, int, int>? OnAddHealthHook; public static event Func<HealthManager, HitInstance, int>? OnDealDamageHook; public static event Action<HeroController, bool, bool>? OnBeforeDeathHook; public static event Action<GameManager>? OnAfterDeathHook; public static event Action<HeroController>? OnGetCocoonHook; public static event Action<string, bool>? OnSetBoolHook; public static event Action<string, int>? OnSetIntHook; public static event Action<string?>? OnRumbleHook; public static event Func<PlayerData, int, int>? OnAddRosariesHook; public static event Func<PlayerData, int, int>? OnTakeRosariesHook; public static event Func<PlayerData, int, int>? OnAddShardsHook; public static event Func<PlayerData, int, int>? OnTakeShardsHook; public static event Action<HeroController>? OnHardLandingHook; public static event Action<DeliveryQuestItem>? OnCourierBreakItemHook; public static event Action? OnFinishedLoadingModsHook; public static event Action? OnMaxHealthUpHook; public static event Action? OnMaxSilkUpHook; public static event Action<ToolItem>? OnToolUnlockHook; public static event Action<SavedItem>? 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<T>(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<string> 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<T>() where T : Enum { Array values = Enum.GetValues(typeof(T)); return (T)values.GetValue(rng.Next(values.Length)); } public static T ChooseRandom<T>(this IEnumerable<T> collection) { return collection.ElementAt(rng.Next(collection.Count())); } public static void SetClassListIf<T>(this T field, string classList, Func<T, bool> 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<T>(this T value, T lowerBound, T upperBound, out T inRangeValue) where T : IComparable<T> { 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<T> SetupValueClamping<T>(this BaseField<T> notifier, T lowerBound, T upperBound, bool delay = true) where T : IComparable<T> { T lowerBound2 = lowerBound; T upperBound2 = upperBound; BaseField<T> notifier2 = notifier; if (delay && notifier2 is TextInputBaseField<T> val) { val.isDelayed = true; } INotifyValueChangedExtensions.RegisterValueChangedCallback<T>((INotifyValueChanged<T>)(object)notifier2, (EventCallback<ChangeEvent<T>>)delegate(ChangeEvent<T> evt) { if (evt.newValue.OutsideRange(lowerBound2, upperBound2, out var inRangeValue)) { notifier2.value = inRangeValue; } }); return notifier2; } public static BaseField<T> SetupGreyout<T>(this BaseField<T> notifier, Func<T, bool> predicate) where T : IComparable<T> { BaseField<T> notifier2 = notifier; Func<T, bool> predicate2 = predicate; INotifyValueChangedExtensions.RegisterValueChangedCallback<T>((INotifyValueChanged<T>)(object)notifier2, (EventCallback<ChangeEvent<T>>)delegate { notifier2.SetClassListIf<BaseField<T>>("grey-value", (Func<BaseField<T>, bool>)((BaseField<T> n) => predicate2(n.value))); }); return notifier2; } public static BaseField<string> SetupSaving(this BaseField<string> 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<string>((INotifyValueChanged<string>)(object)element, (EventCallback<ChangeEvent<string>>)delegate(ChangeEvent<string> evt) { Preset.Custom.SaveSettingChange(settingName2, evt.newValue.Replace(" ", "")); }); if (defaultValue != null) { PresetDefault.SaveDefaultSetting(settingName2, defaultValue.Replace(" ", "")); } return element; } public static BaseField<T> SetupSaving<T>(this BaseField<T> 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<T>((INotifyValueChanged<T>)(object)element, (EventCallback<ChangeEvent<T>>)delegate(ChangeEvent<T> evt) { Preset.Custom.SaveSettingChange(settingName2, evt.newValue); }); if (defaultValue.HasValue) { PresetDefault.SaveDefaultSetting(settingName2, defaultValue); } return element; } public static void Load<T>(this BaseField<T> element, Preset preset) where T : struct, IComparable<T> { if (Preset.TryGetBoundSetting((VisualElement)(object)element, out string settingName)) { T val = preset.Get<T>(settingName) ?? element.value; ChangeEvent<T> pooled = ChangeEvent<T>.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<string> 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<string> pooled = ChangeEvent<string>.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<T> DependsOn<T>(this BaseField<T> element, Toggle other, Func<Toggle, bool> predicate) { BaseField<T> element2 = element; Func<Toggle, bool> predicate2 = predicate; Toggle other2 = other; INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)other2, (EventCallback<ChangeEvent<bool>>)delegate(ChangeEvent<bool> evt) { ((VisualElement)element2).SetEnabled(evt.newValue && predicate2(other2)); }); return element2; } public static BaseField<TSelf> DependsOn<TSelf, TOther>(this BaseField<TSelf> element, BaseField<TOther> other, Func<BaseField<TOther>, bool> predicate) { BaseField<TSelf> element2 = element; Func<BaseField<TOther>, bool> predicate2 = predicate; BaseField<TOther> other2 = other; INotifyValueChangedExtensions.RegisterValueChangedCallback<TOther>((INotifyValueChanged<TOther>)(object)other2, (EventCallback<ChangeEvent<TOther>>)delegate { ((VisualElement)element2).SetEnabled(predicate2(other2)); }); return element2; } public static BaseField<T> DependsOn<T>(this BaseField<T> element, params Toggle[] others) { BaseField<T> element2 = element; Toggle[] others2 = others; if (others2.Length == 0) { return element2; } if (others2.Length == 1) { INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)others2[0], (EventCallback<ChangeEvent<bool>>)delegate(ChangeEvent<bool> evt) { ((VisualElement)element2).SetEnabled(evt.newValue); }); } else { Toggle[] array = others2; for (int i = 0; i < array.Length; i++) { INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)array[i], (EventCallback<ChangeEvent<bool>>)delegate(ChangeEvent<bool> evt) { ((VisualElement)element2).SetEnabled(evt.newValue && others2.All((Toggle x) => ((BaseField<bool>)(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<T>() where T : Enum { return Enum.GetNames(typeof(T)).Select(FriendlyName).ToArray(); } public static DropdownField PopulateDropdown<T>(this DropdownField dropdown, params string[] additionalSettings) where T : Enum { string[] array = DisplayFriendlyEnumNames<T>(); int num = 0; string[] array2 = new string[array.Length + additionalSettings.Length]; ReadOnlySpan<string> readOnlySpan = new ReadOnlySpan<string>(array); readOnlySpan.CopyTo(new Span<string>(array2).Slice(num, readOnlySpan.Length)); num += readOnlySpan.Length; ReadOnlySpan<string> readOnlySpan2 = new ReadOnlySpan<string>(additionalSettings); readOnlySpan2.CopyTo(new Span<string>(array2).Slice(num, readOnlySpan2.Length)); num += readOnlySpan2.Length; return dropdown.PopulateDropdown(array2); } public static DropdownField PopulateDropdown(this DropdownField dropdown, params string[] options) { ((BasePopupField<string, string>)(object)dropdown).choices = options.ToList(); return dropdown; } public static DropdownField RegisterDropdownChangedCallback<T>(this DropdownField dropdown, DropdownChanged<T> callWhenChanged) where T : struct, Enum { DropdownChanged<T> callWhenChanged2 = callWhenChanged; INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)dropdown, (EventCallback<ChangeEvent<string>>)delegate(ChangeEvent<string> evt) { if (Enum.TryParse<T>(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<T>(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<Foldout> SettingsFoldouts; public List<VibeSource> 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<VisualElement> vibeLogEntries = new Queue<VisualElement>(); 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<GUIManager>(); 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<VisualTreeAsset>("ButtplugSong-UXML"); Style = val.LoadAsset<StyleSheet>("ButtplugSong-USS"); Settings = val.LoadAsset<PanelSettings>("ButtplugSong-PanelSettings"); LogRow = val.LoadAsset<VisualTreeAsset>("ButtplugSong-LogRow"); Device = val.LoadAsset<VisualTreeAsset>("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<UIDocument>(); 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<Label>(Root, "DebugInfo", (string)null); SettingsPanel = UQueryExtensions.Q<VisualElement>(Root, "SettingsPanel", (string)null); MainTabView = UQueryExtensions.Q<TabView>(Root, "MainTabView", (string)null); TimerReadout = UQueryExtensions.Q<Label>(Root, "TimerReadout", (string)null); PowerReadout = UQueryExtensions.Q<Label>(Root, "PowerReadout", (string)null); PowerReadoutActual = UQueryExtensions.Q<Label>(Root, "PowerReadoutActual", (string)null); DeathRoll = UQueryExtensions.Q<Label>(Root, "DeathRoll", (string)null); SettingsFoldouts = new List<Foldout>(6) { UQueryExtensions.Q<Foldout>(Root, "VibeSources", (string)null), UQueryExtensions.Q<Foldout>(Root, "LimitsSettings", (string)null), UQueryExtensions.Q<Foldout>(Root, "WaveTypeSettings", (string)null), UQueryExtensions.Q<Foldout>(Root, "TimerSettings", (string)null), UQueryExtensions.Q<Foldout>(Root, "PresetsSettings", (string)null), UQueryExtensions.Q<Foldout>(Root, "StopVibesSettings", (string)null) }; Sources = new List<VibeSource>(7) { new BuzzOnDamage(), new BuzzOnHeal(), new BuzzOnStrike(), new BuzzOnDeath(), new BuzzOnPickups(), new BuzzOnRumble(), new BuzzOnRandom() }; Presets = new PresetSettings(); Limits = new LimitsSettings(); Wave = new WaveSettings(); Timer = new TimerSettings(); StopVibes = new StopTheVibes(); Network = new NetworkSettings(); UISettings = new UISettings(); VibeLog = UQueryExtensions.Q<GroupBox>(Root, "VibeLog", (string)null); LogActivity("Vibe Log Initialized!\nRunning " + ButtplugSongPlugin.Name + " v" + ButtplugSongPlugin.Version + "\nDebug logs are found in \"LogOutput.log\" in your BepInEx folder. This log is just for vibe activity."); ((VisualElement)DebugInfo).AddToClassList("hide"); } public void FinishedLoading() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown SetToPreset(Preset.Custom); Root.RemoveFromClassList("hide"); if ((Object)(object)ManagerSingleton<InputHandler>.Instance != (Object)null) { ManagerSingleton<InputHandler>.Instance.OnCursorVisibilityChange += new CursorVisibilityChange(UpdateSettingsPanelVisibility); } } public void SetToPreset(Preset preset) { Vibe.Log($"Setting to preset: {preset.Identifier} ({preset.Settings.Count})"); foreach (VibeSource source in Sources) { source.SetToPreset(preset); } Limits.SetToPreset(preset); Wave.SetToPreset(preset); Timer.SetToPreset(preset); Presets.SetToPreset(preset); StopVibes.SetToPreset(preset); Network.SetToPreset(preset); UISettings.SetToPreset(preset); } public void AddHooks() { Vibe.Logic.PunctuateChanged += UpdatePunctuateGraphic; Vibe.Logic.AddActivityLog += LogActivity; Vibe.Logic.PowerChanged += UpdateLockSettings; Vibe.Logic.VibeSourceActivated += UpdateLockSettings; } public void UpdatePunctuateGraphic(bool punctuating) { VibeDisplay.SetClassListIf("punctuating", (WaveDisplay x) => Vibe.Logic.Punctuating); } public void LogActivity(object generalMessage) { LogActivity("Debug Message", generalMessage.ToString()); } public void LogActivity(string sourceLine, string vibeLine) { LogActivity(sourceLine, vibeLine, -1f); } public void LogActivity(string sourceLine, string vibeLine, float timerSeconds) { if (string.IsNullOrWhiteSpace(sourceLine) || string.IsNullOrWhiteSpace(vibeLine)) { return; } VisualElement val = (VisualElement)(object)LogRow.Instantiate(); Label val2 = UQueryExtensions.Q<Label>(val, "TimeData", (string)null); Label val3 = UQueryExtensions.Q<Label>(val, "SourceData", (string)null); Label val4 = UQueryExtensions.Q<Label>(val, "VibeData", (string)null); DateTime now = DateTime.Now; float? num = (_lastLogEntry.HasValue ? new float?((float)(now - _lastLogEntry.Value).TotalSeconds) : null); string text = $"[{now.Hour:D2}:{now.Minute:D2}:{now.Second:D2} :{now.Millisecond:D3}] "; if (num.HasValue) { text = text + "Passed: " + VibeLogic.DisplayTime(num.Value) + " real"; if (timerSeconds >= 0f) { text = text + ", " + VibeLogic.DisplayTime(timerSeconds) + " timer"; } } else { text += "First log"; } ((TextElement)val3).text = sourceLine; ((TextElement)val4).text = vibeLine; ((TextElement)val2).text = text; vibeLogEntries.Enqueue(val); ((VisualElement)VibeLog).Add(val); _lastLogEntry = now; while (vibeLogEntries.Count > 100) { ((VisualElement)VibeLog).Remove(vibeLogEntries.Dequeue()); } } public void Update() { float realTime = Time.unscaledDeltaTime; float timerCountdown = Timer.GetTimerCountdown(Time.unscaledDeltaTime); Vibe.Update(realTime, timerCountdown); UpdateWaveDisplay(); UpdatePowerDisplay(); UpdateTimeDisplay(); UpdateDeathRollDisplay(); UpdateToggleUI(); void UpdateDeathRollDisplay() { if (!(deathRollTimeRemaining <= 0f)) { deathRollTimeRemaining -= realTime; if (deathRollTimeRemaining <= 0f) { ((VisualElement)DeathRoll).AddToClassList("hide"); ((VisualElement)DeathRoll).opacity = 0f; deathRollTimeRemaining = 0f; } else if (deathRollTimeRemaining <= 5f) { ((VisualElement)DeathRoll).opacity = (deathRollTimeRemaining / 5f).Clamp(0f, 1f); } } } void UpdatePowerDisplay() { ((TextElement)PowerReadout).text = $"{Vibe.Logic.TargetPower * 100f:f0}%"; if (Vibe.Logic.TargetPower != Vibe.Logic.ActualPower) { ((TextElement)PowerReadoutActual).text = $"{Vibe.Logic.ActualPower * 100f:f0}%"; ((VisualElement)PowerReadoutActual).opacity = 0.75f; } else if (((VisualElement)PowerReadoutActual).opacity > 0f) { ((TextElement)PowerReadoutActual).text = $"{Vibe.Logic.ActualPower * 100f:f0}%"; Label powerReadoutActual = PowerReadoutActual; ((VisualElement)powerReadoutActual).opacity = ((VisualElement)powerReadoutActual).opacity - realTime / 3f; } } void UpdateTimeDisplay() { if (Vibe.Logic.Time >= 60f) { ((TextElement)TimerReadout).text = $"{(int)(Vibe.Logic.Time / 60f)}:{Vibe.Logic.Time % 60f:00}"; } else { ((TextElement)TimerReadout).text = $"{Vibe.Logic.Time:f2}"; } } void UpdateToggleUI() { if (Input.GetKeyDown((KeyCode)287)) { UIHidden = !UIHidden; Root.SetClassListIf<VisualElement>("hide", (Func<VisualElement, bool>)((VisualElement x) => UIHidden)); } } void UpdateWaveDisplay() { _timeSinceLastUpdate += realTime; while (_timeSinceLastUpdate >= 0.0625f) { _timeSinceLastUpdate -= 0.0625f; VibeDisplay?.PushRecordStep(Vibe.Logic.ActualPower); } } } private void UpdateSettingsPanelVisibility(bool isVisible) { SettingsPanel.SetClassListIf<VisualElement>("hide", (Func<VisualElement, bool>)((VisualElement x) => UISettings.AutoCollapseSettings && !isVisible)); } internal void DisplayDeathDice(int rollAmount) { ((TextElement)DeathRoll).text = $"Death roll result: {rollAmount}"; ((VisualElement)DeathRoll).opacity = 1f; ((VisualElement)DeathRoll).RemoveFromClassList("hide"); deathRollTimeRemaining = 20 + Math.Abs(11 - rollAmount); } internal void UpdateLockSettings() { LockSettings(!Vibe.Logic.TimerZero && StopVibes != null && StopVibes.LockSettings); } private void LockSettings(bool disabled = true) { if (_settingsCurrentlyLocked == disabled) { return; } _settingsCurrentlyLocked = disabled; foreach (Foldout settingsFoldout in SettingsFoldouts) { ((VisualElement)settingsFoldout).enabledSelf = !disabled; } } } public abstract class GUISection { public string Identifier { get; private set; } protected static VibeManager Vibe => VibeManager.Instance; protected GUISection(string identifier) { Identifier = identifier; base..ctor(); } protected static void Log(object message) { Log(message.ToString()); } protected static void Log(string message) { Vibe.Log(message); } protected static T Get<T>(string elementName) where T : VisualElement { return Get<T>(elementName, Vibe.UI.Root); } protected static T Get<T>(string elementName, VisualElement root) where T : VisualElement { T val = UQueryExtensions.Q<T>(root, elementName, (string)null); if (val == null) { Log("Searched for " + elementName + " but couldn't find it"); throw new NullReferenceException("Could not find " + elementName + " under root " + root.name); } return val; } } internal class UISettings : GUISection, IPresetLoadable { private static readonly string[] UIScaleOptions = new string[8] { "0.6x", "0.8x", "1x", "1.2x", "1.4x", "1.6x", "1.8x", "2x" }; private static readonly Dictionary<string, string> UIHeightOptions = new Dictionary<string, string> { { "Short", "tab-view-short" }, { "Medium", "tab-view-medium" }, { "Long", "tab-view-long" } }; protected readonly DropdownField _uiScale; protected readonly DropdownField _uiHeight; protected readonly Toggle _displayWaveform; protected readonly Toggle _autoCollapseSettings; public bool AutoCollapseSettings { get { if (_autoCollapseSettings != null) { return ((BaseField<bool>)(object)_autoCollapseSettings).value; } return false; } set { ((BaseField<bool>)(object)_autoCollapseSettings).value = value; } } public UISettings() : base("UI") { _uiScale = GUISection.Get<DropdownField>("UIScale"); INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)((BaseField<string>)(object)_uiScale.PopulateDropdown(UIScaleOptions)).SetupSaving(), (EventCallback<ChangeEvent<string>>)UIScaleChanged); _uiHeight = GUISection.Get<DropdownField>("UIHeight"); INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)((BaseField<string>)(object)_uiHeight.PopulateDropdown(UIHeightOptions.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> x) => x.Key).ToArray())).SetupSaving(), (EventCallback<ChangeEvent<string>>)UIHeightChanged); _displayWaveform = GUISection.Get<Toggle>("DisplayWaveform"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_displayWaveform).SetupSaving<bool>(), (EventCallback<ChangeEvent<bool>>)DisplayWaveformChanged); _autoCollapseSettings = GUISection.Get<Toggle>("AutoCollapseSettings"); ((BaseField<bool>)(object)_autoCollapseSettings).SetupSaving<bool>(); ((PopupField<string>)(object)_uiScale).SetIndexWithoutNotify(2); ((PopupField<string>)(object)_uiHeight).SetIndexWithoutNotify(1); } public void SetToPreset(Preset preset) { ((BaseField<string>)(object)_uiScale).Load(preset); ((BaseField<string>)(object)_uiHeight).Load(preset); ((BaseField<bool>)(object)_displayWaveform).Load<bool>(preset); ((BaseField<bool>)(object)_autoCollapseSettings).Load<bool>(preset); } private void DisplayWaveformChanged(ChangeEvent<bool> evt) { ChangeEvent<bool> evt2 = evt; GUISection.Vibe.UI.VibeDisplay.SetClassListIf("hide", (WaveDisplay x) => !evt2.newValue); } private void UIHeightChanged(ChangeEvent<string> evt) { string value; string text = (UIHeightOptions.TryGetValue(evt.newValue, out value) ? value : "tab-view-medium"); foreach (KeyValuePair<string, string> uIHeightOption in UIHeightOptions) { ((VisualElement)GUISection.Vibe.UI.MainTabView).RemoveFromClassList(uIHeightOption.Value); } ((VisualElement)GUISection.Vibe.UI.MainTabView).AddToClassList(text); } private void UIScaleChanged(ChangeEvent<string> evt) { PanelSettings panelSettings = GUISection.Vibe.UI.UIDoc.panelSettings; string newValue = evt.newValue; if (newValue == null) { goto IL_013c; } int length = newValue.Length; float scale; if (length != 2) { if (length != 4) { goto IL_013c; } switch (newValue[2]) { case '6': break; case '8': goto IL_00a4; case '2': goto IL_00c0; case '4': goto IL_00cf; default: goto IL_013c; } if (!(newValue == "0.6x")) { if (!(newValue == "1.6x")) { goto IL_013c; } scale = 1.6f; } else { scale = 0.6f; } } else { char c = newValue[0]; if (c != '1') { if (c != '2' || !(newValue == "2x")) { goto IL_013c; } scale = 2f; } else { if (!(newValue == "1x")) { goto IL_013c; } scale = 1f; } } goto IL_0142; IL_0142: panelSettings.scale = scale; return; IL_013c: scale = 1f; goto IL_0142; IL_00cf: if (!(newValue == "1.4x")) { goto IL_013c; } scale = 1.4f; goto IL_0142; IL_00a4: if (!(newValue == "0.8x")) { if (!(newValue == "1.8x")) { goto IL_013c; } scale = 1.8f; } else { scale = 0.8f; } goto IL_0142; IL_00c0: if (!(newValue == "1.2x")) { goto IL_013c; } scale = 1.2f; goto IL_0142; } } } namespace ButtplugSong.GUI.VibeSettings { internal class StopTheVibes : GUISection, IPresetLoadable { protected readonly Button _stopTheVibes; protected readonly Label _stopTheVibesCostLabel; protected readonly Toggle _enabled; protected readonly Toggle _buttonCostsResources; protected readonly Toggle _buttonCostsRosaries; protected readonly Toggle _buttonCostsShards; protected readonly FloatField _rosaryCostPercent; protected readonly FloatField _shardCostPercent; protected readonly IntegerField _rosaryCostFlat; protected readonly IntegerField _shardCostFlat; protected readonly Toggle _buttonDisablesMinimums; protected readonly Toggle _lockSettings; public int RosaryCost; public int ShardCost; public bool LockSettings { get { return ((BaseField<bool>)(object)_lockSettings).value; } set { ((BaseField<bool>)(object)_lockSettings).value = value; } } public StopTheVibes() : base("StopVibes") { _stopTheVibes = GUISection.Get<Button>("StopVibes"); _stopTheVibes.clicked += StopTheVibesButtonClicked; _stopTheVibesCostLabel = GUISection.Get<Label>("StopVibesCostLabel"); _enabled = GUISection.Get<Toggle>("StopVibesEnabled"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_enabled).SetupSaving<bool>(true), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>); _buttonCostsResources = GUISection.Get<Toggle>("StopCostsResources"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_buttonCostsResources).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>); _buttonCostsRosaries = GUISection.Get<Toggle>("StopRosaryCost"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_buttonCostsRosaries).SetupSaving<bool>(false).DependsOn<bool>((Toggle[])(object)new Toggle[2] { _enabled, _buttonCostsResources }), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>); _rosaryCostPercent = GUISection.Get<FloatField>("StopRosaryCostPercent"); INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_rosaryCostPercent).SetupSaving<float>(50f).DependsOn<float>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsRosaries }).SetupValueClamping(0f, 100f) .SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)RecalculateCosts<float>); _rosaryCostFlat = GUISection.Get<IntegerField>("StopRosaryCostFlat"); INotifyValueChangedExtensions.RegisterValueChangedCallback<int>((INotifyValueChanged<int>)(object)((BaseField<int>)(object)_rosaryCostFlat).SetupSaving<int>(0).DependsOn<int>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsRosaries }).SetupValueClamping(0, 9999) .SetupGreyout((int x) => x == 0), (EventCallback<ChangeEvent<int>>)RecalculateCosts<int>); _buttonCostsShards = GUISection.Get<Toggle>("StopShardCost"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_buttonCostsShards).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[2] { _enabled, _buttonCostsResources }), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>); _shardCostPercent = GUISection.Get<FloatField>("StopShardCostPercent"); INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_shardCostPercent).SetupSaving<float>(0f).DependsOn<float>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsShards }).SetupValueClamping(0f, 100f) .SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)RecalculateCosts<float>); _shardCostFlat = GUISection.Get<IntegerField>("StopShardCostFlat"); INotifyValueChangedExtensions.RegisterValueChangedCallback<int>((INotifyValueChanged<int>)(object)((BaseField<int>)(object)_shardCostFlat).SetupSaving<int>(400).DependsOn<int>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsShards }).SetupValueClamping(0, 800) .SetupGreyout((int x) => x == 0), (EventCallback<ChangeEvent<int>>)RecalculateCosts<int>); _buttonDisablesMinimums = GUISection.Get<Toggle>("ButtonDisablesMinimums"); ((BaseField<bool>)(object)_buttonDisablesMinimums).SetupSaving<bool>(false).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }); _lockSettings = GUISection.Get<Toggle>("LockSettings"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_lockSettings).SetupSaving<bool>(false), (EventCallback<ChangeEvent<bool>>)LockSettingsChanged); ModHooks.OnAddRosariesHook += CurrencyChanged; ModHooks.OnTakeRosariesHook += CurrencyChanged; ModHooks.OnAddShardsHook += CurrencyChanged; ModHooks.OnTakeShardsHook += CurrencyChanged; } public void SetToPreset(Preset preset) { ((BaseField<bool>)(object)_enabled).Load<bool>(preset); ((BaseField<bool>)(object)_buttonCostsResources).Load<bool>(preset); ((BaseField<bool>)(object)_buttonCostsRosaries).Load<bool>(preset); ((BaseField<float>)(object)_rosaryCostPercent).Load<float>(preset); ((BaseField<int>)(object)_rosaryCostFlat).Load<int>(preset); ((BaseField<bool>)(object)_buttonCostsShards).Load<bool>(preset); ((BaseField<float>)(object)_shardCostPercent).Load<float>(preset); ((BaseField<int>)(object)_shardCostFlat).Load<int>(preset); ((BaseField<bool>)(object)_buttonDisablesMinimums).Load<bool>(preset); ((BaseField<bool>)(object)_lockSettings).Load<bool>(preset); } private void LockSettingsChanged(ChangeEvent<bool> evt) { GUISection.Vibe.UI.UpdateLockSettings(); } private int CurrencyChanged(PlayerData data, int amount) { RecalculateCosts(); return amount; } private void RecalculateCosts<T>(ChangeEvent<T> evt) { RecalculateCosts(); } private void RecalculateCosts() { if (!((BaseField<bool>)(object)_enabled).value || !((BaseField<bool>)(object)_buttonCostsResources).value) { ((VisualElement)_stopTheVibes).enabledSelf = ((BaseField<bool>)(object)_enabled).value; ((TextElement)_stopTheVibesCostLabel).text = (((BaseField<bool>)(object)_enabled).value ? "Free!" : "Button disabled"); return; } RosaryCost = CalculateCost((CurrencyType)0); ShardCost = CalculateCost((CurrencyType)1); if (RosaryCost == 0 && ShardCost == 0) { ((TextElement)_stopTheVibesCostLabel).text = "Free!"; } else if (RosaryCost == 0) { ((TextElement)_stopTheVibesCostLabel).text = string.Format("Costs {0} shard{1}", ShardCost, (ShardCost == 1) ? "" : "s"); } else if (ShardCost == 0) { ((TextElement)_stopTheVibesCostLabel).text = string.Format("Costs {0} rosar{1}", RosaryCost, (RosaryCost == 1) ? "y" : "ies"); } else { ((TextElement)_stopTheVibesCostLabel).text = string.Format("Costs {0} rosar{1}, {2} shard{3}", RosaryCost, (RosaryCost == 1) ? "y" : "ies", ShardCost, (ShardCost == 1) ? "" : "s"); } ((VisualElement)_stopTheVibes).enabledSelf = CurrencyManager.GetCurrencyAmount((CurrencyType)0) >= RosaryCost && CurrencyManager.GetCurrencyAmount((CurrencyType)1) >= ShardCost; int CalculateCost(CurrencyType type) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Invalid comparison between Unknown and I4 if (!((BaseField<bool>)(object)_enabled).value || !((BaseField<bool>)(object)_buttonCostsResources).value) { return 0; } int currencyAmount = CurrencyManager.GetCurrencyAmount(type); if ((int)type == 0 && ((BaseField<bool>)(object)_buttonCostsRosaries).value) { currencyAmount = (int)((float)currencyAmount * ((BaseField<float>)(object)_rosaryCostPercent).value / 100f); return currencyAmount + ((BaseField<int>)(object)_rosaryCostFlat).value; } if ((int)type == 1 && ((BaseField<bool>)(object)_buttonCostsShards).value) { currencyAmount = (int)((float)currencyAmount * ((BaseField<float>)(object)_shardCostPercent).value / 100f); return currencyAmount + ((BaseField<int>)(object)_shardCostFlat).value; } return 0; } } private void StopTheVibesButtonClicked() { if (((BaseField<bool>)(object)_buttonCostsResources).value) { if (((BaseField<bool>)(object)_buttonCostsRosaries).value) { if (RosaryCost > CurrencyManager.GetCurrencyAmount((CurrencyType)0)) { return; } CurrencyManager.TakeCurrency(RosaryCost, (CurrencyType)0, true); } if (((BaseField<bool>)(object)_buttonCostsShards).value) { if (ShardCost > CurrencyManager.GetCurrencyAmount((CurrencyType)1)) { return; } CurrencyManager.TakeCurrency(ShardCost, (CurrencyType)1, true); } } if (((BaseField<bool>)(object)_buttonDisablesMinimums).value) { ((BaseField<bool>)(object)GUISection.Vibe.UI.Limits._minimumsEnabled).value = false; } GUISection.Vibe.Logic.VibeSourceActivation("Stop the Vibes!", 1f, "-", 0f, "+", 0f); } } internal enum CountdownMode { Always, Default, InGame, Unpaused, SuperHot, Never } internal class TimerSettings : GUISection, IPresetLoadable { private readonly DropdownField _timerCountdownMode; private readonly Label _countdownModeDescription; private readonly Toggle _timeStacking; private readonly FloatField _timeStackingMultiplier; private readonly Toggle _vibeWhileTimerPaused; private readonly FloatField _afterZeroPunctuate; private readonly Label _afterZeroPunctuateLabel; private readonly FloatField _afterZeroPower; private readonly Label _percentagesReminder; private readonly Label _zeroPowerWarning; private readonly FloatField _afterZeroTimer; private readonly CyclingButton<AfterZeroMode> _afterZeroPowerMode; public int TimerCountdownModeIndex { get { return ((PopupField<string>)(object)_timerCountdownMode).index; } set { ((PopupField<string>)(object)_timerCountdownMode).index = value; } } public CountdownMode TimerCountdownMode => (CountdownMode)TimerCountdownModeIndex; public TimerSettings() : base("Timer") { _timerCountdownMode = GUISection.Get<DropdownField>("TimerCountdownMode"); ((BaseField<string>)(object)_timerCountdownMode.PopulateDropdown<CountdownMode>(Array.Empty<string>()).RegisterDropdownChangedCallback<CountdownMode>(CountdownModeChanged)).SetupSaving(CountdownMode.Default.ToString()); _countdownModeDescription = GUISection.Get<Label>("CountdownModeDescription"); _timeStacking = GUISection.Get<Toggle>("TimeStacking"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_timeStacking).SetupSaving<bool>(true), (EventCallback<ChangeEvent<bool>>)TimeStackingChanged<bool>); _timeStackingMultiplier = GUISection.Get<FloatField>("TimeStackingMultiplier"); INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_timeStackingMultiplier).SetupSaving<float>(1.2f).SetupGreyout((float x) => x == 1f).SetupValueClamping(0f, 999f) .DependsOn<float>((Toggle[])(object)new Toggle[1] { _timeStacking }), (EventCallback<ChangeEvent<float>>)TimeStackingChanged<float>); _vibeWhileTimerPaused = GUISection.Get<Toggle>("VibeWhileTimerPaused"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_vibeWhileTimerPaused).SetupSaving<bool>(true), (EventCallback<ChangeEvent<bool>>)VibeWhilePausedChanged); _afterZeroPunctuate = GUISection.Get<FloatField>("AfterZeroPunctuate"); INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_afterZeroPunctuate).SetupSaving<float>(0f).SetupValueClamping(0f, 999f).SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)UpdateAfterZeroPunctuate); _afterZeroPunctuateLabel = GUISection.Get<Label>("AfterZeroPunctuateLabel"); _afterZeroTimer = GUISection.Get<FloatField>("AfterZeroTimer"); INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_afterZeroTimer).SetupSaving<float>(0f).SetupValueClamping(0f, 999f).SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)AfterZeroTimerChanged); _percentagesReminder = GUISection.Get<Label>("PercentagesReminder"); _zeroPowerWarning = GUISection.Get<Label>("ZeroPowerWarning"); _afterZeroPower = GUISection.Get<FloatField>("AfterZeroPower"); INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_afterZeroPower).SetupSaving<float>(100f).SetupValueClamping(0f, 100f).SetupGreyout<float>(AfterZeroPowerMeaningless), (EventCallback<ChangeEvent<float>>)AfterZeroPowerChanged); _afterZeroPowerMode = new CyclingButton<AfterZeroMode>("AfterZeroPowerMode", AfterZeroMode.Subtract, AfterZeroModeTextSelector); _afterZeroPowerMode.SetupSaving(AfterZeroMode.Subtract); CyclingButton<AfterZeroMode> afterZeroPowerMode = _afterZeroPowerMode; afterZeroPowerMode.clicked = (Action)Delegate.Combine(afterZeroPowerMode.clicked, new Action(AfterZeroPowerModeChanged)); } public void SetToPreset(Preset preset) { ((BaseField<string>)(object)_timerCountdownMode).Load(preset); ((BaseField<bool>)(object)_timeStacking).Load<bool>(preset); ((BaseField<float>)(object)_timeStackingMultiplier).Load<float>(preset); ((BaseField<bool>)(object)_vibeWhileTimerPaused).Load<bool>(preset); ((BaseField<float>)(object)_afterZeroPunctuate).Load<float>(preset); ((BaseField<float>)(object)_afterZeroTimer).Load<float>(preset); ((BaseField<float>)(object)_afterZeroPower).Load<float>(preset); _afterZeroPowerMode.Load(preset); } private void AfterZeroTimerChanged(ChangeEvent<float> evt) { GUISection.Vibe.Logic.afterZeroTimer = evt.newValue; } private void AfterZeroPowerModeChanged() { GUISection.Vibe.Logic.afterZeroMode = _afterZeroPowerMode.currentMode; UpdateAfterZeroPowerReminderLabels(); } private void AfterZeroPowerChanged(ChangeEvent<float> evt) { GUISection.Vibe.Logic.afterZeroPowerChange = evt.newValue / 100f; UpdateAfterZeroPowerReminderLabels(); } private void UpdateAfterZeroPowerReminderLabels() { _percentagesReminder.SetClassListIf<Label>("hide", (Func<Label, bool>)((Label x) => _afterZeroPowerMode.currentMode != AfterZeroMode.Multiply || ((BaseField<float>)(object)_afterZeroPower).value > 1f)); _zeroPowerWarning.SetClassListIf<Label>("hide", (Func<Label, bool>)((Label x) => !AfterZeroPowerMeaningless(((BaseField<float>)(object)_afterZeroPower).value))); } private bool AfterZeroPowerMeaningless(float value) { return _afterZeroPowerMode.currentMode switch { AfterZeroMode.Subtract => value <= 0f, AfterZeroMode.Multiply => value >= 100f, _ => false, }; } private void VibeWhilePausedChanged(ChangeEvent<bool> evt) { GUISection.Vibe.Logic.vibeWhileTimerPaused = evt.newValue; } private void UpdateAfterZeroPunctuate(ChangeEvent<float> evt) { if (!(evt.newValue < 0f)) { ((TextElement)_afterZeroPunctuateLabel).text = $"Punctuate with {evt.newValue}s of max power, then"; GUISection.Vibe.Logic.afterZeroPunctuate = evt.newValue; } } private void CountdownModeChanged(string newValue, bool isEnum, CountdownMode type) { if (isEnum) { ((TextElement)_countdownModeDescription).text = CountdownModeExplanation(type); } } private void TimeStackingChanged<T>(ChangeEvent<T> evt) { GUISection.Vibe.Logic.ComboMultiplier = (((BaseField<bool>)(object)_timeStacking).value ? ((BaseField<float>)(object)_timeStackingMultiplier).value : 1f); } private static string AfterZeroModeTextSelector(AfterZeroMode mode) { return mode switch { AfterZeroMode.Subtract => "-", AfterZeroMode.Multiply => "x", _ => "?", }; } internal float GetTimerCountdown(float unscaledDeltaTime) { if (!ShouldTimerCountDown(TimerCountdownMode)) { return 0f; } if (TimerCountdownMode == CountdownMode.SuperHot && (Object)(object)HeroController.instance != (Object)null) { float magnitude = ((Vector2)(ref HeroController.instance.current_velocity)).magnitude; float num = ((magnitude <= float.Epsilon) ? 0.03f : ((!(magnitude > 10f)) ? (((Vector2)(ref HeroController.instance.current_velocity)).magnitude / 10f) : (0.9f + ((Vector2)(ref HeroController.instance.current_velocity)).magnitude / 100f))); return unscaledDeltaTime * num; } return unscaledDeltaTime; } private static bool ShouldTimerCountDown(CountdownMode mode) { switch (mode) { case CountdownMode.Always: return true; case CountdownMode.Default: return Time.timeScale > float.Epsilon; case CountdownMode.InGame: return (Object)(object)HeroController.instance != (Object)null && HeroController.instance.isGameplayScene; case CountdownMode.Unpaused: case CountdownMode.SuperHot: return (Object)(object)HeroController.instance != (Object)null && HeroController.instance.isGameplayScene && !HeroController.instance.IsPaused(); case CountdownMode.Never: return false; default: return true; } } private static string CountdownModeExplanation(CountdownMode mode) { return mode switch { CountdownMode.Always => "\"Always\": Always counts down (unless the game freezes, i.e. when loading).", CountdownMode.Default => "\"Default\": Counts down when time is simulated (includes main menu, excludes pausing).", CountdownMode.InGame => "\"In Game\": excludes non-gameplay scenes like the main menu, but includes pausing.", CountdownMode.Unpaused => "\"Unpaused\": stops the timer unless the game is playing (i.e. unpaused).", CountdownMode.SuperHot => "\"Super Hot\": the timer only moves when you move.", CountdownMode.Never => "\"Never\": for if you want to control power/time reduction via subtractive vibe sources.", _ => $"DESCRIPTION FOR COUNTDOWN MODE {mode} NOT IMPLEMENTED", }; } } internal class WaveSettings : GUISection, IPresetLoadable { private static float[] WavePeriods = new float[14] { 0.25f, 0.5f, 0.75f, 1f, 1.5f, 2f, 3f, 4f, 6f, 8f, 12f, 16f, 24f, 32f }; private const string RandomWaveString = "Random Wave"; private readonly DropdownField _waveTypeSelector; private readonly SliderInt _wavePeriod; private readonly Label _wavePeriodLabel; private readonly Button _testWave; public string WaveTypeSelector => ((BaseField<string>)(object)_waveTypeSelector).value; public WaveSettings() : base("WaveType") { _waveTypeSelector = GUISection.Get<DropdownField>("WaveTypeSelector"); _wavePeriod = GUISection.Get<SliderInt>("WavePeriod"); _wavePeriodLabel = GUISection.Get<Label>("WavePeriodLabel"); _testWave = GUISection.Get<Button>("TestWave"); ((BaseField<string>)(object)_waveTypeSelector.PopulateDropdown<WaveType>(new string[1] { "Random Wave" }).RegisterDropdownChangedCallback<WaveType>(WaveTypeChanged)).SetupSaving(WaveType.ConstantLinear.ToString(), "WaveType"); INotifyValueChangedExtensions.RegisterValueChangedCallback<int>((INotifyValueChanged<int>)(object)((BaseField<int>)(object)_wavePeriod).SetupSaving<int>(3, "WavePeriodOptionNumber").DependsOn<int, string>((BaseField<string>)(object)_waveTypeSelector, (BaseField<string> x) => !x.value.StartsWith("Constant")), (EventCallback<ChangeEvent<int>>)WavePeriodChanged); _testWave.clicked += TestWaveClicked; GUISection.Vibe.Logic.VibeSourceActivated += ReRandomizeWave; } public void SetToPreset(Preset preset) { ((BaseField<string>)(object)_waveTypeSelector).Load(preset); ((BaseField<int>)(object)_wavePeriod).Load<int>(preset); WavePeriodChanged(); } private void WavePeriodChanged(ChangeEvent<int> evt) { WavePeriodChanged(); } private void WavePeriodChanged() { GUISection.Vibe.Logic.wavePeriod = WavePeriods[((BaseField<int>)(object)_wavePeriod).value]; ((TextElement)_wavePeriodLabel).text = WavePeriods[((BaseField<int>)(object)_wavePeriod).value].ToString(); } private void TestWaveClicked() { GUISection.Vibe.Logic.VibeSourceActivation("Test Button", 0.25f, "+", 2f, "+", 0f); } private void WaveTypeChanged(string newValue, bool isEnum, WaveType type) { if (isEnum) { GUISection.Vibe.Logic.waveType = type; } } internal void ReRandomizeWave() { if (WaveTypeSelector == "Random Wave") { GUISection.Vibe.Logic.waveType = ExtHelper.ChooseRandom<WaveType>(); } } } } namespace ButtplugSong.GUI.VibeSettings.VibeSources { internal class BuzzOnDamage : VibeSourceWithPunctuate { private readonly Toggle _scaleWithDamage; private readonly Toggle _vulnerableVibing; private readonly FloatField _vulnerableVibingThreshold; public bool ScaleWithDamage { get { return ((BaseField<bool>)(object)_scaleWithDamage).value; } set { ((BaseField<bool>)(object)_scaleWithDamage).value = value; } } public bool VulnerableVibingEnabled { get { return ((BaseField<bool>)(object)_vulnerableVibing).value; } set { ((BaseField<bool>)(object)_vulnerableVibing).value = value; } } public float VulnerableVibingThreshold { get { return ((BaseField<float>)(object)_vulnerableVibingThreshold).value / 100f; } set { ((BaseField<float>)(object)_vulnerableVibingThreshold).value = value * 100f; } } public bool VulnerableVibingActive { get { if (VulnerableVibingEnabled) { return GUISection.Vibe.Logic.TargetPower >= VulnerableVibingThreshold; } return false; } } protected override string _punctuateReminderDescription => "taking damage"; public BuzzOnDamage() : base("Damage", onByDefault: true, 20f, 5f, defaultPunctuate: true) { ModHooks.OnTakeDamageHook += PlayerHit; _scaleWithDamage = GUISection.Get<Toggle>("ScaleWithDamage"); ((BaseField<bool>)(object)_scaleWithDamage).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }); _vulnerableVibing = GUISection.Get<Toggle>("VulnerableVibing"); ((BaseField<bool>)(object)_vulnerableVibing).SetupSaving<bool>(false); _vulnerableVibingThreshold = GUISection.Get<FloatField>("VulnerableVibingThreshold"); ((BaseField<float>)(object)_vulnerableVibingThreshold).SetupSaving<float>(50f).SetupValueClamping(0f, 100f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _vulnerableVibing }); } public override void SetToPreset(Preset preset) { base.SetToPreset(preset); ((BaseField<bool>)(object)_scaleWithDamage).Load<bool>(preset); ((BaseField<bool>)(object)_vulnerableVibing).Load<bool>(preset); ((BaseField<float>)(object)_vulnerableVibingThreshold).Load<float>(preset); } public int PlayerHit(PlayerData data, int damage) { if (GUISection.Vibe.Logic.IsVibing && VulnerableVibingActive) { damage *= 2; } if (!base.Enabled) { return damage; } string subID = ((damage > 1) ? damage.ToString() : string.Empty); if (ScaleWithDamage) { Activate(base.Power * (float)damage, base.Time * (float)damage, subID); } else { Activate(subID); } return damage; } } internal class BuzzOnDeath : VibeSourceWithPunctuate { private readonly Toggle _includeNonLethal; private readonly Toggle _raceTheTimer; public bool RaceTheTimerActive; private readonly Toggle _corpseRunFail; private readonly FloatField _corpseRunFailMultiplier; private readonly Toggle _steelSoul; private readonly FloatField _steelSoulMultiplier; private readonly Toggle _raiseMinimum; private readonly FloatField _raiseMinimumAmount; private readonly Toggle _deathDice; private readonly Label _mostRecentRoll; [CompilerGenerated] private MinimumDefault? <_minimumDefault>k__BackingField; public bool IncludeNonLethal { get { return ((BaseField<bool>)(object)_includeNonLethal).value; } set { ((BaseField<bool>)(object)_includeNonLethal).value = value; } } public bool RaceTheTimer { get { return ((BaseField<bool>)(object)_raceTheTimer).value; } set { ((BaseField<bool>)(object)_raceTheTimer).value = value; } } public bool CorpseRunFail { get { return ((BaseField<bool>)(object)_corpseRunFail).value; } set { ((BaseField<bool>)(object)_corpseRunFail).value = value; } } public float CorpseRunFailMultiplier { get { return ((BaseField<float>)(object)_corpseRunFailMultiplier).value; } set { ((BaseField<float>)(object)_corpseRunFailMultiplier).value = value; } } public bool SteelSoul { get { return ((BaseField<bool>)(object)_steelSoul).value; } set { ((BaseField<bool>)(object)_steelSoul).value = value; } } public float SteelSoulMultiplier { get { return ((BaseField<float>)(object)_steelSoulMultiplier).value; } set { ((BaseField<float>)(object)_steelSoulMultiplier).value = value; } } public bool RaiseMinimum { get { return ((BaseField<bool>)(object)_raiseMinimum).value; } set { ((BaseField<bool>)(object)_raiseMinimum).value = value; } } public float RaiseMinimumAmount { get { return ((BaseField<float>)(object)_raiseMinimumAmount).value / 100f; } set { ((BaseField<float>)(object)_raiseMinimumAmount).value = value * 100f; } } public bool DeathDice { get { return ((BaseField<bool>)(object)_deathDice).value; } set { ((BaseField<bool>)(object)_deathDice).value = value; } } private MinimumDefault? _minimumDefault { get { if (<_minimumDefault>k__BackingField == null) { <_minimumDefault>k__BackingField = (MinimumDefault)GUISection.Vibe.UI.Limits.Minimums.FirstOrDefault((MinimumBase x) => x is MinimumDefault); } return <_minimumDefault>k__BackingField; } } protected override string _punctuateReminderDescription => "death"; public BuzzOnDeath() : base("Death", onByDefault: true, 100f, 10f, defaultPunctuate: true, 60) { ModHooks.OnBeforeDeathHook += OnDeathBefore; ModHooks.OnAfterDeathHook += OnDeathAfter; ModHooks.OnGetCocoonHook += OnGetCocoon; GUISection.Vibe.Logic.TimerHitZero += OnTimerZero; _includeNonLethal = GUISection.Get<Toggle>("IncludeNonLethal"); ((BaseField<bool>)(object)_includeNonLethal).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }); _raceTheTimer = GUISection.Get<Toggle>("RaceTheTimer"); ((BaseField<bool>)(object)_raceTheTimer).SetupSaving<bool>(false).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }); _corpseRunFail = GUISection.Get<Toggle>("CorpseRunFail"); ((BaseField<bool>)(object)_corpseRunFail).SetupSaving<bool>(false); _corpseRunFailMultiplier = GUISection.Get<FloatField>("CorpseRunFailMultiplier"); ((BaseField<float>)(object)_corpseRunFailMultiplier).SetupSaving<float>(2f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _corpseRunFail }).SetupValueClamping(0f, 999f) .SetupGreyout((float x) => x == 1f); _steelSoul = GUISection.Get<Toggle>("SteelSoul"); ((BaseField<bool>)(object)_steelSoul).SetupSaving<bool>(false); _steelSoulMultiplier = GUISection.Get<FloatField>("SteelSoulMultiplier"); ((BaseField<float>)(object)_steelSoulMultiplier).SetupSaving<float>(10f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _steelSoul }).SetupValueClamping(0f, 999f) .SetupGreyout((float x) => x == 1f); _raiseMinimum = GUISection.Get<Toggle>("RaiseMinimum"); INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_raiseMinimum).SetupSaving<bool>(false), (EventCallback<ChangeEvent<bool>>)RaiseMinimumChanged<bool>); _raiseMinimumAmount = GUISection.Get<FloatField>("RaiseMinimumAmount"); ((BaseField<float>)(object)_raiseMinimumAmount).SetupSaving<float>(5f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _raiseMinimum }).SetupValueClamping(0f, 100f) .SetupGreyout((float x) => x == 0f); _deathDice = GUISection.Get<Toggle>("DeathDice"); ((BaseField<bool>)(object)_deathDice).SetupSaving<bool>(false); _mostRecentRoll = GUISection.Get<Label>("MostRecentRoll"); } public override void SetToPreset(Preset preset) { base.SetToPreset(preset); ((BaseField<bool>)(object)_includeNonLethal).Load<bool>(preset); ((BaseField<bool>)(object)_raceTheTimer).Load<bool>(preset); ((BaseField<bool>)(object)_corpseRunFail).Load<bool>(preset); ((BaseField<float>)(object)_corpseRunFailMultiplier).Load<float>(preset); ((BaseField<bool>)(object)_steelSoul).Load<bool>(preset); ((BaseField<float>)(object)_steelSoulMultiplier).Load<float>(preset); ((BaseField<bool>)(object)_raiseMinimum).Load<bool>(preset); ((BaseField<float>)(object)_raiseMinimumAmount).Load<float>(preset); ((BaseField<bool>)(object)_deathDice).Load<bool>(preset); } private static bool HasCocoonOut(PlayerData data) { return !string.IsNullOrEmpty(data.HeroCorpseScene); } private static bool IsSteelSoulMode(PlayerData data) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 PermadeathModes permadeathMode = data.permadeathMode; if (permadeathMode - 1 <= 1) { return true; } return false; } private void RaiseMinimumChanged<T>(ChangeEvent<T> evt) { _minimumDefault?.UpdateReminderText(); } private void OnDeathBefore(HeroController manager, bool nonLethal, bool frostDeath) { if (manager.gm.IsMemoryScene()) { nonLethal = true; } if (base.Enabled && (IncludeNonLethal || !nonLethal)) { Activate(); } PlayerData playerData = manager.playerData; if (SteelSoul && IsSteelSoulMode(playerData)) { GUISection.Vibe.Logic.MultiplyTimer(SteelSoulMultiplier, "Steel Soul Death"); } if (CorpseRunFail && HasCocoonOut(playerData) && !IsSteelSoulMode(playerData)) { GUISection.Vibe.Logic.MultiplyTimer(CorpseRunFailMultiplier, "Corpse Run Death"); } if (RaiseMinimum) { _minimumDefault?.RaiseMinimumDeath(RaiseMinimumAmount); } if (DeathDice) { DeathRoll(); } } private void DeathRoll() { int num = ExtHelper.rng.Next(20) + 1; ((VisualElement)_mostRecentRoll).RemoveFromClassList("hide"); ((TextElement)_mostRecentRoll).text = $"Most recent roll: {num}"; GUISection.Vibe.UI.DisplayDeathDice(num); } private void OnDeathAfter(GameManager gm) { if (((BaseField<bool>)(object)_enabled).value && RaceTheTimer && !IsSteelSoulMode(gm.playerData) && HasCocoonOut(gm.playerData) && !GUISection.Vibe.Logic.TimerZero) { StartRaceTheTimer(); } } private void StartRaceTheTimer() { RaceTheTimerActive = true; UpdateRaceTheTimerGraphic(); } private void OnGetCocoon(HeroController controller) { if (RaceTheTimerActive) { EndRaceTheTimer(timerHitZero: false); } } private void OnTimerZero() { if (RaceTheTimerActive) { EndRaceTheTimer(timerHitZero: true); } } private void EndRaceTheTimer(bool timerHitZero) { if (RaceTheTimerActive && timerHitZero && HasCocoonOut(PlayerData.instance)) { int num = DeleteCorpse(); GUISection.Vibe.UI.LogActivity("Timer Hit Zero : Race the Timer", string.Format("Timer hit 0. Deleting cocoon.\nPlayer lost {0} rosaries.{1}", num, (num > 0) ? " Sorry!" : "")); } RaceTheTimerActive = false; UpdateRaceTheTimerGraphic(); } private static int DeleteCorpse() { int silk = PlayerData.instance.silk; int heroCorpseMoneyPool = PlayerData.instance.HeroCorpseMoneyPool; PlayerData.instance.HeroCorpseMoneyPool = 0; PlayerData.instance.HeroCorpseScene = string.Empty; PlayerData.instance.HeroCorpseMarkerGuid = null; EventRegister.SendEvent("BREAK HERO CORPSE", (GameObject)null); if (PlayerData.instance.silkMax > 9) { PlayerData.instance.IsSilkSpoolBroken = false; EventRegister.SendEvent(EventRegisterEvents.SpoolUnbroken, (GameObject)null); } if (PlayerData.instance.silk > silk) { HeroController.instance.TakeSilk(PlayerData.instance.silk - silk); } return heroCorpseMoneyPool; } private void UpdateRaceTheTimerGraphic() { GUISection.Vibe.UI.TimerReadout.SetClassListIf<Label>("race-the-timer", (Func<Label, bool>)((Label x) => RaceTheTimerActive)); } } internal class BuzzOnHeal : VibeSourceWithPunctuate { private readonly Toggle _noHealingWhileVibing; private readonly Toggle _interruption; private readonly FloatField _interruptionTime; private DateTime _lastHealInterruptionTime = DateTime.MinValue; private static TimeSpan _healInterruptionCooldown = TimeSpan.FromSeconds(1.0); public bool NoHealingWhileVibing { get { return ((BaseField<bool>)(object)_noHealingWhileVibing).value; } set { ((BaseField<bool>)(object)_noHealingWhileVibing).value = value; } } public bool NoHealing { get { if (NoHealingWhileVibing) { return !GUISection.Vibe.Logic.TimerZero; } return false; } } public bool Interruption { get { return ((BaseField<bool>)(object)_interruption).value; } set { ((BaseField<bool>)(object)_interruption).value = value; } } public float InterruptionTime { get { return ((BaseField<float>)(object)_interruptionTime).value; } set { ((BaseField<float>)(object)_interruptionTime).value = value; } } protected override string _punctuateReminderDescription => "healing"; public BuzzOnHeal() : base("Heal", onByDefault: true, 10f, 5f) { ModHooks.OnAddHealthHook += Healed; ModHooks.OnBindInterruptedHook += HealInterrupted; _noHealingWhileVibing = GUISection.Get<Toggle>("NoHealingWhileVibing"); ((BaseField<bool>)(object)_noHealingWhileVibing).SetupSaving<bool>(false); _interruption = GUISection.Get<Toggle>("Interruption"); ((BaseField<bool>)(object)_interruption).SetupSaving<bool>(true); _interruptionTime = GUISection.Get<FloatField>("InterruptionTime"); ((BaseField<float>)(object)_interruptionTime).SetupSaving<float>(2f).SetupValueClamping(0f, 999f).SetupGreyout((float x) => x == 0f) .DependsOn<float>((Toggle[])(object)new Toggle[1] { _interruption }); } public override void SetToPreset(Preset preset) { base.SetToPreset(preset); ((BaseField<bool>)(object)_noHealingWhileVibing).Load<bool>(preset); ((BaseField<bool>)(object)_interruption).Load<bool>(preset); ((BaseField<float>)(object)_interruptionTime).Load<float>(preset); } private int Healed(PlayerData data, int amount) { if (NoHealing) { amount = 0; } if (!base.Enabled) { return amount; } Activate(); return amount; } private void HealInterrupted(HeroController controller) { if (Interruption && !(DateTime.Now - _lastHealInterruptionTime < _healInterruptionCooldown)) { if (controller.WillDoBellBindHit()) { ActivatePunctuation(InterruptionTime / 2f); } else { ActivatePunctuation(InterruptionTime); } _lastHealInterruptionTime = DateTime.Now; } } } internal class BuzzOnPickups : VibeSourceWithPunctuate { private readonly Toggle _scaleWithWeighting; private readonly Dictionary<string, WeightedEvent> KnownItems = new Dictionary<string, WeightedEvent>(); public bool ScaleWithWeighting { get { return ((BaseField<bool>)(object)_scaleWithWeighting).value; } set { ((BaseField<bool>)(object)_scaleWithWeighting).value = value; } } protected override string _punctuateReminderDescription => "collecting an item"; public BuzzOnPickups() : base("Pickups", onByDefault: false, 50f, 5f, defaultPunctuate: true) { ModHooks.OnItemPickupHook += ItemPickup; ModHooks.OnSetBoolHook += OnSetBool; ModHooks.OnSetIntHook += OnSetInt; ModHooks.OnMaxHealthUpHook += OnMaxHealthUp; ModHooks.OnMaxSilkUpHook += OnMaxSilkUp; ModHooks.OnToolUnlockHook += ToolUnlock; _scaleWithWeighting = GUISection.Get<Toggle>("PickupsScaleWithWeighting"); ((BaseField<bool>)(object)_scaleWithWeighting).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }); VisualElement parent = ((VisualElement)GUISection.Get<Label>("PickupsItemListLabel")).parent; KnownItems["Rosary_Set_Frayed"] = CreateUI("FrayedRosaryString", 0.3f, defaultOn: true, gapBelow: false, "Collectable Items"); KnownItems["Rosary_Set_Small"] = CreateUI("RosaryString", 0.6f, defaultOn: true); KnownItems["Rosary_Set_Medium"] = CreateUI("RosaryNecklace", 1.2f, defaultOn: true); KnownItems["Rosary_Set_Large"] = CreateUI("HeavyRosaryNecklace", 2.2f, defaultOn: true); KnownItems["Rosary_Set_Huge_White"] = CreateUI("PaleRosaryNecklace", 3.4f, defaultOn: true, gapBelow: true); KnownItems["Shard Pouch"] = CreateUI("ShardBundle", 0.5f, defaultOn: true); KnownItems["Great Shard"] = CreateUI("BeastShard", 1.4f, defaultOn: true); KnownItems["Pristine Core"] = CreateUI("PristineCore", 2.2f, defaultOn: true); KnownItems["Fixer Idol"] = CreateUI("HornetStatuette", 3f, defaultOn: true); KnownItems["Growstone"] = CreateUI("Growstone", 3f, defaultOn: true, gapBelow: true); KnownItems["Silk Grub"] = CreateUI("SilkEater", 0.8f, defaultOn: true); KnownItems["Tool Metal"] = CreateUI("Craftmetal", 0.5f, defaultOn: true); MapCategory(CreateUI("Relics", 0.6f, defaultOn: true), new string[14] { "Bone Record Wisp Top", "Bone Record Greymoor_flooded_corridor", "Bone Record Bone_East_14", "Bone Record Understore_Map_Room", "Seal Chit City Merchant", "Seal Chit Silk Siphon", "Seal Chit Ward Corpse", "Seal Chit Aspid_01", "Weaver Record Conductor", "Weaver Record Sprint_Challenge", "Weaver Record Weave_08", "Weaver Totem Slab_Bottom", "Weaver Totem Bonetown_upper_room", "Weaver Totem Witch" }); KnownItems["Ancient Egg Abyss Middle"] = CreateUI("ArcaneEgg", 2.4f, defaultOn: true); MapCategory(CreateUI("PsalmCylinders", 0.6f, defaultOn: true), new string[5] { "Psalm Cylinder Hang", "Psalm Cylinder Librarian", "Psalm Cylinder Grindle", "Psalm Cylinder Library Roof", "Psalm Cylinder Ward" }); KnownItems["Librarian Melody Cylinder"] = CreateUI("SacredCylinder", 2f, defaultOn: true, gapBelow: true); MapCategory(CreateUI("ShamanSouls", 0.8f, defaultOn: true), new string[3] { "Snare Soul Bell Hermit", "Snare Soul Churchkeeper", "Snare Soul Swamp Bug" }); MapCategory(CreateUI("OldHearts", 1.5f, defaultOn: true), new string[4] { "Clover Heart", "Coral Heart", "Flower Heart", "Hunter Heart" }); KnownItems["Pale_Oil"] = CreateUI("PaleOil", 2f, defaultOn: true); KnownItems["Wood Witch Item"] = CreateUI("TwistedBud", 6f, defaultOn: true); KnownItems["Plasmium Gland"] = CreateUI("PlasmiumGland", 1f, defaultOn: true); KnownItems["White Flower"] = CreateUI("Everbloom", 2.5f, defaultOn: true, gapBelow: true); MapCategory(CreateUI("OtherQuestItems", 0.2f, defaultOn: true, gapBelow: true), new string[31] { "Broodmother Remains", "Cog Heart Pieces", "Skull King Fragment", "Coral Ingredient", "Rock Roller Item", "Ant Trapper Item", "Beastfly Remains", "Mossberry", "Mossberry Stew", "Pickled Roach Egg", "Shell Flower", "Broken SilkShot", "Extractor Machine Pins", "Vintage Nectar", "Courier Supplies Gourmand", "Courier Supplies", "Courier Supplies Mask Maker", "Courier Supplies Slave", "Song Pilgrim Cloak", "Fine Pin", "Pilgrim Rag", "Plasmium Blood", "Plasmium", "Crow Feather", "Roach Corpse Item", "Enemy Morsel Seared", "Enemy Morsel Shredded", "Silver Bellclapper", "Enemy Morsel Speared", "Common Spine", "Flintgem" }); MapCategory(CreateUI("Mementos", 1f, defaultOn: true, gapBelow: true), new string[7] { "Crowman Memento", "Grey Memento", "Memento Seth", "Memento Garmond", "Hunter Memento", "Sprintmaster Memento", "Memento Surface" }); MapCategory(CreateUI("RedTools", 0.8f, defaultOn: true, gapBelow: false, "Equipment / Upgrades"), new string[23] { "Cogwork Flier", "Cogwork Saw", "Conch Drill", "Curve Claws", "Curve Claws Upgraded", "Screw Attack", "Flea Brew", "Flintstone", "Harpoon", "Extractor", "Pimpilo", "Lifeblood Syringe", "Rosary Cannon", "WebShot Forge", "WebShot Weaver", "WebShot Architect", "Silk Snare", "Sting Shard", "Straight Pin", "Tack", "Tri Pin", "Shakra Ring", "Lightning Rod" }); MapCategory(CreateUI("BlueTools", 0.7f, defaultOn: true), new string[23] { "Dazzle Bind", "Dazzle Bind Upgraded", "Mosscreep Tool 1", "Mosscreep Tool 2", "Flea Charm", "Fractured Mask", "Quickbind", "Longneedle", "Lava Charm", "Revenge Crystal", "Multibind", "Pinstress Tool", "Poison Pouch", "Quick Sling", "Reserve Bind", "Brolly Spike", "Thief Claw", "Spool Extender", "Zap Imbuement", "Bell Bind", "White Ring", "Wisp Lantern", "Maggot Charm" }); MapCategory(CreateUI("YellowTools", 0.6f, defaultOn: true), new string[13] { "Wallcling", "Barbed Wire", "Compass", "Dead Mans Purse", "Rosary Magnet", "Magnetite Dice", "Scuttlebrace", "Bone Necklace", "Shell Satchel", "Sprintmaster", "Musician Charm", "Thief Charm", "Weighted Anklet" }); KnownItems["ToolPouchUpgrades"] = CreateUI("ToolPouch", 0.5f, defaultOn: true); KnownItems["Tool Pouch&Kit Inv"] = KnownItems["ToolPouchUpgrades"]; KnownItems["ToolKitUpgrades"] = CreateUI("CraftingKit", 0.5f, defaultOn: true, gapBelow: true); KnownItems["silkRegenMax"] = CreateUI("SilkHeart", 1.2f, defaultOn: true); KnownItems["Crest Socket Unlocker"] = CreateUI("MemoryLocket", 0.8f, defaultOn: true); KnownItems["heartPieces"] = CreateUI("MaskShard", 0.6f, defaultOn: true); KnownItems["fullHeart"] = CreateUI("FullMask", 1f, defaultOn: true); KnownItems["silkSpoolParts"] = CreateUI("SpoolFragment", 0.4f, defaultOn: true); KnownItems["fullSpool"] = CreateUI("FullSpool", 0.6f, defaultOn: true, gapBelow: true); MapCategory(CreateUI("Maps", 0.2f, defaultOn: true, gapBelow: false, "Navigation / World"), new string[28] { "HasBellhartMap", "HasSwampMap", "HasJudgeStepsMap", "HasHallsMap", "HasCogMap", "HasCradleMap", "HasDocksMap", "HasWildsMap", "HasSongGateMap", "HasGreymoorMap", "HasHangMap", "HasHuntersNestMap", "HasArboriumMap", "HasMossGrottoMap", "HasPeakMap", "HasAqueductMap", "HasCoralMap", "HasShellwoodMap", "HasDustpensMap", "HasAbyssMap", "HasBoneforestMap", "HasSlabMap", "HasCitadelUnderstoreMap", "HasCloverMap", "HasWeavehomeMap", "HasLibraryMap", "HasWardMap", "HasCrawlMap" }); MapCategory(CreateUI("MapMarkers", 0.1f, defaultOn: true), new string[15] { "hasMarker_a", "hasMarker_b", "hasMarker_c", "hasMarker_d", "hasMarker_e", "hasPinBench", "hasPinStag", "hasPinShop", "hasPinTube", "hasPinFleaMarrowlands", "hasPinFleaMidlands", "hasPinFleaBlastedlands", "hasPinFleaCitadel", "hasPinFleaPeaklands", "hasPinFleaMucklands" }); KnownItems["QuillState"] = CreateUI("Quill", 0.3f, defaultOn: true, gapBelow: true); MapCategory(CreateUI("BellwayUnlocks", 0.4f, defaultOn: true), new string[10] { "UnlockedDocksStation", "UnlockedBoneforestEastStation", "UnlockedGreymoorStation", "UnlockedBelltownStation", "UnlockedCoralTowerStation", "UnlockedCityStation", "UnlockedPeakStation", "UnlockedShellwoodStation", "UnlockedShadowStation", "UnlockedAqueductStation" }); MapCategory(CreateUI("VentricaUnlocks", 0.5f, defaultOn: true, gapBelow: true), new string[6] { "UnlockedSongTube", "UnlockedUnderTube", "UnlockedCityBellwayTube", "UnlockedHangTube", "UnlockedEnclaveTube", "UnlockedArboriumTube" }); MapCategory(CreateUI("Keys", 0.6f, defaultOn: true, gapBelow: true), new string[8] { "Architect Key", "Belltown House Key", "Craw Summons", "Dock Key", "Slab Key", "Simple Key", "Ward Boss Key", "Ward Key" }); MapCategory(CreateUI("BellhomeUpgrades", 0.4f, defaultOn: true), new string[7] { "Crawbell", "Farsight", "Materium", "BelltownFurnishingDesk", "BelltownFurnishingFairyLights", "BelltownFurnishingGramaphone", "BelltownFurnishingSpa" }); MapCategory(CreateUI("MateriumEntries", 0.3f, defaultOn: true, gapBelow: true), new string[4] { "Materium-Flintstone", "Materium-Magnetite", "Materium-Voltridian", "Journal_Entry-Void_Tendrils" }); MapCategory(CreateUI("SilkSkills", 1f, defaultOn: true, gapBelow: false, "Abilities"), new string[6] { "Parry", "Silk Boss Needle", "Silk Bomb", "Silk Charge", "Silk Spear", "Thread Sphere" }); KnownItems["hasDash"] = CreateUI("SwiftStep", 0.6f, defaultOn: true); KnownItems["hasWalljump"] = CreateUI("ClingGrip", 1.2f, defaultOn: true); KnownItems["hasHarpoonDash"] = CreateUI("Clawline", 1f, defaultOn: true); KnownItems["hasSuperJump"] = CreateUI("SilkSoar", 2f, defaultOn: true); KnownItems["hasChargeSlash"] = CreateUI("NeedleStrike", 0.6f, defaultOn: true, gapBelow: true); KnownItems["hasBrolly"] = CreateUI("DriftersCloak", 1f, defaul