using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance;
using DobieWan.config;
using GameNetcodeStuff;
using HarmonyLib;
using LethalNetworkAPI;
using Microsoft.CodeAnalysis;
using UnityEngine;
[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: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("LCGhostMod")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.0.0.0")]
[assembly: AssemblyInformationalVersion("0.0.0-alpha.0.20+89218321cbbbc3c0e399b0d99bdc4d85c88162dd")]
[assembly: AssemblyProduct("LCGhostMod")]
[assembly: AssemblyTitle("LCGhostMod")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[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.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace DobieWan
{
[HarmonyPatch(typeof(RoundManager))]
internal class RoundManagerPatch
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void RoundManagerAwake()
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Expected O, but got Unknown
GameObject val = GameObject.Find("Systems");
GameObject val2 = new GameObject("GhostManager", new Type[1] { typeof(GhostManager) });
val2.transform.SetParent(val.transform);
}
}
[BepInPlugin("LCGhostMod", "LCGhostMod", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
private readonly Harmony m_harmony = new Harmony("LCGhostMod");
internal static Plugin Instance { get; private set; }
internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger;
internal AssetBundle AssetBundle { get; private set; }
internal EventConfigs EventConfigs { get; } = new EventConfigs();
internal SfxPlayerConfigs SfxPlayerConfigs { get; } = new SfxPlayerConfigs();
public Plugin()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
Instance = this;
}
private void Awake()
{
Log.LogInfo((object)"LCGhostMod is awake!");
InitConfigs();
TryLoadAssetBundle();
ApplyPluginPatch();
}
private void ApplyPluginPatch()
{
m_harmony.PatchAll(typeof(RoundManagerPatch));
}
private void TryLoadAssetBundle()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (directoryName == null)
{
Log.LogError((object)"Failed to get bundle directory.");
return;
}
AssetBundle = AssetBundle.LoadFromFile(Path.Combine(directoryName, "lcghostmodassets"));
if ((Object)(object)AssetBundle == (Object)null)
{
Log.LogError((object)"Failed to load custom assets.");
}
else
{
Log.LogInfo((object)"LCGhostMod bundle loaded!");
}
}
private void InitConfigs()
{
((IConfigs)EventConfigs).Initialize(((BaseUnityPlugin)this).Config);
((IConfigs)SfxPlayerConfigs).Initialize(((BaseUnityPlugin)this).Config);
}
}
internal class GhostManager : MonoBehaviour
{
private LethalClientMessage<HauntVictimEventData> m_hauntVictimEvent = null;
private LethalClientMessage<VictimHauntedEventData> m_victimHauntedEvent = null;
private PlayerGhostEventDetector m_ghostEventDetector = null;
private PlayerGhostSfxPlayer m_ghostSfxPlayer = null;
private void Start()
{
m_ghostEventDetector = new PlayerGhostEventDetector(TriggerHauntVictimEvent);
m_ghostSfxPlayer = new PlayerGhostSfxPlayer();
m_hauntVictimEvent = new LethalClientMessage<HauntVictimEventData>("HauntVictim", (Action<HauntVictimEventData>)null, (Action<HauntVictimEventData, ulong>)ReceiveHauntVictimEvent);
m_victimHauntedEvent = new LethalClientMessage<VictimHauntedEventData>("VictimHaunted", (Action<VictimHauntedEventData>)null, (Action<VictimHauntedEventData, ulong>)ReceiveVictimHauntedEvent);
Plugin.Log.LogInfo((object)"GhostManager initialized!");
}
private void LateUpdate()
{
m_ghostEventDetector.Simulate();
}
private void TriggerHauntVictimEvent(PlayerControllerB forPlayer)
{
ulong actualClientId = forPlayer.actualClientId;
Plugin.Log.LogInfo((object)$"Triggered haunt victim event for {actualClientId}");
HauntVictimEventData hauntVictimEventData = new HauntVictimEventData(actualClientId);
m_hauntVictimEvent.SendAllClients(hauntVictimEventData, true, false);
}
private void ReceiveHauntVictimEvent(HauntVictimEventData data, ulong fromUser)
{
PlayerControllerB localPlayerController = StartOfRound.Instance.localPlayerController;
Plugin.Log.LogInfo((object)$"Haunt victim received for user {data.SpectatedUserId}. This user is {localPlayerController.actualClientId}");
if (localPlayerController.actualClientId == data.SpectatedUserId)
{
string text = m_ghostSfxPlayer.PlaySfx();
Plugin.Log.LogInfo((object)("Haunt victim: Playing clip " + text));
TriggerVictimHauntedEvent(text);
}
}
private void TriggerVictimHauntedEvent(string audioClipName)
{
Plugin.Log.LogInfo((object)("Triggered victim haunted event with clip " + audioClipName));
VictimHauntedEventData victimHauntedEventData = new VictimHauntedEventData(audioClipName);
m_victimHauntedEvent.SendAllClients(victimHauntedEventData, true, false);
}
private void ReceiveVictimHauntedEvent(VictimHauntedEventData data, ulong fromUser)
{
PlayerControllerB localPlayerController = StartOfRound.Instance.localPlayerController;
Plugin.Log.LogInfo((object)$"Victim haunted received from user {fromUser}. The spectated user is {localPlayerController.actualClientId}");
if (!((Object)(object)localPlayerController.spectatedPlayerScript == (Object)null))
{
ulong actualClientId = localPlayerController.spectatedPlayerScript.actualClientId;
if (actualClientId == fromUser)
{
Plugin.Log.LogInfo((object)("Victim haunted: Playing clip: " + data.ClipName));
m_ghostSfxPlayer.PlaySpectatorSfx(data.ClipName);
}
}
}
}
internal class PlayerGhostEventDetector
{
private readonly EventConfigs m_configs = null;
private readonly Action<PlayerControllerB> m_hauntVictimEventAction = null;
private float m_lastGhostNoiseTime = 0f;
private float m_cooldownTime = 0f;
internal PlayerGhostEventDetector(Action<PlayerControllerB> hauntVictimEventAction)
{
m_configs = Plugin.Instance.EventConfigs;
m_hauntVictimEventAction = hauntVictimEventAction;
m_cooldownTime = GetRandomCooldownTime();
}
internal void Simulate()
{
StartOfRound instance = StartOfRound.Instance;
PlayerControllerB localPlayerController = instance.localPlayerController;
DissonanceComms voiceChatModule = instance.voiceChatModule;
if (!localPlayerController.isPlayerDead || m_lastGhostNoiseTime + m_cooldownTime > Time.time || (Object)(object)voiceChatModule == (Object)null || (Object)(object)localPlayerController.spectatedPlayerScript == (Object)null)
{
return;
}
string localPlayerName = voiceChatModule.LocalPlayerName;
if (!string.IsNullOrEmpty(localPlayerName))
{
VoicePlayerState val = voiceChatModule.FindPlayer(voiceChatModule.LocalPlayerName);
if (val != null && val.IsSpeaking && !(val.Amplitude < m_configs.MinMicAmpToTriggerGhost.Value))
{
TriggerGhostEvent(localPlayerController.spectatedPlayerScript);
}
}
}
private void TriggerGhostEvent(PlayerControllerB toPlayer)
{
m_lastGhostNoiseTime = Time.time;
m_cooldownTime = GetRandomCooldownTime();
m_hauntVictimEventAction?.Invoke(toPlayer);
}
private float GetRandomCooldownTime()
{
float value = m_configs.EventTriggerCooldownMin.Value;
float value2 = m_configs.EventTriggerCooldownMax.Value;
return value + Random.value * (value2 - value);
}
}
internal class PlayerGhostSfxPlayer
{
private readonly SfxPlayerConfigs m_configs = null;
private readonly AudioSource m_audioSource = null;
private readonly AudioClip[] m_ghostSfx = null;
private int m_ghostSfxIndex = 0;
internal PlayerGhostSfxPlayer()
{
m_configs = Plugin.Instance.SfxPlayerConfigs;
AssetBundle assetBundle = Plugin.Instance.AssetBundle;
if (!((Object)(object)assetBundle == (Object)null))
{
m_ghostSfx = assetBundle.LoadAllAssets<AudioClip>();
m_ghostSfx.Shuffle();
ManualLogSource log = Plugin.Log;
AudioClip[] ghostSfx = m_ghostSfx;
log.LogInfo((object)$"Loaded {((ghostSfx != null) ? ghostSfx.Length : 0)} audio clips");
GameObject val = GameObject.Find("Systems/Audios/SFX");
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogError((object)"Failed to find SFX object");
}
else if (!val.TryGetComponent<AudioSource>(ref m_audioSource))
{
Plugin.Log.LogError((object)"Failed to find SFX audio source");
}
else
{
Plugin.Log.LogInfo((object)"PlayerGhostSfxPlayer successfully initialized!");
}
}
}
internal string PlaySfx()
{
if (m_ghostSfx == null || m_ghostSfx.Length == 0 || (Object)(object)m_audioSource == (Object)null)
{
return null;
}
Plugin.Log.LogInfo((object)"ooOOOOOOOoooooOOOoo");
AudioClip nextAudioClip = GetNextAudioClip();
Utility.PlayAudioClipLocalOnly(m_audioSource, nextAudioClip, m_configs.RandomizePitchRange.Value, m_configs.RandomizeVolumeRange.Value);
return ((Object)nextAudioClip).name;
}
internal AudioClip GetNextAudioClip()
{
if (m_ghostSfxIndex >= m_ghostSfx.Length)
{
ReshuffleAudioClips();
m_ghostSfxIndex = 0;
}
return m_ghostSfx[m_ghostSfxIndex++];
}
internal void ReshuffleAudioClips()
{
AudioClip val = m_ghostSfx[^1];
m_ghostSfx.Shuffle();
AudioClip val2 = m_ghostSfx[0];
if (m_ghostSfx.Length > 1 && (Object)(object)val == (Object)(object)val2)
{
m_ghostSfx[0] = m_ghostSfx[^1];
m_ghostSfx[^1] = val2;
}
}
internal void PlaySpectatorSfx(string clipName)
{
if (!TryGetAudioClipByName(clipName, out var audioClip))
{
audioClip = m_ghostSfx[Random.Range(0, m_ghostSfx.Length)];
}
Utility.PlayAudioClipLocalOnly(m_audioSource, audioClip, m_configs.RandomizePitchRange.Value, m_configs.RandomizeVolumeRange.Value);
}
private bool TryGetAudioClipByName(string clipName, out AudioClip audioClip)
{
for (int i = 0; i < m_ghostSfx.Length; i++)
{
AudioClip val = m_ghostSfx[i];
if ((Object)(object)val != (Object)null && ((Object)val).name == clipName)
{
audioClip = val;
return true;
}
}
audioClip = null;
return false;
}
}
[Serializable]
internal struct HauntVictimEventData
{
[SerializeField]
private ulong m_spectatedUserId;
internal ulong SpectatedUserId
{
readonly get
{
return m_spectatedUserId;
}
private set
{
m_spectatedUserId = value;
}
}
internal HauntVictimEventData(ulong mSpectatedUserId)
{
m_spectatedUserId = 0uL;
SpectatedUserId = mSpectatedUserId;
}
}
[Serializable]
internal struct VictimHauntedEventData
{
[SerializeField]
private string m_clipName;
internal string ClipName
{
readonly get
{
return m_clipName;
}
private set
{
m_clipName = value;
}
}
internal VictimHauntedEventData(string clipName)
{
m_clipName = null;
ClipName = clipName;
}
}
internal static class Utility
{
internal static void Shuffle<T>(this IList<T> list)
{
for (int i = 0; i < list.Count; i++)
{
T value = list[i];
int index = Random.Range(i, list.Count);
list[i] = list[index];
list[index] = value;
}
}
internal static bool Contains<T>(this IList<T> list, T value) where T : IEquatable<T>
{
for (int i = 0; i < list.Count; i++)
{
if (list[i].Equals(value))
{
return true;
}
}
return false;
}
internal static void PlayAudioClipLocalOnly(AudioSource audioSource, AudioClip clip, float randomizePitchRange = 0f, float randomizeVolumeRange = 0f, float volume = 1f)
{
if (randomizePitchRange > 0f)
{
audioSource.pitch = Random.Range(1f - randomizePitchRange, 1f + randomizePitchRange);
}
float num = 1f;
if (randomizeVolumeRange > 0f)
{
num = Random.Range(volume - randomizeVolumeRange, volume + randomizeVolumeRange);
}
audioSource.PlayOneShot(clip, num);
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "LCGhostMod";
public const string PLUGIN_NAME = "LCGhostMod";
public const string PLUGIN_VERSION = "1.0.0";
}
}
namespace DobieWan.config
{
internal class EventConfigs : IConfigs
{
private const string GROUP_NAME = "GHOST EVENT";
internal ConfigEntry<float> MinMicAmpToTriggerGhost { get; private set; }
internal ConfigEntry<float> EventTriggerCooldownMin { get; private set; }
internal ConfigEntry<float> EventTriggerCooldownMax { get; private set; }
void IConfigs.Initialize(ConfigFile config)
{
MinMicAmpToTriggerGhost = config.Bind<float>("GHOST EVENT", "Microphone Amp Threshold", 0.4f, "This is the threshold amplitude for your microphone input to trigger a ghost event. A higher value means you must speak louder to trigger the event. Recommended between 0.1 and 2.0.");
EventTriggerCooldownMin = config.Bind<float>("GHOST EVENT", "Min Cooldown Time", 2f, "Minimum length of time between ghost events triggered by you.");
EventTriggerCooldownMax = config.Bind<float>("GHOST EVENT", "Max Cooldown Time", 5f, "Maximum length of time between ghost events triggered by you.");
}
}
public interface IConfigs
{
internal void Initialize(ConfigFile config);
}
internal class SfxPlayerConfigs : IConfigs
{
private const string GROUP_NAME = "SFX";
internal ConfigEntry<float> RandomizePitchRange { get; private set; }
internal ConfigEntry<float> RandomizeVolumeRange { get; private set; }
void IConfigs.Initialize(ConfigFile config)
{
RandomizePitchRange = config.Bind<float>("SFX", "Randomize Pitch Range", 0.2f, "Bigger number means ghost SFX pitch can be randomized further from normal.");
RandomizeVolumeRange = config.Bind<float>("SFX", "Randomize Volume Range", 0.2f, "Bigger number means ghost SFX volume can be randomized further from normal.");
}
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}