Decompiled source of Fuling Heckler v1.0.0

plugins/FulingHeckler.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HeaveHo.Shared;
using Microsoft.CodeAnalysis;
using Nick.Shared;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.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;
		}
	}
}
internal sealed class ConfigurationManagerAttributes
{
	public bool? IsAdvanced;

	public int? Order;

	public Action<ConfigEntryBase> CustomDrawer;

	public bool? HideDefaultButton;

	public bool? ReadOnly;

	public bool? Browsable;

	public string Category;

	public string DispName;

	public string Description;
}
namespace HeaveHo.Shared
{
	internal sealed class ServerSettings : IDisposable
	{
		public enum ServerMod
		{
			NotConnected,
			Checking,
			Installed,
			Missing
		}

		private sealed class ConfigurationManagerAttributes
		{
			public bool? ReadOnly;
		}

		private sealed class SyncedText
		{
			public string Key;

			public Func<string> Own;

			public Action<string> Use;

			public string Given;

			public bool FromServer;

			public bool Pending;

			public byte[] Packed;

			public bool TooBig;

			public MemoryStream Incoming;

			public int NextPart;
		}

		private sealed class Sending
		{
			public ZRpc Rpc;

			public string Key;

			public byte[] Data;

			public int Next;

			public int Parts;
		}

		private const int Protocol = 2;

		private const float ChangeSendGap = 0.5f;

		private const float AnswerWait = 20f;

		private const float FileWait = 1f;

		private const int PartSize = 65536;

		private const int CompressFrom = 1024;

		private const int WarnBytes = 262144;

		private const int MaxBytes = 8388608;

		private const int PaceBelow = 10240;

		private readonly string _send;

		private readonly string _ask;

		private readonly string _dAsk;

		private readonly string _dHello;

		private readonly string _dSend;

		private readonly string _edit;

		private readonly string _dTextAsk;

		private readonly string _dText;

		private readonly string _dAgain;

		private readonly ConfigFile _config;

		private readonly ManualLogSource _log;

		private readonly string _name;

		private readonly List<ConfigEntryBase> _entries = new List<ConfigEntryBase>();

		private readonly Dictionary<ConfigEntryBase, object> _own = new Dictionary<ConfigEntryBase, object>();

		private readonly Dictionary<ConfigEntryBase, object> _server = new Dictionary<ConfigEntryBase, object>();

		private readonly List<GameObject> _guardedPieces = new List<GameObject>();

		private readonly List<Recipe> _guardedRecipes = new List<Recipe>();

		private ZRoutedRpc _registeredOn;

		private bool _applying;

		private bool _reloading;

		private bool _changePending;

		private float _nextChangeSend;

		private float _askedAt = -1f;

		private int _serverProtocol;

		private bool _directSeen;

		private readonly HashSet<ZRpc> _directPeers = new HashSet<ZRpc>();

		private readonly List<SyncedText> _texts = new List<SyncedText>();

		private readonly Dictionary<ZRpc, HashSet<string>> _textPeers = new Dictionary<ZRpc, HashSet<string>>();

		private bool _textPending;

		private float _nextTextSend;

		private bool _readOnlyShown;

		private float _nextAdminCheck;

		private FileSystemWatcher _watcher;

		private volatile bool _fileTouched;

		private float _reloadAt = -1f;

		private string _fileText;

		private readonly List<Sending> _outbox = new List<Sending>();

		private static readonly MethodInfo UpdateAvailablePieces = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null);

		private static readonly List<ServerSettings> Live = new List<ServerSettings>();

		private static Harmony _saveHarmony;

		public string Version { get; }

		public bool FromServer => _server.Count > 0;

		public ServerMod Server { get; private set; }

		public string ServerVersion { get; private set; }

		public bool CanEditServer
		{
			get
			{
				if (!IsServer)
				{
					if (Server == ServerMod.Installed && _serverProtocol >= 1 && (Object)(object)ZNet.instance != (Object)null)
					{
						return ZNet.instance.LocalPlayerIsAdminOrHost();
					}
					return false;
				}
				return true;
			}
		}

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

		public event Action ServerChanged;

		public ServerSettings(string guid, ConfigFile config, ManualLogSource log)
		{
			_send = guid + ".settings";
			_ask = guid + ".settings.ask";
			_dAsk = guid + ".settings.d.ask";
			_dHello = guid + ".settings.d.hello";
			_dSend = guid + ".settings.d.values";
			_edit = guid + ".settings.d.edit";
			_dTextAsk = guid + ".settings.d.textask";
			_dAgain = guid + ".settings.d.again";
			_dText = guid + ".settings.d.text";
			_config = config;
			_log = log;
			BepInPlugin val = PluginOf(guid);
			_name = ((val != null) ? val.Name : null) ?? guid;
			Version = ((val == null) ? null : val.Version?.ToString()) ?? "";
			Live.Add(this);
			if (Live.Count == 1)
			{
				PatchSave();
			}
			WatchFile();
		}

		public ConfigEntry<T> Add<T>(ConfigEntry<T> entry)
		{
			if (entry == null || _entries.Contains((ConfigEntryBase)(object)entry))
			{
				return entry;
			}
			_entries.Add((ConfigEntryBase)(object)entry);
			entry.SettingChanged += OnChanged;
			GiveManagerAttributes((ConfigEntryBase)(object)entry);
			return entry;
		}

		private static void GiveManagerAttributes(ConfigEntryBase entry)
		{
			ConfigDescription description = entry.Description;
			if (description == null || description == ConfigDescription.Empty || (description.Tags ?? new object[0]).Any(IsManagerAttributes))
			{
				return;
			}
			try
			{
				object[] value = new object[1]
				{
					new ConfigurationManagerAttributes()
				}.Concat(description.Tags ?? new object[0]).ToArray();
				AccessTools.Field(typeof(ConfigDescription), "<Tags>k__BackingField")?.SetValue(description, value);
			}
			catch (Exception)
			{
			}
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range = null, params object[] tags)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			if (!description.Contains("[Synced with Server]"))
			{
				description += " [Synced with Server]";
			}
			return Add<T>(_config.Bind<T>(section, key, value, new ConfigDescription(description, range, tags ?? new object[0])));
		}

		public void NeedsServer(GameObject piecePrefab)
		{
			if (!((Object)(object)piecePrefab == (Object)null))
			{
				_guardedPieces.RemoveAll((GameObject p) => (Object)(object)p == (Object)null || ((Object)p).name == ((Object)piecePrefab).name);
				_guardedPieces.Add(piecePrefab);
				Guard();
			}
		}

		public void NeedsServer(Recipe recipe)
		{
			if (!((Object)(object)recipe == (Object)null))
			{
				_guardedRecipes.RemoveAll((Recipe r) => (Object)(object)r == (Object)null || (Object)(object)r == (Object)(object)recipe || (!string.IsNullOrEmpty(((Object)recipe).name) && ((Object)r).name == ((Object)recipe).name));
				_guardedRecipes.Add(recipe);
				Guard();
			}
		}

		public void AddText(string key, Func<string> own, Action<string> use)
		{
			if (string.IsNullOrEmpty(key) || own == null || use == null)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)"AddText needs a key, own and use");
				}
				return;
			}
			_texts.RemoveAll((SyncedText x) => x.Key == key);
			SyncedText syncedText = new SyncedText
			{
				Key = key,
				Own = own,
				Use = use
			};
			_texts.Add(syncedText);
			Give(syncedText, OwnText(syncedText));
			ZRpc val = ((!((Object)(object)ZNet.instance != (Object)null) || IsServer) ? null : ZNet.instance.GetServerPeer()?.m_rpc);
			if (_registeredOn != null && val != null)
			{
				val.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText);
				AskTexts(val);
			}
		}

		public void TextChanged(string key)
		{
			SyncedText syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key);
			if (syncedText != null)
			{
				syncedText.Packed = null;
				if (!syncedText.FromServer)
				{
					Give(syncedText, OwnText(syncedText));
				}
				if (IsServer)
				{
					syncedText.Pending = true;
					_textPending = true;
				}
			}
		}

		public bool TextFromServer(string key)
		{
			return _texts.Any((SyncedText x) => x.Key == key && x.FromServer);
		}

		public void Update()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != _registeredOn)
			{
				Unregister();
				Restore();
				RestoreTexts(give: true);
				_changePending = false;
				_textPending = false;
				SetServer(ServerMod.NotConnected, null, 0);
				if (instance != null)
				{
					Register(instance);
				}
			}
			float unscaledTime = Time.unscaledTime;
			if (_changePending && unscaledTime >= _nextChangeSend)
			{
				SendChange();
			}
			if (_textPending && unscaledTime >= _nextTextSend)
			{
				SendTexts();
			}
			if (_outbox.Count > 0)
			{
				Pump();
			}
			if (Server == ServerMod.Checking && _askedAt >= 0f && unscaledTime - _askedAt > 20f)
			{
				if (_guardedPieces.Count + _guardedRecipes.Count > 0)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("The server didn't answer: " + _name + " isn't installed there (or its copy is older than 2026-10-11), so its pieces and items can't be made on this server."));
					}
				}
				else
				{
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogInfo((object)("The server didn't answer: " + _name + " isn't installed there, or its copy is older" + (FromServer ? " (its settings apply)." : " (your own settings apply).")));
					}
				}
				SetServer(ServerMod.Missing, null, 0);
			}
			if (_fileTouched)
			{
				_fileTouched = false;
				_reloadAt = unscaledTime + 1f;
			}
			if (_reloadAt >= 0f && unscaledTime >= _reloadAt)
			{
				_reloadAt = -1f;
				ReloadFile();
			}
			if (FromServer && unscaledTime >= _nextAdminCheck)
			{
				_nextAdminCheck = unscaledTime + 1f;
				ShowReadOnly();
			}
		}

		public void Dispose()
		{
			Unregister();
			Restore();
			RestoreTexts(give: false);
			SetServer(ServerMod.NotConnected, null, 0);
			foreach (ConfigEntryBase entry in _entries)
			{
				Unsubscribe(entry);
			}
			_entries.Clear();
			_texts.Clear();
			try
			{
				_watcher?.Dispose();
			}
			catch (Exception)
			{
			}
			_watcher = null;
			Live.Remove(this);
			if (Live.Count == 0)
			{
				UnpatchSave();
			}
		}

		private void Register(ZRoutedRpc rpc)
		{
			Forget(rpc, _send);
			Forget(rpc, _ask);
			rpc.Register<ZPackage>(_send, (Action<long, ZPackage>)OnRoutedValues);
			rpc.Register(_ask, (Action<long>)OnRoutedAsk);
			rpc.m_onNewPeer = (Action<long>)Delegate.Combine(rpc.m_onNewPeer, new Action<long>(OnNewPeer));
			_registeredOn = rpc;
			if (IsServer)
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					ListenTo(peer);
					if (peer != null)
					{
						ZRpc rpc2 = peer.m_rpc;
						if (rpc2 != null)
						{
							rpc2.Invoke(_dAgain, Array.Empty<object>());
						}
					}
				}
				return;
			}
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				SetServer(ServerMod.Checking, null, 0);
				if (ZNet.instance.GetServerPeer() != null)
				{
					AskServer();
				}
			}
		}

		private void Unregister()
		{
			if (_registeredOn == null)
			{
				return;
			}
			ZRoutedRpc registeredOn = _registeredOn;
			registeredOn.m_onNewPeer = (Action<long>)Delegate.Remove(registeredOn.m_onNewPeer, new Action<long>(OnNewPeer));
			Forget(_registeredOn, _send);
			Forget(_registeredOn, _ask);
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer?.m_rpc != null)
					{
						string[] array = new string[7] { _dAsk, _dHello, _dSend, _edit, _dTextAsk, _dText, _dAgain };
						foreach (string name in array)
						{
							ForgetDirect(peer.m_rpc, name);
						}
					}
				}
			}
			_directPeers.Clear();
			_textPeers.Clear();
			_outbox.Clear();
			_directSeen = false;
			_registeredOn = null;
		}

		private static void Forget(ZRoutedRpc rpc, string name)
		{
			if (AccessTools.Field(typeof(ZRoutedRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary)
			{
				dictionary.Remove(StringExtensionMethods.GetStableHashCode(name));
			}
		}

		private static void ForgetDirect(ZRpc rpc, string name)
		{
			if (AccessTools.Field(typeof(ZRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary)
			{
				dictionary.Remove(StringExtensionMethods.GetStableHashCode(name));
			}
		}

		private void OnNewPeer(long uid)
		{
			if (IsServer)
			{
				ZNetPeer peer = ZNet.instance.GetPeer(uid);
				ListenTo(peer);
				if (peer?.m_rpc != null)
				{
					SendDirect(peer.m_rpc);
				}
				SendRouted(uid);
			}
			else if ((Object)(object)ZNet.instance != (Object)null)
			{
				ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
				if (serverPeer != null && serverPeer.m_uid == uid)
				{
					AskServer();
				}
			}
		}

		private void ListenTo(ZNetPeer peer)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			if (peer?.m_rpc != null)
			{
				peer.m_rpc.Register(_dAsk, new Method(OnDirectAsk));
				peer.m_rpc.Register<ZPackage>(_edit, (Action<ZRpc, ZPackage>)OnEdit);
				peer.m_rpc.Register<ZPackage>(_dTextAsk, (Action<ZRpc, ZPackage>)OnTextAsk);
			}
		}

		private void OnDirectAsk(ZRpc rpc)
		{
			if (IsServer)
			{
				_directPeers.Add(rpc);
				SendDirect(rpc);
			}
		}

		private void OnRoutedAsk(long sender)
		{
			if (IsServer)
			{
				SendRouted(sender);
			}
		}

		private void SendDirect(ZRpc rpc)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(2);
			val.Write(Version);
			rpc.Invoke(_dHello, new object[1] { val });
			if (_entries.Count > 0)
			{
				rpc.Invoke(_dSend, new object[1] { Values() });
			}
		}

		private void SendRouted(long target)
		{
			if (ZRoutedRpc.instance != null && _entries.Count > 0)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(target, _send, new object[1] { Values() });
			}
		}

		private ZPackage Values()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(_entries.Count);
			foreach (ConfigEntryBase entry in _entries)
			{
				val.Write(entry.Definition.Section);
				val.Write(entry.Definition.Key);
				val.Write(TomlTypeConverter.ConvertToString(entry.BoxedValue, entry.SettingType));
			}
			return val;
		}

		private void AskServer()
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null);
			if (val?.m_rpc == null)
			{
				return;
			}
			_askedAt = Time.unscaledTime;
			val.m_rpc.Register<ZPackage>(_dHello, (Action<ZRpc, ZPackage>)OnDirectHello);
			val.m_rpc.Register<ZPackage>(_dSend, (Action<ZRpc, ZPackage>)OnDirectValues);
			val.m_rpc.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText);
			val.m_rpc.Register(_dAgain, new Method(OnAskAgain));
			val.m_rpc.Invoke(_dAsk, Array.Empty<object>());
			AskTexts(val.m_rpc);
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(val.m_uid, _ask, Array.Empty<object>());
				}
			}
			catch (Exception)
			{
			}
		}

		private static bool IsServerConnection(ZRpc rpc)
		{
			if (!IsServer)
			{
				ZNet instance = ZNet.instance;
				return ((instance == null) ? null : instance.GetServerPeer()?.m_rpc) == rpc;
			}
			return false;
		}

		private void OnAskAgain(ZRpc rpc)
		{
			if (IsServerConnection(rpc))
			{
				rpc.Invoke(_dAsk, Array.Empty<object>());
				AskTexts(rpc);
			}
		}

		private void OnDirectHello(ZRpc rpc, ZPackage pkg)
		{
			if (!IsServerConnection(rpc))
			{
				return;
			}
			try
			{
				int protocol = pkg.ReadInt();
				string text = pkg.ReadString();
				if (text != Version && text != ServerVersion)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("The server has " + _name + " " + text + ", you have " + Version + ": some things may not match. Use the same version as the server."));
					}
				}
				_directSeen = true;
				SetServer(ServerMod.Installed, text, protocol);
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not read the server's hello: " + ex.Message));
				}
			}
		}

		private void OnDirectValues(ZRpc rpc, ZPackage pkg)
		{
			if (IsServerConnection(rpc))
			{
				TakeValues(pkg);
			}
		}

		private void OnRoutedValues(long sender, ZPackage pkg)
		{
			if (!_directSeen && !IsServer)
			{
				ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null);
				if (val != null && val.m_uid == sender)
				{
					TakeValues(pkg);
				}
			}
		}

		private void TakeValues(ZPackage pkg)
		{
			try
			{
				int num = pkg.ReadInt();
				List<string> list = new List<string>();
				for (int i = 0; i < num; i++)
				{
					string section = pkg.ReadString();
					string key = pkg.ReadString();
					string text = pkg.ReadString();
					ConfigEntryBase val = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key));
					if (val != null)
					{
						object obj;
						try
						{
							obj = TomlTypeConverter.ConvertToValue(text, val.SettingType);
						}
						catch (Exception)
						{
							continue;
						}
						if (!_own.ContainsKey(val))
						{
							_own[val] = val.BoxedValue;
						}
						_server[val] = obj;
						if (!object.Equals(val.BoxedValue, obj))
						{
							Apply(val, obj);
							list.Add(section + "/" + key + " = " + text);
						}
					}
				}
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)($"Settings from the server apply while you play here ({_server.Count})" + ((list.Count > 0) ? (": " + string.Join(", ", list.ToArray())) : "")));
				}
				ShowReadOnly();
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not read the server's settings: " + ex2.Message));
				}
			}
		}

		private void Apply(ConfigEntryBase e, object value)
		{
			bool saveOnConfigSet = _config.SaveOnConfigSet;
			_applying = true;
			try
			{
				_config.SaveOnConfigSet = false;
				e.BoxedValue = value;
			}
			finally
			{
				_config.SaveOnConfigSet = saveOnConfigSet;
				_applying = false;
			}
		}

		private void Restore()
		{
			if (_server.Count == 0)
			{
				return;
			}
			foreach (KeyValuePair<ConfigEntryBase, object> item in _own)
			{
				Apply(item.Key, item.Value);
			}
			_own.Clear();
			_server.Clear();
			ShowReadOnly();
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)"Your own settings apply again");
			}
		}

		private void OnChanged(object sender, EventArgs args)
		{
			if (_applying)
			{
				return;
			}
			ConfigEntryBase val = (ConfigEntryBase)((sender is ConfigEntryBase) ? sender : null);
			if (val == null)
			{
				return;
			}
			if (_server.TryGetValue(val, out var value))
			{
				if (!_reloading && CanEditServer)
				{
					SendEdit(val);
				}
				else
				{
					_own[val] = val.BoxedValue;
				}
				Apply(val, value);
			}
			else if (IsServer)
			{
				_changePending = true;
				if (Time.unscaledTime >= _nextChangeSend)
				{
					SendChange();
				}
			}
		}

		private void SendChange()
		{
			_changePending = false;
			_nextChangeSend = Time.unscaledTime + 0.5f;
			if (!IsServer)
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer?.m_rpc == null)
				{
					continue;
				}
				if (_directPeers.Contains(peer.m_rpc))
				{
					if (_entries.Count > 0)
					{
						peer.m_rpc.Invoke(_dSend, new object[1] { Values() });
					}
				}
				else
				{
					SendRouted(peer.m_uid);
				}
			}
		}

		private void SendEdit(ConfigEntryBase e)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			ZNet instance = ZNet.instance;
			ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null);
			if (val?.m_rpc != null)
			{
				ZPackage val2 = new ZPackage();
				val2.Write(e.Definition.Section);
				val2.Write(e.Definition.Key);
				val2.Write(TomlTypeConverter.ConvertToString(e.BoxedValue, e.SettingType));
				val.m_rpc.Invoke(_edit, new object[1] { val2 });
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)("Asked the server to set " + e.Definition.Section + "/" + e.Definition.Key));
				}
			}
		}

		private void OnEdit(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				if (!IsServer)
				{
					return;
				}
				ISocket socket = rpc.GetSocket();
				string text = ((socket != null) ? socket.GetHostName() : null) ?? "";
				ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc));
				string section = pkg.ReadString();
				string key = pkg.ReadString();
				string text2 = pkg.ReadString();
				if (!ZNet.instance.IsAdmin(text))
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)((val?.m_playerName ?? text) + " tried to change " + section + "/" + key + " but isn't on the admin list"));
					}
					if (_entries.Count > 0)
					{
						rpc.Invoke(_dSend, new object[1] { Values() });
					}
					return;
				}
				ConfigEntryBase val2 = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key));
				if (val2 != null)
				{
					val2.BoxedValue = TomlTypeConverter.ConvertToValue(text2, val2.SettingType);
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogInfo((object)("Admin " + (val?.m_playerName ?? text) + " set " + section + "/" + key + " = " + text2));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = _log;
				if (log3 != null)
				{
					log3.LogWarning((object)("Could not take an admin's change: " + ex.Message));
				}
			}
		}

		private void ShowReadOnly()
		{
			bool flag = FromServer && !CanEditServer;
			if (flag == _readOnlyShown)
			{
				return;
			}
			_readOnlyShown = flag;
			foreach (ConfigEntryBase entry in _entries)
			{
				ConfigDescription description = entry.Description;
				object[] array = ((description != null) ? description.Tags : null) ?? new object[0];
				foreach (object obj in array)
				{
					if (IsManagerAttributes(obj))
					{
						try
						{
							AccessTools.Field(obj.GetType(), "ReadOnly")?.SetValue(obj, flag ? ((object)true) : null);
						}
						catch (Exception)
						{
						}
					}
				}
			}
		}

		private static bool IsManagerAttributes(object tag)
		{
			if (tag != null)
			{
				return tag.GetType().Name == "ConfigurationManagerAttributes";
			}
			return false;
		}

		private string OwnText(SyncedText t)
		{
			try
			{
				return t.Own() ?? "";
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not read " + t.Key + ": " + ex.Message));
				}
				return t.Given ?? "";
			}
		}

		private void Give(SyncedText t, string text)
		{
			if (t.Given != null && t.Given == text)
			{
				return;
			}
			t.Given = text;
			try
			{
				t.Use(text);
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not use " + t.Key + ": " + ex));
				}
			}
		}

		private void RestoreTexts(bool give)
		{
			foreach (SyncedText text in _texts)
			{
				text.Incoming = null;
				if (!text.FromServer)
				{
					continue;
				}
				text.FromServer = false;
				if (give)
				{
					Give(text, OwnText(text));
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogInfo((object)("Your own " + text.Key + " applies again"));
					}
				}
			}
		}

		private void AskTexts(ZRpc server)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			if (_texts.Count == 0 || server == null)
			{
				return;
			}
			ZPackage val = new ZPackage();
			val.Write(_texts.Count);
			foreach (SyncedText text in _texts)
			{
				val.Write(text.Key);
			}
			server.Invoke(_dTextAsk, new object[1] { val });
		}

		private void OnTextAsk(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				if (!IsServer)
				{
					return;
				}
				int num = pkg.ReadInt();
				HashSet<string> hashSet = new HashSet<string>();
				for (int i = 0; i < num && i < 1000; i++)
				{
					string key = pkg.ReadString();
					if (_texts.Any((SyncedText x) => x.Key == key))
					{
						hashSet.Add(key);
					}
				}
				if (_textPeers.TryGetValue(rpc, out var value))
				{
					hashSet.UnionWith(value);
				}
				foreach (ZRpc item in _textPeers.Keys.Where((ZRpc r) => !r.IsConnected()).ToList())
				{
					_textPeers.Remove(item);
				}
				_textPeers[rpc] = hashSet;
				foreach (SyncedText text in _texts)
				{
					if (hashSet.Contains(text.Key))
					{
						SendText(rpc, text);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not answer a player's ask for server data: " + ex.Message));
				}
			}
		}

		private void SendTexts()
		{
			_textPending = false;
			_nextTextSend = Time.unscaledTime + 0.5f;
			if (!IsServer)
			{
				return;
			}
			foreach (SyncedText text in _texts)
			{
				if (!text.Pending)
				{
					continue;
				}
				text.Pending = false;
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer?.m_rpc != null && _textPeers.TryGetValue(peer.m_rpc, out var value) && value.Contains(text.Key))
					{
						SendText(peer.m_rpc, text);
					}
				}
			}
		}

		private void SendText(ZRpc rpc, SyncedText t)
		{
			byte[] array = Packed(t);
			if (array != null)
			{
				_outbox.RemoveAll((Sending s) => s.Rpc == rpc && s.Key == t.Key);
				_outbox.Add(new Sending
				{
					Rpc = rpc,
					Key = t.Key,
					Data = array,
					Parts = Math.Max(1, (array.Length + 65536 - 1) / 65536)
				});
				Pump();
			}
		}

		private void Pump()
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			for (int num = _outbox.Count - 1; num >= 0; num--)
			{
				Sending sending = _outbox[num];
				try
				{
					while (sending.Next < sending.Parts && sending.Rpc.IsConnected() && sending.Rpc.GetSocket().GetSendQueueSize() < 10240)
					{
						int num2 = sending.Next * 65536;
						int num3 = Math.Min(65536, sending.Data.Length - num2);
						byte[] array = new byte[num3];
						Buffer.BlockCopy(sending.Data, num2, array, 0, num3);
						ZPackage val = new ZPackage();
						val.Write(sending.Key);
						val.Write(sending.Next);
						val.Write(sending.Parts);
						val.Write(array);
						sending.Rpc.Invoke(_dText, new object[1] { val });
						sending.Next++;
					}
					if (sending.Next >= sending.Parts || !sending.Rpc.IsConnected())
					{
						_outbox.RemoveAt(num);
					}
				}
				catch (Exception ex)
				{
					_outbox.RemoveAt(num);
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("Could not send " + sending.Key + " to a player: " + ex.Message));
					}
				}
			}
		}

		private byte[] Packed(SyncedText t)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (t.Packed == null)
			{
				string text = OwnText(t);
				ZPackage val = new ZPackage();
				val.Write(text);
				ZPackage val2 = new ZPackage();
				bool flag = text.Length > 1024;
				val2.Write(flag);
				if (flag)
				{
					val2.WriteCompressed(val);
				}
				else
				{
					val2.Write(val);
				}
				t.Packed = val2.GetArray();
				t.TooBig = t.Packed.Length > 8388608;
				string arg = $"{t.Packed.Length / 1024} KB" + (flag ? $" gzipped ({text.Length / 1024} KB of text)" : "");
				if (t.TooBig)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogError((object)$"{t.Key} is too big to send to players ({arg}, at most {8} MB): they keep their own.");
					}
				}
				else if (t.Packed.Length > 262144)
				{
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogWarning((object)$"{t.Key} is big to send to every player who joins ({arg}): it takes about {(float)t.Packed.Length / 153600f:0} s of their connection, and their world loads slower meanwhile.");
					}
				}
			}
			if (!t.TooBig)
			{
				return t.Packed;
			}
			return null;
		}

		private void OnText(ZRpc rpc, ZPackage pkg)
		{
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			if (!IsServerConnection(rpc))
			{
				return;
			}
			SyncedText syncedText = null;
			try
			{
				string key = pkg.ReadString();
				int num = pkg.ReadInt();
				int num2 = pkg.ReadInt();
				byte[] array = pkg.ReadByteArray();
				syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key);
				if (syncedText == null)
				{
					return;
				}
				if (num2 < 1 || num2 > 129 || num < 0 || num >= num2)
				{
					throw new InvalidDataException($"part {num} of {num2}");
				}
				if (num == 0)
				{
					syncedText.Incoming = new MemoryStream();
					syncedText.NextPart = 0;
				}
				if (syncedText.Incoming == null || num != syncedText.NextPart)
				{
					throw new InvalidDataException($"part {num} came out of order");
				}
				syncedText.Incoming.Write(array, 0, array.Length);
				syncedText.NextPart = num + 1;
				if (num >= num2 - 1)
				{
					ZPackage val = new ZPackage(syncedText.Incoming.ToArray());
					syncedText.Incoming = null;
					ZPackage val2 = (val.ReadBool() ? val.ReadCompressedPackage() : val.ReadPackage());
					string text = val2.ReadString();
					syncedText.FromServer = true;
					Give(syncedText, text);
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogInfo((object)$"The server's {key} applies while you play here ({text.Length / 1024} KB)");
					}
				}
			}
			catch (Exception ex)
			{
				if (syncedText != null)
				{
					syncedText.Incoming = null;
				}
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not read the server's data" + ((syncedText != null) ? (" for " + syncedText.Key) : "") + ": " + ex.Message));
				}
			}
		}

		private void SetServer(ServerMod state, string version, int protocol)
		{
			if (state != ServerMod.Checking)
			{
				_askedAt = -1f;
			}
			bool flag = state != Server || version != ServerVersion;
			Server = state;
			ServerVersion = version;
			_serverProtocol = protocol;
			if (!flag)
			{
				return;
			}
			Guard();
			try
			{
				this.ServerChanged?.Invoke();
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("ServerChanged: " + ex));
				}
			}
		}

		private void Guard()
		{
			bool flag = Server == ServerMod.NotConnected || Server == ServerMod.Installed;
			bool flag2 = false;
			foreach (GameObject guardedPiece in _guardedPieces)
			{
				Piece val = (((Object)(object)guardedPiece != (Object)null) ? guardedPiece.GetComponent<Piece>() : null);
				if ((Object)(object)val != (Object)null && val.m_enabled != flag)
				{
					val.m_enabled = flag;
					flag2 = true;
				}
			}
			foreach (Recipe guardedRecipe in _guardedRecipes)
			{
				if ((Object)(object)guardedRecipe != (Object)null && guardedRecipe.m_enabled != flag)
				{
					guardedRecipe.m_enabled = flag;
					flag2 = true;
				}
			}
			if (flag2 && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				try
				{
					UpdateAvailablePieces?.Invoke(Player.m_localPlayer, null);
				}
				catch (Exception)
				{
				}
			}
		}

		private void WatchFile()
		{
			try
			{
				string configFilePath = _config.ConfigFilePath;
				_watcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath))
				{
					NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite)
				};
				FileSystemEventHandler value = delegate
				{
					_fileTouched = true;
				};
				_watcher.Changed += value;
				_watcher.Created += value;
				_watcher.Renamed += delegate
				{
					_fileTouched = true;
				};
				_watcher.EnableRaisingEvents = true;
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Edits to the config file apply after a restart (can't watch it: " + ex.Message + ")"));
				}
			}
		}

		private void ReloadFile()
		{
			string text = ReadFile(_config.ConfigFilePath);
			if (text == null)
			{
				if (File.Exists(_config.ConfigFilePath))
				{
					_reloadAt = Time.unscaledTime + 1f;
				}
			}
			else
			{
				if (text == _fileText)
				{
					return;
				}
				_fileText = text;
				bool saveOnConfigSet = _config.SaveOnConfigSet;
				_reloading = true;
				try
				{
					_config.SaveOnConfigSet = false;
					_config.Reload();
				}
				catch (Exception ex)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("Could not read the edited config file (check the line you changed): " + ex.Message));
					}
				}
				finally
				{
					_config.SaveOnConfigSet = saveOnConfigSet;
					_reloading = false;
				}
			}
		}

		private void PatchSave()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0067: Expected O, but got Unknown
			try
			{
				_saveHarmony = new Harmony(_send + ".save");
				_saveHarmony.Patch((MethodBase)AccessTools.Method(typeof(ConfigFile), "Save", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ServerSettings), "BeforeSave", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ServerSettings), "AfterSave", (Type[])null), (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not keep the server's values out of your config file: " + ex.Message));
				}
			}
		}

		private static void UnpatchSave()
		{
			try
			{
				Harmony saveHarmony = _saveHarmony;
				if (saveHarmony != null)
				{
					saveHarmony.UnpatchSelf();
				}
			}
			catch (Exception)
			{
			}
			_saveHarmony = null;
		}

		private static void BeforeSave(ConfigFile __instance)
		{
			foreach (ServerSettings item in Live)
			{
				if (item._config != __instance)
				{
					continue;
				}
				foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._own)
				{
					SetQuietly(item2.Key, item2.Value);
				}
			}
		}

		private static Exception AfterSave(ConfigFile __instance, Exception __exception)
		{
			foreach (ServerSettings item in Live)
			{
				if (item._config != __instance)
				{
					continue;
				}
				foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._server)
				{
					SetQuietly(item2.Key, item2.Value);
				}
				item._fileText = ReadFile(__instance.ConfigFilePath);
			}
			return __exception;
		}

		private static string ReadFile(string path)
		{
			try
			{
				return File.Exists(path) ? File.ReadAllText(path) : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static void SetQuietly(ConfigEntryBase e, object value)
		{
			AccessTools.Field(((object)e).GetType(), "_typedValue")?.SetValue(e, value);
		}

		private static BepInPlugin PluginOf(string guid)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			Type[] array;
			try
			{
				array = typeof(ServerSettings).Assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				array = ex.Types.Where((Type t) => t != null).ToArray();
			}
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				object[] customAttributes = type.GetCustomAttributes(typeof(BepInPlugin), inherit: false);
				for (int num2 = 0; num2 < customAttributes.Length; num2++)
				{
					BepInPlugin val = (BepInPlugin)customAttributes[num2];
					if (val.GUID == guid || guid.StartsWith(val.GUID + "."))
					{
						return val;
					}
				}
			}
			return null;
		}

		private void Unsubscribe(ConfigEntryBase e)
		{
			((object)e).GetType().GetEvent("SettingChanged")?.RemoveEventHandler(e, new EventHandler(OnChanged));
		}
	}
}
namespace FulingHeckler
{
	internal static class Commands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__4_0;

			internal void <Register>b__4_0(ConsoleEventArgs args)
			{
				Run(args.Context, args.Args);
			}
		}

		private const string Command = "heckler";

		private const string Usage = "heckler summon [emote] | heckler torch | heckler lurker|redlight|tombstone|copycat|campfire|thief|gifter | heckler freeze | heckler unfreeze | heckler where | heckler portrait|photo [distance] [turn] [height] [size] [above] | heckler status";

		private static readonly FieldRef<Dictionary<string, ConsoleCommand>> TerminalCommands = AccessTools.StaticFieldRefAccess<Dictionary<string, ConsoleCommand>>(AccessTools.Field(typeof(Terminal), "commands"));

		private static readonly Dictionary<string, SceneKind> SceneNames = new Dictionary<string, SceneKind>
		{
			{
				"lurker",
				SceneKind.Lurker
			},
			{
				"redlight",
				SceneKind.RedLight
			},
			{
				"tombstone",
				SceneKind.Tombstone
			},
			{
				"copycat",
				SceneKind.Copycat
			},
			{
				"campfire",
				SceneKind.Campfire
			},
			{
				"thief",
				SceneKind.CoinThief
			},
			{
				"gifter",
				SceneKind.Gifter
			}
		};

		private const int PortraitLayer = 31;

		internal static void Register()
		{
			//IL_0033: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			object obj = <>c.<>9__4_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					Run(args.Context, args.Args);
				};
				<>c.<>9__4_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("heckler", "Fuling Heckler: heckler summon [emote] | heckler torch | heckler lurker|redlight|tombstone|copycat|campfire|thief|gifter | heckler freeze | heckler unfreeze | heckler where | heckler portrait|photo [distance] [turn] [height] [size] [above] | heckler status", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		internal static void Unregister()
		{
			TerminalCommands.Invoke().Remove("heckler");
		}

		private static string Portrait(FulingActor actor, float distance, float turn, float height, int size, float above, bool scene = false)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: 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_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: 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_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Expected O, but got Unknown
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Expected O, but got Unknown
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Expected O, but got Unknown
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			Renderer[] componentsInChildren = ((Component)actor).GetComponentsInChildren<Renderer>(true);
			Dictionary<GameObject, int> dictionary = new Dictionary<GameObject, int>();
			GameObject val = null;
			RenderTexture val2 = null;
			try
			{
				Animator componentInChildren = ((Component)actor).GetComponentInChildren<Animator>();
				Transform val3 = (((Object)(object)componentInChildren != (Object)null && componentInChildren.isHuman) ? componentInChildren.GetBoneTransform((HumanBodyBones)10) : null);
				Vector3 val4 = (((Object)(object)val3 != (Object)null) ? val3.position : (((Component)actor).transform.position + Vector3.up * 1.4f)) + Vector3.up * height;
				Vector3 position = val4 + Quaternion.Euler(0f, turn, 0f) * ((Component)actor).transform.forward * distance + Vector3.up * above;
				if (!scene)
				{
					Renderer[] array = componentsInChildren;
					foreach (Renderer val5 in array)
					{
						dictionary[((Component)val5).gameObject] = ((Component)val5).gameObject.layer;
						((Component)val5).gameObject.layer = 31;
					}
				}
				val = new GameObject("FulingHeckler_PortraitCamera");
				Camera val6 = val.AddComponent<Camera>();
				if (scene && (Object)(object)Camera.main != (Object)null)
				{
					val6.CopyFrom(Camera.main);
				}
				((Behaviour)val6).enabled = false;
				if (!scene)
				{
					val6.cullingMask = int.MinValue;
					val6.clearFlags = (CameraClearFlags)2;
					val6.backgroundColor = new Color(0f, 0f, 0f, 0f);
				}
				val6.fieldOfView = (scene ? 40f : 30f);
				val6.nearClipPlane = 0.05f;
				val6.allowHDR = false;
				((Component)val6).transform.position = position;
				((Component)val6).transform.LookAt(val4);
				int num = (scene ? (size * 9 / 16) : size);
				val2 = (val6.targetTexture = new RenderTexture(size, num, 24, (RenderTextureFormat)0)
				{
					antiAliasing = 8
				});
				val6.aspect = (float)size / (float)num;
				val6.Render();
				RenderTexture active = RenderTexture.active;
				RenderTexture.active = val2;
				Texture2D val8 = new Texture2D(size, num, (TextureFormat)(scene ? 3 : 4), false);
				val8.ReadPixels(new Rect(0f, 0f, (float)size, (float)num), 0, 0);
				val8.Apply();
				RenderTexture.active = active;
				string text = Path.Combine(Paths.ConfigPath, "FulingHeckler_portraits");
				Directory.CreateDirectory(text);
				string text2 = Path.Combine(text, DateTime.Now.ToString("yyyyMMdd-HHmmss-fff") + ".png");
				File.WriteAllBytes(text2, ImageConversion.EncodeToPNG(val8));
				Object.Destroy((Object)(object)val8);
				return text2;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Portrait failed (" + ex.GetType().Name + ": " + ex.Message + ")."));
				return null;
			}
			finally
			{
				foreach (KeyValuePair<GameObject, int> item in dictionary)
				{
					if ((Object)(object)item.Key != (Object)null)
					{
						item.Key.layer = item.Value;
					}
				}
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				if ((Object)(object)val2 != (Object)null)
				{
					val2.Release();
					Object.Destroy((Object)(object)val2);
				}
			}
		}

		private static void Run(Terminal t, string[] a)
		{
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			string text = ((a.Length > 1) ? a[1].ToLowerInvariant() : "status");
			FulingActor current = FulingActor.Current;
			switch (text)
			{
			case "summon":
				Plugin.Instance.TestSummon(emoteTest: false, null, (a.Length > 2) ? string.Join(" ", a, 2, a.Length - 2) : null);
				break;
			case "torch":
				Plugin.Instance.TestSummon(emoteTest: false, true);
				break;
			case "freeze":
			case "unfreeze":
				if ((Object)(object)current == (Object)null)
				{
					t.AddString("No Fuling is out.");
					break;
				}
				current.Freeze(text == "freeze");
				t.AddString((text == "freeze") ? "He holds still (heckler unfreeze lets him go on)." : "He carries on.");
				break;
			case "where":
			{
				if ((Object)(object)current == (Object)null)
				{
					t.AddString("No Fuling is out.");
					break;
				}
				Vector3 position = ((Component)current).transform.position;
				t.AddString($"Fuling at {position.x:0.00} {position.y:0.00} {position.z:0.00}, facing {((Component)current).transform.eulerAngles.y:0}");
				break;
			}
			case "photo":
			case "portrait":
			{
				if ((Object)(object)current == (Object)null)
				{
					t.AddString("No Fuling is out.");
					break;
				}
				string text2 = Portrait(current, F(2, 1.3f), F(3, 0f), F(4, -0.1f), (int)F(5, 1024f), F(6, 0f), text == "photo");
				t.AddString((text2 != null) ? ("Portrait saved: " + text2) : "Couldn't take the portrait (see the log).");
				break;
			}
			case "status":
				t.AddString(string.Format("Fuling Heckler {0}: {1}, {2} quips ({3}), ", "1.0.0", Plugin.Enabled.Value ? "on" : "off", Plugin.Quips.Count, Plugin.Language) + string.Format("{0} loot entries, {1} fire(s) loaded, a Fuling {2}.", Plugin.Loot.Entries.Count, Scenes.Fires.Count, ((Object)(object)current != (Object)null) ? "is out" : "isn't out"));
				break;
			default:
			{
				if (SceneNames.TryGetValue(text, out var value))
				{
					Plugin.Instance.TestScene(value);
				}
				else
				{
					t.AddString("heckler summon [emote] | heckler torch | heckler lurker|redlight|tombstone|copycat|campfire|thief|gifter | heckler freeze | heckler unfreeze | heckler where | heckler portrait|photo [distance] [turn] [height] [size] [above] | heckler status");
				}
				break;
			}
			}
			float F(int i, float d)
			{
				if (a.Length <= i || !float.TryParse(a[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					return d;
				}
				return result;
			}
		}
	}
	public enum When
	{
		First,
		Repeat,
		Milestone,
		Any
	}
	public enum DeathCategory
	{
		Environment,
		Falling,
		Trees,
		Creatures,
		FriendlyFire,
		Other
	}
	public class DeathInfo
	{
		public string HitType;

		public string KillerPrefab;

		public string KillerName;

		public bool KillerIsPlayer;

		public bool KillerIsSelf;
	}
	public class DeathEvent
	{
		public string Key;

		public DeathCategory Category;

		public string KillerName;

		public List<string> Chain;
	}
	public static class DeathClassifier
	{
		private static readonly HashSet<string> Bosses = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Eikthyr", "gd_king", "Bonemass", "Dragon", "GoblinKing", "SeekerQueen", "Fader" };

		public static DeathEvent Classify(DeathInfo d)
		{
			string text;
			DeathCategory category;
			switch (d.HitType ?? "")
			{
			case "Drowning":
				text = "drowning";
				category = DeathCategory.Environment;
				break;
			case "Fall":
				text = "fall";
				category = DeathCategory.Falling;
				break;
			case "CinderFire":
			case "Burning":
				text = "burning";
				category = DeathCategory.Environment;
				break;
			case "Freezing":
				text = "freezing";
				category = DeathCategory.Environment;
				break;
			case "Poisoned":
				text = "poison";
				category = DeathCategory.Environment;
				break;
			case "Smoke":
				text = "smoke";
				category = DeathCategory.Environment;
				break;
			case "EdgeOfWorld":
				text = "edge";
				category = DeathCategory.Environment;
				break;
			case "Water":
			case "AshlandsOcean":
				text = "boiled";
				category = DeathCategory.Environment;
				break;
			case "Tree":
				text = "tree";
				category = DeathCategory.Trees;
				break;
			case "Self":
				text = "self";
				category = DeathCategory.FriendlyFire;
				break;
			case "Cart":
				text = "cart";
				category = DeathCategory.Other;
				break;
			case "Boat":
				text = "boat";
				category = DeathCategory.Other;
				break;
			case "Structural":
				text = "structural";
				category = DeathCategory.Other;
				break;
			case "Stalagtite":
				text = "stalactite";
				category = DeathCategory.Environment;
				break;
			case "AshlandsLava":
				text = "lava";
				category = DeathCategory.Environment;
				break;
			case "Incinerator":
				text = "incinerator";
				category = DeathCategory.Other;
				break;
			case "Catapult":
			case "Turret":
				text = "siege";
				category = DeathCategory.Other;
				break;
			default:
				text = null;
				category = DeathCategory.Other;
				break;
			}
			List<string> list = new List<string>();
			if (text == null)
			{
				if (d.KillerIsSelf)
				{
					text = "self";
					category = DeathCategory.FriendlyFire;
				}
				else if (d.KillerIsPlayer)
				{
					text = "friend";
					category = DeathCategory.FriendlyFire;
				}
				else
				{
					if (!string.IsNullOrEmpty(d.KillerPrefab))
					{
						text = "enemy:" + d.KillerPrefab.ToLowerInvariant();
						category = DeathCategory.Creatures;
						list.Add(text);
						if (Bosses.Contains(d.KillerPrefab))
						{
							list.Add("boss");
						}
						list.Add("enemy");
						list.Add("generic");
						return new DeathEvent
						{
							Key = text,
							Category = category,
							KillerName = d.KillerName,
							Chain = list
						};
					}
					text = "unknown";
					category = DeathCategory.Other;
				}
			}
			list.Add(text);
			list.Add("generic");
			return new DeathEvent
			{
				Key = text,
				Category = category,
				KillerName = d.KillerName,
				Chain = list
			};
		}
	}
	public class QuipLine
	{
		public string Event;

		public When When;

		public string Text;

		public bool UsesKiller => Text.Contains("{killer}");
	}
	public class QuipBook
	{
		public readonly List<QuipLine> Lines = new List<QuipLine>();

		public readonly List<string> Warnings = new List<string>();

		private static readonly Dictionary<string, string> RetiredLines = new Dictionary<string, string>
		{
			{ "enemy:drake | any | Ice-bird freeze you! {me} stay away from mountain. Mountain is mean.", "enemy:hatchling | any | Ice-bird freeze you! {me} stay away from mountain. Mountain is mean." },
			{ "generic | repeat | Tall-one die {count} time. {me} counting on fingers. Need more fingers.", "generic | repeat | Tall-one die {n} times. {me} counting on fingers. Need more fingers." },
			{ "fall | repeat | Tall-one jump, tall-one go splat. {count} splat! {me} love this game.", "fall | repeat | Tall-one jump, tall-one go splat. {n} splats! {me} love this game." },
			{ "tree | repeat | Tree kill you {count} time. {me} think trees on Fuling side.", "tree | repeat | Tree kill you {n} times. {me} think trees on Fuling side." },
			{ "enemy:deer | repeat | Deer get you {count} time. Deer is not even angry. Deer just walking.", "enemy:deer | repeat | Deer get you {n} times. Deer not even angry. Deer just walking." },
			{ "enemy:blob | any | Puddle beat you. Angry puddle. {me} has seen everything now.", "enemy:blob | any | Puddle beat you. Angry puddle. {me} seen everything now." },
			{ "enemy:goblin | any | My cousin get you! Cousin will be so happy! {me} bring cake.", "enemy:goblin | any | {me} cousin get you! Cousin will be so happy! {me} bring cake." },
			{ "enemy:goblinarcher | any | Cousin with bow! Good shot cousin!", "enemy:goblinarcher | any | Cousin with bow! Good shot, cousin!" }
		};

		private static readonly HashSet<string> AddedLines = new HashSet<string> { "lava | first | Floor is lava! Tall-one not know game? {me} know game. {me} WIN game.", "lava | repeat | Hot rock again! {me} smell you from far away. Crispy.", "lava | repeat | Tall-one go swimming in fire-water. {me} stay on cold rock. Smart {me}.", "incinerator | any | Tall-one jump in big junk-box! Box eat tall-one! {me} put junk in there, not tall-ones!", "incinerator | any | Poof! Tall-one gone! Like junk! {me} laughing SO hard!" };

		public int Count => Lines.Count;

		public void Load(IEnumerable<string> rawLines)
		{
			Lines.Clear();
			Warnings.Clear();
			int num = 0;
			foreach (string rawLine in rawLines)
			{
				num++;
				string text = rawLine.Trim();
				if (text.Length == 0 || text.StartsWith("#"))
				{
					continue;
				}
				string[] array = text.Split(new char[1] { '|' }, 3);
				if (array.Length < 3)
				{
					Warnings.Add($"line {num}: expected 'event | when | text', skipped.");
					continue;
				}
				if (!Enum.TryParse<When>(array[1].Trim(), ignoreCase: true, out var result) || !Enum.IsDefined(typeof(When), result))
				{
					Warnings.Add($"line {num}: unknown 'when' '{array[1].Trim()}' (use first, repeat, milestone or any), skipped.");
					continue;
				}
				string text2 = array[2].Trim();
				if (text2.Length != 0)
				{
					Lines.Add(new QuipLine
					{
						Event = array[0].Trim().ToLowerInvariant(),
						When = result,
						Text = text2
					});
				}
			}
		}

		public static string ReadBuiltInQuips(string language)
		{
			if (string.IsNullOrEmpty(language) || language == "English")
			{
				return ReadDefaultQuips();
			}
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using Stream stream = executingAssembly.GetManifestResourceStream("FulingHeckler.Quips." + language + ".txt");
			if (stream == null)
			{
				return null;
			}
			using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
			return streamReader.ReadToEnd();
		}

		public static bool SameText(string a, string b)
		{
			return Norm(a).SequenceEqual(Norm(b));
			static IEnumerable<string> Norm(string t)
			{
				string value;
				return from l in (t ?? "").Replace("\r", "").Split(new char[1] { '\n' })
					select string.Join(" | ", from x in l.Split(new char[1] { '|' })
						select string.Join(" ", x.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))) into l
					where l.Length > 0 && !l.StartsWith("#")
					select (!RetiredLines.TryGetValue(l, out value)) ? l : value into l
					where !AddedLines.Contains(l)
					select l;
			}
		}

		public static string ReadDefaultQuips()
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using Stream stream = executingAssembly.GetManifestResourceStream("FulingHeckler.DefaultQuips.txt");
			if (stream == null)
			{
				return "generic | any | Hehehe! Fuling has no words. Fuling laughing too hard.\n";
			}
			using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
			return streamReader.ReadToEnd();
		}
	}
	public static class Placeholders
	{
		public static string TallOne = "tall-one";

		public static string Something = "something";

		public static Func<int, string> OrdinalWord;

		public static string Fill(string text, string playerName, string killerName, int count)
		{
			return text.Replace("{name}", string.IsNullOrEmpty(playerName) ? TallOne : playerName).Replace("{killer}", string.IsNullOrEmpty(killerName) ? Something : killerName).Replace("{count}", Ordinal(count))
				.Replace("{n}", count.ToString());
		}

		public static string FillMe(string text, string ownName, double nameChance, Random rng)
		{
			if (string.IsNullOrEmpty(text) || text.IndexOf("{me}", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return text;
			}
			string replacement = ((!string.IsNullOrEmpty(ownName) && rng.NextDouble() < nameChance) ? ownName : "Fuling");
			return Regex.Replace(text, "\\{me\\}", replacement, RegexOptions.IgnoreCase);
		}

		public static string Ordinal(int n)
		{
			if (OrdinalWord != null)
			{
				return OrdinalWord(n);
			}
			int num = n % 100;
			string text = ((num >= 11 && num <= 13) ? "th" : ((n % 10 == 1) ? "st" : ((n % 10 == 2) ? "nd" : ((n % 10 == 3) ? "rd" : "th"))));
			return n + text;
		}
	}
	public class PickerSettings
	{
		public float ChanceToComment = 0.6f;

		public bool AlwaysCommentOnFirst = true;

		public float CooldownSeconds = 90f;

		public HashSet<int> Milestones = new HashSet<int> { 5, 10, 25, 50, 100 };

		public static HashSet<int> ParseMilestones(string s)
		{
			HashSet<int> hashSet = new HashSet<int>();
			string[] array = (s ?? "").Split(new char[1] { ',' });
			foreach (string text in array)
			{
				if (int.TryParse(text.Trim(), out var result) && result > 1)
				{
					hashSet.Add(result);
				}
			}
			return hashSet;
		}
	}
	public class Decision
	{
		public string Line;

		public bool IsFirst;

		public bool IsMilestone;

		public string Reason;

		public List<string> DeathChain;

		public bool IsSpecial
		{
			get
			{
				if (!IsFirst)
				{
					return IsMilestone;
				}
				return true;
			}
		}
	}
	public class ImpressionBook
	{
		public readonly Dictionary<string, List<string>> Moves = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		public static ImpressionBook Parse(string s)
		{
			ImpressionBook impressionBook = new ImpressionBook();
			string[] array = (s ?? "").Split(new char[1] { '|' });
			foreach (string text in array)
			{
				int num = text.LastIndexOf(':');
				if (num > 0)
				{
					string text2 = text.Substring(0, num).Trim();
					List<string> list = NameList.Parse(text.Substring(num + 1));
					if (text2.Length > 0 && list.Count > 0)
					{
						impressionBook.Moves[text2] = list;
					}
				}
			}
			return impressionBook;
		}

		public List<string> For(IList<string> chain)
		{
			if (chain != null)
			{
				foreach (string item in chain)
				{
					if (item != null && Moves.TryGetValue(item, out var value))
					{
						return value;
					}
				}
			}
			if (!Moves.TryGetValue("generic", out var value2))
			{
				return new List<string>();
			}
			return value2;
		}
	}
	public static class QuipPicker
	{
		private static readonly Queue<string> Recent = new Queue<string>();

		private const int RecentMemory = 10;

		public static Decision Decide(QuipBook book, DeathEvent ev, int count, PickerSettings s, float secondsSinceLastComment, bool categoryEnabled, Random rng)
		{
			if (!categoryEnabled)
			{
				return Quiet("category turned off");
			}
			bool flag = count == 1;
			bool flag2 = s.Milestones.Contains(count);
			if (!((flag && s.AlwaysCommentOnFirst) || flag2))
			{
				if (secondsSinceLastComment < s.CooldownSeconds)
				{
					return Quiet("on cooldown");
				}
				if (rng.NextDouble() >= (double)s.ChanceToComment)
				{
					return Quiet("chose to stay home");
				}
			}
			When[] whens = ((!flag) ? ((!flag2) ? new When[2]
			{
				When.Repeat,
				When.Any
			} : new When[3]
			{
				When.Milestone,
				When.Repeat,
				When.Any
			}) : new When[2]
			{
				When.First,
				When.Any
			});
			QuipLine quipLine = Find(book, ev.Chain, whens, rng);
			if (quipLine == null)
			{
				return Quiet("no matching lines in quips file");
			}
			string text = (flag2 ? "milestone" : (flag ? "first time" : "repeat"));
			return new Decision
			{
				Line = quipLine.Text,
				IsFirst = flag,
				IsMilestone = flag2,
				Reason = "Fuling heckles (" + text + ")",
				DeathChain = ev.Chain
			};
		}

		private static QuipLine Find(QuipBook book, List<string> chain, When[] whens, Random rng)
		{
			foreach (string key in chain)
			{
				foreach (When w in whens)
				{
					List<QuipLine> list = book.Lines.Where((QuipLine q) => q.Event == key && q.When == w).ToList();
					if (list.Count > 0)
					{
						return PickAvoidingRepeats(list, rng);
					}
				}
			}
			return null;
		}

		private static QuipLine PickAvoidingRepeats(List<QuipLine> candidates, Random rng)
		{
			List<QuipLine> list = candidates.Where((QuipLine q) => !Recent.Contains(q.Text)).ToList();
			List<QuipLine> list2 = ((list.Count > 0) ? list : candidates);
			QuipLine quipLine = list2[rng.Next(list2.Count)];
			Recent.Enqueue(quipLine.Text);
			while (Recent.Count > 10)
			{
				Recent.Dequeue();
			}
			return quipLine;
		}

		public static string PickForTest(QuipBook book, Random rng)
		{
			List<QuipLine> list = book.Lines.Where((QuipLine q) => !q.UsesKiller).ToList();
			if (list.Count != 0)
			{
				return list[rng.Next(list.Count)].Text;
			}
			return null;
		}

		private static Decision Quiet(string reason)
		{
			return new Decision
			{
				Line = null,
				Reason = reason
			};
		}
	}
	public class DeathMemory
	{
		private readonly string _folder;

		private readonly Dictionary<string, Dictionary<string, int>> _cache = new Dictionary<string, Dictionary<string, int>>();

		public DeathMemory(string folder)
		{
			_folder = folder;
		}

		public int Increment(string playerName, string key)
		{
			Dictionary<string, int> dictionary = Load(playerName);
			dictionary.TryGetValue(key, out var value);
			value = (dictionary[key] = value + 1);
			Save(playerName, dictionary);
			return value;
		}

		public int Get(string playerName, string key)
		{
			Load(playerName).TryGetValue(key, out var value);
			return value;
		}

		public void Reset(string playerName)
		{
			_cache[playerName] = new Dictionary<string, int>();
			try
			{
				File.Delete(PathFor(playerName));
			}
			catch
			{
			}
		}

		private Dictionary<string, int> Load(string playerName)
		{
			if (_cache.TryGetValue(playerName, out var value))
			{
				return value;
			}
			value = new Dictionary<string, int>();
			try
			{
				string path = PathFor(playerName);
				if (File.Exists(path))
				{
					string[] array = File.ReadAllLines(path);
					foreach (string text in array)
					{
						int num = text.LastIndexOf('=');
						if (num > 0 && int.TryParse(text.Substring(num + 1), out var result))
						{
							value[text.Substring(0, num)] = result;
						}
					}
				}
			}
			catch
			{
			}
			_cache[playerName] = value;
			return value;
		}

		private void Save(string playerName, Dictionary<string, int> t)
		{
			try
			{
				Directory.CreateDirectory(_folder);
				File.WriteAllLines(PathFor(playerName), (from kv in t
					orderby kv.Key
					select kv.Key + "=" + kv.Value).ToArray());
			}
			catch
			{
			}
		}

		private string PathFor(string playerName)
		{
			string text = new string((playerName ?? "unknown").Select((char c) => (!Path.GetInvalidFileNameChars().Contains(c)) ? c : '_').ToArray());
			return Path.Combine(_folder, text + ".txt");
		}
	}
	public static class NameList
	{
		public static List<string> Parse(string s)
		{
			return (from x in (s ?? "").Split(new char[1] { ',' })
				select x.Trim() into x
				where x.Length > 0
				select x).ToList();
		}
	}
	public class Fit
	{
		public float Up;

		public float Forward;

		public float Side;

		public float Tilt;

		public float Turn;

		public float Size = 1f;

		public static readonly Fit Identity = new Fit();

		public override string ToString()
		{
			CultureInfo c = CultureInfo.InvariantCulture;
			return string.Join(", ", new float[6] { Up, Forward, Side, Tilt, Turn, Size }.Select((float v) => v.ToString("0.###", c)).ToArray());
		}

		public static bool TryParse(string s, out Fit fit)
		{
			fit = null;
			string[] array = (s ?? "").Split(new char[1] { ',' });
			if (array.Length != 6)
			{
				return false;
			}
			float[] array2 = new float[6];
			for (int i = 0; i < 6; i++)
			{
				if (!float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i]))
				{
					return false;
				}
			}
			fit = new Fit
			{
				Up = array2[0],
				Forward = array2[1],
				Side = array2[2],
				Tilt = array2[3],
				Turn = array2[4],
				Size = ((array2[5] <= 0f) ? 1f : array2[5])
			};
			return true;
		}
	}
	public class FitBook
	{
		public readonly Dictionary<string, Fit> Fits = new Dictionary<string, Fit>(StringComparer.OrdinalIgnoreCase);

		public static FitBook Parse(string s)
		{
			FitBook fitBook = new FitBook();
			string[] array = (s ?? "").Split(new char[1] { '|' });
			foreach (string text in array)
			{
				int num = text.IndexOf(':');
				if (num > 0)
				{
					string text2 = text.Substring(0, num).Trim();
					if (text2.Length > 0 && Fit.TryParse(text.Substring(num + 1), out var fit))
					{
						fitBook.Fits[text2] = fit;
					}
				}
			}
			return fitBook;
		}

		public Fit For(string helmet)
		{
			if (helmet != null && Fits.TryGetValue(helmet, out var value))
			{
				return value;
			}
			if (!Fits.TryGetValue("default", out var value2))
			{
				return Fit.Identity;
			}
			return value2;
		}

		public bool HasOwn(string helmet)
		{
			if (helmet != null)
			{
				return Fits.ContainsKey(helmet);
			}
			return false;
		}

		public override string ToString()
		{
			IOrderedEnumerable<string> source = Fits.Keys.OrderBy<string, string>((string k) => (!k.Equals("default", StringComparison.OrdinalIgnoreCase)) ? k : "", StringComparer.OrdinalIgnoreCase);
			return string.Join(" | ", source.Select((string k) => $"{k}: {Fits[k]}").ToArray());
		}
	}
	public enum LootTier
	{
		Junk,
		Meh,
		Nice,
		Treasure
	}
	public class LootEntry
	{
		public string Item;

		public double Weight;

		public int Min = 1;

		public int Max = 1;

		public LootTier Tier;
	}
	public class LootTable
	{
		public readonly List<LootEntry> Entries = new List<LootEntry>();

		public readonly List<string> Warnings = new List<string>();

		public void Load(IEnumerable<string> rawLines)
		{
			Entries.Clear();
			Warnings.Clear();
			int num = 0;
			foreach (string rawLine in rawLines)
			{
				num++;
				string text = rawLine.Trim();
				if (text.Length == 0 || text.StartsWith("#"))
				{
					continue;
				}
				string[] array = (from p in text.Split(new char[1] { '|' })
					select p.Trim()).ToArray();
				if (array.Length < 2 || array[0].Length == 0)
				{
					Warnings.Add($"line {num}: expected 'item | weight | amount | tier', skipped.");
					continue;
				}
				LootEntry lootEntry = new LootEntry
				{
					Item = array[0]
				};
				if (!double.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out lootEntry.Weight) || lootEntry.Weight <= 0.0)
				{
					Warnings.Add($"line {num}: weight '{array[1]}' must be a positive number, skipped.");
					continue;
				}
				if (array.Length >= 3 && array[2].Length > 0)
				{
					string[] array2 = array[2].Split(new char[1] { '-' });
					if (!int.TryParse(array2[0].Trim(), out lootEntry.Min) || lootEntry.Min < 1)
					{
						Warnings.Add($"line {num}: amount '{array[2]}' should look like 2 or 1-3, skipped.");
						continue;
					}
					lootEntry.Max = lootEntry.Min;
					if (array2.Length > 1 && (!int.TryParse(array2[1].Trim(), out lootEntry.Max) || lootEntry.Max < lootEntry.Min))
					{
						Warnings.Add($"line {num}: amount '{array[2]}' should look like 2 or 1-3, skipped.");
						continue;
					}
				}
				if (array.Length >= 4 && array[3].Length > 0 && !Enum.TryParse<LootTier>(array[3], ignoreCase: true, out lootEntry.Tier))
				{
					Warnings.Add($"line {num}: tier '{array[3]}' should be junk, meh, nice or treasure; using junk.");
					lootEntry.Tier = LootTier.Junk;
				}
				Entries.Add(lootEntry);
			}
		}

		public LootEntry Pick(Random rng)
		{
			double num = Entries.Sum((LootEntry e) => e.Weight);
			if (num <= 0.0)
			{
				return null;
			}
			double num2 = rng.NextDouble() * num;
			foreach (LootEntry entry in Entries)
			{
				num2 -= entry.Weight;
				if (num2 < 0.0)
				{
					return entry;
				}
			}
			return Entries[Entries.Count - 1];
		}

		public static string ReadDefault()
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using Stream stream = executingAssembly.GetManifestResourceStream("FulingHeckler.DefaultLoot.txt");
			if (stream == null)
			{
				return "Pukeberries | 1 | 1-2 | junk\n";
			}
			using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
			return streamReader.ReadToEnd();
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class PlayerOnDeathPatch
	{
		private static Player _lastHandled;

		private static void Prefix(Player __instance)
		{
			try
			{
				if ((Object)(object)Plugin.Instance == (Object)null || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)__instance == (Object)(object)_lastHandled)
				{
					return;
				}
				_lastHandled = __instance;
				DeathInfo deathInfo = new DeathInfo();
				HitData value = Traverse.Create((object)__instance).Field("m_lastHit").GetValue<HitData>();
				if (value != null)
				{
					deathInfo.HitType = ((object)Unsafe.As<HitType, HitType>(ref value.m_hitType)/*cast due to .constrained prefix*/).ToString();
					Character attacker = value.GetAttacker();
					if ((Object)(object)attacker != (Object)null)
					{
						Player val = (Player)(object)((attacker is Player) ? attacker : null);
						if (val != null)
						{
							deathInfo.KillerIsPlayer = true;
							deathInfo.KillerIsSelf = (Object)(object)val == (Object)(object)__instance;
							deathInfo.KillerName = val.GetPlayerName();
						}
						else
						{
							deathInfo.KillerPrefab = Utils.GetPrefabName(((Component)attacker).gameObject);
							deathInfo.KillerName = ((Localization.instance != null) ? Localization.instance.Localize(attacker.m_name) : attacker.m_name);
						}
					}
				}
				Plugin.Instance.OnLocalPlayerDied(__instance, deathInfo);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Fuling couldn't read that death: " + ex.Message));
				}
			}
		}
	}
	internal class Delivery : MonoBehaviour
	{
		private Decision _pending;

		private Player _deadBody;

		private float _aliveSince = -1f;

		private static readonly FieldRef<Chat, float> ChatHideTimer = AccessTools.FieldRefAccess<Chat, float>("m_hideTimer");

		public void QueueAfterRespawn(Player deadBody, Decision d)
		{
			_pending = d;
			_deadBody = deadBody;
			_aliveSince = -1f;
		}

		private void Update()
		{
			if (_pending == null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)localPlayer == (Object)(object)_deadBody || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting())
			{
				_aliveSince = -1f;
				return;
			}
			if (_aliveSince < 0f)
			{
				_aliveSince = Time.time;
			}
			if (!(Time.time - _aliveSince < Plugin.DeliveryDelaySeconds.Value))
			{
				Decision pending = _pending;
				_pending = null;
				string line = pending.Line;
				bool isSpecial = pending.IsSpecial;
				bool isMilestone = pending.IsMilestone;
				List<string> deathChain = pending.DeathChain;
				Deliver(localPlayer, line, isSpecial, isMilestone, emoteTest: false, null, isTest: false, null, null, forceOutfit: false, fitting: false, deathChain);
			}
		}

		public void Deliver(Player p, string line, bool special, bool milestone, bool emoteTest = false, bool? forceTorch = null, bool isTest = false, string forcedEmote = null, string forcedHelmet = null, bool forceOutfit = false, bool fitting = false, List<string> deathChain = null)
		{
			DeliveryMode value = Plugin.DeliveryModeSetting.Value;
			if (isTest || emoteTest || forceTorch.HasValue || value == DeliveryMode.Always || (value == DeliveryMode.SpecialOnly && special))
			{
				Performance performance = new Performance();
				performance.Line = line;
				performance.IsMilestone = milestone;
				performance.DeathChain = deathChain;
				performance.EmoteTest = emoteTest;
				performance.ForceTorch = forceTorch;
				performance.ForcedEmote = forcedEmote;
				performance.ForcedHelmet = forcedHelmet;
				performance.ForceOutfit = forceOutfit;
				performance.Fitting = fitting;
				performance.FallbackToText = ShowLine;
				Performance show = performance;
				if (FulingActor.TrySpawn(p, show))
				{
					return;
				}
			}
			ShowLine(line);
		}

		internal static void ShowLine(string text)
		{
			//IL_00aa: 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)
			TextPosition value = Plugin.TextPos.Value;
			if (value == TextPosition.Off)
			{
				Plugin.Log.LogInfo((object)"Fuling's line by text: none (Text Position is Off).");
				return;
			}
			string text2 = Plugin.FillMe(text);
			Plugin.Log.LogInfo((object)$"Fuling's line by text ({value}): {text2}");
			if (value == TextPosition.Chat && (Object)(object)Chat.instance != (Object)null)
			{
				((Terminal)Chat.instance).AddString(Look.Name, text2, (Type)1, false);
				try
				{
					ChatHideTimer.Invoke(Chat.instance) = 0f;
					return;
				}
				catch
				{
					return;
				}
			}
			if (!((Object)(object)MessageHud.instance == (Object)null))
			{
				string text3 = "<color=#E07A3A><b>" + Look.Name + ":</b></color> " + text2;
				MessageType val = (MessageType)((value != TextPosition.Center) ? 1 : 2);
				MessageHud.instance.ShowMessage(val, text3, 0, (Sprite)null, false, true);
			}
		}

		public static void ShowPlainText(string text)
		{
			if (!((Object)(object)MessageHud.instance == (Object)null))
			{
				MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
		}
	}
	internal class EmotePlayer
	{
		private static List<AnimationClip> _playerClips;

		private static bool _loggedClips;

		private readonly Animator _animator;

		private readonly RuntimeAnimatorController _original;

		private readonly AnimationClip _stagger;

		private readonly Dictionary<AnimationClip, AnimationClip> _base = new Dictionary<AnimationClip, AnimationClip>();

		private static bool _loggedOwnClips;

		private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string>
		{
			{ "comehere", "GetOverHere" },
			{ "challenge", "ChallengeAccepted" }
		};

		public bool Ready
		{
			get
			{
				if ((Object)(object)_original != (Object)null)
				{
					return (Object)(object)_stagger != (Object)null;
				}
				return false;
			}
		}

		public EmotePlayer(Animator fuling)
		{
			_animator = fuling;
			_original = (((Object)(object)fuling != (Object)null) ? fuling.runtimeAnimatorController : null);
			if ((Object)(object)_original == (Object)null)
			{
				return;
			}
			_stagger = ((IEnumerable<AnimationClip>)_original.animationClips).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip c) => (Object)(object)c != (Object)null && ((Object)c).name.IndexOf("stagger", StringComparison.OrdinalIgnoreCase) >= 0));
			if ((Object)(object)_stagger == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Emotes: couldn't find the Fuling's stagger animation to borrow; he'll use his own moves.");
			}
			if (!_loggedOwnClips)
			{
				_loggedOwnClips = true;
				Plugin.Log.LogInfo((object)("Fuling's own animations: " + string.Join(", ", (from n in (from c in _original.animationClips
						where (Object)(object)c != (Object)null
						select ((Object)c).name).Distinct()
					orderby n
					select n).ToArray())));
			}
		}

		public void SetTorchPose(string runName, string idleName)
		{
			if ((Object)(object)_original == (Object)null)
			{
				return;
			}
			AnimationClip val = Find(runName);
			AnimationClip val2 = Find(idleName);
			List<string> list = new List<string>();
			foreach (AnimationClip item in _original.animationClips.Where((AnimationClip c) => (Object)(object)c != (Object)null).Distinct())
			{
				HashSet<string> hashSet = new HashSet<string>(Regex.Split(((Object)item).name.ToLowerInvariant(), "[^a-z0-9]+"));
				if (!hashSet.Contains("attack") && !hashSet.Contains("sleep") && !hashSet.Contains("dead") && !hashSet.Contains("death"))
				{
					if ((Object)(object)val != (Object)null && hashSet.Contains("run"))
					{
						_base[item] = val;
						list.Add(((Object)item).name + "->" + ((Object)val).name);
					}
					else if ((Object)(object)val2 != (Object)null && hashSet.Any((string w) => w == "idle" || w.StartsWith("idle")))
					{
						_base[item] = val2;
						list.Add(((Object)item).name + "->" + ((Object)val2).name);
					}
				}
			}
			Plugin.Log.LogInfo((object)((list.Count > 0) ? ("Torch pose: " + string.Join(", ", list.ToArray())) : $"Torch pose: nothing to swap (run '{runName}' found={(Object)(object)val != (Object)null}, idle '{idleName}' found={(Object)(object)val2 != (Object)null})."));
			Apply(null);
		}

		private void Apply(AnimationClip emote)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			if ((Object)(object)_original == (Object)null)
			{
				return;
			}
			if (_base.Count == 0 && (Object)(object)emote == (Object)null)
			{
				if ((Object)(object)_animator.runtimeAnimatorController != (Object)(object)_original)
				{
					_animator.runtimeAnimatorController = _original;
				}
				return;
			}
			AnimatorOverrideController val = new AnimatorOverrideController(_original);
			foreach (KeyValuePair<AnimationClip, AnimationClip> item in _base)
			{
				val[item.Key] = item.Value;
			}
			if ((Object)(object)emote != (Object)null && (Object)(object)_stagger != (Object)null)
			{
				val[_stagger] = emote;
			}
			_animator.runtimeAnimatorController = (RuntimeAnimatorController)(object)val;
		}

		public static List<AnimationClip> PlayerClips()
		{
			if (_playerClips != null && _playerClips.Count > 0)
			{
				return _playerClips;
			}
			List<AnimationClip> list = new List<AnimationClip>();
			Player localPlayer = Player.m_localPlayer;
			Animator val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).GetComponentInChildren<Animator>() : null);
			if ((Object)(object)val != (Object)null && (Object)(object)val.runtimeAnimatorController != (Object)null)
			{
				list = (from c in val.runtimeAnimatorController.animationClips
					where (Object)(object)c != (Object)null
					group c by ((Object)c).name into g
					select g.First() into c
					orderby ((Object)c).name
					select c).ToList();
			}
			if (!_loggedClips && list.Count > 0)
			{
				_loggedClips = true;
				Plugin.Log.LogInfo((object)($"Player animations ({list.Count}): " + string.Join(", ", list.Select((AnimationClip c) => ((Object)c).name).ToArray())));
			}
			_playerClips = list;
			return list;
		}

		public static AnimationClip Find(string wanted)
		{
			if (string.IsNullOrEmpty(wanted))
			{
				return null;
			}
			if (Aliases.TryGetValue(Norm(wanted), out var value))
			{
				wanted = value;
			}
			string w = Norm(wanted);
			List<AnimationClip> list = (from c in PlayerClips()
				where Norm(((Object)c).name).Contains(w)
				select c).ToList();
			if (list.Count == 0)
			{
				return null;
			}
			return ((IEnumerable<AnimationClip>)list).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip c) => Norm(((Object)c).name) == w)) ?? ((IEnumerable<AnimationClip>)list).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip c) => ((Object)c).name.IndexOf("emote", StringComparison.OrdinalIgnoreCase) >= 0)) ?? list.OrderBy((AnimationClip c) => ((Object)c).name.Length).First();
		}

		private static string Norm(string s)
		{
			return new string((s ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
		}

		public AnimationClip TryPlay(IList<string> wanted, Random rng)
		{
			if (!Ready || wanted == null || wanted.Count == 0)
			{
				return null;
			}
			List<AnimationClip> list = (from c in wanted.Select(Find)
				where (Object)(object)c != (Object)null
				select c).Distinct().ToList();
			if (list.Count == 0)
			{
				Plugin.Log.LogInfo((object)("Emotes: none of [" + string.Join(", ", wanted.ToArray()) + "] matched a player animation. See the 'Player animations' log line for names."));
				return null;
			}
			AnimationClip val = list[rng.Next(list.Count)];
			Apply(val);
			_animator.SetTrigger("stagger");
			Plugin.DebugLog("Fuling performs " + ((Object)val).name + ".");
			return val;
		}

		public void Restore()
		{
			Apply(null);
		}
	}
	internal static class EmoteExperiment
	{
		public static void Run(Animator fuling, EmotePlayer emotes, Random rng)
		{
			ManualLogSource log = Plugin.Log;
			Player localPlayer = Player.m_localPlayer;
			Animator val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).GetComponentInChildren<Animator>() : null);
			if ((Object)(object)fuling == (Object)null || (Object)(object)val == (Object)null)
			{
				log.LogWarning((object)"Emote test: no Fuling or no player.");
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(from t in ((Component)val).GetComponentsInChildren<Transform>(true)
				select ((Object)t).name);
			HashSet<string> hashSet2 = new HashSet<string>(from t in ((Component)fuling).GetComponentsInChildren<Transform>(true)
				select ((Object)t).name);
			log.LogInfo((object)($"Emote test: player rig humanoid={val.isHuman}, Fuling rig humanoid={fuling.isHuman}. " + $"Bone names in common: {hashSet.Count(hashSet2.Contains)} of the player's {hashSet.Count}."));
			EmotePlayer.PlayerClips();
			AnimationClip val2 = emotes?.TryPlay(new string[1] { "wave" }, rng);
			log.LogInfo((object)(((Object)(object)val2 != (Object)null) ? ("Emote test: playing '" + ((Object)val2).name + "'.") : "Emote test: couldn't play a wave."));
		}
	}
	internal enum SceneKind
	{
		Heckle,
		Lurker,
		RedLight,
		Tombstone,
		Copycat,
		Campfire,
		CoinThief,
		Gifter
	}
	internal class Performance
	{
		public SceneKind Scene;

		public Vector3? SpawnAt;

		public Vector3? FacePoint;

		public Quaternion? SpawnRotation;

		public string SeatPose;

		public string Line;

		public bool IsMilestone;

		public List<string> DeathChain;

		public bool EmoteTest;

		public bool? ForceTorch;

		public string ForcedEmote;

		public string ForcedHelmet;

		public bool ForceOutfit;

		public bool Fitting;

		public Action<string> FallbackToText;

		public bool Puppet;

		public string PuppetHelmet;

		public List<string> PuppetOutfit;

		public bool PuppetTorch;
	}
	internal class FulingActor : MonoBehaviour
	{
		private const float SpawnDistance = 18f;

		private const float RunSpeed = 6f;

		private const float LeaveDistance = 28f;

		private const float MaxLifetime = 60f;

		private const float BubbleHeight = 2.1f;

		private const float BubbleCullDistance = 30f;

		private const float TurnSpeed = 540f;

		private static FulingActor _current;

		private static bool _loggedAnimatorParams;

		private static GameObject _staging;

		private static readonly Random Rng = new Random();

		private Animator _animator;

		private readonly HashSet<string> _animParams = new HashSet<string>();

		private Performance _show;

		private EmotePlayer _emotes;

		private bool _hasTorch;

		private GameObject _prefab;

		private Transform _sourceVisual;

		private Transform _copyVisual;

		private SkinnedMeshRenderer _body;

		private GameObject _helmet;

		private HelmetFitter _fitter;

		private bool _endFitting;

		private SkinColor _skin;

		private bool _animReady;

		private bool _talkDone;

		private bool _playingDead;

		private static readonly string[] BuiltInFleeLines = new string[5] { "Gotta go!", "Too close! {me} go!", "Personal space! Bye!", "NOPE!", "{me} not touch you! Bye bye!" };

		private string _helmetItem;

		private string _emoteNow = "";

		private bool _deadNow;

		private readonly List<string> _outfitWorn = new List<string>();

		private float _speedNow;

		private SoundMoment? _loopSent;

		private float _pokeSpotted = -99f;

		private float _pokeWatched = -99f;

		private float _pokeClose = -99f;

		private VisitCaster _net;

		private float _nextMove;

		private float _nextState;

		private static readonly Dictionary<string, FulingActor> Puppets = new Dictionary<string, FulingActor>();

		private static readonly HashSet<string> FailedPuppets = new HashSet<string>();

		private string _puppetKey;

		private Vector3 _offset;

		private Vector3 _targetPos;

		private float _targetYaw;

		private float _lastHeard;

		private float _bornAt;

		private float _nextPoke;

		private float _seenFor;

		private SoundMoment? _puppetLoop;

		private long _directorId;

		private int _visit;

		private float _notice;

		private float _watchAngle;

		private float _catch;

		private bool _isEcho;

		private float _frozenClose;

		private string _pendingEmote;

		private float _pendingEmoteAt;

		private const float StepUp = 0.3f;

		private const float BodyRadius = 0.4f;

		private const float KneeHeight = 0.35000002f;

		private const float ChestHeight = 0.9f;

		private const float RepathSeconds = 0.5f;

		private static readonly float[] Sidesteps = new float[6] { 35f, -35f, 70f, -70f, 105f, -105f };

		private static int _groundMask = -1;

		private static int _terrainLayer = -1;

		private static readonly List<Vector3> _probePath = new List<Vector3>();

		private readonly List<Vector3> _path = new List<Vector3>();

		private bool _frozen;

		private Vector3 _pathGoal;

		private float _pathAt = -99f;

		private bool _havePath;

		public static FulingActor Current => _current;

		public string HelmetName
		{
			get
			{
				if (!((Object)(object)_helmet != (Object)null))
				{
					return null;
				}
				return ((Object)_helmet).name;
			}
		}

		public bool IsFitting
		{
			get
			{
				if (_show != null && _show.Fitting)
				{
					return !_endFitting;
				}
				return false;
			}
		}

		internal Vector3 Offset => _offset;

		private Vector3 Head => ((Component)this).transform.position + Vector3.up * 1.2f;

		private static int GroundMask
		{
			get
			{
				if (_groundMask == -1)
				{
					_groundMask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "terrain", "piece", "vehicle" });
					_terrainLayer = LayerMask.NameToLayer("terrain");
				}
				return _groundMask;
			}
		}

		public static bool TrySpawn(Player player, Performance show)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)player == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
				{
					return false;
				}
				if ((Object)(object)_current != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_current).gameObject);
				}
				string type = Look.Type;
				GameObject prefab = ZNetScene.instance.GetPrefab(type);
				if ((Object)(object)prefab == (Object)null)
				{
					Plugin.Log.LogWarning((object)("No creature called '" + type + "' in this game. Check 'Fuling Type' in the config."));
					return false;
				}
				Vector3 spawn;
				if (show.SpawnAt.HasValue)
				{
					spawn = show.SpawnAt.Value;
				}
				else if (!FindSpawnPoint(player, out spawn))
				{
					Plugin.DebugLog("No dry ground nearby for the Fuling; using text instead.");
					return false;
				}
				Animator componentInChildren = prefab.GetComponentInChildren<Animator>(true);
				GameObject val = BuildVisualCopy(prefab, componentInChildren, spawn);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				FulingActor fulingActor = val.AddComponent<FulingActor>();
				fulingActor._show = show;
				fulingActor._prefab = prefab;
				fulingActor._sourceVisual = ((Component)componentInChildren).transform;
				fulingActor._copyVisual = val.transform.Find("Visual");
				fulingActor._skin = new SkinColor(((Component)fulingActor._copyVisual).GetComponentsInChildren<Renderer>(true));
				fulingActor._skin.Apply();
				fulingActor.DressUp();
				fulingActor.InitAnimator();
				_current = fulingActor;
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't summon the Fuling (" + ex.GetType().Name + ": " + ex.Message + "). Using text instead."));
				return false;
			}
		}

		private static GameObject BuildVisualCopy(GameObject prefab, Animator sourceAnim, Vector3 position)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)sourceAnim == (Object)null)
			{
				Plugin.Log.LogWarning((object)("'" + ((Object)prefab).name + "' has no animated model to borrow."));
				return null;
			}
			if ((Object)(object)_staging == (Object)null)
			{
				_staging = new GameObject("FulingHeckler_Staging");
				_staging.SetActive(false);
				Object.DontDestroyOnLoad((Object)(object)_staging);
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)sourceAnim).gameObject, _staging.transform, false);
			((Object)val).name = "Visual";
			Gear.Strip(val);
			if ((Object)(object)val.GetComponentInChildren<ZNetView>(true) != (Object)null)
			{
				Plugin.Log.LogError((object)"Safety check failed: the Fuling copy still had network components. Not spawning.");
				Object.DestroyImmediate((Object)(object)val);
				return null;
			}
			GameObject val2 = new GameObject("FulingHeckler_Fuling");
			val2.transform.position = position;
			val.transform.SetParent(val2.transform, false);
			val.transform.localPosition = Vector3.zero;
			val.transform.localRotation = Quaternion.identity;
			return val2;
		}

		private void DressUp()
		{
			Gear.LogOwnGearOnce(_prefab);
			_body = Gear.FindBodyOnCopy(_prefab, _sourceVisual, _copyVisual);
			if (_show.Puppet)
			{
				DressAsTold();
				return;
			}
			string text = _show.ForcedHelmet;
			if (text == null && Rng.NextDouble() < (double)Look.HelmetChance)
			{
				List<string> list = NameList.Parse(Look.Helmets);
				if (list.Count > 0)
				{
					text = list[Rng.Next(list.Count)];
				}
			}
			if (text != null)
			{
				WearHelmet(text, _show.ForcedHelmet != null);
			}
			foreach (string item in NameList.Parse(Look.OutfitItems))
			{
				if (_show.ForceOutfit || !(Rng.NextDouble() >= (double)Look.OutfitPieceChance))
				{
					if (Gear.TryAttach(item, null, _body, _copyVisual, 1f, out var reason))
					{
						_outfitWorn.Add(item);
						Plugin.DebugLog("Fuling is wearing " + item + ".");
					}
					else
					{
						Plugin.Log.LogInfo((object)("Fuling couldn't wear " + item + ": " + reason));
					}
				}
			}
			bool? forceTorch = _show.ForceTorch;
			bool num;
			if (!forceTorch.HasValue)
			{
				if (!IsNight())
				{
					return;
				}
				num = Rng.NextDouble() < (double)Look.TorchAtNightChance;
			}
			else
			{
				num = forceTorch == true;
			}
			if (num)
			{
				Transform rigidJoint = Gear.FindSlotOnCopy(_prefab, _sourceVisual, _copyVisual, "m_rightHand");
				if (Gear.TryAttach(Look.TorchItem, rigidJoint, _body, _copyVisual, Look.HeldItemSize, out var reason2))
				{
					Plugin.DebugLog("Fuling brought a torch.");
					_hasTorch = true;
				}
				else
				{
					Plugin.Log.LogInfo((object)("Fuling couldn't hold " + Look.TorchItem + ": " + reason2));
				}
			}
		}

		public bool WearHelmet(string name, bool logResult)
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_helmet != (Object)null)
			{
				Object.Destroy((Object)(object)_helmet);
				_helmet = null;
				_fitter = null;
			}
			_helmetItem = null;
			Transform val = Gear.FindSlotOnCopy(_prefab, _sourceVisual, _copyVisual, "m_helmet");
			string reason;
			GameObject attached;
			Vector3 itemWorldScale;
			bool flag = Gear.TryAttach(name, val, _body, _copyVisual, 1f, out reason, out attached, out itemWorldScale);
			if (flag)
			{
				_helmet = attached;
				_helmetItem = name;
				if ((Object)(object)val != (Object)null && (Object)(object)attached.transform.parent == (Object)(object)val)
				{
					_fitter = attached.AddComponent<HelmetFitter>();
					_fitter.ItemName = name;
					_fitter.Init(((Component)this).transform, val, itemWorldScale);
					_fitter.Apply((_show.Fitting ? Plugin.SavedFits() : Look.Fits()).For(name));
				}
			}
			if (logResult)
			{
				Plugin.Log.LogInfo((object)(flag ? ("Helmet: wearing " + name + (((Object)(object)_fitter != (Object)null) ? "" : " (bends with the body, no fitting needed)") + ".") : ("Helmet: couldn't wear " + name + ": " + reason)));
			}
			else if (!flag)
			{
				Plugin.Log.LogInfo((object)("Fuling couldn't wear " + name + ": " + reason));
			}
			return flag;
		}

		public void EndFitting()
		{
			_endFitting = true;
		}

		private void Update()
		{
			_skin?.Apply();
			if (IsFitting && (Object)(object)_fitter != (Object)null)
			{
				_fitter.Apply(Plugin.SliderFit());
			}
		}

		internal static bool IsNight()
		{
			try
			{
				return (Object)(object)EnvMan.instance != (Object)null && EnvMan.IsNight();
			}
			catch
			{
				return false;
			}
		}

		private static bool FindSpawnPoint(Player player, out Vector3 spawn)
		{
			return PickSpot(player, SpawnCandidates(player), out spawn);
		}

		private static IEnumerable<Vector3> SpawnCandidates(Player player)
		{
			Vector3 origin = ((Component)player).transform.position;
			Vector3 look = ViewForward(player);
			float[] angles = new float[9] { 0f, 25f, -25f, 50f, -50f, 75f, -75f, 110f, -110f };
			float[] array = new float[2] { 18f, 10.8f };
			float[] array2 = array;
			foreach (float dist in array2)
			{
				float[] array3 = angles;
				foreach (float num in array3)
				{
					yield return origin + Quaternion.Euler(0f, num + Random.Range(-10f, 10f), 0f) * look * dist;
				}
			}
		}

		private static Vector3 ViewForward(Player player)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			Transform val = (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : null);
			Vector3 val2 = (((Object)(object)val != (Object)null) ? val.forward : ((Component)player).transform.forward);
			val2.y = 0f;
			if (!(((Vector3)(ref val2)).sqrMagnitude < 0.01f))
			{
				return ((Vector3)(ref val2)).normalized;
			}
			return Vector3.forward;
		}

		private static bool IsUnderwater(Vector3 p)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			float num = 30f;
			try
			{
				if ((Object)(object)ZoneSystem.instance != (Object)null)
				{
					num = ZoneSystem.instance.m_waterLevel;
				}
			}
			catch
			{
			}
			return p.y < num - 0.3f;
		}

		private void InitAnimator()
		{
			if (_animReady)
			{
				return;
			}
			_animReady = true;
			_animator = ((Component)this).GetComponentInChildren<Animator>(true);
			if (!((Object)(object)_animator != (Object)null))
			{
				return;
			}
			_animator.applyRootMotion = false;
			_animator.cullingMode = (AnimatorCullingMode)0;
			try
			{
				_animator.fireEvents = false;
			}
			catch
			{
			}
			AnimatorControllerParameter[] parameters = _animator.parameters;
			foreach (AnimatorControllerParameter val in parameters)
			{
				_animParams.Add(val.name);
			}
			_emotes = new EmotePlayer(_animator);
			if (_hasTorch && Plugin.TorchPose.Value)
			{
				_emotes.SetTorchPose(Plugin.TorchRunAnimation.Value, Plugin.TorchIdleAnimation.Value);
			}
			if (!_loggedAnimatorParams)
			{
				_loggedAnimatorParams = true;
				string text = string.Join(", ", _animator.parameters.Select((AnimatorControllerParameter x) => $"{x.name} ({x.type})").ToArray());
				Plugin.Log.LogInfo((object)("Fuling animator parameters: " + text));
			}
		}

		private void Start()
		{
			InitAnimator();
			if (_show.Puppet)
			{
				StartPuppet();
				return;
			}
			if (!_show.Fitting)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject, (_show.Scene == SceneKind.Heckle) ? 60f : 240f);
			}
			StartBroadcasting();
			switch (_show.Scene)
			{
			case SceneKind.Lurker:
				((MonoBehaviour)this).StartCoroutine(Lurk());
				break;
			case SceneKind.RedLight:
				((MonoBehaviour)this).StartCoroutine(RedLight());
				break;
			case SceneKind.Tombstone:
				((MonoBehaviour)this).StartCoroutine(TombstoneDance());
				break;
			case SceneKind.Campfire:
				((MonoBehaviour)this).StartCoroutine(CampfireSquat());
				break;
			case SceneKind.Copycat:
				PoofHere(Plugin.ArrivalEffects.Value);
				((MonoBehaviour)this).StartCoroutine(Copycat());
				break;
			case SceneKind.Gifter:
				((MonoBehaviour)this).StartCoroutine(Gifter());
				break;
			case SceneKind.CoinThief:
				PoofHere(Plugin.ArrivalEffects.Value);
				Sfx(SoundMoment.Arrive);
				((MonoBehaviour)this).StartCoroutine(CoinThief());
				break;
			default:
				PoofHere(Plugin.ArrivalEffects.Value);
				Sfx(SoundMoment.Arrive);
				((MonoBehaviour)this).StartCoroutine(Perform());
				break;
			}
		}

		private IEnumerator Perform()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				yield break;
			}
			float num = Mathf.Min(Plugin.StopDistanceMin.Value, Plugin.StopDistanceMax.Value);
			float num2 = Mathf.Max(Plugin.StopDistanceMin.Value, Plugin.StopDistanceMax.Value);
			float stopAt = (_show.Fitting ? 3f : Random.Range(num, num2));
			float timeout = 10f;
			while (timeout > 0f)
			{
				localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					Object.Destroy((Object)(object)((Component)this).gameObject);
					yield break;
				}
				Vector3 val = Flat(((Component)localPlayer).transform.position - ((Component)this).transform.position);
				float magnitude = ((Vector3)(ref val)).magnitude;
				if (magnitude <= stopAt + 0.15f)
				{
					break;
				}
				MoveToward(((Vector3)(ref val)).normalized, Mathf.Min(6f * Time.deltaTime, magnitude - stopAt), 6f, ((Component)localPlayer).transform.position);
				timeout -= Time.deltaTime;
				yield return null;
			}
			if (_show.Fitting)
			{
				SetSpeed(0f);
				TrySayLine("{me} stand still. Make {me} pretty!", 4f);
				while (!_endFitting)
				{
					Player localPlayer2 = Player.m_localPlayer;
					if ((Object)(object)localPlayer2 == (Object)null)
					{
						Object.Destroy((Object)(object)((Component)this).gameObject);
						yield break;
					}
					FaceDirection(Flat(((Component)localPlayer2).transform.position - ((Component)this).transform.position));
					yield return null;
				}
				TrySayLine("{me} look GOOD