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 REPO Twitch Integration v1.2.0
BepInEx/plugins/RepoTwitchIntegration.dll
Decompiled a week 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.Globalization; using System.IO; using System.Linq.Expressions; using System.Net; using System.Net.WebSockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using MenuLib; using MenuLib.MonoBehaviors; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using REPOLib; using REPOLib.Modules; using TMPro; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RepoTwitchIntegration")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("RepoTwitchIntegration")] [assembly: AssemblyTitle("RepoTwitchIntegration")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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; } } } namespace RepoTwitchIntegration { public enum MessageLocationOption { CentreLeft, LowerLeft, LowerCentre, LowerRight, CentreRight, CentreTop } public enum TwitchPairTriggerMode { Event, Enemy, Random, Both } public enum TwitchCheerTriggerMode { Event, Enemy, Random } public enum TwitchFirstTimeChatterTriggerMode { RandomEnemy, RandomDangerLevel1Enemy } public enum TwitchTriggerOutputMode { Random, All, RandomPerTrigger } public enum EnemySpawnHandlingOption { TargetPlayer, RandomPlayer } public enum MultiplayerDefaultHandlingOption { TargetPlayer, RandomPlayer, AllPlayers } public enum MultiplayerHandlingOption { Default, TargetPlayer, RandomPlayer, AllPlayers } public enum RtiTargetCategory { Enemy, Event, Boon } public enum BoonExecutionKind { Placeholder, SpawnItem, PlayerEffect, VanEnergy, Valuable } public enum BoonAssetKind { None, Item, Valuable, PlayerEffect, VanEnergy } public enum DonationValueMethod { Static, ScalingTotal, ScalingExtract } public enum MimicFuseType { Refresh, Continuous } public enum BoonSpawnedItemCleanupMode { Timer, Level, Permanent } public enum RtiStatusEffectIconKind { Buff, Debuff } public enum RtiTeleportDistanceMode { Fixed, PlayerReachableDistancePercentage } internal sealed class RtiDropdownEnumConfig<TEnum> where TEnum : struct, Enum { private readonly ConfigEntry<string> entry; private readonly TEnum defaultValue; private readonly HashSet<string> validNames; public TEnum Value { get { string value = entry.Value; if (!string.IsNullOrWhiteSpace(value) && validNames.Contains(value.Trim()) && Enum.TryParse<TEnum>(value.Trim(), ignoreCase: true, out var result) && Enum.IsDefined(typeof(TEnum), result)) { return result; } return defaultValue; } set { entry.Value = value.ToString(); } } private RtiDropdownEnumConfig(ConfigEntry<string> entry, TEnum defaultValue, IEnumerable<string> validNames) { this.entry = entry; this.defaultValue = defaultValue; this.validNames = new HashSet<string>(validNames ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase); } public static RtiDropdownEnumConfig<TEnum> Bind(ConfigFile config, string section, string key, TEnum defaultValue, string description, params TEnum[] values) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown TEnum[] values2 = ((values != null && values.Length != 0) ? values : GetAllValues()); string[] valueNames = GetValueNames(values2); ConfigEntry<string> val = ConfigFileOrderManager.Bind(config, section, key, defaultValue.ToString(), new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueList<string>(valueNames), Array.Empty<object>())); return new RtiDropdownEnumConfig<TEnum>(val, defaultValue, valueNames); } private static TEnum[] GetAllValues() { Array values = Enum.GetValues(typeof(TEnum)); TEnum[] array = new TEnum[values.Length]; for (int i = 0; i < values.Length; i++) { if (values.GetValue(i) is TEnum val) { array[i] = val; } } return array; } private static string[] GetValueNames(TEnum[] values) { if (values == null || values.Length == 0) { return Enum.GetNames(typeof(TEnum)); } string[] array = new string[values.Length]; for (int i = 0; i < values.Length; i++) { array[i] = values[i].ToString(); } return array; } } public class TriggerRateLimit { public int Every { get; private set; } public int Maximum { get; private set; } public TriggerRateLimit(int every, int maximum) { Every = Math.Max(0, every); Maximum = Math.Max(0, maximum); } public static TriggerRateLimit Parse(string rawValue, int defaultEvery, int defaultMaximum) { if (string.IsNullOrWhiteSpace(rawValue)) { return new TriggerRateLimit(defaultEvery, defaultMaximum); } string[] array = rawValue.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); int every = defaultEvery; int maximum = defaultMaximum; if (array.Length >= 1 && int.TryParse(array[0].Trim(), out var result)) { every = result; } if (array.Length >= 2 && int.TryParse(array[1].Trim(), out var result2)) { maximum = result2; } return new TriggerRateLimit(every, maximum); } } internal static class RtiHypnosisOverrideRegistry { private sealed class GumballOverrideRecord { public int ComponentInstanceId { get; set; } public int TargetPlayerIndex { get; set; } public float ExpireAtTime { get; set; } public string User { get; set; } = ""; public string TriggerId { get; set; } = ""; public bool LoggedBlockedDeactivate { get; set; } public bool AllowNativeVisibilityChanges { get; set; } } private static readonly object LockObject = new object(); private static readonly Dictionary<int, GumballOverrideRecord> ActiveOverrides = new Dictionary<int, GumballOverrideRecord>(); internal static bool IsRegistered(object instance) { GumballOverrideRecord record; return TryGetRecord(instance, out record); } public static bool Register(GameObject spawnedObject, int targetPlayerIndex, float duration, string user, string triggerId, out string report) { return Register(spawnedObject, targetPlayerIndex, duration, user, triggerId, allowNativeVisibilityChanges: false, out report); } public static bool Register(GameObject spawnedObject, int targetPlayerIndex, float duration, string user, string triggerId, bool allowNativeVisibilityChanges, out string report) { report = ""; Component val = FindGumballValuableComponent(spawnedObject); if ((Object)(object)val == (Object)null) { report = "Could not find GumballValuable component."; return false; } int instanceID = ((Object)val).GetInstanceID(); lock (LockObject) { ActiveOverrides[instanceID] = new GumballOverrideRecord { ComponentInstanceId = instanceID, TargetPlayerIndex = Math.Max(0, targetPlayerIndex), ExpireAtTime = Time.time + Math.Max(0.1f, duration), User = (user ?? ""), TriggerId = (triggerId ?? ""), AllowNativeVisibilityChanges = allowNativeVisibilityChanges }; } report = "Registered RTI gumball override.\n Component: " + ((object)val).GetType().Name + "\n Instance ID: " + instanceID + "\n Target player index: " + Math.Max(0, targetPlayerIndex) + "\n Duration: " + Math.Max(0.1f, duration) + "\n Allow native visibility changes: " + allowNativeVisibilityChanges; return true; } public static void Unregister(GameObject spawnedObject) { Component val = FindGumballValuableComponent(spawnedObject); if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); lock (LockObject) { ActiveOverrides.Remove(instanceID); } if (Plugin.ExtendedLogging.Value) { Plugin.Log.LogInfo((object)("RTI gumball override unregistered:\n Object: " + ((Object)spawnedObject).name + "\n Instance ID: " + instanceID)); } } } public static bool IsControlled(object instance) { GumballOverrideRecord record; return TryGetRecord(instance, out record); } public static bool ShouldBlockPlayerStateChanged(object instance, object[] args, out string reason) { reason = ""; if (!TryGetRecord(instance, out GumballOverrideRecord record)) { return false; } if (record.AllowNativeVisibilityChanges) { return false; } if (args == null || args.Length < 1) { return false; } if (!(args[0] is bool flag) || 1 == 0) { return false; } if (flag) { return false; } int num = -1; if (args.Length >= 2 && args[1] != null) { try { num = Convert.ToInt32(args[1]); } catch { num = -1; } } reason = "Blocked GumballValuable deactivation while RTI override is active.\n Target player index: " + record.TargetPlayerIndex + "\n Requested player index: " + num + "\n User: " + record.User + "\n Trigger ID: " + record.TriggerId; LogBlockedDeactivateOnce(record, reason); return true; } public static bool ShouldBlockOffMethod(object instance, string methodName, out string reason) { reason = ""; if (!TryGetRecord(instance, out GumballOverrideRecord record)) { return false; } if (record.AllowNativeVisibilityChanges) { return false; } reason = "Blocked GumballValuable off method while RTI override is active.\n Method: " + methodName + "\n User: " + record.User + "\n Trigger ID: " + record.TriggerId; LogBlockedDeactivateOnce(record, reason); return true; } public static bool ShouldBlockBoolFalseMethod(object instance, string methodName, object[] args, out string reason) { reason = ""; if (!TryGetRecord(instance, out GumballOverrideRecord record)) { return false; } if (record.AllowNativeVisibilityChanges) { return false; } if (args == null || args.Length < 1) { return false; } if (!(args[0] is bool flag) || 1 == 0) { return false; } if (flag) { return false; } reason = "Blocked GumballValuable bool(false) method while RTI override is active.\n Method: " + methodName + "\n User: " + record.User + "\n Trigger ID: " + record.TriggerId; LogBlockedDeactivateOnce(record, reason); return true; } private static bool TryGetRecord(object instance, out GumballOverrideRecord record) { record = null; if (instance == null) { return false; } Object val = (Object)((instance is Object) ? instance : null); if (val == null) { return false; } int instanceID = val.GetInstanceID(); lock (LockObject) { if (!ActiveOverrides.TryGetValue(instanceID, out record)) { return false; } if (Time.time > record.ExpireAtTime) { ActiveOverrides.Remove(instanceID); record = null; return false; } return true; } } private static void LogBlockedDeactivateOnce(GumballOverrideRecord record, string reason) { if (record == null || !Plugin.ExtendedLogging.Value) { return; } lock (LockObject) { if (record.LoggedBlockedDeactivate) { return; } record.LoggedBlockedDeactivate = true; } Plugin.Log.LogInfo((object)reason); } private static Component FindGumballValuableComponent(GameObject spawnedObject) { if ((Object)(object)spawnedObject == (Object)null) { return null; } Component[] componentsInChildren = spawnedObject.GetComponentsInChildren<Component>(true); Component[] array = componentsInChildren; foreach (Component val in array) { if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); while (type != null) { if (string.Equals(type.Name, "GumballValuable", StringComparison.OrdinalIgnoreCase) || string.Equals(type.FullName, "GumballValuable", StringComparison.OrdinalIgnoreCase)) { return val; } type = type.BaseType; } } return null; } } internal static class RtiNativeGumballHoldRegistry { private sealed class NativeHoldRecord { public int ComponentInstanceId { get; set; } public float ExpireAtTime { get; set; } public string User { get; set; } = ""; public string TriggerId { get; set; } = ""; } private static readonly object LockObject = new object(); private static readonly Dictionary<int, NativeHoldRecord> ActiveNativeHolds = new Dictionary<int, NativeHoldRecord>(); public static bool Register(GameObject spawnedObject, float duration, string user, string triggerId, out string report) { report = ""; Component val = FindGumballValuableComponent(spawnedObject); if ((Object)(object)val == (Object)null) { report = "Could not find GumballValuable component."; return false; } int instanceID = ((Object)val).GetInstanceID(); float num = Math.Max(0.1f, duration); lock (LockObject) { ActiveNativeHolds[instanceID] = new NativeHoldRecord { ComponentInstanceId = instanceID, ExpireAtTime = Time.time + num, User = (user ?? ""), TriggerId = (triggerId ?? "") }; } string report2; bool result = ForceNativeHeldState(val, held: true, out report2); report = "Registered RTI native gumball hold simulation.\n Component: " + ((object)val).GetType().Name + "\n Instance ID: " + instanceID + "\n Duration: " + num.ToString("0.###") + "\n Forced held immediately: " + result + "\n Held report: " + report2; return result; } public static void Unregister(GameObject spawnedObject) { Component val = FindGumballValuableComponent(spawnedObject); if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); lock (LockObject) { ActiveNativeHolds.Remove(instanceID); } ForceNativeHeldState(val, held: false, out string _); } } public static bool TryRefreshForNativeUpdate(object instance) { if (instance == null) { return false; } Object val = (Object)((instance is Object) ? instance : null); if (val == null) { return false; } int instanceID = val.GetInstanceID(); lock (LockObject) { if (!ActiveNativeHolds.TryGetValue(instanceID, out NativeHoldRecord value)) { return false; } if (Time.time > value.ExpireAtTime) { ActiveNativeHolds.Remove(instanceID); return false; } } string report; return ForceNativeHeldState(instance, held: true, out report); } public static bool ForceNativeHoldTick(GameObject spawnedObject, out string report) { report = ""; Component val = FindGumballValuableComponent(spawnedObject); if ((Object)(object)val == (Object)null) { report = "Could not find GumballValuable component."; return false; } return ForceNativeHeldState(val, held: true, out report); } private static bool ForceNativeHeldState(object gumballInstance, bool held, out string report) { report = ""; if (gumballInstance == null) { report = "Gumball instance was null."; return false; } PhysGrabObject val = ResolvePhysGrabObject(gumballInstance); if ((Object)(object)val == (Object)null) { report = "Could not resolve PhysGrabObject from GumballValuable."; return false; } string report2; bool result = TrySetRuntimeMemberValue(val, "grabbed", held, out report2); bool flag = true; bool flag2 = true; string report3; if (held) { flag = TrySetRuntimeMemberValue(val, "grabbedTimer", 3f, out report3); flag2 = TrySetRuntimeMemberValue(val, "heldByLocalPlayer", false, out report3); } else { flag = TrySetRuntimeMemberValue(val, "grabbedTimer", 0f, out report3); } report = "PhysGrabObject: " + ((Object)val).name + "; held=" + held + "; set grabbed=" + result + "; set grabbedTimer=" + flag + "; set heldByLocalPlayer=" + flag2 + "; grabbed report=" + report2; return result; } private static PhysGrabObject ResolvePhysGrabObject(object gumballInstance) { if (gumballInstance == null) { return null; } object runtimeMemberObject = GetRuntimeMemberObject(gumballInstance, "physGrabObject"); PhysGrabObject val = (PhysGrabObject)((runtimeMemberObject is PhysGrabObject) ? runtimeMemberObject : null); if (val != null) { return val; } Component val2 = (Component)((gumballInstance is Component) ? gumballInstance : null); if (val2 != null) { PhysGrabObject component = val2.GetComponent<PhysGrabObject>(); if ((Object)(object)component != (Object)null) { return component; } component = val2.GetComponentInParent<PhysGrabObject>(); if ((Object)(object)component != (Object)null) { return component; } component = val2.GetComponentInChildren<PhysGrabObject>(true); if ((Object)(object)component != (Object)null) { return component; } } return null; } private static Component FindGumballValuableComponent(GameObject spawnedObject) { if ((Object)(object)spawnedObject == (Object)null) { return null; } Component[] componentsInChildren = spawnedObject.GetComponentsInChildren<Component>(true); Component[] array = componentsInChildren; foreach (Component val in array) { if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); while (type != null) { if (string.Equals(type.Name, "GumballValuable", StringComparison.OrdinalIgnoreCase) || string.Equals(type.FullName, "GumballValuable", StringComparison.OrdinalIgnoreCase)) { return val; } type = type.BaseType; } } return null; } private static object GetRuntimeMemberObject(object target, string memberName) { if (target == null || string.IsNullOrWhiteSpace(memberName)) { return null; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(field.IsStatic ? null : target); } catch { } } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { try { MethodInfo getMethod = property.GetGetMethod(nonPublic: true); return property.GetValue((getMethod != null && getMethod.IsStatic) ? null : target); } catch { } } type = type.BaseType; } return null; } private static bool TrySetRuntimeMemberValue(object target, string memberName, object? value, out string report) { report = ""; if (target == null) { report = "Target was null."; return false; } if (string.IsNullOrWhiteSpace(memberName)) { report = "Member name was blank."; return false; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { field.SetValue(field.IsStatic ? null : target, ConvertRuntimeValue(value, field.FieldType)); return true; } catch (Exception ex) { report = ex.Message; return false; } } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { try { MethodInfo setMethod = property.GetSetMethod(nonPublic: true); property.SetValue((setMethod != null && setMethod.IsStatic) ? null : target, ConvertRuntimeValue(value, property.PropertyType)); return true; } catch (Exception ex2) { report = ex2.Message; return false; } } type = type.BaseType; } report = "Member was not found or was not writable."; return false; } private static object ConvertRuntimeValue(object? value, Type targetType) { if (targetType == null) { return value; } if (value == null) { return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; } Type type = value.GetType(); if (targetType.IsAssignableFrom(type)) { return value; } if (targetType == typeof(float)) { return Convert.ToSingle(value); } if (targetType == typeof(double)) { return Convert.ToDouble(value); } if (targetType == typeof(int)) { return Convert.ToInt32(value); } if (targetType == typeof(bool)) { return Convert.ToBoolean(value); } return Convert.ChangeType(value, targetType); } } internal static class RtiNativeBoomboxHoldRegistry { private sealed class NativeBoomboxHoldRecord { public int ComponentInstanceId { get; set; } public float ExpireAtTime { get; set; } public string User { get; set; } = ""; public string TriggerId { get; set; } = ""; } private static readonly object LockObject = new object(); private static readonly Dictionary<int, NativeBoomboxHoldRecord> ActiveNativeHolds = new Dictionary<int, NativeBoomboxHoldRecord>(); public static bool Register(GameObject spawnedObject, float duration, string user, string triggerId, out string report) { report = ""; Component val = FindValuableBoomboxComponent(spawnedObject); if ((Object)(object)val == (Object)null) { report = "Could not find ValuableBoombox component."; return false; } int instanceID = ((Object)val).GetInstanceID(); float num = Math.Max(0.1f, duration); lock (LockObject) { ActiveNativeHolds[instanceID] = new NativeBoomboxHoldRecord { ComponentInstanceId = instanceID, ExpireAtTime = Time.time + num, User = (user ?? ""), TriggerId = (triggerId ?? "") }; } string report2; bool result = ForceNativeHeldState(val, held: true, out report2); report = "Registered RTI native boombox hold simulation.\n Component: " + ((object)val).GetType().Name + "\n Instance ID: " + instanceID + "\n Duration: " + num.ToString("0.###") + "\n Forced held immediately: " + result + "\n Held report: " + report2; return result; } public static void Unregister(GameObject spawnedObject) { Component val = FindValuableBoomboxComponent(spawnedObject); if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); lock (LockObject) { ActiveNativeHolds.Remove(instanceID); } ForceNativeHeldState(val, held: false, out string _); } } public static bool TryRefreshForNativeUpdate(object instance) { if (instance == null) { return false; } Object val = (Object)((instance is Object) ? instance : null); if (val == null) { return false; } int instanceID = val.GetInstanceID(); lock (LockObject) { if (!ActiveNativeHolds.TryGetValue(instanceID, out NativeBoomboxHoldRecord value)) { return false; } if (Time.time > value.ExpireAtTime) { ActiveNativeHolds.Remove(instanceID); return false; } } string report; return ForceNativeHeldState(instance, held: true, out report); } public static bool ForceNativeHoldTick(GameObject spawnedObject, out string report) { report = ""; Component val = FindValuableBoomboxComponent(spawnedObject); if ((Object)(object)val == (Object)null) { report = "Could not find ValuableBoombox component."; return false; } return ForceNativeHeldState(val, held: true, out report); } private static bool ForceNativeHeldState(object boomboxInstance, bool held, out string report) { report = ""; if (boomboxInstance == null) { report = "Boombox instance was null."; return false; } PhysGrabObject val = ResolvePhysGrabObject(boomboxInstance); if ((Object)(object)val == (Object)null) { report = "Could not resolve PhysGrabObject from ValuableBoombox."; return false; } string report2; bool result = TrySetRuntimeMemberValue(val, "grabbed", held, out report2); bool flag = true; bool flag2; string report3; if (held) { flag2 = TrySetRuntimeMemberValue(val, "grabbedTimer", 3f, out report3); flag = TrySetRuntimeMemberValue(val, "heldByLocalPlayer", false, out report3); } else { flag2 = TrySetRuntimeMemberValue(val, "grabbedTimer", 0f, out report3); } report = "PhysGrabObject: " + ((Object)val).name + "; held=" + held + "; set grabbed=" + result + "; set grabbedTimer=" + flag2 + "; set heldByLocalPlayer=" + flag + "; grabbed report=" + report2; return result; } private static PhysGrabObject ResolvePhysGrabObject(object boomboxInstance) { if (boomboxInstance == null) { return null; } object runtimeMemberObject = GetRuntimeMemberObject(boomboxInstance, "physGrabObject"); PhysGrabObject val = (PhysGrabObject)((runtimeMemberObject is PhysGrabObject) ? runtimeMemberObject : null); if (val != null) { return val; } Component val2 = (Component)((boomboxInstance is Component) ? boomboxInstance : null); if (val2 != null) { PhysGrabObject component = val2.GetComponent<PhysGrabObject>(); if ((Object)(object)component != (Object)null) { return component; } component = val2.GetComponentInParent<PhysGrabObject>(); if ((Object)(object)component != (Object)null) { return component; } component = val2.GetComponentInChildren<PhysGrabObject>(true); if ((Object)(object)component != (Object)null) { return component; } } return null; } private static Component FindValuableBoomboxComponent(GameObject spawnedObject) { if ((Object)(object)spawnedObject == (Object)null) { return null; } Component[] componentsInChildren = spawnedObject.GetComponentsInChildren<Component>(true); Component[] array = componentsInChildren; foreach (Component val in array) { if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); while (type != null) { if (string.Equals(type.Name, "ValuableBoombox", StringComparison.OrdinalIgnoreCase) || string.Equals(type.FullName, "ValuableBoombox", StringComparison.OrdinalIgnoreCase)) { return val; } type = type.BaseType; } } return null; } private static object GetRuntimeMemberObject(object target, string memberName) { if (target == null || string.IsNullOrWhiteSpace(memberName)) { return null; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(field.IsStatic ? null : target); } catch { } } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { try { MethodInfo getMethod = property.GetGetMethod(nonPublic: true); return property.GetValue((getMethod != null && getMethod.IsStatic) ? null : target); } catch { } } type = type.BaseType; } return null; } private static bool TrySetRuntimeMemberValue(object target, string memberName, object? value, out string report) { report = ""; if (target == null) { report = "Target was null."; return false; } if (string.IsNullOrWhiteSpace(memberName)) { report = "Member name was blank."; return false; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { field.SetValue(field.IsStatic ? null : target, ConvertRuntimeValue(value, field.FieldType)); return true; } catch (Exception ex) { report = ex.Message; return false; } } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { try { MethodInfo setMethod = property.GetSetMethod(nonPublic: true); property.SetValue((setMethod != null && setMethod.IsStatic) ? null : target, ConvertRuntimeValue(value, property.PropertyType)); return true; } catch (Exception ex2) { report = ex2.Message; return false; } } type = type.BaseType; } report = "Member was not found or was not writable."; return false; } private static object ConvertRuntimeValue(object? value, Type targetType) { if (targetType == null) { return value; } if (value == null) { return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; } Type type = value.GetType(); if (targetType.IsAssignableFrom(type)) { return value; } if (targetType == typeof(float)) { return Convert.ToSingle(value); } if (targetType == typeof(double)) { return Convert.ToDouble(value); } if (targetType == typeof(int)) { return Convert.ToInt32(value); } if (targetType == typeof(bool)) { return Convert.ToBoolean(value); } return Convert.ChangeType(value, targetType); } } [HarmonyPatch] internal static class RtiGumballValuableNativeHoldUpdatePatch { private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("GumballValuable"); if (!(type == null)) { MethodInfo method = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (method != null) { yield return method; } } } private static void Prefix(object __instance) { RtiNativeGumballHoldRegistry.TryRefreshForNativeUpdate(__instance); } } [HarmonyPatch] internal static class RtiValuableBoomboxNativeHoldUpdatePatch { private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("ValuableBoombox"); if (!(type == null)) { MethodInfo method = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (method != null) { yield return method; } } } private static void Prefix(object __instance) { RtiNativeBoomboxHoldRegistry.TryRefreshForNativeUpdate(__instance); } } internal static class RtiSingleplayerNativeTtsRuntime { private sealed class PendingSpeech { public PlayerAvatar Avatar { get; set; } = null; public string Message { get; set; } = ""; public bool Crouching { get; set; } public float QueuedAtRealtime { get; set; } } private sealed class RtiSingleplayerNativeTtsRuntimeController : MonoBehaviour { private void Update() { UpdateRuntime(); } } private const string NativeTtsResourcePath = "TTS"; private const float PendingSpeechTimeoutSeconds = 5f; private const float SpeechStartGraceSeconds = 0.5f; private const float ChatManagerContinuationSeconds = 0.5f; private const int MaximumPendingSpeechCount = 10; private static readonly Queue<PendingSpeech> PendingSpeeches = new Queue<PendingSpeech>(); private static readonly FieldInfo? TtsPlayerAvatarField = AccessTools.Field(typeof(TTSVoice), "playerAvatar"); private static readonly FieldInfo? PlayerAvatarIsDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static readonly FieldInfo? PlayerAvatarIsCrouchingField = AccessTools.Field(typeof(PlayerAvatar), "isCrouching"); private static readonly FieldInfo? TtsIsPlayerAvatarDisabledPrevField = AccessTools.Field(typeof(TTSVoice), "isPlayerAvatarDisabledPrev"); private static readonly FieldInfo? TtsIsSpeakingField = AccessTools.Field(typeof(TTSVoice), "isSpeaking"); private static readonly FieldInfo? TtsAudioSourceField = AccessTools.Field(typeof(TTSVoice), "audioSource"); private static readonly FieldInfo? TtsActiveVoiceField = AccessTools.Field(typeof(TTSVoice), "activeVoice"); private static readonly FieldInfo? TtsNoClipLoudnessTimerField = AccessTools.Field(typeof(TTSVoice), "noClipLoudnessTimer"); private static readonly FieldInfo? LobbyChatUiTtsVoiceField = AccessTools.Field(typeof(LobbyChatUI), "ttsVoice"); private static readonly MethodInfo? TtsVoiceTextMethod = AccessTools.Method(typeof(TTSVoice), "VoiceText", new Type[2] { typeof(string), typeof(float) }, (Type[])null); private static readonly FieldInfo? PlayerVoiceChatClipLoudnessTtsField = AccessTools.Field(typeof(PlayerVoiceChat), "clipLoudnessTTS"); private static readonly Type? KlattersynthSpeechType = AccessTools.TypeByName("Strobotnik.Klattersynth.Speech"); private static readonly MethodInfo? KlattersynthGetCurrentLoudnessMethod = ((KlattersynthSpeechType == null) ? null : AccessTools.Method(KlattersynthSpeechType, "getCurrentLoudness", Type.EmptyTypes, (Type[])null)); private static readonly FieldInfo? ChatManagerIsSpeakingTimerField = AccessTools.Field(typeof(ChatManager), "isSpeakingTimer"); private static readonly MethodInfo? ChatManagerPossessChatCustomLogicMethod = AccessTools.Method(typeof(ChatManager), "PossessChatCustomLogic", (Type[])null, (Type[])null); private static GameObject? controllerObject; private static RtiSingleplayerNativeTtsRuntimeController? controllerComponent; private static GameObject? runtimeObject; private static TTSVoice? runtimeVoice; private static AudioSource? runtimeAudioSource; private static Component? runtimeLowPassLogic; private static FieldInfo? runtimeLowPassFetchField; private static FieldInfo? runtimeLowPassVolumeField; private static string runtimeSpeechPlaybackReport = ""; private static PlayerAvatar? boundAvatar; private static float speechStartGraceUntilTime; private static float continuationUntilTime; private static bool activeLastFrame; private static bool voiceTextSubmissionInProgress; private static string deferredFirstVoiceText = ""; private static bool lobbyChatUiBindingLogged; public static bool IsSingleplayer() { try { return !SemiFunc.IsMultiplayer(); } catch { return true; } } public static bool TryQueueSpeech(PlayerAvatar avatar, string message, bool crouching, out string report) { report = ""; if (!IsSingleplayer()) { report = "The standalone native TTS runtime is only used in singleplayer."; return false; } if ((Object)(object)avatar == (Object)null) { report = "PlayerAvatar was null."; return false; } if (string.IsNullOrWhiteSpace(message)) { report = "The TTS message was blank."; return false; } EnsureController(); if (!TryEnsureRuntime(avatar, out string report2)) { report = report2; return false; } int num = 0; while (PendingSpeeches.Count >= 10) { PendingSpeeches.Dequeue(); num++; } PendingSpeeches.Enqueue(new PendingSpeech { Avatar = avatar, Message = message, Crouching = crouching, QueuedAtRealtime = Time.realtimeSinceStartup }); continuationUntilTime = Mathf.Max(continuationUntilTime, Time.time + 0.5f); report = "Queued the message for the native singleplayer TTS runtime. Pending queue: " + PendingSpeeches.Count + "/" + 10 + "; discarded oldest pending messages: " + num + ". " + report2; return true; } public static bool TryUpdateManagedVoice(TTSVoice voice) { if (!IsManagedVoice(voice)) { return false; } bool flag = IsAudioSourcePlaying(); PlayerAvatar val = boundAvatar; if ((Object)(object)val == (Object)null) { flag = false; } else { bool boolFieldValue = GetBoolFieldValue(PlayerAvatarIsDisabledField, val, fallback: false); bool boolFieldValue2 = GetBoolFieldValue(TtsIsPlayerAvatarDisabledPrevField, voice, boolFieldValue); if (boolFieldValue != boolFieldValue2) { try { voice.StopAndClearVoice(); } catch { } flag = false; SetFieldValue(TtsIsPlayerAvatarDisabledPrevField, voice, boolFieldValue); } } SetFieldValue(TtsIsSpeakingField, voice, flag); if (!flag) { SetFieldValue(TtsNoClipLoudnessTimerField, voice, 0f); } if (flag) { continuationUntilTime = Mathf.Max(continuationUntilTime, Time.time + 0.5f); } return true; } public static bool IsManagedVoiceForNativeUpdate(TTSVoice voice) { return IsManagedVoice(voice); } public static void TryBindLobbyChatUi(LobbyChatUI lobbyChatUi) { if (!IsSingleplayer() || (Object)(object)lobbyChatUi == (Object)null || !lobbyChatUi.isGameplay || (Object)(object)runtimeVoice == (Object)null || LobbyChatUiTtsVoiceField == null) { return; } try { LobbyChatUiTtsVoiceField.SetValue(lobbyChatUi, runtimeVoice); if (!lobbyChatUiBindingLogged && Plugin.ExtendedLogging != null && Plugin.ExtendedLogging.Value) { lobbyChatUiBindingLogged = true; Plugin.Log.LogInfo((object)"Singleplayer native TTS bound the existing gameplay LobbyChatUI to the standalone TTSVoice."); } } catch (Exception exception) { if (!lobbyChatUiBindingLogged) { lobbyChatUiBindingLogged = true; Plugin.Log.LogWarning((object)("Singleplayer native TTS could not bind the existing gameplay LobbyChatUI: " + GetInnermostExceptionMessage(exception))); } } } public static float GetSafeClipLoudnessTts(PlayerVoiceChat? voiceChat) { if ((Object)(object)voiceChat != (Object)null) { try { object obj = PlayerVoiceChatClipLoudnessTtsField?.GetValue(voiceChat); if (obj is float result) { return result; } if (obj != null) { return Convert.ToSingle(obj); } } catch { } } if (!IsSingleplayer() || (Object)(object)runtimeVoice == (Object)null) { return 0f; } try { object obj3 = TtsActiveVoiceField?.GetValue(runtimeVoice); if (obj3 != null && KlattersynthGetCurrentLoudnessMethod != null) { object obj4 = KlattersynthGetCurrentLoudnessMethod.Invoke(obj3, null); if (obj4 is float result2) { return result2; } if (obj4 != null) { return Convert.ToSingle(obj4); } } } catch { } return IsAudioSourcePlaying() ? 0.01f : 0f; } public static bool TryDeferKlattersynthVoiceText(object speechInstance, StringBuilder text) { if (!voiceTextSubmissionInProgress || !IsSingleplayer() || (Object)(object)runtimeVoice == (Object)null || TtsVoiceTextMethod == null) { return false; } Component val = (Component)((speechInstance is Component) ? speechInstance : null); if ((Object)(object)val == (Object)null) { return false; } TTSVoice componentInParent = val.GetComponentInParent<TTSVoice>(); if ((Object)(object)componentInParent == (Object)null || !IsManagedVoice(componentInParent)) { return false; } if (string.IsNullOrEmpty(deferredFirstVoiceText)) { deferredFirstVoiceText = ((text == null) ? "" : text.ToString()); } return true; } private static bool TryRebaseDeferredVoiceText() { string text = deferredFirstVoiceText; deferredFirstVoiceText = ""; if (string.IsNullOrEmpty(text) || (Object)(object)runtimeVoice == (Object)null || TtsVoiceTextMethod == null) { return false; } try { TtsVoiceTextMethod.Invoke(runtimeVoice, new object[2] { text, Time.time }); return true; } catch (Exception exception) { Plugin.Log.LogWarning((object)("Singleplayer native TTS could not rebase the first spoken-word popup timing: " + GetInnermostExceptionMessage(exception))); return false; } } public static bool IsPendingOrSpeaking() { return PendingSpeeches.Count > 0 || IsAudioSourcePlaying() || Time.time < speechStartGraceUntilTime; } public static void RefreshChatManagerSpeakingTimer(ChatManager chatManager) { if ((Object)(object)chatManager == (Object)null || !IsPendingOrSpeaking()) { return; } try { float num = 0f; if (ChatManagerIsSpeakingTimerField?.GetValue(chatManager) is float num2) { num = num2; } if (num < 0.2f) { ChatManagerIsSpeakingTimerField?.SetValue(chatManager, 0.2f); } } catch { } } public static void TickInactiveChatManager(ChatManager chatManager) { if ((Object)(object)chatManager == (Object)null || !ShouldContinueChatManagerTick()) { return; } try { ChatManagerPossessChatCustomLogicMethod?.Invoke(chatManager, null); } catch (Exception ex) { if (Plugin.ExtendedLogging != null && Plugin.ExtendedLogging.Value) { Plugin.Log.LogWarning((object)("Singleplayer native TTS could not continue the inactive ChatManager possession tick: " + ex.Message)); } } } private static void EnsureController() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown if (!((Object)(object)controllerObject != (Object)null) || !((Object)(object)controllerComponent != (Object)null)) { controllerObject = new GameObject("RTI Singleplayer Native TTS Runtime"); ((Object)controllerObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)controllerObject); controllerComponent = controllerObject.AddComponent<RtiSingleplayerNativeTtsRuntimeController>(); } } private static void UpdateRuntime() { if (!IsSingleplayer()) { CleanupVoiceRuntime(clearPending: true); return; } PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)boundAvatar != (Object)null && ((Object)(object)instance == (Object)null || (Object)(object)boundAvatar != (Object)(object)instance)) { CleanupVoiceRuntime(clearPending: true); } if ((Object)(object)instance != (Object)null && (Object)(object)runtimeLowPassLogic != (Object)null) { UpdateLowPassVolume(instance); } if (PendingSpeeches.Count > 0) { PendingSpeech pendingSpeech = PendingSpeeches.Peek(); string report; if ((Object)(object)pendingSpeech.Avatar == (Object)null || (Object)(object)instance == (Object)null || (Object)(object)pendingSpeech.Avatar != (Object)(object)instance) { PendingSpeeches.Dequeue(); continuationUntilTime = Time.time + 0.5f; } else if (Time.realtimeSinceStartup - pendingSpeech.QueuedAtRealtime > 5f) { PendingSpeeches.Dequeue(); continuationUntilTime = Time.time + 0.5f; Plugin.Log.LogWarning((object)("Singleplayer native TTS discarded a queued message because the native TTS prefab did not become ready within " + 5f.ToString("0.###") + " seconds.")); } else if (TryEnsureRuntime(instance, out report) && !IsAudioSourcePlaying() && IsRuntimeReady()) { PendingSpeeches.Dequeue(); try { voiceTextSubmissionInProgress = true; deferredFirstVoiceText = ""; try { runtimeVoice.TTSSpeakNow(pendingSpeech.Message, pendingSpeech.Crouching); } finally { voiceTextSubmissionInProgress = false; } bool flag = TryRebaseDeferredVoiceText(); speechStartGraceUntilTime = Time.time + 0.5f; continuationUntilTime = Time.time + 0.5f; if (Plugin.ExtendedLogging != null && Plugin.ExtendedLogging.Value) { Plugin.Log.LogInfo((object)("Singleplayer native TTS message submitted to the game TTS prefab.\n Message: " + pendingSpeech.Message + "\n Crouching: " + pendingSpeech.Crouching + "\n Playback path: " + runtimeSpeechPlaybackReport + "\n Spoken-word popup timing rebased: " + flag + "\n Audio source playing immediately after submission: " + IsAudioSourcePlaying())); } } catch (Exception ex) { voiceTextSubmissionInProgress = false; deferredFirstVoiceText = ""; continuationUntilTime = Time.time + 0.5f; Plugin.Log.LogWarning((object)("Singleplayer native TTS could not start the queued message: " + ex.Message)); } } } bool flag2 = IsPendingOrSpeaking(); if (flag2) { continuationUntilTime = Mathf.Max(continuationUntilTime, Time.time + 0.5f); } else if (activeLastFrame) { continuationUntilTime = Time.time + 0.5f; } activeLastFrame = flag2; } private static bool TryEnsureRuntime(PlayerAvatar avatar, out string report) { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) report = ""; if ((Object)(object)avatar == (Object)null) { report = "PlayerAvatar was null."; return false; } if ((Object)(object)runtimeObject != (Object)null && (Object)(object)runtimeVoice != (Object)null && (Object)(object)runtimeAudioSource != (Object)null && (Object)(object)boundAvatar == (Object)(object)avatar) { report = "The native TTS runtime was already bound to the active player avatar."; return true; } CleanupVoiceRuntime(clearPending: false); GameObject val = Resources.Load<GameObject>("TTS"); if ((Object)(object)val == (Object)null) { report = "Resources.Load<GameObject>(\"TTS\") returned null."; return false; } GameObject val2 = null; try { val2 = Object.Instantiate<GameObject>(val); if ((Object)(object)val2 == (Object)null) { report = "Instantiating the native TTS prefab returned null."; return false; } ((Object)val2).name = "RTI Singleplayer TTS"; ((Object)val2).hideFlags = (HideFlags)61; val2.transform.SetParent(((Component)avatar).transform, false); val2.transform.localPosition = Vector3.zero; val2.transform.localRotation = Quaternion.identity; TTSVoice component = val2.GetComponent<TTSVoice>(); AudioSource component2 = val2.GetComponent<AudioSource>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null) { report = "The native TTS prefab did not contain both TTSVoice and AudioSource components."; Object.Destroy((Object)(object)val2); return false; } boundAvatar = avatar; runtimeObject = val2; runtimeVoice = component; runtimeAudioSource = component2; SetFieldValue(TtsPlayerAvatarField, runtimeVoice, avatar); SetFieldValue(TtsIsPlayerAvatarDisabledPrevField, runtimeVoice, GetBoolFieldValue(PlayerAvatarIsDisabledField, avatar, fallback: false)); SetFieldValue(TtsIsSpeakingField, runtimeVoice, false); if (!val2.activeSelf) { val2.SetActive(true); } ((Behaviour)component2).enabled = true; component2.mute = false; component2.volume = 0.8f; component2.pitch = 1f; component2.spatialBlend = 0f; if (!TryConfigureNonStreamingSpeech(val2, component2, out runtimeSpeechPlaybackReport)) { runtimeSpeechPlaybackReport = "native streaming fallback; " + runtimeSpeechPlaybackReport; } ConfigureLowPassLogic(val2, avatar); report = "Loaded and instantiated Resources/TTS for the active singleplayer avatar."; if (Plugin.ExtendedLogging != null && Plugin.ExtendedLogging.Value) { Plugin.Log.LogInfo((object)("Singleplayer native TTS runtime created.\n Resource path: TTS\n Player avatar: " + ((Object)avatar).name + "\n Audio mixer group: " + (((Object)(object)component2.outputAudioMixerGroup == (Object)null) ? "none" : ((Object)component2.outputAudioMixerGroup).name) + "\n Volume: " + component2.volume.ToString("0.###") + "\n Spatial blend: " + component2.spatialBlend.ToString("0.###") + "\n Speech playback: " + runtimeSpeechPlaybackReport)); } return true; } catch (Exception ex) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } boundAvatar = null; runtimeObject = null; runtimeVoice = null; runtimeAudioSource = null; runtimeLowPassLogic = null; runtimeLowPassFetchField = null; runtimeLowPassVolumeField = null; runtimeSpeechPlaybackReport = ""; report = "Creating the native TTS runtime failed: " + ex.Message; return false; } } private static bool IsRuntimeReady() { if ((Object)(object)runtimeVoice == (Object)null || (Object)(object)runtimeAudioSource == (Object)null) { return false; } try { object obj = TtsAudioSourceField?.GetValue(runtimeVoice); object obj2 = TtsActiveVoiceField?.GetValue(runtimeVoice); return obj is AudioSource && obj2 != null; } catch { return false; } } private static bool IsManagedVoice(TTSVoice voice) { if ((Object)(object)voice == (Object)null || (Object)(object)runtimeVoice == (Object)null) { return false; } try { return ((Object)voice).GetInstanceID() == ((Object)runtimeVoice).GetInstanceID(); } catch { return false; } } private static bool IsAudioSourcePlaying() { try { return (Object)(object)runtimeAudioSource != (Object)null && runtimeAudioSource.isPlaying; } catch { return false; } } private static bool ShouldContinueChatManagerTick() { return IsSingleplayer() && (IsPendingOrSpeaking() || Time.time < continuationUntilTime); } private static void CleanupVoiceRuntime(bool clearPending) { if ((Object)(object)runtimeVoice != (Object)null) { try { runtimeVoice.StopAndClearVoice(); } catch { } } if ((Object)(object)runtimeObject != (Object)null) { try { Object.Destroy((Object)(object)runtimeObject); } catch { } } runtimeObject = null; runtimeVoice = null; runtimeAudioSource = null; runtimeLowPassLogic = null; runtimeLowPassFetchField = null; runtimeLowPassVolumeField = null; runtimeSpeechPlaybackReport = ""; boundAvatar = null; speechStartGraceUntilTime = 0f; activeLastFrame = false; voiceTextSubmissionInProgress = false; deferredFirstVoiceText = ""; lobbyChatUiBindingLogged = false; if (clearPending) { PendingSpeeches.Clear(); continuationUntilTime = Time.time + 0.5f; } } private static bool TryConfigureNonStreamingSpeech(GameObject createdObject, AudioSource audioSource, out string report) { report = ""; try { Type type = AccessTools.TypeByName("Strobotnik.Klattersynth.Speech"); Type type2 = AccessTools.TypeByName("Strobotnik.Klattersynth.SpeechSynth"); if (type == null || type2 == null) { report = "Klattersynth Speech or SpeechSynth was not found."; return false; } Component[] componentsInChildren = createdObject.GetComponentsInChildren(type, true); if (componentsInChildren.Length == 0) { report = "The native TTS prefab contained no Speech components."; return false; } FieldInfo fieldInfo = AccessTools.Field(type, "useStreamingMode"); FieldInfo fieldInfo2 = AccessTools.Field(type, "msPerSpeechFrame"); FieldInfo fieldInfo3 = AccessTools.Field(type, "flutter"); FieldInfo fieldInfo4 = AccessTools.Field(type, "flutterSpeed"); FieldInfo fieldInfo5 = AccessTools.Field(type, "speechSynth"); FieldInfo fieldInfo6 = AccessTools.Field(type, "talking"); MethodInfo methodInfo = AccessTools.Method(type, "stop", new Type[1] { typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(type2, "init", new Type[6] { typeof(AudioSource), typeof(bool), typeof(int), typeof(int), typeof(int), typeof(float) }, (Type[])null); if (fieldInfo == null || fieldInfo2 == null || fieldInfo3 == null || fieldInfo4 == null || fieldInfo5 == null || methodInfo == null || methodInfo2 == null) { report = "The required Klattersynth Speech fields or methods could not be resolved."; return false; } int num = 0; Component[] array = componentsInChildren; foreach (Component val in array) { if (!((Object)(object)val == (Object)null)) { methodInfo.Invoke(val, new object[1] { true }); object obj = Activator.CreateInstance(type2); if (obj != null) { int num2 = Convert.ToInt32(fieldInfo2.GetValue(val)); int num3 = Convert.ToInt32(fieldInfo3.GetValue(val)); float num4 = Convert.ToSingle(fieldInfo4.GetValue(val)); fieldInfo.SetValue(val, false); methodInfo2.Invoke(obj, new object[6] { audioSource, false, 11025, num2, num3, num4 }); fieldInfo5.SetValue(val, obj); fieldInfo6?.SetValue(val, false); num++; } } } if (num == 0) { report = "No native Speech component could be switched to non-streaming playback."; return false; } report = "native pregenerated AudioClip playback on " + num + " Speech component(s)"; return true; } catch (Exception exception) { report = "Non-streaming Klattersynth setup failed: " + GetInnermostExceptionMessage(exception); return false; } } private static string GetInnermostExceptionMessage(Exception exception) { Exception ex = exception; while (ex.InnerException != null) { ex = ex.InnerException; } return ex.Message; } private static void ConfigureLowPassLogic(GameObject createdObject, PlayerAvatar avatar) { runtimeLowPassLogic = null; runtimeLowPassFetchField = null; runtimeLowPassVolumeField = null; try { Type type = AccessTools.TypeByName("AudioLowPassLogic"); if (type == null) { return; } Component component = createdObject.GetComponent(type); if (!((Object)(object)component == (Object)null)) { runtimeLowPassLogic = component; runtimeLowPassFetchField = AccessTools.Field(type, "Fetch"); runtimeLowPassVolumeField = AccessTools.Field(type, "Volume"); Behaviour val = (Behaviour)(object)((component is Behaviour) ? component : null); if (val != null) { val.enabled = true; } SetLowPassFieldValue(runtimeLowPassFetchField, true); AccessTools.Method(type, "Setup", Type.EmptyTypes, (Type[])null)?.Invoke(component, null); UpdateLowPassVolume(avatar); } } catch { runtimeLowPassLogic = null; runtimeLowPassFetchField = null; runtimeLowPassVolumeField = null; } } private static void UpdateLowPassVolume(PlayerAvatar avatar) { if (!((Object)(object)runtimeLowPassLogic == (Object)null) && !((Object)(object)avatar == (Object)null)) { float num = (GetBoolFieldValue(PlayerAvatarIsCrouchingField, avatar, fallback: false) ? 0.8f : 1f); SetLowPassFieldValue(runtimeLowPassVolumeField, num); } } private static void SetLowPassFieldValue(FieldInfo? field, object value) { if ((Object)(object)runtimeLowPassLogic == (Object)null || field == null) { return; } try { field.SetValue(runtimeLowPassLogic, value); } catch { } } private static bool GetBoolFieldValue(FieldInfo? field, object target, bool fallback) { if (field == null || target == null) { return fallback; } try { return (field.GetValue(target) is bool flag) ? flag : fallback; } catch { return fallback; } } private static void SetFieldValue(FieldInfo? field, object target, object? value) { if (field == null || target == null) { return; } try { field.SetValue(target, value); } catch { } } } [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSpeak")] internal static class RtiPlayerAvatarSingleplayerNativeTtsPatch { [HarmonyPrefix] private static bool Prefix(PlayerAvatar __instance, string __0, bool __1) { if (!RtiSingleplayerNativeTtsRuntime.IsSingleplayer()) { return true; } if (RtiSingleplayerNativeTtsRuntime.TryQueueSpeech(__instance, __0, __1, out string report)) { if (Plugin.ExtendedLogging != null && Plugin.ExtendedLogging.Value) { Plugin.Log.LogInfo((object)("PlayerAvatar.ChatMessageSpeak routed to the singleplayer native TTS runtime. " + report)); } return false; } Plugin.Log.LogWarning((object)("PlayerAvatar.ChatMessageSpeak could not use the singleplayer native TTS runtime. " + report)); return true; } } [HarmonyPatch] internal static class RtiSingleplayerKlattersynthVoiceTextTimingPatch { private static IEnumerable<MethodBase> TargetMethods() { Type speechType = AccessTools.TypeByName("Strobotnik.Klattersynth.Speech"); if (!(speechType == null)) { MethodInfo voiceTextMethod = AccessTools.Method(speechType, "VoiceText", new Type[2] { typeof(StringBuilder), typeof(float) }, (Type[])null); if (voiceTextMethod != null) { yield return voiceTextMethod; } } } [HarmonyPrefix] private static bool Prefix(object __instance, StringBuilder __0) { return !RtiSingleplayerNativeTtsRuntime.TryDeferKlattersynthVoiceText(__instance, __0); } } [HarmonyPatch(typeof(LobbyChatUI), "Update")] internal static class RtiSingleplayerLobbyChatUiPatch { [HarmonyPrefix] private static void Prefix(LobbyChatUI __instance) { RtiSingleplayerNativeTtsRuntime.TryBindLobbyChatUi(__instance); } } [HarmonyPatch(typeof(TTSVoice), "Update")] internal static class RtiSingleplayerNativeTtsVoiceUpdatePatch { private static bool safeLoudnessPatchApplied; private static bool nativeUpdateExceptionLogged; [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerVoiceChat), "clipLoudnessTTS"); MethodInfo methodInfo = AccessTools.Method(typeof(RtiSingleplayerNativeTtsRuntime), "GetSafeClipLoudnessTts", new Type[1] { typeof(PlayerVoiceChat) }, (Type[])null); if (fieldInfo == null || methodInfo == null) { Plugin.Log.LogWarning((object)"Singleplayer native TTS subtitle patch could not resolve PlayerVoiceChat.clipLoudnessTTS or its safe replacement method."); return list; } int num = 0; for (int i = 0; i < list.Count; i++) { if (!(list[i].opcode != OpCodes.Ldfld) && object.Equals(list[i].operand, fieldInfo)) { list[i].opcode = OpCodes.Call; list[i].operand = methodInfo; num++; } } safeLoudnessPatchApplied = num > 0; if (!safeLoudnessPatchApplied) { Plugin.Log.LogWarning((object)"Singleplayer native TTS subtitle patch found no PlayerVoiceChat.clipLoudnessTTS access in TTSVoice.Update. The previous safe managed update fallback will remain active."); } else if (Plugin.ExtendedLogging != null && Plugin.ExtendedLogging.Value) { Plugin.Log.LogInfo((object)("Singleplayer native TTS subtitle patch preserved the native TTSVoice.Update and replaced " + num + " clip-loudness access(es) with the null-safe singleplayer source.")); } return list; } [HarmonyPrefix] private static bool Prefix(TTSVoice __instance) { if (safeLoudnessPatchApplied) { return true; } return !RtiSingleplayerNativeTtsRuntime.TryUpdateManagedVoice(__instance); } [HarmonyFinalizer] private static Exception? Finalizer(TTSVoice __instance, Exception? __exception) { if (__exception == null || !RtiSingleplayerNativeTtsRuntime.IsManagedVoiceForNativeUpdate(__instance)) { return __exception; } RtiSingleplayerNativeTtsRuntime.TryUpdateManagedVoice(__instance); if (!nativeUpdateExceptionLogged) { nativeUpdateExceptionLogged = true; Plugin.Log.LogWarning((object)("Singleplayer native TTS native update encountered an exception and used the safe managed fallback for this frame: " + __exception.Message)); } return null; } } [HarmonyPatch(typeof(ChatManager), "PossessChatCustomLogic")] internal static class RtiChatManagerSingleplayerNativeTtsSpeakingPatch { [HarmonyPostfix] private static void Postfix(ChatManager __instance) { RtiSingleplayerNativeTtsRuntime.RefreshChatManagerSpeakingTimer(__instance); } } [HarmonyPatch(typeof(ChatManager), "Update")] internal static class RtiChatManagerSingleplayerNativeTtsContinuationPatch { [HarmonyPostfix] private static void Postfix(ChatManager __instance) { if (!((Object)(object)__instance == (Object)null) && __instance.StateIsInactive()) { RtiSingleplayerNativeTtsRuntime.TickInactiveChatManager(__instance); } } } [HarmonyPatch(typeof(ChatManager), "PossessChatScheduleEnd")] internal static class RtiChatManagerSingleplayerSchedulePreparationPatch { private static readonly MethodInfo? ImportantFetchesMethod = AccessTools.Method(typeof(ChatManager), "ImportantFetches", (Type[])null, (Type[])null); private static bool missingMethodLogged; [HarmonyPrefix] private static void Prefix(ChatManager __instance) { if ((Object)(object)__instance == (Object)null || !RtiSingleplayerNativeTtsRuntime.IsSingleplayer()) { return; } if (ImportantFetchesMethod == null) { if (!missingMethodLogged) { missingMethodLogged = true; Plugin.Log.LogWarning((object)"Singleplayer possessed-chat preparation could not resolve ChatManager.ImportantFetches."); } return; } try { ImportantFetchesMethod.Invoke(__instance, null); } catch (TargetInvocationException ex) { Exception innerException = ex.InnerException; Plugin.Log.LogWarning((object)("Singleplayer possessed-chat preparation failed before PossessChatScheduleEnd: " + ((innerException == null) ? ex.Message : innerException.Message))); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Singleplayer possessed-chat preparation failed before PossessChatScheduleEnd: " + ex2.Message)); } } } [HarmonyPatch(typeof(ChatManager), "Update")] internal static class RtiChatManagerSingleplayerPossessedUpdatePatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); MethodInfo methodInfo = AccessTools.Method(typeof(SemiFunc), "IsMultiplayer", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(RtiChatManagerSingleplayerPossessedUpdatePatch), "IsMultiplayerOrPossessedChatActive", new Type[1] { typeof(ChatManager) }, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Plugin.Log.LogWarning((object)"Singleplayer possessed-chat patch could not resolve its required methods."); return list; } List<int> list2 = new List<int>(); for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.Calls(list[i], methodInfo)) { list2.Add(i); } } if (list2.Count != 2) { Plugin.Log.LogWarning((object)("Singleplayer possessed-chat patch expected two SemiFunc.IsMultiplayer calls in ChatManager.Update, but found " + list2.Count + ". The method was left unchanged.")); return list; } int num = 0; foreach (int item in list2) { int num2 = item + num; CodeInstruction val = list[num2]; val.opcode = OpCodes.Ldarg_0; val.operand = null; list.Insert(num2 + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo2)); num++; } return list; } private static bool IsMultiplayerOrPossessedChatActive(ChatManager chatManager) { if (SemiFunc.IsMultiplayer()) { return true; } if ((Object)(object)chatManager == (Object)null) { return false; } return chatManager.StateIsPossessed() || chatManager.StateIsSend(); } } [HarmonyPatch(typeof(ValuableLovePotion), "Update")] internal static class RtiValuableLovePotionSingleplayerUpdatePatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); MethodInfo methodInfo = AccessTools.Method(typeof(SemiFunc), "IsMultiplayer", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(RtiValuableLovePotionSingleplayerUpdatePatch), "ShouldRunNativeLovePotionUpdate", Type.EmptyTypes, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Plugin.Log.LogWarning((object)"Singleplayer native Love Potion patch could not resolve its required methods."); return list; } List<int> list2 = new List<int>(); for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.Calls(list[i], methodInfo)) { list2.Add(i); } } if (list2.Count != 1) { Plugin.Log.LogWarning((object)("Singleplayer native Love Potion patch expected one SemiFunc.IsMultiplayer call in ValuableLovePotion.Update, but found " + list2.Count + ". The method was left unchanged.")); return list; } CodeInstruction val = list[list2[0]]; val.opcode = OpCodes.Call; val.operand = methodInfo2; return list; } private static bool ShouldRunNativeLovePotionUpdate() { return true; } } [HarmonyPatch] internal static class RtiGumballValuablePlayerStatePatch { private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("GumballValuable"); if (type == null) { yield break; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo method in array) { if (!(method == null) && (string.Equals(method.Name, "PlayerStateChanged", StringComparison.OrdinalIgnoreCase) || string.Equals(method.Name, "PlayerStateChangedRPC", StringComparison.OrdinalIgnoreCase))) { yield return method; } } } private static bool Prefix(object __instance, object[] __args) { if (RtiHypnosisOverrideRegistry.ShouldBlockPlayerStateChanged(__instance, __args, out string _)) { return false; } return true; } } [HarmonyPatch] internal static class RtiGumballValuableOffMethodPatch { private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("GumballValuable"); if (type == null) { yield break; } string[] methodNames = new string[3] { "ScreenSpiralOff", "CloseEyes", "TurnOffHypnosisLines" }; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo method in array) { if (method == null) { continue; } string[] array2 = methodNames; for (int j = 0; j < array2.Length; j++) { if (string.Equals(b: array2[j], a: method.Name, comparisonType: StringComparison.OrdinalIgnoreCase)) { yield return method; break; } } } } private static bool Prefix(MethodBase __originalMethod, object __instance) { string methodName = ((__originalMethod == null) ? "unknown" : __originalMethod.Name); if (RtiHypnosisOverrideRegistry.ShouldBlockOffMethod(__instance, methodName, out string _)) { return false; } return true; } } [HarmonyPatch] internal static class RtiGumballValuableBoolFalsePatch { private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("GumballValuable"); if (type == null) { yield break; } string[] methodNames = new string[2] { "SetLightEnableState", "ToggleParticles" }; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo method in array) { if (method == null) { continue; } string[] array2 = methodNames; for (int j = 0; j < array2.Length; j++) { if (string.Equals(b: array2[j], a: method.Name, comparisonType: StringComparison.OrdinalIgnoreCase)) { yield return method; break; } } } } private static bool Prefix(MethodBase __originalMethod, object __instance, object[] __args) { string methodName = ((__originalMethod == null) ? "unknown" : __originalMethod.Name); if (RtiHypnosisOverrideRegistry.ShouldBlockBoolFalseMethod(__instance, methodName, __args, out string _)) { return false; } return true; } } internal static class RtiMimicEventOptions { private static ConfigEntry<MimicFuseType>? fuseTypeConfig; private static ConfigEntry<string>? fuseTimerRangeConfig; private static ConfigEntry<string>? explosionDamageRangeConfig; private static ConfigEntry<int>? explosionSfxVolumeConfig; public static MimicFuseType FuseType => fuseTypeConfig?.Value ?? MimicFuseType.Refresh; public static string FuseTimerRange => fuseTimerRangeConfig?.Value ?? "2-10"; public static string ExplosionDamageRange => explosionDamageRangeConfig?.Value ?? "50"; public static string KnockbackRadiusRange => "2"; public static string KnockbackStrengthRange => "100"; public static string KnockbackUpwardsModifierRange => "2"; public static int ExplosionSfxVolume => Mathf.Clamp(explosionSfxVolumeConfig?.Value ?? 80, 0, 100); public static void BindConfigs(ConfigFile config) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown fuseTypeConfig = ConfigFileOrderManager.Bind(config, "Event - Mimic", "Fuse Type", MimicFuseType.Continuous, "Refresh = dropping the Mimic bag resets the fuse and chooses a new fuse length next time it is held.\nContinuous = dropping the Mimic bag pauses the current fuse until it is held again."); fuseTimerRangeConfig = ConfigFileOrderManager.Bind(config, "Event - Mimic", "Fuse Timer Range", "0.25-3", "Fuse length in seconds. Use a single value or a range, clamped from 0 to 100. Examples: 2-10, 5, 0.5-3."); explosionDamageRangeConfig = ConfigFileOrderManager.Bind(config, "Event - Mimic", "Explosion Damage", "10", "Damage used by the Mimic explosion. Use a single value or a range. Examples: 50, 25-75."); explosionSfxVolumeConfig = ConfigFileOrderManager.Bind(config, "Event - Mimic", "Explosion SFX Volume", 80, new ConfigDescription("Controls whether RTI calls the native big explosion sound. 0 disables the explicit native sound call. Values above 0 enable it at the game's native volume; the exact native method does not expose volume scaling.", (AcceptableValueBase)null, Array.Empty<object>())); } public static float PickFuseSeconds(out string report) { report = ""; string fuseTimerRange = FuseTimerRange; if (!TryPickFloatFromRange(fuseTimerRange, 2f, 10f, 0f, 100f, out var selectedValue, out report)) { selectedValue = Random.Range(2f, 10f); } return Mathf.Clamp(selectedValue, 0f, 100f); } public static int PickExplosionDamage(out string report) { float num = PickConfiguredFloat(ExplosionDamageRange, 50f, 50f, 0f, 1000f, "Explosion damage", out report); return Mathf.RoundToInt(num); } public static float PickKnockbackRadius(out string report) { return PickConfiguredFloat(KnockbackRadiusRange, 0.8f, 0.8f, 0f, 100f, "Knockback radius", out report); } public static int PickKnockbackStrength(out string report) { float num = PickConfiguredFloat(KnockbackStrengthRange, 100f, 100f, 0f, 5000f, "Knockback strength", out report); return Mathf.RoundToInt(num); } public static float PickKnockbackUpwardsModifier(out string report) { return PickConfiguredFloat(KnockbackUpwardsModifierRange, 2f, 2f, 0f, 100f, "Knockback upwards modifier", out report); } private static float PickConfiguredFloat(string rawValue, float fallbackMinimum, float fallbackMaximum, float clampMinimum, float clampMaximum, string label, out string report) { if (!TryPickFloatFromRange(rawValue, fallbackMinimum, fallbackMaximum, clampMinimum, clampMaximum, out float selectedValue, out string report2)) { selectedValue = Random.Range(fallbackMinimum, fallbackMaximum); } selectedValue = Mathf.Clamp(selectedValue, clampMinimum, clampMaximum); report = label + ": " + report2; return selectedValue; } private static bool TryPickFloatFromRange(string rawValue, float fallbackMinimum, float fallbackMaximum, float clampMinimum, float clampMaximum, out float selectedValue, out string report) { selectedValue = 0f; report = ""; if (string.IsNullOrWhiteSpace(rawValue)) { selectedValue = Random.Range(fallbackMinimum, fallbackMaximum); report = "Range was blank. Used fallback range " + fallbackMinimum + "-" + fallbackMaximum + "."; return false; } string text = rawValue.Trim().Replace("–", "-").Replace("—", "-"); string[] array = text.Split(new char[1] { '-' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 1) { if (!TryParseFloat(array[0], out var result)) { selectedValue = Random.Range(fallbackMinimum, fallbackMaximum); report = "Could not parse single fuse value: " + rawValue; return false; } selectedValue = Mathf.Clamp(result, clampMinimum, clampMaximum); report = "Selected fixed fuse value " + selectedValue.ToString("0.###") + " from config value " + rawValue + "."; return true; } if (array.Length >= 2) { if (!TryParseFloat(array[0], out var result2) || !TryParseFloat(array[1], out var result3)) { selectedValue = Random.Range(fallbackMinimum, fallbackMaximum); report = "Could not parse fuse range: " + rawValue; return false; } result2 = Mathf.Clamp(result2, clampMinimum, clampMaximum); result3 = Mathf.Clamp(result3, clampMinimum, clampMaximum); if (result3 < result2) { float num = result2; result2 = result3; result3 = num; } selectedValue = (Mathf.Approximately(result2, result3) ? result2 : Random.Range(result2, result3)); report = "Selected random fuse value " + selectedValue.ToString("0.###") + " from range " + result2.ToString("0.###") + "-" + result3.ToString("0.###") + "."; return true; } selectedValue = Random.Range(fallbackMinimum, fallbackMaximum); report = "Fuse range had no usable parts: " + rawValue; return false; } private static bool TryParseFloat(string value, out float result) { if (float.TryParse(value.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { return true; } return float.TryParse(value.Trim(), out result); } } internal static class RtiAirConditioningEventOptions { private static ConfigEntry<int>? outerRingFanCountConfig; private static ConfigEntry<float>? outerRingRadiusConfig; public static int OuterRingFanCount => Math.Max(0, outerRingFanCountConfig?.Value ?? 7); public static float OuterRingRadius => Math.Max(0f, outerRingRadiusConfig?.Value ?? 6f); public static void BindConfigs(ConfigFile config, string section) { outerRingFanCountConfig = ConfigFileOrderManager.Bind(config, section, "Outer Ring Fan Count", 7, "Number of fans spawned in the outer ring around each selected target player. Set to 0 to disable the outer ring."); outerRingRadiusConfig = ConfigFileOrderManager.Bind(config, section, "Outer Ring Radius", 6f, "Radius around each selected target player used to place the outer ring of fans."); } } internal static class RtiTeleportEventOptions { private const string Section = "Event - Teleport"; private static RtiDropdownEnumConfig<RtiTeleportDistanceMode>? distanceModeConfig; private static ConfigEntry<float>? minimumDistanceConfig; private static ConfigEntry<float>? maximumDistanceConfig; private static ConfigEntry<float>? minimumDistancePercentConfig; private static ConfigEntry<float>? maximumDistancePercentConfig; private static ConfigEntry<int>? sfxVolumeConfig; public static RtiTeleportDistanceMode DistanceMode => distanceModeConfig?.Value ?? RtiTeleportDistanceMode.PlayerReachableDistancePercentage; public static float MinimumDistance => Mathf.Clamp(minimumDistanceConfig?.Value ?? 10f, 0f, 1000f); public static float MaximumDistance => Mathf.Clamp(maximumDistanceConfig?.Value ?? 40f, 0f, 1000f); public static float MinimumDistancePercent => Mathf.Clamp(minimumDistancePercentConfig?.Value ?? 40f, 0f, 100f); public static float MaximumDistancePercent => Mathf.Clamp(maximumDistancePercentConfig?.Value ?? 80f, 0f, 100f); public static int LocationCacheSampleCount => 500; public static bool UseIndoorFallbackRanges => true; public static int SfxVolume => Mathf.Clamp(sfxVolumeConfig?.Value ?? 50, 0, 100); public static void GetDistanceRange(out float minimumDistance, out float maximumDistance) { minimumDistance = MinimumDistance; maximumDistance = MaximumDistance; NormalizeDistanceRange(ref minimumDistance, ref maximumDistance); } public unsafe static void GetEffectiveDistanceRangeForOrigin(Vector3 origin, out float minimumDistance, out float maximumDistance, out string report) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) report = ""; if (DistanceMode == RtiTeleportDistanceMode.PlayerReachableDistancePercentage) { float num = MinimumDistancePercent; float num2 = MaximumDistancePercent; if (num2 < num) { float num3 = num; num = num2; num2 = num3; } if (RtiTeleportLocationCache.TryGetFurthestReachableFlatDistance(origin, out float furthestReachableDistance, out Vector3 furthestReachablePoint, out string report2) && furthestReachableDistance > 0.01f) { minimumDistance = furthestReachableDistance * (num / 100f); maximumDistance = furthestReachableDistance * (num2 / 100f); NormalizeDistanceRange(ref minimumDistance, ref maximumDistance); string[] obj = new string[16] { "Distance mode: PlayerReachableDistancePercentage. Origin: ", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; Vector3 val = origin; obj[1] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[2] = ". Furthest reachable cached point: "; val = furthestReachablePoint; obj[3] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[4] = ". Furthest reachable flat distance: "; obj[5] = furthestReachableDistance.ToString("0.###"); obj[6] = ". Percent range: "; obj[7] = num.ToString("0.###"); obj[8] = "% - "; obj[9] = num2.ToString("0.###"); obj[10] = "%. Effective range: "; obj[11] = minimumDistance.ToString("0.###"); obj[12] = " - "; obj[13] = maximumDistance.ToString("0.###"); obj[14] = ". Cache: "; obj[15] = report2; report = string.Concat(obj); } else { GetDistanceRange(out minimumDistance, out maximumDistance); Vector3 val = origin; report = "Distance mode: PlayerReachableDistancePercentage, but no usable reachable cached point was available from origin " + ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString() + ". Fixed distance range was used instead. Cache: " + report2; } } else { GetDistanceRange(out minimumDistance, out maximumDistance); string[] obj2 = new string[7] { "Distance mode: Fixed. Origin: ", null, null, null, null, null, null }; Vector3 val = origin; obj2[1] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj2[2] = ". Effective range: "; obj2[3] = minimumDistance.ToString("0.###"); obj2[4] = " - "; obj2[5] = maximumDistance.ToString("0.###"); obj2[6] = "."; report = string.Concat(obj2); } } public static void BindConfigs(ConfigFile config) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown distanceModeConfig = RtiDropdownEnumConfig<RtiTeleportDistanceMode>.Bind(config, "Event - Teleport", "Distance Mode", RtiTeleportDistanceMode.PlayerReachableDistancePercentage, "Fixed = use Minimum Distance and Maximum Distance as world units. PlayerReachableDistancePercentage = calculate a separate effective range for each target by finding the furthest currently safe cached NavMesh point with a complete path from that target, then applying Minimum Distance Percent and Maximum Distance Percent to that player-relative distance.", RtiTeleportDistanceMode.Fixed, RtiTeleportDistanceMode.PlayerReachableDistancePercentage); minimumDistanceConfig = ConfigFileOrderManager.Bind(config, "Event - Teleport", "Minimum Distance", 30f, new ConfigDescription("Minimum distance for Fixed-mode or fallback when PlayerReachableDistancePercentagefinds no usable waypoint.", (AcceptableValueBase)null, Array.Empty<object>())); maximumDistanceConfig = ConfigFileOrderManager.Bind(config, "Event - Teleport", "Maximum Distance", 250f, new ConfigDescription("Maximum distance for Fixed-mode or fallback when PlayerReachableDistancePercentagefinds no usable waypoint.", (AcceptableValueBase)null, Array.Empty<object>())); minimumDistancePercentConfig = ConfigFileOrderManager.Bind(config, "Event - Teleport", "Minimum Distance Percent", 40f, new ConfigDescription("Minimum percentage of the furthest currently safe cached NavMesh distance reachable from each target player's current position. Example: 40 means the destination must be at least 40% of that target's maximum currently reachable cached distance.", (AcceptableValueBase)null, Array.Empty<object>())); maximumDistancePercentConfig = ConfigFileOrderManager.Bind(config, "Event - Teleport", "Maximum Distance Percent", 80f, new ConfigDescription("Maximum percentage of the furthest currently safe cached NavMesh distance reachable from each target player's current position. Example: 80 means the destination may be up to 80% of that target's maximum currently reachable cached distance.", (AcceptableValueBase)null, Array.Empty<object>())); sfxVolumeConfig = ConfigFileOrderManager.Bind(config, "Event - Teleport", "SFX Volume", 60, new ConfigDescription("Volume for the Teleport event placeholder sound effect. 0 is silent, 100 is full volume.", (AcceptableValueBase)null, Array.Empty<object>())); } private static void NormalizeDistanceRange(ref float minimumDistance, ref float maximumDistance) { minimumDistance = Mathf.Clamp(minimumDistance, 0f, 1000f); maximumDistance = Mathf.Clamp(maximumDistance, 0f, 1000f); if (maximumDistance < minimumDistance) { float num = minimumDistance; minimumDistance = maximumDistance; maximumDistance = num; } } } internal static class RtiTetherEventOptions { private static ConfigEntry<float>? teleportDistanceConfig; private static ConfigEntry<float>? tetherLengthConfig; private static ConfigEntry<float>? edgePullStrengthConfig; private static ConfigEntry<float>? tumblePullStrengthConfig; private static ConfigEntry<int>? humVolumeConfig; private static ConfigEntry<float>? activationGracePeriodConfig; private static ConfigEntry<float>? visualHeightOffsetConfig; private static ConfigEntry<float>? visualDistanceFromPlayerConfig; private static ConfigEntry<bool>? playerDonutConfig; public static float TeleportDistance => Mathf.Clamp(teleportDistanceConfig?.Value ?? 2f, 0.5f, 10f); public static float TetherLength => Mathf.Clamp(tetherLengthConfig?.Value ?? 3f, 1f, 30f); public static float EdgePullStrength => Mathf.Clamp(edgePullStrengthConfig?.Value ?? 6f, 0f, 50f); public static float TumblePullStrength => Mathf.Clamp(tumblePullStrengthConfig?.Value ?? 45f, 0f, 200f); public static int HumVolume => Mathf.Clamp(humVolumeConfig?.Value ?? 35, 0, 100); public static float ActivationGracePeriod => Mathf.Clamp(activationGracePeriodConfig?.Value ?? 3f, 0f, 20f); public static float VisualHeightOffset => Mathf.Clamp(visualHeightOffsetConfig?.Value ?? (-0.2f), -5f, 5f); public static float VisualDistanceFromPlayer => Mathf.Clamp(visualDistanceFromPlayerConfig?.Value ?? 0.8f, 0.05f, 5f); public static bool PlayerDonut => playerDonutConfig?.Value ?? true; public static void BindConfigs(ConfigFile config) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected O, but got Unknown teleportDistanceConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Teleport Distance", 2f, new ConfigDescription("Distance from the anchor player where the linked player is placed when Tether starts.", (AcceptableValueBase)null, Array.Empty<object>())); tetherLengthConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Tether Length", 7.5f, new ConfigDescription("Distance allowed between tethered players before tumble punishment triggers.", (AcceptableValueBase)null, Array.Empty<object>())); edgePullStrengthConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Edge Pull Strength", 5f, new ConfigDescription("Subtle pull applied when players are close to the end of the tether. 0 disables the warning pull.", (AcceptableValueBase)null, Array.Empty<object>())); tumblePullStrengthConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Pull Strength", 15f, new ConfigDescription("Velocity/force applied toward the linked player when the tether is stretched beyond its length.", (AcceptableValueBase)null, Array.Empty<object>())); humVolumeConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Hum Volume", 35, new ConfigDescription("Maximum volume for the local tether warning hum. 0 disables the hum.", (AcceptableValueBase)null, Array.Empty<object>())); activationGracePeriodConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Activation Grace Period", 6f, new ConfigDescription("Seconds of grace at the start of Tether before any pulling or punishment begins, and before another hard pull/tumble after a punishment. The initial teleport still happens immediately.", (AcceptableValueBase)null, Array.Empty<object>())); visualHeightOffsetConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Height Offset", 0f, new ConfigDescription("Vertical offset applied to the tether tube endpoints, endpoint orbs, and player donuts relative to each player's visual tether point.", (AcceptableValueBase)null, Array.Empty<object>())); visualDistanceFromPlayerConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Distance From Player", 0.4f, new ConfigDescription("Distance that each tether endpoint and orb is moved inward from its player. The player donut uses the same value as its radius so it intersects the orb.", (AcceptableValueBase)null, Array.Empty<object>())); playerDonutConfig = ConfigFileOrderManager.Bind(config, "Event - Tether", "Tether Donut", defaultValue: true, "If enabled, an animated tether ring is drawn around each linked player. If disabled, only the tether tube and endpoint orbs are shown."); } } internal static class RtiTimezonesEventOptions { private static ConfigEntry<float>? zoneMinimumRadiusConfig; private static ConfigEntry<float>? zoneMaximumRadiusConfig; private static ConfigEntry<float>? zoneSpawnIntervalConfig; private static ConfigEntry<float>? zoneGrowSecondsConfig; private static ConfigEntry<float>? zoneLingerSecondsConfig; private static ConfigEntry<float>? zoneShrinkSecondsConfig; private static ConfigEntry<float>? maximumVerticalOffsetConfig; private static ConfigEntry<float>? spawnRadiusMinimumConfig; private static ConfigEntry<float>? spawnRadiusMaximumConfig; private static ConfigEntry<bool>? affectPhysicsObjectsConfig; public static float ZoneMinimumRadius => Mathf.Clamp(zoneMinimumRadiusConfig?.Value ?? 1.5f, 0.1f, 100f); public static float ZoneMaximumRadius => Mathf.Clamp(zoneMaximumRadiusConfig?.Value ?? 2.75f, 0.1f, 100f); public static float ZoneSpawnInterval => Mathf.Clamp(zoneSpawnIntervalConfig?.Value ?? 2.5f, 0.1f, 60f); public static float ZoneGrowSeconds => Mathf.Clamp(zoneGrowSecondsConfig?.Value ?? 1f, 0.05f, 30f); public static float ZoneLingerSeconds => Mathf.Clamp(zoneLingerSecondsConfig?.Value ?? 4f, 0f, 60f); public static float ZoneShrinkSeconds => Mathf.Clamp(zoneShrinkSecondsConfig?.Value ?? 4f, 0.05f, 60f); public static float MaximumVerticalOffset => Mathf.Clamp(maximumVerticalOffsetConfig?.Value ?? 0f, 0f, 25f); public static int MaximumActiveZones => 12; public static int MaximumPlacementAttempts => 6; public static float ZoneVisualOpacity => 0.05f; public static float SpawnRadiusMinimum => Mathf.Clamp(spawnRadiusMinimumConfig?.Value ?? 0f, 0f, 1000f); public static float SpawnRadiusMaximum => Mathf.Clamp(spawnRadiusMaximumConfig?.Value ?? 5f, 0f, 1000f); public static bool ZoneContactOutlineEnabled => true; public static float ZoneOutlineOpacity => 0.85f; public static float ZoneOutlineWidth => 0.035f; public static bool ZoneSwirlLinesEnabled => true; public static bool ZoneFloatingMotesEnabled => true; public static float ZoneLingerPulseStrength => 0.035f; public static int ZoneLingerPulseCount => 2; public static bool ZoneCameraVignetteEnabled => true; public static float ZoneCameraVignetteOpacity => 1f; public static float ZoneCameraVignetteSwirlSpeed => 180f; public static float TimezonesCameraSmoothing => 0.5f; public static bool AffectPhysicsObjects => affectPhysicsObjectsConfig?.Value ?? true; public static float PhysicsObjectVelocityMultiplier => 0.05f; public static void GetZoneRadiusRange(out float minimumRadius, out float maximumRadius) { minimumRadius = ZoneMinimumRadius; maximumRadius = ZoneMaximumRadius; if (maximumRadius < minimumRadius) { float num = minimumRadius; minimumRadius = maximumRadius; maximumRadius = num; } } public static void GetSpawnRadiusRange(out float minimumRadius, out float maximumRadius) { minimumRadius = SpawnRadiusMinimum; maximumRadius = SpawnRadiusMaximum; if (maximumRadius < minimumRadius) { float num = minimumRadius; minimumRadius = maximumRadius; maximumRadius = num; } } public static void BindConfigs(ConfigFile config) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Expected O, but got Unknown zoneMinimumRadiusConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Zone Minimum Radius", 1.5f, new ConfigDescription("Minimum radius for each Chronoshifter sphere.", (AcceptableValueBase)null, Array.Empty<object>())); zoneMaximumRadiusConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Zone Maximum Radius", 5f, new ConfigDescription("Maximum radius for each Chronoshifter sphere.", (AcceptableValueBase)null, Array.Empty<object>())); zoneSpawnIntervalConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Zone Spawn Interval", 5.5f, new ConfigDescription("Seconds between Chronoshifter placement attempts. One zone is attempted per target player each interval.", (AcceptableValueBase)null, Array.Empty<object>())); zoneGrowSecondsConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Zone Grow Seconds", 1f, new ConfigDescription("Seconds each Chronoshifter sphere takes to grow from zero to full radius using ease-out.", (AcceptableValueBase)null, Array.Empty<object>())); zoneLingerSecondsConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Zone Linger Seconds", 6f, new ConfigDescription("Seconds each Chronoshifter sphere remains at full radius.", (AcceptableValueBase)null, Array.Empty<object>())); zoneShrinkSecondsConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Zone Shrink Seconds", 4f, new ConfigDescription("Seconds each Chronoshifter sphere takes to shrink from full radius to zero using ease-in.", (AcceptableValueBase)null, Array.Empty<object>())); maximumVerticalOffsetConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Maximum Vertical Offset", 3f, new ConfigDescription("Maximum random height above the resolved ground point for each Chronoshifter sphere.", (AcceptableValueBase)null, Array.Empty<object>())); spawnRadiusMinimumConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Spawn Radius Minimum", 2f, new ConfigDescription("Minimum distance from the target player's current position when selecting where a Chronoshifter sphere can spawn. Use 0 to allow zones directly on or near the target.", (AcceptableValueBase)null, Array.Empty<object>())); spawnRadiusMaximumConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Spawn Radius Maximum", 7f, new ConfigDescription("Maximum distance from the target player's current position when selecting where a Chronoshifter sphere can spawn.", (AcceptableValueBase)null, Array.Empty<object>())); affectPhysicsObjectsConfig = ConfigFileOrderManager.Bind(config, "Event - Chronoshifters", "Affect Physics Objects", defaultValue: true, "If enabled, physics objects inside active Chronoshifter spheres have their velocity and angular velocity damped while they remain inside the sphere. The velocity multiplier is hardcoded to 0.05."); } } internal static class RtiMoshpitEventOptions { private sealed class BananaSongConfigRecord { public string AssetName { get; set; } = ""; public float Bpm { get; set; } } private sealed class BananaSongCandidate { public string AssetName { get; set; } = ""; public AudioClip Clip { get; set; } = null; public float Length { get; set; } public float Bpm { get; set; } } public const float MusicFadeInSeconds = 0.35f; public const float MusicFadeOutSeconds = 1.5f; public const float DefaultHeadbangAngularSpeed = 15f; private const float BananaSongStartSafetyMarginSeconds = 0.15f; private const float MinimumUsefulBananaSongBpm = 20f; private const float MaximumUsefulBananaSongBpm = 400f; private const string DefaultBananaSongAssetNames = "RTI_BananaSong_01;182;RTI_BananaSong_02;140;RTI_BananaSong_03;105;RTI_BananaSong_04;180,RTI_BananaSong_05;101"; private static ConfigEntry<bool>? bananaModeEnabledConfig; private static ConfigEntry<int>? musicVolumeConfig; public static int MusicVolume => Mathf.Clamp(musicVolumeConfig?.Value ?? 100, 0, 1000); public static bool BananaModeEnabled => bananaModeEnabledConfig?.Value ?? true; public static bool BananaSongsEnabled => BananaModeEnabled; public static string BananaSongAssetNames => "RTI_BananaSong_01;182;RTI_BananaSong_02;140;RTI_BananaSong_03;105;RTI_BananaSong_04;180,RTI_BananaSong_05;101"; public static float GetRequiredBananaSongPlaybackSeconds(float eventDuration) { return Mathf.Max(0.1f, eventDuration) + 1.5f + 0.15f; } public static List<string> GetBananaSongAssetNames() { List<string> list = new List<string>(); List<BananaSongConfigRecord> bananaSongConfigRecords = GetBananaSongConfigRecords(); foreach (BananaSongConfigRecord item in bananaSongConfigRecords) { if (item != null && !string.IsNullOrWhiteSpace(item.AssetName)) { list.Add(item.AssetName); } } return list; } public static bool TryGetBananaSongBpmForAssetName(string assetName, out float bpm) { bpm = 0f; if (string.IsNullOrWhiteSpace(assetName)) { return false; } List<BananaSongConfigRecord> bananaSongConfigRecords = GetBananaSongConfigRecords(); foreach (BananaSongConfigRecord item in bananaSongConfigRecords) { if (item == null || string.IsNullOrWhiteSpace(item.AssetName) || !string.Equals(item.AssetName, assetName.Trim(), StringComparison.OrdinalIgnoreCase)) { continue; } if (item.Bpm <= 0.001f) { return false; } bpm = Mathf.Clamp(item.Bpm, 20f, 400f); return true; } return false; } public static float GetHeadbangAngularSpeedForBpm(float bpm) { if (bpm <= 0.001f) { return 15f; } float num = Mathf.Clamp(bpm, 20f, 400f); return Mathf.Clamp(num / 60f * MathF.PI * 2f, 0.1f, 60f); } public static string FormatBpmForLog(float bpm) { if (bpm <= 0.001f) { return "not configured"; } return Mathf.Clamp(bpm, 20f, 400f).ToString("0.###", CultureInfo.InvariantCulture); } public static bool TryPickBananaSongAssetName(out string assetName, out string report) { float startTime; float bpm; return TryPickBananaSongAssetName(0f, out assetName, out