Decompiled source of One in 10000 v1.0.0

One_in_10000.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("1 in 10000")]
[assembly: AssemblyProduct("1 in 10000")]
[assembly: AssemblyCompany("ModdedWolf")]
[assembly: AssemblyDescription("1 in 10000 chance of a Foxy jumpscare in Valheim.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 OneIn10000
{
	internal static class ChromaKey
	{
		internal static Color32[] Apply(RenderTexture source, Texture2D destination, Color32[] buffer)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null || (Object)(object)destination == (Object)null)
			{
				return buffer;
			}
			int num = ((Texture)destination).width * ((Texture)destination).height;
			RenderTexture active = RenderTexture.active;
			try
			{
				RenderTexture.active = source;
				destination.ReadPixels(new Rect(0f, 0f, (float)((Texture)destination).width, (float)((Texture)destination).height), 0, 0, false);
			}
			finally
			{
				RenderTexture.active = active;
			}
			buffer = destination.GetPixels32();
			if (buffer == null || buffer.Length != num)
			{
				return buffer;
			}
			int num2 = 32;
			for (int i = 0; i < num; i++)
			{
				Color32 val = buffer[i];
				int num3 = ((val.r > val.b) ? val.r : val.b);
				int num4 = val.g - num3;
				if (num4 >= 72)
				{
					buffer[i] = new Color32((byte)0, (byte)0, (byte)0, (byte)0);
				}
				else if (num4 > num2)
				{
					int num5 = 255 - (num4 - num2) * 255 / 40;
					if (num5 < 0)
					{
						num5 = 0;
					}
					if (val.g > num3)
					{
						val.g = (byte)num3;
					}
					val.a = (byte)num5;
					buffer[i] = val;
				}
				else
				{
					if (val.g > num3 + 10)
					{
						val.g = (byte)(num3 + 10);
					}
					val.a = byte.MaxValue;
					buffer[i] = val;
				}
			}
			destination.SetPixels32(buffer);
			destination.Apply(false, false);
			return buffer;
		}
	}
	internal static class EmbeddedVideo
	{
		internal const string ResourceName = "foxy.mp4";

		internal static bool TryExtract(out string path, out string error)
		{
			Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("foxy.mp4");
			if (manifestResourceStream == null)
			{
				path = null;
				error = "1 in 10000: embedded foxy.mp4 is missing from the DLL.";
				return false;
			}
			try
			{
				string text = Path.Combine(Path.GetTempPath(), "OneIn10000");
				Directory.CreateDirectory(text);
				path = Path.Combine(text, "foxy.mp4");
				if (!File.Exists(path) || new FileInfo(path).Length != manifestResourceStream.Length)
				{
					using FileStream destination = File.Create(path);
					manifestResourceStream.CopyTo(destination);
				}
				error = null;
				return true;
			}
			catch (IOException ex)
			{
				path = null;
				error = "1 in 10000: failed to unpack the embedded video. " + ex.Message;
				return false;
			}
			finally
			{
				manifestResourceStream.Dispose();
			}
		}
	}
	internal static class FoxyNet
	{
		internal const string PlayRpc = "ModdedWolf_OneIn10000_Play";

		internal const string SettingsRpc = "ModdedWolf_OneIn10000_Settings";

		internal const string HelloRpc = "ModdedWolf_OneIn10000_Hello";

		private static ZRoutedRpc _registeredOn;

		internal static bool IsServer()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}

		internal static bool IsDedicated()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsDedicated();
			}
			return false;
		}

		internal static bool IsWorldHost()
		{
			return IsServer();
		}

		internal static bool IsInPlayableWorld()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			if (IsDedicated())
			{
				return true;
			}
			return (Object)(object)Player.m_localPlayer != (Object)null;
		}

		internal static void TryRegister(Action<long> onPlay, Action<long, float> onSettings, Action<long, string> onHello)
		{
			if (ZRoutedRpc.instance != null && _registeredOn != ZRoutedRpc.instance)
			{
				ZRoutedRpc.instance.Register("ModdedWolf_OneIn10000_Play", onPlay);
				ZRoutedRpc.instance.Register<float>("ModdedWolf_OneIn10000_Settings", onSettings);
				ZRoutedRpc.instance.Register<string>("ModdedWolf_OneIn10000_Hello", onHello);
				_registeredOn = ZRoutedRpc.instance;
				OneIn10000Plugin.Log.LogInfo((object)"Registered jumpscare RPCs.");
			}
		}

		internal static bool IsRegistered()
		{
			if (_registeredOn != null)
			{
				return _registeredOn == ZRoutedRpc.instance;
			}
			return false;
		}

		internal static void Unregister()
		{
			_registeredOn = null;
		}

		internal static void SendPlay(long peerId)
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "ModdedWolf_OneIn10000_Play", Array.Empty<object>());
			}
		}

		internal static void SendSettings(long peerId, float chancePercent)
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "ModdedWolf_OneIn10000_Settings", new object[1] { chancePercent });
			}
		}

		internal static void SendHello(string version)
		{
			if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null))
			{
				ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
				if (serverPeer != null && serverPeer.m_uid != 0L)
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "ModdedWolf_OneIn10000_Hello", new object[1] { version });
				}
			}
		}

		internal static void KickPeer(ZNetPeer peer, string reason)
		{
			if (peer == null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			OneIn10000Plugin.Log.LogWarning((object)$"Kicking peer {peer.m_uid} ({peer.m_playerName}): {reason}");
			try
			{
				string playerName = peer.m_playerName;
				if (!string.IsNullOrEmpty(playerName))
				{
					ZNet.instance.Kick(playerName);
					return;
				}
				MethodInfo method = typeof(ZNet).GetMethod("InternalKick", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(ZNetPeer) }, null);
				if (method != null)
				{
					method.Invoke(ZNet.instance, new object[1] { peer });
					return;
				}
				typeof(ZNet).GetMethod("SendDisconnect", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(ZNetPeer) }, null)?.Invoke(ZNet.instance, new object[1] { peer });
				ZNet.instance.Disconnect(peer);
			}
			catch (Exception ex)
			{
				OneIn10000Plugin.Log.LogError((object)$"Failed to kick peer {peer.m_uid}: {ex.Message}");
			}
		}

		internal static ZNetPeer FindPeer(long peerId)
		{
			List<ZNetPeer> remotePeers = GetRemotePeers();
			for (int i = 0; i < remotePeers.Count; i++)
			{
				ZNetPeer val = remotePeers[i];
				if (val != null && val.m_uid == peerId)
				{
					return val;
				}
			}
			return null;
		}

		internal static List<ZNetPeer> GetRemotePeers()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return new List<ZNetPeer>();
			}
			List<ZNetPeer> connectedPeers = ZNet.instance.GetConnectedPeers();
			if (connectedPeers != null && connectedPeers.Count > 0)
			{
				return connectedPeers;
			}
			return ZNet.instance.GetPeers() ?? new List<ZNetPeer>();
		}

		internal static bool IsTrustedSender(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			if (IsServer())
			{
				return true;
			}
			if (sender == 0L)
			{
				return true;
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer == null)
			{
				return true;
			}
			return sender == serverPeer.m_uid;
		}
	}
	internal sealed class JumpscareController
	{
		private const float PrepareTimeoutSeconds = 8f;

		private const float StallTimeoutSeconds = 8f;

		private GameObject _root;

		private RawImage _image;

		private AspectRatioFitter _aspectFitter;

		private VideoPlayer _player;

		private AudioSource _audio;

		private RenderTexture _videoRt;

		private Texture2D _keyed;

		private Color32[] _chromaBuffer;

		private bool _started;

		private bool _framePending;

		private bool _finished;

		private bool _endFramePresented;

		private bool _loggedPlayError;

		private float _lastFrameTime;

		private int _playId;

		internal bool IsPlaying { get; private set; }

		internal void EnsureOverlay()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Expected O, but got Unknown
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Expected O, but got Unknown
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Expected O, but got Unknown
			if (!((Object)(object)_root != (Object)null))
			{
				_root = new GameObject("OneIn10000Overlay", new Type[1] { typeof(RectTransform) });
				Object.DontDestroyOnLoad((Object)(object)_root);
				Canvas obj = _root.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 32767;
				obj.pixelPerfect = false;
				CanvasScaler obj2 = _root.AddComponent<CanvasScaler>();
				obj2.uiScaleMode = (ScaleMode)1;
				obj2.referenceResolution = new Vector2(1920f, 1080f);
				obj2.screenMatchMode = (ScreenMatchMode)0;
				obj2.matchWidthOrHeight = 0.5f;
				GameObject val = new GameObject("Foxy", new Type[1] { typeof(RectTransform) });
				val.transform.SetParent(_root.transform, false);
				_image = val.AddComponent<RawImage>();
				((Graphic)_image).raycastTarget = false;
				((Graphic)_image).color = Color.white;
				_image.uvRect = new Rect(0f, 0f, 1f, 1f);
				((Behaviour)_image).enabled = false;
				RectTransform rectTransform = ((Graphic)_image).rectTransform;
				rectTransform.anchorMin = Vector2.zero;
				rectTransform.anchorMax = Vector2.one;
				rectTransform.pivot = new Vector2(0.5f, 0.5f);
				rectTransform.offsetMin = Vector2.zero;
				rectTransform.offsetMax = Vector2.zero;
				_aspectFitter = val.AddComponent<AspectRatioFitter>();
				_aspectFitter.aspectMode = (AspectMode)3;
				_audio = _root.AddComponent<AudioSource>();
				_audio.playOnAwake = false;
				_audio.loop = false;
				_audio.spatialBlend = 0f;
				_audio.ignoreListenerPause = true;
				_audio.bypassListenerEffects = true;
				_player = _root.AddComponent<VideoPlayer>();
				_player.playOnAwake = false;
				_player.isLooping = false;
				_player.waitForFirstFrame = true;
				_player.skipOnDrop = false;
				_player.playbackSpeed = 1f;
				_player.timeUpdateMode = (VideoTimeUpdateMode)2;
				_player.sendFrameReadyEvents = true;
				_player.source = (VideoSource)1;
				_player.renderMode = (VideoRenderMode)2;
				_player.aspectRatio = (VideoAspectRatio)3;
				_player.audioOutputMode = (VideoAudioOutputMode)1;
				_player.controlledAudioTrackCount = 1;
				_player.frameReady += new FrameReadyEventHandler(OnFrameReady);
				_player.loopPointReached += new EventHandler(OnFinished);
				_player.errorReceived += new ErrorEventHandler(OnError);
			}
		}

		internal IEnumerator Play(string path)
		{
			EnsureOverlay();
			Stop();
			int playId = ++_playId;
			IsPlaying = true;
			_loggedPlayError = false;
			_audio.volume = 1f;
			_player.url = path;
			_player.Prepare();
			float timeout = Time.realtimeSinceStartup + 8f;
			while (IsPlaying && playId == _playId && !_player.isPrepared && Time.realtimeSinceStartup < timeout)
			{
				yield return null;
			}
			if (!IsPlaying || playId != _playId)
			{
				yield break;
			}
			if (!_player.isPrepared)
			{
				OneIn10000Plugin.Log.LogError((object)("Video failed to prepare: " + path));
				Stop();
				yield break;
			}
			EnsureTargets();
			if (_player.audioTrackCount > 0)
			{
				_player.EnableAudioTrack((ushort)0, true);
				_player.SetTargetAudioSource((ushort)0, _audio);
			}
			_started = true;
			_lastFrameTime = Time.realtimeSinceStartup;
			_player.Play();
		}

		internal void TickPlayback()
		{
			if (IsPlaying && _started)
			{
				if (_finished && _endFramePresented)
				{
					Stop();
				}
				else if (!_finished && Time.realtimeSinceStartup - _lastFrameTime >= 8f)
				{
					OneIn10000Plugin.Log.LogError((object)"1 in 10000: video playback stalled.");
					Stop();
				}
			}
		}

		internal void TickChroma()
		{
			if (IsPlaying && _framePending && !((Object)(object)_videoRt == (Object)null) && !((Object)(object)_keyed == (Object)null))
			{
				_framePending = false;
				_chromaBuffer = ChromaKey.Apply(_videoRt, _keyed, _chromaBuffer);
				_image.texture = (Texture)(object)_keyed;
				((Behaviour)_image).enabled = true;
				if (_finished)
				{
					_endFramePresented = true;
				}
			}
		}

		internal void Dispose()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			Stop();
			if ((Object)(object)_player != (Object)null)
			{
				_player.frameReady -= new FrameReadyEventHandler(OnFrameReady);
				_player.loopPointReached -= new EventHandler(OnFinished);
				_player.errorReceived -= new ErrorEventHandler(OnError);
			}
			ReleaseTargets();
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
				_root = null;
			}
		}

		private void EnsureTargets()
		{
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Expected O, but got Unknown
			int num = (int)((_player.width != 0) ? _player.width : 640);
			int num2 = (int)((_player.height != 0) ? _player.height : 360);
			float num3 = Mathf.Min(1f, 640f / (float)Mathf.Max(num, num2));
			int num4 = Mathf.Max(1, Mathf.RoundToInt((float)num * num3));
			int num5 = Mathf.Max(1, Mathf.RoundToInt((float)num2 * num3));
			float num6 = ((_player.pixelAspectRatioNumerator != 0 && _player.pixelAspectRatioDenominator != 0) ? ((float)_player.pixelAspectRatioNumerator / (float)_player.pixelAspectRatioDenominator) : 1f);
			_aspectFitter.aspectRatio = (float)num * num6 / (float)num2;
			if ((Object)(object)_videoRt != (Object)null && (((Texture)_videoRt).width != num4 || ((Texture)_videoRt).height != num5))
			{
				ReleaseTargets();
			}
			if ((Object)(object)_videoRt == (Object)null)
			{
				_videoRt = new RenderTexture(num4, num5, 0, (RenderTextureFormat)0)
				{
					name = "OneIn10000RT",
					wrapMode = (TextureWrapMode)1,
					filterMode = (FilterMode)1,
					antiAliasing = 1
				};
				_videoRt.Create();
				_keyed = new Texture2D(num4, num5, (TextureFormat)4, false)
				{
					name = "OneIn10000Keyed",
					wrapMode = (TextureWrapMode)1,
					filterMode = (FilterMode)1
				};
			}
			_player.targetTexture = _videoRt;
			_image.texture = (Texture)(object)_keyed;
		}

		private void ReleaseTargets()
		{
			if ((Object)(object)_player != (Object)null)
			{
				_player.targetTexture = null;
			}
			if ((Object)(object)_videoRt != (Object)null)
			{
				_videoRt.Release();
				Object.Destroy((Object)(object)_videoRt);
				_videoRt = null;
			}
			if ((Object)(object)_keyed != (Object)null)
			{
				Object.Destroy((Object)(object)_keyed);
				_keyed = null;
			}
			_chromaBuffer = null;
		}

		private void OnFrameReady(VideoPlayer _, long frame)
		{
			if (IsPlaying && _started)
			{
				_lastFrameTime = Time.realtimeSinceStartup;
				_framePending = true;
			}
		}

		private void OnFinished(VideoPlayer _)
		{
			if (IsPlaying && _started)
			{
				_finished = true;
				_framePending = true;
			}
		}

		private void OnError(VideoPlayer _, string message)
		{
			if (!_loggedPlayError)
			{
				_loggedPlayError = true;
				OneIn10000Plugin.Log.LogError((object)("1 in 10000 video error: " + message));
			}
			Stop();
		}

		private void Stop()
		{
			_playId++;
			IsPlaying = false;
			_started = false;
			_framePending = false;
			_finished = false;
			_endFramePresented = false;
			if ((Object)(object)_image != (Object)null)
			{
				((Behaviour)_image).enabled = false;
			}
			if ((Object)(object)_player != (Object)null)
			{
				_player.Stop();
			}
			if ((Object)(object)_audio != (Object)null)
			{
				_audio.Stop();
			}
		}
	}
	[BepInPlugin("ModdedWolf.OneIn10000", "1 in 10000", "1.0.0")]
	public class OneIn10000Plugin : BaseUnityPlugin
	{
		public const string GUID = "ModdedWolf.OneIn10000";

		public const string NAME = "1 in 10000";

		public const string VERSION = "1.0.0";

		internal const int ProcessWidth = 640;

		private const float RollIntervalSeconds = 1f;

		private const float PerPlayerCooldown = 2f;

		private const float SettingsSyncInterval = 3f;

		private const float HelloResendInterval = 3f;

		private const float HelloGraceSeconds = 20f;

		internal static OneIn10000Plugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<float> ChancePercent;

		private JumpscareController _jumpscare;

		private float _nextRollTime;

		private float _nextSettingsSync;

		private float _nextHelloSend;

		private bool _loggedMissingVideo;

		private float _appliedChance = 0.01f;

		private readonly Dictionary<long, float> _cooldownUntil = new Dictionary<long, float>();

		private readonly HashSet<long> _verifiedPeers = new HashSet<long>();

		private readonly Dictionary<long, float> _peerFirstSeen = new Dictionary<long, float>();

		internal const float PlaybackVolume = 1f;

		private void Awake()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ChancePercent = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ChancePercent", 0.01f, new ConfigDescription("Host/server only. Percent chance each second, rolled separately for every player. Client values are ignored.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 100f), Array.Empty<object>()));
			_appliedChance = ChancePercent.Value;
			_jumpscare = new JumpscareController();
			_nextRollTime = Time.unscaledTime + 5f;
			_nextSettingsSync = Time.unscaledTime + 2f;
			_nextHelloSend = 0f;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"1 in 10000 v1.0.0 loaded. Host/server owns ChancePercent. Volume is fixed. Matching client version is required.");
		}

		private void Update()
		{
			FoxyNet.TryRegister(RPC_Play, RPC_Settings, RPC_Hello);
			if (!FoxyNet.IsDedicated())
			{
				_jumpscare.TickPlayback();
			}
			if (!FoxyNet.IsWorldHost())
			{
				TrySendHello();
			}
			else if (FoxyNet.IsInPlayableWorld())
			{
				EnforceClientVersions();
				if (Time.unscaledTime >= _nextSettingsSync)
				{
					_nextSettingsSync = Time.unscaledTime + 3f;
					SyncSettingsToPeers();
				}
				if (!(Time.unscaledTime < _nextRollTime))
				{
					_nextRollTime = Time.unscaledTime + 1f;
					RollEveryPlayer();
				}
			}
		}

		private void LateUpdate()
		{
			if (!FoxyNet.IsDedicated() && _jumpscare.IsPlaying)
			{
				_jumpscare.TickChroma();
			}
		}

		private void OnDestroy()
		{
			FoxyNet.Unregister();
			_jumpscare?.Dispose();
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void TrySendHello()
		{
			if (!FoxyNet.IsRegistered() || (Object)(object)ZNet.instance == (Object)null || FoxyNet.IsWorldHost())
			{
				return;
			}
			if (FoxyNet.IsInPlayableWorld())
			{
				if (!float.IsPositiveInfinity(_nextHelloSend))
				{
					FoxyNet.SendHello("1.0.0");
					_nextHelloSend = float.PositiveInfinity;
				}
				return;
			}
			if (float.IsPositiveInfinity(_nextHelloSend))
			{
				_nextHelloSend = 0f;
			}
			if (!(Time.unscaledTime < _nextHelloSend))
			{
				_nextHelloSend = Time.unscaledTime + 3f;
				FoxyNet.SendHello("1.0.0");
			}
		}

		private void EnforceClientVersions()
		{
			List<ZNetPeer> remotePeers = FoxyNet.GetRemotePeers();
			HashSet<long> hashSet = new HashSet<long>();
			for (int i = 0; i < remotePeers.Count; i++)
			{
				ZNetPeer val = remotePeers[i];
				if (val == null || !IsPeerConnected(val))
				{
					continue;
				}
				hashSet.Add(val.m_uid);
				if (!IsPeerReadyForHandshake(val))
				{
					continue;
				}
				if (!_peerFirstSeen.ContainsKey(val.m_uid))
				{
					_peerFirstSeen[val.m_uid] = Time.unscaledTime;
				}
				if (!_verifiedPeers.Contains(val.m_uid))
				{
					float num = _peerFirstSeen[val.m_uid];
					if (!(Time.unscaledTime - num < 20f))
					{
						FoxyNet.KickPeer(val, string.Format("missing {0} v{1} handshake (no Hello within {2:0}s)", "1 in 10000", "1.0.0", 20f));
						_peerFirstSeen.Remove(val.m_uid);
					}
				}
			}
			PrunePeerTracking(hashSet);
		}

		private static bool IsPeerReadyForHandshake(ZNetPeer peer)
		{
			if (!string.IsNullOrEmpty(peer.m_playerName))
			{
				return true;
			}
			try
			{
				return peer.IsReady();
			}
			catch
			{
				return false;
			}
		}

		private void PrunePeerTracking(HashSet<long> present)
		{
			List<long> list = null;
			foreach (long key in _peerFirstSeen.Keys)
			{
				if (!present.Contains(key))
				{
					if (list == null)
					{
						list = new List<long>();
					}
					list.Add(key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					long num = list[i];
					_peerFirstSeen.Remove(num);
					_verifiedPeers.Remove(num);
					_cooldownUntil.Remove(num);
				}
			}
		}

		private void SyncSettingsToPeers()
		{
			List<ZNetPeer> remotePeers = FoxyNet.GetRemotePeers();
			for (int i = 0; i < remotePeers.Count; i++)
			{
				ZNetPeer val = remotePeers[i];
				if (val != null && IsPeerConnected(val) && _verifiedPeers.Contains(val.m_uid))
				{
					FoxyNet.SendSettings(val.m_uid, ChancePercent.Value);
				}
			}
		}

		private void RollEveryPlayer()
		{
			if (!FoxyNet.IsDedicated() && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				long uID = ZNet.GetUID();
				if (RolledHit(uID))
				{
					Log.LogInfo((object)$"Jumpscare hit for host at {ChancePercent.Value}%.");
					PlayLocal();
				}
			}
			List<ZNetPeer> remotePeers = FoxyNet.GetRemotePeers();
			for (int i = 0; i < remotePeers.Count; i++)
			{
				ZNetPeer val = remotePeers[i];
				if (val != null && IsPeerConnected(val) && _verifiedPeers.Contains(val.m_uid) && RolledHit(val.m_uid))
				{
					Log.LogInfo((object)$"Jumpscare hit for peer {val.m_uid} at {ChancePercent.Value}%.");
					FoxyNet.SendPlay(val.m_uid);
				}
			}
		}

		private static bool IsPeerConnected(ZNetPeer peer)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (peer.m_uid == 0L)
			{
				return false;
			}
			try
			{
				if (!peer.IsReady())
				{
					return peer.m_characterID != ZDOID.None;
				}
			}
			catch
			{
			}
			return true;
		}

		private bool RolledHit(long playerId)
		{
			if (_cooldownUntil.TryGetValue(playerId, out var value) && Time.unscaledTime < value)
			{
				return false;
			}
			if (Random.Range(0f, 100f) >= ChancePercent.Value)
			{
				return false;
			}
			_cooldownUntil[playerId] = Time.unscaledTime + 2f;
			return true;
		}

		private void RPC_Play(long sender)
		{
			if (!FoxyNet.IsTrustedSender(sender))
			{
				Log.LogWarning((object)$"Ignored jumpscare RPC from untrusted sender {sender}.");
				return;
			}
			Log.LogInfo((object)"Received jumpscare from the server.");
			PlayLocal();
		}

		private void RPC_Settings(long sender, float chancePercent)
		{
			if (!FoxyNet.IsWorldHost() && FoxyNet.IsTrustedSender(sender))
			{
				chancePercent = Mathf.Clamp(chancePercent, 0.01f, 100f);
				if (!(Mathf.Abs(_appliedChance - chancePercent) < 0.0001f))
				{
					_appliedChance = chancePercent;
					Log.LogInfo((object)$"Server jumpscare chance is {chancePercent}% per player each second.");
				}
			}
		}

		private void RPC_Hello(long sender, string version)
		{
			if (!FoxyNet.IsWorldHost())
			{
				return;
			}
			if (string.IsNullOrEmpty(version))
			{
				version = "(empty)";
			}
			ZNetPeer val = FoxyNet.FindPeer(sender);
			if (val == null)
			{
				Log.LogWarning((object)$"Hello from unknown peer {sender} with version {version}.");
				return;
			}
			if (!_peerFirstSeen.ContainsKey(sender))
			{
				_peerFirstSeen[sender] = Time.unscaledTime;
			}
			if (version != "1.0.0")
			{
				FoxyNet.KickPeer(val, "need 1 in 10000 v1.0.0, got " + version);
				_verifiedPeers.Remove(sender);
				_peerFirstSeen.Remove(sender);
			}
			else if (_verifiedPeers.Add(sender))
			{
				Log.LogInfo((object)string.Format("Peer {0} verified with {1} v{2}.", sender, "1 in 10000", "1.0.0"));
				FoxyNet.SendSettings(sender, ChancePercent.Value);
			}
		}

		private void PlayLocal()
		{
			if (FoxyNet.IsDedicated() || _jumpscare.IsPlaying)
			{
				return;
			}
			if (!EmbeddedVideo.TryExtract(out var path, out var error))
			{
				if (!_loggedMissingVideo)
				{
					Log.LogWarning((object)error);
					_loggedMissingVideo = true;
				}
			}
			else
			{
				_loggedMissingVideo = false;
				((MonoBehaviour)this).StartCoroutine(_jumpscare.Play(path));
			}
		}
	}
}