using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("com.github.Thanks.BlastBuddy")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("com.github.Thanks.BlastBuddy")]
[assembly: AssemblyTitle("com.github.Thanks.BlastBuddy")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace BlastBuddy
{
[BepInPlugin("com.github.Thanks.BlastBuddy", "Blast Buddy", "0.0.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class BlastBuddyPlugin : BaseUnityPlugin
{
private enum ConfigKey
{
ModEnabled,
ActivationDelaySeconds,
TriggerRadius,
ExitRadius,
BlastRadius,
TriggerCooldownSeconds,
ScanIntervalSeconds,
SafetyCheckIntervalSeconds,
FartForce,
TriggerChance,
NarrowSpaceCheckRadius,
NarrowSpaceColliderThreshold,
EnableRescueSuppression,
EnableNarrowSpaceSuppression
}
[CompilerGenerated]
private sealed class <EnumerateCandidateTransforms>d__97 : IEnumerable<Transform>, IEnumerable, IEnumerator<Transform>, IEnumerator, IDisposable
{
private int <>1__state;
private Transform <>2__current;
private int <>l__initialThreadId;
private object menuInstance;
public object <>3__menuInstance;
private Type menuType;
public Type <>3__menuType;
Transform IEnumerator<Transform>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <EnumerateCandidateTransforms>d__97(int <>1__state)
{
this.<>1__state = <>1__state;
<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
object? obj2;
Transform val2;
switch (<>1__state)
{
default:
return false;
case 0:
{
<>1__state = -1;
object obj = menuInstance;
Component val = (Component)((obj is Component) ? obj : null);
if (val != null)
{
<>2__current = val.transform;
<>1__state = 1;
return true;
}
goto IL_004d;
}
case 1:
<>1__state = -1;
goto IL_004d;
case 2:
{
<>1__state = -1;
break;
}
IL_004d:
obj2 = menuType.GetProperty("Content", InstanceBindingFlags)?.GetValue(menuInstance);
val2 = (Transform)((obj2 is Transform) ? obj2 : null);
if (val2 != null)
{
<>2__current = val2;
<>1__state = 2;
return true;
}
break;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
[DebuggerHidden]
IEnumerator<Transform> IEnumerable<Transform>.GetEnumerator()
{
<EnumerateCandidateTransforms>d__97 <EnumerateCandidateTransforms>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<EnumerateCandidateTransforms>d__ = this;
}
else
{
<EnumerateCandidateTransforms>d__ = new <EnumerateCandidateTransforms>d__97(0);
}
<EnumerateCandidateTransforms>d__.menuInstance = <>3__menuInstance;
<EnumerateCandidateTransforms>d__.menuType = <>3__menuType;
return <EnumerateCandidateTransforms>d__;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<Transform>)this).GetEnumerator();
}
}
[CompilerGenerated]
private sealed class <EnumerateModConfigUiRoots>d__96 : IEnumerable<Transform>, IEnumerable, IEnumerator<Transform>, IEnumerator, IDisposable
{
private int <>1__state;
private Transform <>2__current;
private int <>l__initialThreadId;
private object menuInstance;
public object <>3__menuInstance;
private Type menuType;
public Type <>3__menuType;
private HashSet<int> <visited>5__2;
private IEnumerator<Transform> <>7__wrap2;
Transform IEnumerator<Transform>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <EnumerateModConfigUiRoots>d__96(int <>1__state)
{
this.<>1__state = <>1__state;
<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<visited>5__2 = null;
<>7__wrap2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
try
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<visited>5__2 = new HashSet<int>();
<>7__wrap2 = EnumerateCandidateTransforms(menuInstance, menuType).GetEnumerator();
<>1__state = -3;
break;
case 1:
<>1__state = -3;
break;
}
while (<>7__wrap2.MoveNext())
{
Transform current = <>7__wrap2.Current;
if ((Object)(object)current != (Object)null && <visited>5__2.Add(((Object)current).GetInstanceID()))
{
<>2__current = current;
<>1__state = 1;
return true;
}
}
<>m__Finally1();
<>7__wrap2 = null;
return false;
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<>7__wrap2 != null)
{
<>7__wrap2.Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
[DebuggerHidden]
IEnumerator<Transform> IEnumerable<Transform>.GetEnumerator()
{
<EnumerateModConfigUiRoots>d__96 <EnumerateModConfigUiRoots>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<EnumerateModConfigUiRoots>d__ = this;
}
else
{
<EnumerateModConfigUiRoots>d__ = new <EnumerateModConfigUiRoots>d__96(0);
}
<EnumerateModConfigUiRoots>d__.menuInstance = <>3__menuInstance;
<EnumerateModConfigUiRoots>d__.menuType = <>3__menuType;
return <EnumerateModConfigUiRoots>d__;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<Transform>)this).GetEnumerator();
}
}
[CompilerGenerated]
private sealed class <GetAliasDefinitions>d__86 : IEnumerable<ConfigDefinition>, IEnumerable, IEnumerator<ConfigDefinition>, IEnumerator, IDisposable
{
private int <>1__state;
private ConfigDefinition <>2__current;
private int <>l__initialThreadId;
private ConfigKey configKey;
public ConfigKey <>3__configKey;
private string <englishSection>5__2;
private string <chineseSection>5__3;
private string <englishKey>5__4;
private string <chineseKey>5__5;
ConfigDefinition IEnumerator<ConfigDefinition>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <GetAliasDefinitions>d__86(int <>1__state)
{
this.<>1__state = <>1__state;
<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<englishSection>5__2 = null;
<chineseSection>5__3 = null;
<englishKey>5__4 = null;
<chineseKey>5__5 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Expected O, but got Unknown
//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
//IL_00cf: Expected O, but got Unknown
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<englishSection>5__2 = GetSectionName(isChineseLanguage: false);
<chineseSection>5__3 = GetSectionName(isChineseLanguage: true);
<englishKey>5__4 = GetKeyName(configKey, isChineseLanguage: false);
<chineseKey>5__5 = GetKeyName(configKey, isChineseLanguage: true);
<>2__current = new ConfigDefinition(<englishSection>5__2, <englishKey>5__4);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<>2__current = new ConfigDefinition(<englishSection>5__2, <chineseKey>5__5);
<>1__state = 2;
return true;
case 2:
<>1__state = -1;
<>2__current = new ConfigDefinition(<chineseSection>5__3, <englishKey>5__4);
<>1__state = 3;
return true;
case 3:
<>1__state = -1;
<>2__current = new ConfigDefinition(<chineseSection>5__3, <chineseKey>5__5);
<>1__state = 4;
return true;
case 4:
{
<>1__state = -1;
if (TryGetLegacyDefinition(configKey, out ConfigDefinition definition))
{
<>2__current = definition;
<>1__state = 5;
return true;
}
break;
}
case 5:
<>1__state = -1;
break;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
[DebuggerHidden]
IEnumerator<ConfigDefinition> IEnumerable<ConfigDefinition>.GetEnumerator()
{
<GetAliasDefinitions>d__86 <GetAliasDefinitions>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<GetAliasDefinitions>d__ = this;
}
else
{
<GetAliasDefinitions>d__ = new <GetAliasDefinitions>d__86(0);
}
<GetAliasDefinitions>d__.configKey = <>3__configKey;
return <GetAliasDefinitions>d__;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<ConfigDefinition>)this).GetEnumerator();
}
}
public const string PluginGuid = "com.github.Thanks.BlastBuddy";
public const string PluginName = "Blast Buddy";
public const string PluginVersion = "0.0.1";
private const string ModConfigPluginGuid = "com.github.PEAKModding.PEAKLib.ModConfig";
private const int SimplifiedChineseLanguageIndex = 9;
private const float DefaultActivationDelaySeconds = 30f;
private const float DefaultTriggerRadius = 2.2f;
private const float DefaultExitRadius = 3.8f;
private const float DefaultBlastRadius = 3.4f;
private const float DefaultTriggerCooldownSeconds = 1.4f;
private const float DefaultScanIntervalSeconds = 0.18f;
private const float DefaultSafetyCheckIntervalSeconds = 0.25f;
private const float DefaultFartForce = 320f;
private const float DefaultTriggerChance = 1f;
private const float DefaultNarrowSpaceCheckRadius = 1.1f;
private const int DefaultNarrowSpaceColliderThreshold = 16;
private const string ExplosionVfxPath = "VFX_SporeExploExploEdibleSpawn";
private const string ExplosionNoKnockbackVfxPath = "VFX_SporeExploExploEdibleSpawn_NoKnockback";
private static readonly BindingFlags InstanceBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly BindingFlags StaticBindingFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly PropertyInfo? ConfigFileEntriesProperty = typeof(ConfigFile).GetProperty("Entries", InstanceBindingFlags);
private static BlastBuddyPlugin? _instance;
private readonly HashSet<int> _nearbyActivePlayers = new HashSet<int>();
private readonly Dictionary<int, HashSet<int>> _hostNearbyPlayersBySource = new Dictionary<int, HashSet<int>>();
private readonly List<Character> _scanBuffer = new List<Character>();
private readonly List<Character> _roomCharacterBuffer = new List<Character>();
private readonly List<int> _actorIdBuffer = new List<int>();
private readonly Collider[] _narrowSpaceBuffer = (Collider[])(object)new Collider[24];
private float _nextAllowedTriggerAt;
private float _nextScanAt;
private float _nextSafetyCheckAt;
private float _gameplayEnteredAt = -1f;
private bool _cachedSafetySuppressed;
private bool _loggedActivationDelay;
private bool _loggedSafetySuppression;
private bool _loggedRescueSuppression;
private string? _lastSafetyReason;
private bool _lastDetectedChineseLanguage;
private ConfigEntry<bool>? _modEnabledConfig;
private ConfigEntry<float>? _activationDelaySecondsConfig;
private ConfigEntry<float>? _triggerRadiusConfig;
private ConfigEntry<float>? _exitRadiusConfig;
private ConfigEntry<float>? _blastRadiusConfig;
private ConfigEntry<float>? _triggerCooldownSecondsConfig;
private ConfigEntry<float>? _scanIntervalSecondsConfig;
private ConfigEntry<float>? _safetyCheckIntervalSecondsConfig;
private ConfigEntry<float>? _fartForceConfig;
private ConfigEntry<float>? _triggerChanceConfig;
private ConfigEntry<float>? _narrowSpaceCheckRadiusConfig;
private ConfigEntry<int>? _narrowSpaceColliderThresholdConfig;
private ConfigEntry<bool>? _enableRescueSuppressionConfig;
private ConfigEntry<bool>? _enableNarrowSpaceSuppressionConfig;
private void Awake()
{
_instance = this;
_lastDetectedChineseLanguage = DetectChineseLanguage();
BindConfig(_lastDetectedChineseLanguage);
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Blast Buddy] Loaded. Hosts drive room-wide syncing, guests fall back to local behavior, and both paths now share one unified trigger cooldown.");
ResetState();
}
private void Update()
{
HandleLanguageChangeIfNeeded();
if (!IsModEnabled())
{
ResetState();
return;
}
if ((Object)(object)GameHandler.Instance == (Object)null || !GameHandler.IsInGameplayScene)
{
ResetState();
return;
}
if (!IsGameplayActivationReady())
{
_nearbyActivePlayers.Clear();
_scanBuffer.Clear();
return;
}
if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
{
HandleHostAuthoritativeRoom();
return;
}
if (!TryGetLocalCharacter(out Character character))
{
_nearbyActivePlayers.Clear();
_scanBuffer.Clear();
return;
}
if (!IsCompatibleMultiplayerSession())
{
_nearbyActivePlayers.Clear();
_scanBuffer.Clear();
return;
}
if (IsSafetySuppressed(character))
{
if (!_loggedSafetySuppression)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)("[Blast Buddy] Paused because a safety condition is active: " + (_lastSafetyReason ?? "unknown") + "."));
_loggedSafetySuppression = true;
}
_nearbyActivePlayers.Clear();
_scanBuffer.Clear();
return;
}
_loggedSafetySuppression = false;
if (Time.time < _nextScanAt)
{
return;
}
_nextScanAt = Time.time + GetScanIntervalSeconds();
if (ScanNearbyPlayers(character))
{
if (!_loggedRescueSuppression)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Blast Buddy] Paused because a downed teammate is nearby.");
_loggedRescueSuppression = true;
}
_nearbyActivePlayers.Clear();
return;
}
_loggedRescueSuppression = false;
Character val = null;
bool flag = false;
foreach (Character item in _scanBuffer)
{
int characterIdentity = GetCharacterIdentity(item);
if (_nearbyActivePlayers.Add(characterIdentity))
{
flag = true;
if (val == null)
{
val = item;
}
}
}
SyncTrackedActorIds(_nearbyActivePlayers, _scanBuffer);
if (flag && !((Object)(object)val == (Object)null) && !(Time.time < _nextAllowedTriggerAt))
{
if (!ShouldTriggerForThisEntry())
{
_nextAllowedTriggerAt = Time.time + GetTriggerCooldownSeconds();
}
else if (TryTriggerEffect(character, val))
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Blast Buddy] Local blast triggered.");
_nextAllowedTriggerAt = Time.time + GetTriggerCooldownSeconds();
}
}
}
private void HandleHostAuthoritativeRoom()
{
if (Time.time < _nextScanAt)
{
return;
}
_nextScanAt = Time.time + GetScanIntervalSeconds();
_roomCharacterBuffer.Clear();
List<Character> allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters();
if (allPlayerCharacters == null || allPlayerCharacters.Count <= 1)
{
_hostNearbyPlayersBySource.Clear();
return;
}
foreach (Character item in allPlayerCharacters)
{
if (!((Object)(object)item == (Object)null) && !((Object)(object)item.data == (Object)null) && ((Component)item).gameObject.activeInHierarchy)
{
_roomCharacterBuffer.Add(item);
}
}
if (IsRescueSuppressionEnabled() && HasNearbyDownedPair(_roomCharacterBuffer))
{
if (!_loggedRescueSuppression)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Blast Buddy] Host scan paused because a downed teammate is nearby.");
_loggedRescueSuppression = true;
}
_hostNearbyPlayersBySource.Clear();
return;
}
_loggedRescueSuppression = false;
Character val = null;
foreach (Character item2 in _roomCharacterBuffer)
{
if (IsSafetySuppressedForCharacter(item2))
{
_hostNearbyPlayersBySource.Remove(GetCharacterIdentity(item2));
continue;
}
bool num = ScanNearbyPlayersForSource(item2, _roomCharacterBuffer);
int characterIdentity = GetCharacterIdentity(item2);
if (num)
{
_hostNearbyPlayersBySource.Remove(characterIdentity);
continue;
}
if (!_hostNearbyPlayersBySource.TryGetValue(characterIdentity, out HashSet<int> value))
{
value = new HashSet<int>();
_hostNearbyPlayersBySource[characterIdentity] = value;
}
bool flag = false;
foreach (Character item3 in _scanBuffer)
{
int characterIdentity2 = GetCharacterIdentity(item3);
if (value.Add(characterIdentity2))
{
flag = true;
}
}
SyncTrackedActorIds(value, _scanBuffer);
if (flag && (Object)(object)val == (Object)null)
{
val = item2;
}
}
if (!((Object)(object)val == (Object)null) && !(Time.time < _nextAllowedTriggerAt))
{
if (!ShouldTriggerForThisEntry())
{
_nextAllowedTriggerAt = Time.time + GetTriggerCooldownSeconds();
}
else if (TryTriggerBlast(val, _roomCharacterBuffer))
{
((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}] Host blast triggered for actor {1}.", "Blast Buddy", GetCharacterIdentity(val)));
_nextAllowedTriggerAt = Time.time + GetTriggerCooldownSeconds();
}
}
}
private bool ScanNearbyPlayers(Character localCharacter)
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
_scanBuffer.Clear();
List<Character> allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters();
if (allPlayerCharacters == null || allPlayerCharacters.Count <= 1)
{
return false;
}
Vector3 center = localCharacter.Center;
foreach (Character item in allPlayerCharacters)
{
if (!IsRelevantOtherCharacter(localCharacter, item))
{
continue;
}
int characterIdentity = GetCharacterIdentity(item);
float num = (_nearbyActivePlayers.Contains(characterIdentity) ? GetExitRadius() : GetTriggerRadius());
Vector3 val = item.Center - center;
if (!(((Vector3)(ref val)).sqrMagnitude > num * num))
{
if (IsRescueSuppressionEnabled() && IsDowned(item))
{
return true;
}
_scanBuffer.Add(item);
}
}
return false;
}
private bool ScanNearbyPlayersForSource(Character sourceCharacter, List<Character> characters)
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
_scanBuffer.Clear();
int characterIdentity = GetCharacterIdentity(sourceCharacter);
_hostNearbyPlayersBySource.TryGetValue(characterIdentity, out HashSet<int> value);
Vector3 center = sourceCharacter.Center;
foreach (Character character in characters)
{
if (!IsRelevantOtherCharacter(sourceCharacter, character))
{
continue;
}
int characterIdentity2 = GetCharacterIdentity(character);
float num = ((value != null && value.Contains(characterIdentity2)) ? GetExitRadius() : GetTriggerRadius());
Vector3 val = character.Center - center;
if (!(((Vector3)(ref val)).sqrMagnitude > num * num))
{
if (IsRescueSuppressionEnabled() && IsDowned(character))
{
return true;
}
_scanBuffer.Add(character);
}
}
return false;
}
private bool HasNearbyDownedPair(List<Character> characters)
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
float exitRadiusSquared = GetExitRadiusSquared();
for (int i = 0; i < characters.Count; i++)
{
Character val = characters[i];
for (int j = 0; j < characters.Count; j++)
{
if (i == j)
{
continue;
}
Character val2 = characters[j];
if (IsRelevantOtherCharacter(val, val2) && IsDowned(val2))
{
Vector3 val3 = val2.Center - val.Center;
if (((Vector3)(ref val3)).sqrMagnitude <= exitRadiusSquared)
{
return true;
}
}
}
}
return false;
}
private void SyncTrackedActorIds(HashSet<int> trackedActorIds, List<Character> characters)
{
_actorIdBuffer.Clear();
foreach (Character character in characters)
{
_actorIdBuffer.Add(GetCharacterIdentity(character));
}
trackedActorIds.IntersectWith(_actorIdBuffer);
}
private static bool TryGetLocalCharacter(out Character? character)
{
character = null;
if ((Object)(object)Player.localPlayer == (Object)null)
{
return false;
}
character = Player.localPlayer.character;
if ((Object)(object)character == (Object)null || !character.IsLocal || !((Component)character).gameObject.activeInHierarchy)
{
character = null;
return false;
}
return true;
}
private bool IsGameplayActivationReady()
{
if (!GameHandler.IsInGameplayScene)
{
_gameplayEnteredAt = -1f;
_loggedActivationDelay = false;
return false;
}
if (_gameplayEnteredAt < 0f)
{
_gameplayEnteredAt = Time.time;
}
if (Time.time - _gameplayEnteredAt >= GetActivationDelaySeconds())
{
_loggedActivationDelay = false;
return true;
}
if (!_loggedActivationDelay)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}] Waiting {1:0} seconds after gameplay starts before enabling effects.", "Blast Buddy", GetActivationDelaySeconds()));
_loggedActivationDelay = true;
}
return false;
}
private bool ShouldSuppressForSafety(Character character, out string? reason)
{
if (IsRescueSuppressionEnabled() && IsCharacterReviving(character))
{
reason = (DetectChineseLanguage() ? "正在救人" : "reviving");
return true;
}
if (IsNarrowSpaceSuppressionEnabled() && IsInNarrowSpace(character))
{
reason = (DetectChineseLanguage() ? "狭窄地形" : "narrow space");
return true;
}
reason = null;
return false;
}
private bool IsSafetySuppressed(Character localCharacter)
{
if (Time.time >= _nextSafetyCheckAt)
{
_nextSafetyCheckAt = Time.time + GetSafetyCheckIntervalSeconds();
_cachedSafetySuppressed = ShouldSuppressForSafety(localCharacter, out _lastSafetyReason);
}
return _cachedSafetySuppressed;
}
private bool IsSafetySuppressedForCharacter(Character character)
{
string reason;
return ShouldSuppressForSafety(character, out reason);
}
private static bool IsCompatibleMultiplayerSession()
{
if (PhotonNetwork.InRoom)
{
return true;
}
List<Character> allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters();
if (allPlayerCharacters != null)
{
return allPlayerCharacters.Count > 1;
}
return false;
}
private static bool IsCharacterReviving(Character character)
{
if (HasTruthyMember(character, "revive", "reviving", "isreviving"))
{
return true;
}
PhotonView component = ((Component)character).GetComponent<PhotonView>();
if ((Object)(object)component != (Object)null && component.IsMine)
{
return HasTruthyMember(Player.localPlayer, "revive", "reviving", "isreviving");
}
return false;
}
private bool IsInNarrowSpace(Character character)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
int num = Physics.OverlapSphereNonAlloc(character.Center, GetNarrowSpaceCheckRadius(), _narrowSpaceBuffer, -1, (QueryTriggerInteraction)1);
int num2 = 0;
for (int i = 0; i < num; i++)
{
Collider val = _narrowSpaceBuffer[i];
if (!((Object)(object)val == (Object)null))
{
Rigidbody attachedRigidbody = val.attachedRigidbody;
if (!((Object)(object)attachedRigidbody != (Object)null) || !((Component)attachedRigidbody).transform.IsChildOf(((Component)character).transform))
{
num2++;
}
}
}
Array.Clear(_narrowSpaceBuffer, 0, _narrowSpaceBuffer.Length);
return num2 >= GetNarrowSpaceColliderThreshold();
}
private static bool IsRelevantOtherCharacter(Character sourceCharacter, Character otherCharacter)
{
if ((Object)(object)otherCharacter != (Object)null && (Object)(object)otherCharacter != (Object)(object)sourceCharacter && (Object)(object)otherCharacter.data != (Object)null && !otherCharacter.data.dead)
{
return ((Component)otherCharacter).gameObject.activeInHierarchy;
}
return false;
}
private static bool IsDowned(Character character)
{
if (!character.data.dead && !character.data.passedOut)
{
return character.data.fullyPassedOut;
}
return true;
}
private static int GetCharacterIdentity(Character character)
{
PhotonView component = ((Component)character).GetComponent<PhotonView>();
if ((Object)(object)component != (Object)null && component.OwnerActorNr > 0)
{
return component.OwnerActorNr;
}
return ((Object)character).GetInstanceID();
}
private static bool HasTruthyMember(object? target, params string[] tokens)
{
if (target == null)
{
return false;
}
Type type = target.GetType();
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
PropertyInfo[] properties = type.GetProperties(bindingAttr);
foreach (PropertyInfo propertyInfo in properties)
{
if (!NameMatches(propertyInfo.Name, tokens) || propertyInfo.GetIndexParameters().Length != 0)
{
continue;
}
try
{
if (IsTruthy(propertyInfo.GetValue(target)))
{
return true;
}
}
catch
{
}
}
FieldInfo[] fields = type.GetFields(bindingAttr);
foreach (FieldInfo fieldInfo in fields)
{
if (!NameMatches(fieldInfo.Name, tokens))
{
continue;
}
try
{
if (IsTruthy(fieldInfo.GetValue(target)))
{
return true;
}
}
catch
{
}
}
return false;
}
private static bool NameMatches(string name, string[] tokens)
{
string text = name.Replace("_", string.Empty).ToLowerInvariant();
foreach (string value in tokens)
{
if (text.Contains(value))
{
return true;
}
}
return false;
}
private static bool IsTruthy(object? value)
{
if (value != null)
{
if (!(value is bool result))
{
if (!(value is int num))
{
if (!(value is long num2))
{
if (!(value is float value2))
{
if (value is double value3)
{
return Math.Abs(value3) > 0.001;
}
return false;
}
return Math.Abs(value2) > 0.001f;
}
return num2 != 0;
}
return num != 0;
}
return result;
}
return false;
}
private static bool ShouldTriggerForThisEntry()
{
return Random.value <= GetTriggerChance();
}
private static float GetTriggerChance()
{
return GetConfiguredChance(GetPluginInstance()._triggerChanceConfig, 1f);
}
private static float GetTriggerCooldownSeconds()
{
return GetConfiguredPositiveFloat(GetPluginInstance()._triggerCooldownSecondsConfig, 1.4f);
}
private bool TryTriggerEffect(Character localCharacter, Character triggerCharacter)
{
try
{
return (PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient) ? TryTriggerBlast(triggerCharacter, _roomCharacterBuffer) : ApplyOriginalFartBlast(localCharacter, _scanBuffer);
}
catch (Exception arg)
{
((BaseUnityPlugin)this).Logger.LogError((object)string.Format("[{0}] Failed to trigger the fart effect: {1}", "Blast Buddy", arg));
return false;
}
}
private bool TryTriggerBlast(Character sourceCharacter, List<Character> characters)
{
return ApplyOriginalFartBlast(sourceCharacter, characters);
}
private bool ApplyOriginalFartBlast(Character sourceCharacter, IEnumerable<Character> potentialTargets)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
Vector3 center = sourceCharacter.Center;
if ((Object)(object)GameUtils.instance != (Object)null)
{
GameUtils.instance.SpawnResourceAtPositionNetworked("VFX_SporeExploExploEdibleSpawn", center, (RpcTarget)5);
GameUtils.instance.RPC_SpawnResourceAtPosition("VFX_SporeExploExploEdibleSpawn_NoKnockback", center);
}
bool result = false;
foreach (Character potentialTarget in potentialTargets)
{
if (IsValidBlastTarget(sourceCharacter, potentialTarget, center))
{
PhotonView val = potentialTarget.refs?.view ?? ((Component)potentialTarget).GetComponent<PhotonView>();
if (!((Object)(object)val == (Object)null))
{
val.RPC("RPCA_AddForceToBodyPart", (RpcTarget)5, new object[3]
{
(object)(BodypartType)0,
Vector3.zero,
Vector3.up * GetFartForce()
});
result = true;
}
}
}
return result;
}
private bool IsValidBlastTarget(Character sourceCharacter, Character targetCharacter, Vector3 blastCenter)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)targetCharacter == (Object)null || (Object)(object)targetCharacter == (Object)(object)sourceCharacter || (Object)(object)targetCharacter.data == (Object)null || !((Component)targetCharacter).gameObject.activeInHierarchy || IsDowned(targetCharacter))
{
return false;
}
Vector3 val = targetCharacter.Center - blastCenter;
return ((Vector3)(ref val)).sqrMagnitude <= GetBlastRadiusSquared();
}
private void ResetState()
{
_nearbyActivePlayers.Clear();
_hostNearbyPlayersBySource.Clear();
_scanBuffer.Clear();
_roomCharacterBuffer.Clear();
_nextAllowedTriggerAt = Time.time;
_nextScanAt = Time.time;
_nextSafetyCheckAt = Time.time;
_gameplayEnteredAt = -1f;
_cachedSafetySuppressed = false;
_loggedActivationDelay = false;
_loggedSafetySuppression = false;
_loggedRescueSuppression = false;
_lastSafetyReason = null;
}
private void BindConfig(bool isChineseLanguage)
{
string sectionName = GetSectionName(isChineseLanguage);
_modEnabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>(sectionName, GetKeyName(ConfigKey.ModEnabled, isChineseLanguage), true, CreateConfigDescription(ConfigKey.ModEnabled, isChineseLanguage));
_activationDelaySecondsConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.ActivationDelaySeconds, isChineseLanguage), 30f, CreateConfigDescription(ConfigKey.ActivationDelaySeconds, isChineseLanguage));
_triggerRadiusConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.TriggerRadius, isChineseLanguage), 2.2f, CreateConfigDescription(ConfigKey.TriggerRadius, isChineseLanguage));
_exitRadiusConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.ExitRadius, isChineseLanguage), 3.8f, CreateConfigDescription(ConfigKey.ExitRadius, isChineseLanguage));
_blastRadiusConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.BlastRadius, isChineseLanguage), 3.4f, CreateConfigDescription(ConfigKey.BlastRadius, isChineseLanguage));
_triggerCooldownSecondsConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.TriggerCooldownSeconds, isChineseLanguage), 1.4f, CreateConfigDescription(ConfigKey.TriggerCooldownSeconds, isChineseLanguage));
_scanIntervalSecondsConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.ScanIntervalSeconds, isChineseLanguage), 0.18f, CreateConfigDescription(ConfigKey.ScanIntervalSeconds, isChineseLanguage));
_safetyCheckIntervalSecondsConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.SafetyCheckIntervalSeconds, isChineseLanguage), 0.25f, CreateConfigDescription(ConfigKey.SafetyCheckIntervalSeconds, isChineseLanguage));
_fartForceConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.FartForce, isChineseLanguage), 320f, CreateConfigDescription(ConfigKey.FartForce, isChineseLanguage));
_triggerChanceConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.TriggerChance, isChineseLanguage), 1f, CreateConfigDescription(ConfigKey.TriggerChance, isChineseLanguage));
_narrowSpaceCheckRadiusConfig = ((BaseUnityPlugin)this).Config.Bind<float>(sectionName, GetKeyName(ConfigKey.NarrowSpaceCheckRadius, isChineseLanguage), 1.1f, CreateConfigDescription(ConfigKey.NarrowSpaceCheckRadius, isChineseLanguage));
_narrowSpaceColliderThresholdConfig = ((BaseUnityPlugin)this).Config.Bind<int>(sectionName, GetKeyName(ConfigKey.NarrowSpaceColliderThreshold, isChineseLanguage), 16, CreateConfigDescription(ConfigKey.NarrowSpaceColliderThreshold, isChineseLanguage));
_enableRescueSuppressionConfig = ((BaseUnityPlugin)this).Config.Bind<bool>(sectionName, GetKeyName(ConfigKey.EnableRescueSuppression, isChineseLanguage), true, CreateConfigDescription(ConfigKey.EnableRescueSuppression, isChineseLanguage));
_enableNarrowSpaceSuppressionConfig = ((BaseUnityPlugin)this).Config.Bind<bool>(sectionName, GetKeyName(ConfigKey.EnableNarrowSpaceSuppression, isChineseLanguage), true, CreateConfigDescription(ConfigKey.EnableNarrowSpaceSuppression, isChineseLanguage));
MigrateLocalizedConfigEntries();
ClampConfigValues();
}
private ConfigDescription CreateConfigDescription(ConfigKey configKey, bool isChineseLanguage)
{
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Expected O, but got Unknown
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Expected O, but got Unknown
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Expected O, but got Unknown
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00cc: Expected O, but got Unknown
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00f2: Expected O, but got Unknown
//IL_0112: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Expected O, but got Unknown
//IL_0138: Unknown result type (might be due to invalid IL or missing references)
//IL_013e: Expected O, but got Unknown
//IL_015e: Unknown result type (might be due to invalid IL or missing references)
//IL_0164: Expected O, but got Unknown
//IL_0181: Unknown result type (might be due to invalid IL or missing references)
//IL_0187: Expected O, but got Unknown
//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
//IL_01aa: Expected O, but got Unknown
//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
//IL_01c6: Expected O, but got Unknown
//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
//IL_01db: Expected O, but got Unknown
return (ConfigDescription)(configKey switch
{
ConfigKey.ActivationDelaySeconds => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 180f), Array.Empty<object>()),
ConfigKey.TriggerRadius => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 6f), Array.Empty<object>()),
ConfigKey.ExitRadius => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 8f), Array.Empty<object>()),
ConfigKey.BlastRadius => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 8f), Array.Empty<object>()),
ConfigKey.TriggerCooldownSeconds => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), Array.Empty<object>()),
ConfigKey.ScanIntervalSeconds => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>()),
ConfigKey.SafetyCheckIntervalSeconds => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>()),
ConfigKey.FartForce => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 1200f), Array.Empty<object>()),
ConfigKey.TriggerChance => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()),
ConfigKey.NarrowSpaceCheckRadius => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 3f), Array.Empty<object>()),
ConfigKey.NarrowSpaceColliderThreshold => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()),
_ => (object)new ConfigDescription(GetLocalizedDescription(configKey, isChineseLanguage), (AcceptableValueBase)null, Array.Empty<object>()),
});
}
private void MigrateLocalizedConfigEntries()
{
IDictionary orphanedEntries = GetOrphanedEntries(((BaseUnityPlugin)this).Config);
if (orphanedEntries == null || orphanedEntries.Count == 0)
{
return;
}
bool flag = false;
ConfigEntryBase[] configEntriesSnapshot = GetConfigEntriesSnapshot(((BaseUnityPlugin)this).Config);
foreach (ConfigEntryBase val in configEntriesSnapshot)
{
if (!(((val != null) ? val.Definition : null) == (ConfigDefinition)null) && TryGetConfigKey(val.Definition.Key, out var configKey))
{
flag |= TryMigrateConfigValue(val, configKey, orphanedEntries);
}
}
if (flag)
{
((BaseUnityPlugin)this).Config.Save();
}
}
private static bool TryMigrateConfigValue(ConfigEntryBase entry, ConfigKey configKey, IDictionary orphanedEntries)
{
foreach (ConfigDefinition aliasDefinition in GetAliasDefinitions(configKey))
{
if (!DefinitionsEqual(aliasDefinition, entry.Definition) && orphanedEntries.Contains(aliasDefinition))
{
object obj = orphanedEntries[aliasDefinition];
if (obj != null)
{
entry.SetSerializedValue(obj.ToString());
}
orphanedEntries.Remove(aliasDefinition);
return true;
}
}
return false;
}
[IteratorStateMachine(typeof(<GetAliasDefinitions>d__86))]
private static IEnumerable<ConfigDefinition> GetAliasDefinitions(ConfigKey configKey)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <GetAliasDefinitions>d__86(-2)
{
<>3__configKey = configKey
};
}
private static bool TryGetLegacyDefinition(ConfigKey configKey, out ConfigDefinition definition)
{
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Expected O, but got Unknown
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Expected O, but got Unknown
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_0078: Expected O, but got Unknown
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Expected O, but got Unknown
//IL_0098: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Expected O, but got Unknown
//IL_00be: Unknown result type (might be due to invalid IL or missing references)
//IL_00c4: Expected O, but got Unknown
//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
//IL_00d7: Expected O, but got Unknown
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Expected O, but got Unknown
//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
//IL_00ea: Expected O, but got Unknown
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_00fd: Expected O, but got Unknown
//IL_010a: Unknown result type (might be due to invalid IL or missing references)
//IL_0110: Expected O, but got Unknown
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0123: Expected O, but got Unknown
//IL_0130: Unknown result type (might be due to invalid IL or missing references)
//IL_0136: Expected O, but got Unknown
//IL_0143: Unknown result type (might be due to invalid IL or missing references)
//IL_0149: Expected O, but got Unknown
switch (configKey)
{
case ConfigKey.ActivationDelaySeconds:
definition = new ConfigDefinition("Timing", "ActivationDelaySeconds");
return true;
case ConfigKey.TriggerRadius:
definition = new ConfigDefinition("Range", "TriggerRadius");
return true;
case ConfigKey.ExitRadius:
definition = new ConfigDefinition("Range", "ExitRadius");
return true;
case ConfigKey.BlastRadius:
definition = new ConfigDefinition("Range", "BlastRadius");
return true;
case ConfigKey.TriggerCooldownSeconds:
definition = new ConfigDefinition("Timing", "TriggerCooldownSeconds");
return true;
case ConfigKey.FartForce:
definition = new ConfigDefinition("Effects", "FartForce");
return true;
case ConfigKey.ScanIntervalSeconds:
definition = new ConfigDefinition("Timing", "ScanIntervalSeconds");
return true;
case ConfigKey.SafetyCheckIntervalSeconds:
definition = new ConfigDefinition("Timing", "SafetyCheckIntervalSeconds");
return true;
case ConfigKey.TriggerChance:
definition = new ConfigDefinition("Chance", "TriggerChance");
return true;
case ConfigKey.NarrowSpaceCheckRadius:
definition = new ConfigDefinition("Safety", "NarrowSpaceCheckRadius");
return true;
case ConfigKey.NarrowSpaceColliderThreshold:
definition = new ConfigDefinition("Safety", "NarrowSpaceColliderThreshold");
return true;
case ConfigKey.EnableRescueSuppression:
definition = new ConfigDefinition("Safety", "EnableRescueSuppression");
return true;
case ConfigKey.EnableNarrowSpaceSuppression:
definition = new ConfigDefinition("Safety", "EnableNarrowSpaceSuppression");
return true;
default:
definition = new ConfigDefinition(string.Empty, string.Empty);
return false;
}
}
private static IDictionary? GetOrphanedEntries(ConfigFile configFile)
{
return ((object)configFile)?.GetType().GetProperty("OrphanedEntries", InstanceBindingFlags)?.GetValue(configFile) as IDictionary;
}
private static ConfigEntryBase[] GetConfigEntriesSnapshot(ConfigFile configFile)
{
if (!(ConfigFileEntriesProperty?.GetValue(configFile) is IDictionary dictionary) || dictionary.Count == 0)
{
return Array.Empty<ConfigEntryBase>();
}
ConfigEntryBase[] array = (ConfigEntryBase[])(object)new ConfigEntryBase[dictionary.Count];
dictionary.Values.CopyTo(array, 0);
return array;
}
private static bool DefinitionsEqual(ConfigDefinition left, ConfigDefinition right)
{
if (string.Equals(left.Section, right.Section, StringComparison.Ordinal))
{
return string.Equals(left.Key, right.Key, StringComparison.Ordinal);
}
return false;
}
private void HandleLanguageChangeIfNeeded()
{
bool flag = DetectChineseLanguage();
if (flag != _lastDetectedChineseLanguage)
{
_lastDetectedChineseLanguage = flag;
ApplyLocalizedConfigMetadata(flag);
}
TryLocalizeVisibleModConfigUi();
}
private void ApplyLocalizedConfigMetadata(bool isChineseLanguage)
{
ConfigEntryBase[] configEntriesSnapshot = GetConfigEntriesSnapshot(((BaseUnityPlugin)this).Config);
foreach (ConfigEntryBase val in configEntriesSnapshot)
{
if (!(((val != null) ? val.Definition : null) == (ConfigDefinition)null) && val.Description != null && TryGetConfigKey(val.Definition.Key, out var configKey))
{
((object)val.Description).GetType().GetField("<Description>k__BackingField", InstanceBindingFlags)?.SetValue(val.Description, GetLocalizedDescription(configKey, isChineseLanguage));
}
}
}
private static bool TryGetConfigKey(string keyName, out ConfigKey configKey)
{
foreach (ConfigKey value in Enum.GetValues(typeof(ConfigKey)))
{
if (string.Equals(keyName, GetKeyName(value, isChineseLanguage: false), StringComparison.OrdinalIgnoreCase) || string.Equals(keyName, GetKeyName(value, isChineseLanguage: true), StringComparison.OrdinalIgnoreCase))
{
configKey = value;
return true;
}
}
configKey = ConfigKey.ModEnabled;
return false;
}
private void TryLocalizeVisibleModConfigUi()
{
if (!TryGetModConfigMenuInstance(out Type menuType, out object menuInstance))
{
return;
}
Behaviour val = (Behaviour)((menuInstance is Behaviour) ? menuInstance : null);
if (val == null || !val.isActiveAndEnabled || !((Component)val).gameObject.activeInHierarchy)
{
return;
}
Dictionary<string, string> dictionary = BuildModConfigUiLocalizationMap(DetectChineseLanguage());
foreach (Transform item in EnumerateModConfigUiRoots(menuInstance, menuType))
{
TMP_Text[] componentsInChildren = ((Component)item).GetComponentsInChildren<TMP_Text>(true);
foreach (TMP_Text val2 in componentsInChildren)
{
string text = val2.text?.Trim() ?? string.Empty;
if (!string.IsNullOrWhiteSpace(text) && dictionary.TryGetValue(text, out var value) && !string.Equals(val2.text, value, StringComparison.Ordinal))
{
val2.text = value;
}
}
}
}
private static bool TryGetModConfigMenuInstance(out Type? menuType, out object? menuInstance)
{
menuType = null;
menuInstance = null;
if (!Chainloader.PluginInfos.TryGetValue("com.github.PEAKModding.PEAKLib.ModConfig", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null)
{
return false;
}
Assembly assembly = ((object)value.Instance).GetType().Assembly;
menuType = assembly.GetType("PEAKLib.ModConfig.Components.ModdedSettingsMenu");
menuInstance = menuType?.GetProperty("Instance", StaticBindingFlags)?.GetValue(null);
if (menuType != null)
{
return menuInstance != null;
}
return false;
}
[IteratorStateMachine(typeof(<EnumerateModConfigUiRoots>d__96))]
private IEnumerable<Transform> EnumerateModConfigUiRoots(object menuInstance, Type menuType)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <EnumerateModConfigUiRoots>d__96(-2)
{
<>3__menuInstance = menuInstance,
<>3__menuType = menuType
};
}
[IteratorStateMachine(typeof(<EnumerateCandidateTransforms>d__97))]
private static IEnumerable<Transform> EnumerateCandidateTransforms(object menuInstance, Type menuType)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <EnumerateCandidateTransforms>d__97(-2)
{
<>3__menuInstance = menuInstance,
<>3__menuType = menuType
};
}
private Dictionary<string, string> BuildModConfigUiLocalizationMap(bool isChineseLanguage)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
AddUiLocalizationPair(dictionary, "Blast Buddy", GetLocalizedModDisplayName(isChineseLanguage));
AddUiLocalizationPair(dictionary, GetLocalizedModDisplayName(!isChineseLanguage), GetLocalizedModDisplayName(isChineseLanguage));
AddUiLocalizationPair(dictionary, GetSectionName(isChineseLanguage: false), GetSectionName(isChineseLanguage));
AddUiLocalizationPair(dictionary, GetSectionName(isChineseLanguage: true), GetSectionName(isChineseLanguage));
foreach (ConfigKey value in Enum.GetValues(typeof(ConfigKey)))
{
AddUiLocalizationPair(dictionary, GetKeyName(value, isChineseLanguage: false), GetKeyName(value, isChineseLanguage));
AddUiLocalizationPair(dictionary, GetKeyName(value, isChineseLanguage: true), GetKeyName(value, isChineseLanguage));
AddUiLocalizationPair(dictionary, GetLocalizedDescription(value, isChineseLanguage: false), GetLocalizedDescription(value, isChineseLanguage));
AddUiLocalizationPair(dictionary, GetLocalizedDescription(value, isChineseLanguage: true), GetLocalizedDescription(value, isChineseLanguage));
}
return dictionary;
}
private static void AddUiLocalizationPair(Dictionary<string, string> map, string source, string localized)
{
if (!string.IsNullOrWhiteSpace(source) && !string.IsNullOrWhiteSpace(localized))
{
string key = source.Trim();
string text2 = (map[key] = localized.Trim());
map[text2] = text2;
}
}
private static bool DetectChineseLanguage()
{
if (TryGetConfiguredGameLanguage(out var isChineseLanguage))
{
return isChineseLanguage;
}
if (TryGetLocalizedTextLanguageName(out string languageName))
{
return IsChineseLanguageName(languageName);
}
return false;
}
private static bool TryGetConfiguredGameLanguage(out bool isChineseLanguage)
{
isChineseLanguage = false;
try
{
if (!PlayerPrefs.HasKey("LanguageSetting"))
{
return false;
}
int result = PlayerPrefs.GetInt("LanguageSetting", int.MinValue);
if (result != int.MinValue)
{
isChineseLanguage = result == 9;
return true;
}
string @string = PlayerPrefs.GetString("LanguageSetting", string.Empty);
if (string.IsNullOrWhiteSpace(@string))
{
return false;
}
if (int.TryParse(@string, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
{
isChineseLanguage = result == 9;
return true;
}
isChineseLanguage = IsChineseLanguageName(@string);
return true;
}
catch
{
return false;
}
}
private static bool TryGetLocalizedTextLanguageName(out string languageName)
{
languageName = string.Empty;
try
{
languageName = ((object)(Language)(ref LocalizedText.CURRENT_LANGUAGE)).ToString();
return !string.IsNullOrWhiteSpace(languageName);
}
catch
{
return false;
}
}
private static bool IsChineseLanguageName(string languageName)
{
if (string.IsNullOrWhiteSpace(languageName))
{
return false;
}
if (languageName.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) < 0 && languageName.IndexOf("中文", StringComparison.OrdinalIgnoreCase) < 0)
{
return languageName.StartsWith("zh", StringComparison.OrdinalIgnoreCase);
}
return true;
}
private static string GetSectionName(bool isChineseLanguage)
{
if (!isChineseLanguage)
{
return "Blast Buddy";
}
return "屁友冲击波";
}
private static string GetLocalizedModDisplayName(bool isChineseLanguage)
{
if (!isChineseLanguage)
{
return "Blast Buddy";
}
return "屁友冲击波";
}
private static string GetKeyName(ConfigKey configKey, bool isChineseLanguage)
{
return configKey switch
{
ConfigKey.ModEnabled => isChineseLanguage ? "模组总开关" : "Enable Mod",
ConfigKey.ActivationDelaySeconds => isChineseLanguage ? "生效延迟(秒)" : "Activation Delay (Seconds)",
ConfigKey.TriggerRadius => isChineseLanguage ? "进入判定距离(米)" : "Enter Radius (Meters)",
ConfigKey.ExitRadius => isChineseLanguage ? "离开判定距离(米)" : "Exit Radius (Meters)",
ConfigKey.BlastRadius => isChineseLanguage ? "爆炸影响半径(米)" : "Blast Radius (Meters)",
ConfigKey.TriggerCooldownSeconds => isChineseLanguage ? "触发冷却(秒)" : "Trigger Cooldown (Seconds)",
ConfigKey.ScanIntervalSeconds => isChineseLanguage ? "距离扫描间隔(秒)" : "Proximity Scan Interval (Seconds)",
ConfigKey.SafetyCheckIntervalSeconds => isChineseLanguage ? "安全检查间隔(秒)" : "Safety Check Interval (Seconds)",
ConfigKey.FartForce => isChineseLanguage ? "放屁冲击力" : "Fart Force",
ConfigKey.TriggerChance => isChineseLanguage ? "触发概率" : "Trigger Chance",
ConfigKey.NarrowSpaceCheckRadius => isChineseLanguage ? "狭窄地形检测半径(米)" : "Narrow Space Check Radius (Meters)",
ConfigKey.NarrowSpaceColliderThreshold => isChineseLanguage ? "狭窄地形判定碰撞体数量" : "Narrow Space Collider Threshold",
ConfigKey.EnableRescueSuppression => isChineseLanguage ? "救人相关保护" : "Rescue Safety Protection",
ConfigKey.EnableNarrowSpaceSuppression => isChineseLanguage ? "狭窄地形保护" : "Narrow Space Protection",
_ => string.Empty,
};
}
private static string GetLocalizedDescription(ConfigKey configKey, bool isChineseLanguage)
{
return configKey switch
{
ConfigKey.ModEnabled => isChineseLanguage ? "总开关。关闭后模组完全停止工作,不再扫描距离,也不会触发放屁效果。适合排查问题或临时停用。" : "Master switch for the mod. When disabled, the system stops scanning and stops triggering the fart effect.",
ConfigKey.ActivationDelaySeconds => isChineseLanguage ? "进入正式游戏后等待多久才开始生效。大厅和载入阶段不会触发;默认 30 秒,避免开局立刻干扰队友。" : "How long to wait after gameplay begins before the effect can activate. The lobby and loading screens never trigger it. Default: 30 seconds.",
ConfigKey.TriggerRadius => isChineseLanguage ? "进入判定距离。队友首次靠近到该距离内,才算真正进入触发范围。默认 2.2 米,减少过远误判。" : "Entry radius. A teammate must come within this distance before the mod counts them as entering the trigger zone. Default: 2.2 meters.",
ConfigKey.ExitRadius => isChineseLanguage ? "离开判定距离。已经进入后,必须离开到这个更大的距离外才会真正重置状态,用来减少边界反复抖动。默认 3.8 米。" : "Exit radius. Once someone has entered, they must move beyond this larger distance before the state resets. Default: 3.8 meters.",
ConfigKey.BlastRadius => isChineseLanguage ? "真正会受到原版 Fart 爆炸影响的队友范围。默认 3.4 米,体感仍然明显,但不会像大范围乱炸那样混乱。" : "The real teammate impact radius of the replicated vanilla Fart blast. Default: 3.4 meters for noticeable feedback without excessive room-wide chaos.",
ConfigKey.TriggerCooldownSeconds => isChineseLanguage ? "统一触发冷却。无论主机同步还是客机本地回退,都共用这一个冷却时间。默认 1.4 秒,专门为多人房降低连环爆炸频率。" : "Unified trigger cooldown shared by both host-driven syncing and client-side fallback. Default: 1.4 seconds to reduce chain blasts in busy rooms.",
ConfigKey.ScanIntervalSeconds => isChineseLanguage ? "距离扫描频率。默认 0.18 秒,仍然足够灵敏,但比更激进的扫描更适合多人房稳定体验。" : "How often proximity is scanned. Default: 0.18 seconds for good responsiveness without over-scanning crowded rooms.",
ConfigKey.SafetyCheckIntervalSeconds => isChineseLanguage ? "安全条件检查频率。默认 0.25 秒,用于救人和狭窄地形保护。" : "How often safety conditions are checked. Default: 0.25 seconds for revive and cramped-terrain protection.",
ConfigKey.FartForce => isChineseLanguage ? "原版 Fart 炸飞力度。只作用于附近队友,不影响放屁者自己。默认 320,仍然明显,但比更高数值更不容易把多人房炸成失控状态。" : "Launch force used by the vanilla-style Fart blast. It affects nearby teammates but not the source player. Default: 320 for a strong feel without turning every room into chaos.",
ConfigKey.TriggerChance => isChineseLanguage ? "统一触发概率,范围 0 到 1。无论主机同步还是客机本地回退,都使用这一套概率。1 表示 100% 触发。" : "Unified trigger chance from 0 to 1. Both host-driven syncing and client-side fallback use this same value. A value of 1 means 100%.",
ConfigKey.NarrowSpaceCheckRadius => isChineseLanguage ? "狭窄地形检测半径。默认 1.1 米,会统计该范围内碰撞体数量来判断当前位置是否过于拥挤。" : "Detection radius for cramped terrain checks. Default: 1.1 meters.",
ConfigKey.NarrowSpaceColliderThreshold => isChineseLanguage ? "狭窄地形判定阈值。默认 16;达到该数量时会临时停用效果,减少狭窄地形误炸。" : "Cramped-terrain threshold. Default: 16 colliders before the effect pauses.",
ConfigKey.EnableRescueSuppression => isChineseLanguage ? "启用后,附近有倒地玩家或你正在救人时,模组会临时停用,避免干扰救援。" : "When enabled, the mod pauses if a downed teammate is nearby or if you are reviving someone.",
ConfigKey.EnableNarrowSpaceSuppression => isChineseLanguage ? "启用后,在狭窄地形中临时停用放屁效果,减少把队友炸下去或卡进地形的问题。" : "When enabled, the mod pauses in cramped terrain to reduce accidental launches into hazards or geometry.",
_ => string.Empty,
};
}
private static void ClampConfigValues()
{
BlastBuddyPlugin pluginInstance = GetPluginInstance();
if (pluginInstance._activationDelaySecondsConfig != null)
{
pluginInstance._activationDelaySecondsConfig.Value = Mathf.Clamp(pluginInstance._activationDelaySecondsConfig.Value, 0f, 180f);
}
if (pluginInstance._triggerRadiusConfig != null)
{
pluginInstance._triggerRadiusConfig.Value = Mathf.Clamp(pluginInstance._triggerRadiusConfig.Value, 1f, 6f);
}
if (pluginInstance._exitRadiusConfig != null)
{
pluginInstance._exitRadiusConfig.Value = Mathf.Clamp(pluginInstance._exitRadiusConfig.Value, 1f, 8f);
}
if (pluginInstance._blastRadiusConfig != null)
{
pluginInstance._blastRadiusConfig.Value = Mathf.Clamp(pluginInstance._blastRadiusConfig.Value, 1f, 8f);
}
if (pluginInstance._triggerCooldownSecondsConfig != null)
{
pluginInstance._triggerCooldownSecondsConfig.Value = Mathf.Clamp(pluginInstance._triggerCooldownSecondsConfig.Value, 0.05f, 10f);
}
if (pluginInstance._scanIntervalSecondsConfig != null)
{
pluginInstance._scanIntervalSecondsConfig.Value = Mathf.Clamp(pluginInstance._scanIntervalSecondsConfig.Value, 0.05f, 2f);
}
if (pluginInstance._safetyCheckIntervalSecondsConfig != null)
{
pluginInstance._safetyCheckIntervalSecondsConfig.Value = Mathf.Clamp(pluginInstance._safetyCheckIntervalSecondsConfig.Value, 0.05f, 2f);
}
if (pluginInstance._fartForceConfig != null)
{
pluginInstance._fartForceConfig.Value = Mathf.Clamp(pluginInstance._fartForceConfig.Value, 50f, 1200f);
}
if (pluginInstance._triggerChanceConfig != null)
{
pluginInstance._triggerChanceConfig.Value = Mathf.Clamp01(pluginInstance._triggerChanceConfig.Value);
}
if (pluginInstance._narrowSpaceCheckRadiusConfig != null)
{
pluginInstance._narrowSpaceCheckRadiusConfig.Value = Mathf.Clamp(pluginInstance._narrowSpaceCheckRadiusConfig.Value, 0.5f, 3f);
}
if (pluginInstance._narrowSpaceColliderThresholdConfig != null)
{
pluginInstance._narrowSpaceColliderThresholdConfig.Value = Mathf.Clamp(pluginInstance._narrowSpaceColliderThresholdConfig.Value, 1, 32);
}
if (pluginInstance._triggerRadiusConfig != null && pluginInstance._exitRadiusConfig != null)
{
pluginInstance._exitRadiusConfig.Value = Mathf.Max(pluginInstance._exitRadiusConfig.Value, pluginInstance._triggerRadiusConfig.Value);
}
}
private bool IsModEnabled()
{
return _modEnabledConfig?.Value ?? true;
}
private bool IsRescueSuppressionEnabled()
{
return _enableRescueSuppressionConfig?.Value ?? true;
}
private bool IsNarrowSpaceSuppressionEnabled()
{
return _enableNarrowSpaceSuppressionConfig?.Value ?? true;
}
private float GetActivationDelaySeconds()
{
return GetConfiguredNonNegativeFloat(_activationDelaySecondsConfig, 30f);
}
private float GetTriggerRadius()
{
return GetConfiguredPositiveFloat(_triggerRadiusConfig, 2.2f);
}
private float GetExitRadius()
{
return Math.Max(GetTriggerRadius(), GetConfiguredPositiveFloat(_exitRadiusConfig, 3.8f));
}
private float GetBlastRadius()
{
return GetConfiguredPositiveFloat(_blastRadiusConfig, 3.4f);
}
private float GetBlastRadiusSquared()
{
float blastRadius = GetBlastRadius();
return blastRadius * blastRadius;
}
private float GetExitRadiusSquared()
{
float exitRadius = GetExitRadius();
return exitRadius * exitRadius;
}
private float GetTriggerCooldownSecondsValue()
{
return GetConfiguredPositiveFloat(_triggerCooldownSecondsConfig, 1.4f);
}
private float GetScanIntervalSeconds()
{
return GetConfiguredPositiveFloat(_scanIntervalSecondsConfig, 0.18f);
}
private float GetSafetyCheckIntervalSeconds()
{
return GetConfiguredPositiveFloat(_safetyCheckIntervalSecondsConfig, 0.25f);
}
private float GetFartForce()
{
return GetConfiguredPositiveFloat(_fartForceConfig, 320f);
}
private float GetNarrowSpaceCheckRadius()
{
return GetConfiguredPositiveFloat(_narrowSpaceCheckRadiusConfig, 1.1f);
}
private int GetNarrowSpaceColliderThreshold()
{
return Math.Max(1, _narrowSpaceColliderThresholdConfig?.Value ?? 16);
}
private static float GetConfiguredPositiveFloat(ConfigEntry<float>? entry, float fallback)
{
float num = entry?.Value ?? fallback;
if (!(num > 0f))
{
return fallback;
}
return num;
}
private static float GetConfiguredNonNegativeFloat(ConfigEntry<float>? entry, float fallback)
{
float num = entry?.Value ?? fallback;
if (!(num >= 0f))
{
return fallback;
}
return num;
}
private static float GetConfiguredChance(ConfigEntry<float>? entry, float fallback)
{
return Mathf.Clamp01(entry?.Value ?? fallback);
}
private static BlastBuddyPlugin GetPluginInstance()
{
return _instance ?? throw new InvalidOperationException("BlastBuddyPlugin instance is not available yet.");
}
}
}