Decompiled source of BuriedTreasures v1.3.9

BepInEx/plugins/BuriedTreasures/BuriedTreasures.dll

Decompiled a month 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.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BuriedTreasures.Api;
using BuriedTreasures.Bottle;
using BuriedTreasures.Burying;
using BuriedTreasures.Chest;
using BuriedTreasures.Digging;
using BuriedTreasures.Islands;
using BuriedTreasures.Items;
using BuriedTreasures.Loot;
using BuriedTreasures.Memo;
using BuriedTreasures.Memo.Messages;
using BuriedTreasures.Memo.TreasureMap;
using BuriedTreasures.Models;
using BuriedTreasures.Save;
using BuriedTreasures.Twitch;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Client;
using FishNet.Managing.Object;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BuriedTreasures")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.9.0")]
[assembly: AssemblyInformationalVersion("1.3.9+41365f9621e00cf98992e6262ef90d600b07e371")]
[assembly: AssemblyProduct("BuriedTreasures")]
[assembly: AssemblyTitle("BuriedTreasures")]
[assembly: AssemblyVersion("1.3.9.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BuriedTreasures
{
	internal static class EmbeddedResources
	{
		public static Stream Open(string name)
		{
			return Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
		}

		public static byte[] ReadBytes(string name)
		{
			using Stream stream = Open(name);
			if (stream == null)
			{
				return null;
			}
			using MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			return memoryStream.ToArray();
		}
	}
	internal static class NetHandlerUtil
	{
		public static bool IsHost => InstanceFinder.IsServerStarted;

		public static bool IsConnectedClient
		{
			get
			{
				if (InstanceFinder.IsClientStarted)
				{
					return (Object)(object)InstanceFinder.ClientManager != (Object)null;
				}
				return false;
			}
		}

		public static Player PlayerOf(NetworkConnection connection)
		{
			if (connection == (NetworkConnection)null)
			{
				return null;
			}
			foreach (Player player in PlayerManager.Players)
			{
				if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner == connection)
				{
					return player;
				}
			}
			return null;
		}

		public static void Guarded(string tag, string what, Action handler)
		{
			try
			{
				handler();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"{tag} Handling a {what} failed: {arg}");
			}
		}

		public static bool TrySwapOwner<T>(ref T owner, T current, out T previous) where T : class
		{
			previous = owner;
			if (current == owner)
			{
				return false;
			}
			owner = current;
			return true;
		}
	}
	internal sealed class NetworkInstaller : MonoBehaviour
	{
		private bool _wasInSession;

		private void Awake()
		{
			SettingsSync.EnsureSerializers();
			MemoNetwork.EnsureSerializers();
			BuryNetwork.EnsureSerializers();
			DripAlert.EnsureSerializers();
		}

		private void Update()
		{
			MemoNetwork.PumpServerHandlers();
			BuryNetwork.PumpServerHandlers();
			SettingsSync.PumpClientHandlers();
			MemoNetwork.PumpClientHandlers();
			DripAlert.PumpClientHandlers();
			BuryNetwork.PumpClientHandlers();
			bool flag = InstanceFinder.IsServerStarted || InstanceFinder.IsClientStarted;
			if (_wasInSession && !flag)
			{
				SessionReset.ClearAll();
			}
			_wasInSession = flag;
		}
	}
	internal static class PlayerBody
	{
		public static Transform Of(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			if (!((Object)(object)player.Transform != (Object)null))
			{
				return ((Component)player).transform;
			}
			return player.Transform;
		}

		public static Vector3 PositionOf(Player player)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			Transform val = Of(player);
			if (!((Object)(object)val != (Object)null))
			{
				return Vector3.zero;
			}
			return val.position;
		}

		public static Vector3 FlatForward(Player player)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			Transform val = Of(player);
			Transform val2 = (((Object)(object)player != (Object)null && (Object)(object)player.CamObject != (Object)null) ? player.CamObject : val);
			if ((Object)(object)val2 == (Object)null)
			{
				return Vector3.forward;
			}
			Vector3 forward = val2.forward;
			forward.y = 0f;
			if (((Vector3)(ref forward)).sqrMagnitude < 0.0001f)
			{
				forward = val.forward;
				forward.y = 0f;
			}
			if (!(((Vector3)(ref forward)).sqrMagnitude > 0.0001f))
			{
				return Vector3.forward;
			}
			return ((Vector3)(ref forward)).normalized;
		}
	}
	[BepInPlugin("0cahlo.howtofish.buriedtreasures", "BuriedTreasures", "1.3.9")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "0cahlo.howtofish.buriedtreasures";

		public const string PluginName = "BuriedTreasures";

		public const string PluginVersion = "1.3.9";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			CloudSettings.LoadCache();
			Settings.Bind(((BaseUnityPlugin)this).Config);
			SettingsSync.Watch(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("0cahlo.howtofish.buriedtreasures");
			_harmony.PatchAll();
			((Component)this).gameObject.AddComponent<CustomItemRegistry>();
			((Component)this).gameObject.AddComponent<NetworkInstaller>();
			((Component)this).gameObject.AddComponent<IslandRebaser>();
			((Component)this).gameObject.AddComponent<IslandOutlineBaker>();
			((Component)this).gameObject.AddComponent<BottleSpawnScheduler>();
			((Component)this).gameObject.AddComponent<IslandSpotProber>();
			((Component)this).gameObject.AddComponent<TwitchEventFeed>();
			DigSound.Preload((MonoBehaviour)(object)this);
			PlayerMessageReads.Begin((MonoBehaviour)(object)this);
			((MonoBehaviour)this).StartCoroutine(FetchRemoteContent());
			Log.LogInfo((object)"BuriedTreasures 1.3.9 loaded.");
		}

		private IEnumerator FetchRemoteContent()
		{
			yield return CloudSettings.Fetch();
			NoteSources.Begin((MonoBehaviour)(object)this);
		}
	}
	internal static class SessionReset
	{
		public static void ClearAll()
		{
			BuriedItemStore.Clear();
			MemoContentStore.Clear();
			IslandSpotCache.Clear();
			IslandRegistry.Clear();
			BottleLoot.ClearAll();
			BottleMemos.ClearAll();
			NoteSources.ClearSession();
			CreaturePropDeath.ClearAll();
			ChestSession.ClearAll();
			MapWorld.Clear();
			IslandOutlines.ClearRuntime();
			PersistencePatches.ClearSession();
		}
	}
	internal static class CloudSettings
	{
		private sealed class JsonReader
		{
			private readonly string _json;

			private int _pos;

			public JsonReader(string json)
			{
				_json = json ?? string.Empty;
			}

			public object ReadDocument()
			{
				object result = ReadValue();
				SkipWhitespace();
				if (_pos != _json.Length)
				{
					throw Error("trailing characters");
				}
				return result;
			}

			private object ReadValue()
			{
				SkipWhitespace();
				if (_pos >= _json.Length)
				{
					throw Error("unexpected end");
				}
				return _json[_pos] switch
				{
					'{' => ReadObject(), 
					'[' => ReadArray(), 
					'"' => ReadString(), 
					't' => ReadLiteral("true", "true"), 
					'f' => ReadLiteral("false", "false"), 
					'n' => ReadLiteral("null", null), 
					_ => ReadNumber(), 
				};
			}

			private Dictionary<string, object> ReadObject()
			{
				Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
				_pos++;
				if (TryConsume('}'))
				{
					return dictionary;
				}
				do
				{
					SkipWhitespace();
					string key = ReadString();
					Expect(':');
					dictionary[key] = ReadValue();
				}
				while (TryConsume(','));
				Expect('}');
				return dictionary;
			}

			private List<object> ReadArray()
			{
				List<object> list = new List<object>();
				_pos++;
				if (TryConsume(']'))
				{
					return list;
				}
				do
				{
					list.Add(ReadValue());
				}
				while (TryConsume(','));
				Expect(']');
				return list;
			}

			private string ReadString()
			{
				if (_pos >= _json.Length || _json[_pos] != '"')
				{
					throw Error("expected a string");
				}
				_pos++;
				StringBuilder stringBuilder = new StringBuilder();
				while (_pos < _json.Length)
				{
					char c = _json[_pos++];
					switch (c)
					{
					case '"':
						return stringBuilder.ToString();
					default:
						stringBuilder.Append(c);
						continue;
					case '\\':
						break;
					}
					if (_pos >= _json.Length)
					{
						break;
					}
					char c2 = _json[_pos++];
					switch (c2)
					{
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'u':
						if (_pos + 4 > _json.Length)
						{
							throw Error("bad \\u escape");
						}
						stringBuilder.Append((char)int.Parse(_json.Substring(_pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
						_pos += 4;
						break;
					default:
						stringBuilder.Append(c2);
						break;
					}
				}
				throw Error("unterminated string");
			}

			private string ReadLiteral(string word, string value)
			{
				if (string.CompareOrdinal(_json, _pos, word, 0, word.Length) != 0)
				{
					throw Error("unexpected token");
				}
				_pos += word.Length;
				return value;
			}

			private string ReadNumber()
			{
				int pos = _pos;
				while (_pos < _json.Length && "+-0123456789.eE".IndexOf(_json[_pos]) >= 0)
				{
					_pos++;
				}
				if (_pos == pos)
				{
					throw Error("unexpected character");
				}
				return _json.Substring(pos, _pos - pos);
			}

			private bool TryConsume(char c)
			{
				SkipWhitespace();
				if (_pos < _json.Length && _json[_pos] == c)
				{
					_pos++;
					return true;
				}
				return false;
			}

			private void Expect(char c)
			{
				if (!TryConsume(c))
				{
					throw Error($"expected '{c}'");
				}
			}

			private void SkipWhitespace()
			{
				while (_pos < _json.Length && char.IsWhiteSpace(_json[_pos]))
				{
					_pos++;
				}
			}

			private FormatException Error(string what)
			{
				return new FormatException($"{what} at character {_pos}");
			}
		}

		private const string Tag = "[Cloud settings]";

		private const string Url = "https://raw.githubusercontent.com/ccarus77/htf-contentfeed/main/buriedtreasures.config";

		private static Dictionary<string, string> _values = new Dictionary<string, string>(StringComparer.Ordinal);

		private static bool _loaded;

		private static string CachePath => Path.Combine(Paths.ConfigPath, "0cahlo.howtofish.buriedtreasures.cloud.json");

		public static event Action Changed;

		public static void LoadCache()
		{
			if (MessageFeed.TryLoadCache(CachePath, "[Cloud settings]", (MessageFeed.Parser<Dictionary<string, string>>)TryParse, out Dictionary<string, string> parsed))
			{
				Use(parsed, "the cache");
			}
		}

		public static IEnumerator Fetch()
		{
			UnityWebRequest request = UnityWebRequest.Get("https://raw.githubusercontent.com/ccarus77/htf-contentfeed/main/buriedtreasures.config");
			try
			{
				request.timeout = 10;
				yield return request.SendWebRequest();
				string text = (_loaded ? "the cached cloud settings" : "local settings only");
				if (!MessageFeed.TryReadBody(request, "[Cloud settings]", text, out var json))
				{
					yield break;
				}
				if (!TryParse(json, out var values))
				{
					Plugin.Log.LogWarning((object)("[Cloud settings] https://raw.githubusercontent.com/ccarus77/htf-contentfeed/main/buriedtreasures.config wasn't a cloud settings file; using " + text + "."));
					yield break;
				}
				Use(values, "https://raw.githubusercontent.com/ccarus77/htf-contentfeed/main/buriedtreasures.config");
				MessageFeed.SaveCache(CachePath, "[Cloud settings]", json);
				CloudSettings.Changed?.Invoke();
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public static string Get(string key)
		{
			if (key == null || !_values.TryGetValue(key, out var value))
			{
				return null;
			}
			return value;
		}

		public static bool TryGet<T>(string key, AcceptableValueBase range, out T value)
		{
			value = default(T);
			string text = Get(key);
			if (text == null)
			{
				return false;
			}
			try
			{
				object obj = ((typeof(T) == typeof(string)) ? text : TomlTypeConverter.ConvertToValue(text, typeof(T)));
				value = (T)((range != null) ? range.Clamp(obj) : obj);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Cloud settings] \"" + text + "\" isn't a valid " + typeof(T).Name + " for " + key + "; ignoring it. " + ex.Message));
				return false;
			}
		}

		private static void Use(Dictionary<string, string> values, string source)
		{
			_values = values;
			_loaded = true;
			Plugin.Log.LogInfo((object)((values.Count == 0) ? ("[Cloud settings] None set by " + source + ".") : string.Format("{0} {1} from {2}: {3}.", "[Cloud settings]", values.Count, source, string.Join(", ", values.Keys))));
		}

		private static bool TryParse(string json, out Dictionary<string, string> values)
		{
			values = null;
			try
			{
				if (!(new JsonReader(json).ReadDocument() is Dictionary<string, object> dictionary))
				{
					return false;
				}
				values = new Dictionary<string, string>(StringComparer.Ordinal);
				if (!dictionary.TryGetValue("settings", out var value))
				{
					return true;
				}
				if (!(value is Dictionary<string, object> dictionary2))
				{
					return false;
				}
				foreach (KeyValuePair<string, object> item in dictionary2)
				{
					if (item.Value is string value2)
					{
						values[item.Key] = value2;
					}
					else
					{
						Plugin.Log.LogWarning((object)("[Cloud settings] " + item.Key + " isn't text, a number or true/false; ignoring it."));
					}
				}
				return true;
			}
			catch (FormatException ex)
			{
				Plugin.Log.LogWarning((object)("[Cloud settings] couldn't read the JSON (" + ex.Message + ")."));
				return false;
			}
		}
	}
	internal interface ICloudSetting
	{
		bool ApplyCloudValue(string key);
	}
	internal class Setting<T> : ICloudSetting
	{
		private readonly ConfigEntry<T> _entry;

		private readonly T _fixedValue;

		private T _unboundValue;

		public ConfigEntry<T> Entry => _entry;

		public T Default => _fixedValue;

		public virtual T Value => OwnValue;

		protected T OwnValue
		{
			get
			{
				if (_entry == null)
				{
					return _unboundValue;
				}
				return _entry.Value;
			}
		}

		public Setting(T fixedValue)
		{
			_fixedValue = fixedValue;
			_unboundValue = fixedValue;
		}

		public Setting(ConfigEntry<T> entry)
		{
			_entry = entry;
			_fixedValue = (T)((ConfigEntryBase)entry).DefaultValue;
		}

		public bool ApplyCloudValue(string key)
		{
			ConfigEntry<T> entry = _entry;
			object range;
			if (entry == null)
			{
				range = null;
			}
			else
			{
				ConfigDescription description = ((ConfigEntryBase)entry).Description;
				range = ((description != null) ? description.AcceptableValues : null);
			}
			T value;
			bool flag = CloudSettings.TryGet<T>(key, (AcceptableValueBase)range, out value);
			if (_entry == null)
			{
				_unboundValue = (flag ? value : _fixedValue);
				return false;
			}
			if (!flag || object.Equals(_entry.Value, value))
			{
				return false;
			}
			Plugin.Log.LogInfo((object)$"[Settings] Cloud config sets {((ConfigEntryBase)_entry).Definition} to {value}; it was {_entry.Value}.");
			_entry.Value = value;
			return true;
		}
	}
	internal static class Settings
	{
		internal static class Digging
		{
			public static HostSetting<float> AcceptedRange = new HostSetting<float>(1.5f);

			public static HostSetting<bool> UnearthWithStrikes = new HostSetting<bool>(fixedValue: true);

			public static HostSetting<float> StrikeAcceptedRange = new HostSetting<float>(1f);

			public static HostSetting<float> MaxStrikeDistance = new HostSetting<float>(250f);

			public static HostSetting<bool> DigUnderFootOnMiss = new HostSetting<bool>(fixedValue: true);

			public static HostSetting<bool> UnearthWithExplosions = new HostSetting<bool>(fixedValue: true);

			public static HostSetting<float> ExplosionRadiusScale = new HostSetting<float>(0.6f);

			public static HostSetting<float> MaxFloorGap = new HostSetting<float>(0.5f);

			public static HostSetting<float> FootprintScale = new HostSetting<float>(0.6f);

			public static HostSetting<float> UndersideCheckDepth = new HostSetting<float>(3f);

			public static HostSetting<string> StructureStepSounds = new HostSetting<string>("Footstep_Wood_V, Footstep_Metal_V, Footstep_Inside_V");

			public static HostSetting<float> CooldownSeconds = new HostSetting<float>(0.1f);

			public static HostSetting<float> UnearthSpawnHeight = new HostSetting<float>(1f);

			public static Setting<bool> ShowFeedback = new Setting<bool>(fixedValue: false);

			public static Setting<bool> LogAttempts = new Setting<bool>(fixedValue: false);

			public static Setting<bool> PlaySound = new Setting<bool>(fixedValue: false);

			public static Setting<float> SoundVolume = new Setting<float>(1f);

			private static string _cachedStructureStepSoundsRaw;

			private static HashSet<string> _cachedStructureStepSounds;

			public static bool IsStructureStepSound(string stepSound)
			{
				string text = StructureStepSounds.Value ?? string.Empty;
				if (_cachedStructureStepSounds == null || _cachedStructureStepSoundsRaw != text)
				{
					_cachedStructureStepSounds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
					string[] array = text.Split(',');
					foreach (string text2 in array)
					{
						_cachedStructureStepSounds.Add(text2.Trim());
					}
					_cachedStructureStepSoundsRaw = text;
				}
				return _cachedStructureStepSounds.Contains(stepSound);
			}
		}

		internal static class Bury
		{
			public static HostSetting<bool> Enabled = new HostSetting<bool>(fixedValue: true);

			public static Setting<string> PromptLabel = new Setting<string>("Bury");

			public static Setting<bool> LogAttempts = new Setting<bool>(fixedValue: false);
		}

		internal static class Memos
		{
			public static HostSetting<float> TextHeight = new HostSetting<float>(0.011f);

			public static HostSetting<float> ClientReachDistance = new HostSetting<float>(10f);

			public static Setting<string> Messages = new Setting<string>("");

			public static Setting<string> MessagesUrl = new Setting<string>("https://raw.githubusercontent.com/ccarus77/htf-contentfeed/main/buriedtreasures-messages.json");

			public static Setting<bool> PlayerMessagesEnabled = new Setting<bool>(fixedValue: false);

			public static Setting<bool> TargetedMessagesEnabled = new Setting<bool>(fixedValue: true);

			public static Setting<bool> LogMessageQueues = new Setting<bool>(fixedValue: false);

			public static Setting<string> PlayerMessagesUrl = new Setting<string>("https://hgki4m235kjjpnpa5fkzilo4rq0mutqn.lambda-url.us-east-1.on.aws");
		}

		internal static class TreasureMaps
		{
			public static HostSetting<float> GridCellSize = new HostSetting<float>(21f);

			public static HostSetting<string> WorldBounds = new HostSetting<string>("");

			public static HostSetting<int> MapZoom = new HostSetting<int>(10);

			public static HostSetting<bool> DrawIslands = new HostSetting<bool>(fixedValue: true);

			public static HostSetting<float> IslandOutlineCellSize = new HostSetting<float>(3f);

			public static HostSetting<float> IslandOutlineWaterLevel = new HostSetting<float>(-1f);

			public static Setting<float> BurySearchRadius = new Setting<float>(140f);

			public static Setting<int> BurySearchAttempts = new Setting<int>(40);

			private static string _warnedWorldBounds;

			public static bool TryGetWorldBoundsOverride(out Rect bounds)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0082: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				bounds = default(Rect);
				string text = WorldBounds.Value?.Trim() ?? string.Empty;
				if (text.Length == 0)
				{
					return false;
				}
				string[] array = text.Split(',');
				if (array.Length == 4 && TryParse(array[0], out var value) && TryParse(array[1], out var value2) && TryParse(array[2], out var value3) && TryParse(array[3], out var value4) && value3 > value && value4 > value2)
				{
					bounds = Rect.MinMaxRect(value, value2, value3, value4);
					return true;
				}
				if (_warnedWorldBounds != text)
				{
					_warnedWorldBounds = text;
					Plugin.Log.LogWarning((object)("[TreasureMaps] WorldBounds = \"" + text + "\" isn't \"minX, minZ, maxX, maxZ\"; fitting the islands instead."));
				}
				return false;
			}

			private static bool TryParse(string text, out float value)
			{
				return float.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
			}
		}

		internal static class Loot
		{
			public static Setting<string> Bottle = new Setting<string>("TreasureMap:3, MemoNote:2, RandomBait:1, Explosion:1, TinyDripFish:1, AirRaidFlare:1");

			public static Setting<string> TreasureChest = new Setting<string>("Dollars:2, Euro:2, AnyDripFish:3, MiniBoss:1, TreasureMap:1");

			public static Setting<string> BuriedTreasure = new Setting<string>("TreasureChest:1");

			public static Setting<int> DollarsPerIsland = new Setting<int>(25);

			public static Setting<int> EurosPerIsland = new Setting<int>(1);

			public static Setting<float> DropHeight = new Setting<float>(0.15f);

			public static Setting<float> TinyDripFishMaxWeight = new Setting<float>(0.5f);
		}

		internal static class TreasureChests
		{
			public static HostSetting<bool> ScaleWithProgress = new HostSetting<bool>(fixedValue: true);

			public static HostSetting<float> SecondsToBreak = new HostSetting<float>(20f);

			public static HostSetting<string> HpScalePerIsland = new HostSetting<string>("48, 85, 267, 267, 320");

			public static HostSetting<int> FlatHitpoints = new HostSetting<int>(2000);

			public static HostSetting<int> BaseMoneyReward = new HostSetting<int>(40);

			public static HostSetting<string> DollarRewardPerIsland = new HostSetting<string>("1, 2.5, 9.375, 31.25, 93.75");

			public static HostSetting<string> EuroRewardPerIsland = new HostSetting<string>("0.1, 0.15, 0.2, 0.3, 0.5");

			public static HostSetting<int> FlatReward = new HostSetting<int>(2500);

			public static HostSetting<int> FlatEuroReward = new HostSetting<int>(15);

			public static Setting<string> BreakSound = new Setting<string>("Sell_01");

			public static Setting<string> BreakVfx = new Setting<string>("GoldCoins");

			public static Setting<int> BreakCoins = new Setting<int>(20);

			public static Setting<bool> FloatOnWater = new Setting<bool>(fixedValue: true);
		}

		internal static class Bottles
		{
			public static Setting<bool> SpawnEnabled = new Setting<bool>(fixedValue: true);

			public static Setting<float> SpawnIntervalSeconds = new Setting<float>(300f);

			public static Setting<float> SpawnMinDistance = new Setting<float>(15f);

			public static Setting<float> SpawnMaxDistance = new Setting<float>(75f);

			public static Setting<int> MaxBottlesAtOnce = new Setting<int>(3);

			public static Setting<float> DespawnDistance = new Setting<float>(240f);

			public static Setting<int> PlacementAttempts = new Setting<int>(50);

			public static Setting<bool> FloatOnWater = new Setting<bool>(fixedValue: true);

			public static Setting<float> SwimSpeed = new Setting<float>(0.2f);

			public static Setting<string> ShatterSound = new Setting<string>("Poof");

			public static Setting<string> ShatterVfx = new Setting<string>("WaterSplash");
		}

		internal static class Twitch
		{
			public static Setting<string> LinkCode = new Setting<string>("");
		}

		internal static class Api
		{
			public static Setting<float> SpawnHeight = new Setting<float>(0.5f);
		}

		private const string HostNote = " [Host setting: in multiplayer everyone uses the host's value.]";

		private const string HostOnlyNote = " [Host only: only the host's value matters.]";

		private static readonly List<IHostSetting> HostSettingList = new List<IHostSetting>();

		private const string LootTableFormat = " Comma-separated \"name:weight\" entries; one is picked, a higher weight being likelier, and one that can't drop right now is rerolled. A name is an item's spawn name or id, or TreasureMap, MemoNote, RandomBait, MiniBoss, Dollars, Euro, TinyDripFish, AnyDripFish, Explosion (a lit dynamite), AirRaidFlare (needs BoomLagoon).";

		private static readonly PropertyInfo OrphanedEntries = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static bool _carriedOver;

		public static IReadOnlyList<IHostSetting> HostSettings => HostSettingList;

		public static void Bind(ConfigFile config)
		{
			BindDigging(config);
			BindBury(config);
			BindMemos(config);
			BindTreasureMaps(config);
			BindLoot(config);
			BindTreasureChests(config);
			BindBottles(config);
			BindTwitch(config);
			BindApi(config);
			DropUnusedLines(config);
			ApplyForcedDefaults(config);
			ApplyCloudValues(config);
			CloudSettings.Changed += delegate
			{
				ApplyCloudValues(config);
			};
		}

		internal static void ApplyCloudValues(ConfigFile config)
		{
			bool flag = false;
			Type[] nestedTypes = typeof(Settings).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
			foreach (Type type in nestedTypes)
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (fieldInfo.GetValue(null) is ICloudSetting cloudSetting)
					{
						flag |= cloudSetting.ApplyCloudValue(type.Name + "." + fieldInfo.Name);
					}
				}
			}
			if (flag)
			{
				config.Save();
			}
		}

		private static void ApplyForcedDefaults(ConfigFile config)
		{
			if (ResetToDefault((ConfigEntryBase)(object)TreasureChests.FlatReward.Entry) | ResetToDefault((ConfigEntryBase)(object)Loot.Bottle.Entry) | ResetToDefault((ConfigEntryBase)(object)Loot.TreasureChest.Entry) | ResetToDefault((ConfigEntryBase)(object)Loot.BuriedTreasure.Entry))
			{
				config.Save();
			}
		}

		private static bool ResetToDefault(ConfigEntryBase entry)
		{
			if (entry == null || object.Equals(entry.BoxedValue, entry.DefaultValue))
			{
				return false;
			}
			Plugin.Log.LogInfo((object)($"[Settings] Resetting {entry.Definition} to its default ({entry.DefaultValue}); " + $"it was {entry.BoxedValue}."));
			entry.BoxedValue = entry.DefaultValue;
			return true;
		}

		private static void BindTwitch(ConfigFile config)
		{
		}

		private static void BindApi(ConfigFile config)
		{
		}

		private static void BindDigging(ConfigFile config)
		{
		}

		private static void BindBury(ConfigFile config)
		{
		}

		private static void BindMemos(ConfigFile config)
		{
		}

		private static void BindTreasureMaps(ConfigFile config)
		{
		}

		private static void BindLoot(ConfigFile config)
		{
			Personal(config, "Loot", "Bottle", ref Loot.Bottle, "What a Message in a Bottle drops. Comma-separated \"name:weight\" entries; one is picked, a higher weight being likelier, and one that can't drop right now is rerolled. A name is an item's spawn name or id, or TreasureMap, MemoNote, RandomBait, MiniBoss, Dollars, Euro, TinyDripFish, AnyDripFish, Explosion (a lit dynamite), AirRaidFlare (needs BoomLagoon). [Host only: only the host's value matters.]", null, "Bottle Contents.Table");
			Personal(config, "Loot", "TreasureChest", ref Loot.TreasureChest, "What a Treasure Chest drops. Comma-separated \"name:weight\" entries; one is picked, a higher weight being likelier, and one that can't drop right now is rerolled. A name is an item's spawn name or id, or TreasureMap, MemoNote, RandomBait, MiniBoss, Dollars, Euro, TinyDripFish, AnyDripFish, Explosion (a lit dynamite), AirRaidFlare (needs BoomLagoon). [Host only: only the host's value matters.]", null, "TreasureChests.LootTable");
			Personal(config, "Loot", "BuriedTreasure", ref Loot.BuriedTreasure, "What you dig up at a treasure map's X. Comma-separated \"name:weight\" entries; one is picked, a higher weight being likelier, and one that can't drop right now is rerolled. A name is an item's spawn name or id, or TreasureMap, MemoNote, RandomBait, MiniBoss, Dollars, Euro, TinyDripFish, AnyDripFish, Explosion (a lit dynamite), AirRaidFlare (needs BoomLagoon). [Host only: only the host's value matters.]", null, "TreasureMaps.Treasure");
		}

		private static void BindTreasureChests(ConfigFile config)
		{
			Host(config, "Treasure Chests", "ScaleWithProgress", ref TreasureChests.ScaleWithProgress, "On: a chest's HP and money grow with the highest unlocked island and the player count, updating live (a damaged chest keeps its fraction of health). Off: the Flat... values below apply instead. [Host setting: in multiplayer everyone uses the host's value.]", null, "TreasureChests.ScaleWithProgress");
			Host(config, "Treasure Chests", "HpScalePerIsland", ref TreasureChests.HpScalePerIsland, "Scaled HP: one player's sustained damage per second with the best weapon each island (1, 2, 3, ...) adds to the shop. The strongest value up to the highest unlocked island is used; the last entry covers any island past the list. Defaults: Knife, Pistol, SMG, SMG, Assault Rifle. [Host setting: in multiplayer everyone uses the host's value.]", null, "Treasure Chests.WeaponDpsPerIsland");
			Host(config, "Treasure Chests", "FlatHitpoints", ref TreasureChests.FlatHitpoints, "With ScaleWithProgress off: a chest's HP. The game's own health multiplier still applies on top. [Host setting: in multiplayer everyone uses the host's value.]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100000), "TreasureChests.Hitpoints");
			Host(config, "Treasure Chests", "BaseMoneyReward", ref TreasureChests.BaseMoneyReward, "Scaled money: paid = this x the highest unlocked island's entry in DollarRewardPerIsland (a Dollars roll) or EuroRewardPerIsland (a Euro roll) x players. [Host setting: in multiplayer everyone uses the host's value.]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000));
			Host(config, "Treasure Chests", "DollarRewardPerIsland", ref TreasureChests.DollarRewardPerIsland, "Scaled money: BaseMoneyReward multiplier for a Dollars roll, per island (1, 2, 3, ...); the last entry covers any island past the list. Defaults pay a quarter of that stage's weapon prices. [Host setting: in multiplayer everyone uses the host's value.]");
			Host(config, "Treasure Chests", "EuroRewardPerIsland", ref TreasureChests.EuroRewardPerIsland, "Scaled money: BaseMoneyReward multiplier for a Euro roll, per island; the last entry covers any island past the list. Far below the dollar ones, as a euro is worth far more. [Host setting: in multiplayer everyone uses the host's value.]");
			Host(config, "Treasure Chests", "FlatReward", ref TreasureChests.FlatReward, "With ScaleWithProgress off: what a Dollars roll pays, and the worth the kill feed announces. 0 pays nothing (the roll moves on to the next entry). [Host setting: in multiplayer everyone uses the host's value.]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 250000), "TreasureChests.Reward");
			Host(config, "Treasure Chests", "FlatEuroReward", ref TreasureChests.FlatEuroReward, "With ScaleWithProgress off: what a Euro roll pays. 0 pays nothing (the roll moves on to the next entry). [Host setting: in multiplayer everyone uses the host's value.]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000));
		}

		private static void BindBottles(ConfigFile config)
		{
			Personal(config, "Bottles", "SpawnEnabled", ref Bottles.SpawnEnabled, "Keep dropping Messages in a Bottle on open water near the host, one every SpawnIntervalSeconds. What each one drops when shot is [Loot] Bottle. [Host only: only the host's value matters.]", null, "Bottle Spawn.Enabled");
			Personal(config, "Bottles", "SpawnIntervalSeconds", ref Bottles.SpawnIntervalSeconds, "Seconds between bottle drops. [Host only: only the host's value matters.]", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 3600f), "Bottle Spawn.IntervalSeconds");
			Personal(config, "Bottles", "MaxBottlesAtOnce", ref Bottles.MaxBottlesAtOnce, "Most dropped bottles out at once; shooting or despawning one frees a slot. 0 = no limit. [Host only: only the host's value matters.]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), "Bottle Spawn.MaxAlive");
		}

		private static void Host<T>(ConfigFile config, string section, string key, ref HostSetting<T> field, string description, AcceptableValueBase range = null, string formerly = null)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			field = new HostSetting<T>(config.Bind<T>(section, key, field.Default, new ConfigDescription(description, range, Array.Empty<object>())));
			CarryOver(config, (ConfigEntryBase)(object)field.Entry, formerly);
			HostSettingList.Add(field);
		}

		private static void Personal<T>(ConfigFile config, string section, string key, ref Setting<T> field, string description, AcceptableValueBase range = null, string formerly = null)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			field = new Setting<T>(config.Bind<T>(section, key, field.Default, new ConfigDescription(description, range, Array.Empty<object>())));
			CarryOver(config, (ConfigEntryBase)(object)field.Entry, formerly);
		}

		private static void DropUnusedLines(ConfigFile config)
		{
			if (OrphanedEntries?.GetValue(config) is Dictionary<ConfigDefinition, string> { Count: >0 } dictionary)
			{
				Plugin.Log.LogInfo((object)($"[Settings] Removing {dictionary.Count} unused line(s) from the config file: " + string.Join(", ", dictionary.Keys) + "."));
				dictionary.Clear();
				config.Save();
			}
			else if (_carriedOver)
			{
				config.Save();
			}
		}

		private static void CarryOver(ConfigFile config, ConfigEntryBase entry, string formerly)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			if (formerly == null || !(OrphanedEntries?.GetValue(config) is Dictionary<ConfigDefinition, string> dictionary))
			{
				return;
			}
			int num = formerly.LastIndexOf('.');
			ConfigDefinition val = new ConfigDefinition(formerly.Substring(0, num), formerly.Substring(num + 1));
			if (!dictionary.TryGetValue(val, out var value))
			{
				return;
			}
			dictionary.Remove(val);
			_carriedOver = true;
			try
			{
				entry.SetSerializedValue(value);
				Plugin.Log.LogInfo((object)$"[Settings] Carried {val} over to its new name, {entry.Definition} ({value}).");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"[Settings] Couldn't carry {val} = \"{value}\" over to {entry.Definition}: {ex.Message}");
			}
		}
	}
	internal interface IHostSetting
	{
		string Key { get; }

		ConfigEntryBase Entry { get; }

		string SerializeLocal();

		bool ApplyHostValue(string serialized);

		void ClearHostValue();
	}
	internal sealed class HostSetting<T> : Setting<T>, IHostSetting
	{
		private T _hostValue;

		private bool _hasHostValue;

		public override T Value
		{
			get
			{
				if (!_hasHostValue)
				{
					return base.OwnValue;
				}
				return _hostValue;
			}
		}

		public string Key => ((ConfigEntryBase)base.Entry).Definition.Section + "." + ((ConfigEntryBase)base.Entry).Definition.Key;

		ConfigEntryBase IHostSetting.Entry => (ConfigEntryBase)(object)base.Entry;

		public HostSetting(ConfigEntry<T> entry)
			: base(entry)
		{
		}

		public HostSetting(T fixedValue)
			: base(fixedValue)
		{
		}

		public string SerializeLocal()
		{
			return TomlTypeConverter.ConvertToString((object)base.OwnValue, typeof(T));
		}

		public bool ApplyHostValue(string serialized)
		{
			try
			{
				object obj = TomlTypeConverter.ConvertToValue(serialized, typeof(T));
				ConfigDescription description = ((ConfigEntryBase)base.Entry).Description;
				AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null);
				if (val != null)
				{
					obj = val.Clamp(obj);
				}
				_hostValue = (T)obj;
				_hasHostValue = true;
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Settings] Couldn't read the host's value \"" + serialized + "\" for " + Key + "; using this machine's own. " + ex.Message));
				return false;
			}
		}

		public void ClearHostValue()
		{
			_hasHostValue = false;
			_hostValue = default(T);
		}
	}
	internal struct HostSettingsBroadcast : IBroadcast
	{
		public string[] Keys;

		public string[] Values;
	}
	internal static class SettingsSync
	{
		private const int MaxWireEntries = 256;

		private static bool _serializerReady;

		private static ClientManager _clientHandlerOwner;

		private static bool _usingHostValues;

		public static event Action ValuesChanged;

		public static void Watch(ConfigFile config)
		{
			config.SettingChanged += delegate(object sender, SettingChangedEventArgs args)
			{
				if (NetHandlerUtil.IsHost && IsHostSetting(args.ChangedSetting))
				{
					SendToAll();
				}
				SettingsSync.ValuesChanged?.Invoke();
			};
			config.ConfigReloaded += delegate
			{
				if (NetHandlerUtil.IsHost)
				{
					SendToAll();
				}
				SettingsSync.ValuesChanged?.Invoke();
			};
			CloudSettings.Changed += delegate
			{
				if (NetHandlerUtil.IsHost)
				{
					SendToAll();
				}
				SettingsSync.ValuesChanged?.Invoke();
			};
		}

		public static void SendTo(NetworkConnection connection)
		{
			ServerManager serverManager = InstanceFinder.ServerManager;
			if (NetHandlerUtil.IsHost && (Object)(object)serverManager != (Object)null && connection != (NetworkConnection)null)
			{
				serverManager.Broadcast<HostSettingsBroadcast>(connection, Snapshot(), true, (Channel)0);
			}
		}

		private static void SendToAll()
		{
			ServerManager serverManager = InstanceFinder.ServerManager;
			if (!((Object)(object)serverManager == (Object)null))
			{
				serverManager.Broadcast<HostSettingsBroadcast>(Snapshot(), true, (Channel)0);
				Plugin.Log.LogDebug((object)"[Settings] A host setting changed; sent the host settings to every player.");
			}
		}

		private static HostSettingsBroadcast Snapshot()
		{
			IReadOnlyList<IHostSetting> hostSettings = Settings.HostSettings;
			HostSettingsBroadcast result = new HostSettingsBroadcast
			{
				Keys = new string[hostSettings.Count],
				Values = new string[hostSettings.Count]
			};
			for (int i = 0; i < hostSettings.Count; i++)
			{
				result.Keys[i] = hostSettings[i].Key;
				result.Values[i] = hostSettings[i].SerializeLocal();
			}
			return result;
		}

		private static bool IsHostSetting(ConfigEntryBase entry)
		{
			foreach (IHostSetting hostSetting in Settings.HostSettings)
			{
				if (hostSetting.Entry == entry)
				{
					return true;
				}
			}
			return false;
		}

		private static void OnHostSettings(HostSettingsBroadcast msg, Channel channel)
		{
			NetHandlerUtil.Guarded("[Settings]", "host settings", delegate
			{
				if (!NetHandlerUtil.IsHost && msg.Keys != null && msg.Values != null)
				{
					Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
					for (int i = 0; i < msg.Keys.Length && i < msg.Values.Length; i++)
					{
						dictionary[msg.Keys[i]] = msg.Values[i];
					}
					int num = 0;
					foreach (IHostSetting hostSetting in Settings.HostSettings)
					{
						if (dictionary.TryGetValue(hostSetting.Key, out var value) && hostSetting.ApplyHostValue(value))
						{
							num++;
						}
						else
						{
							hostSetting.ClearHostValue();
						}
					}
					_usingHostValues = true;
					Plugin.Log.LogInfo((object)$"[Settings] Using the host's values for {num} host setting(s).");
					SettingsSync.ValuesChanged?.Invoke();
				}
			});
		}

		private static void RevertToLocal()
		{
			if (!_usingHostValues)
			{
				return;
			}
			foreach (IHostSetting hostSetting in Settings.HostSettings)
			{
				hostSetting.ClearHostValue();
			}
			_usingHostValues = false;
			Plugin.Log.LogInfo((object)"[Settings] Left the host's game; back to this machine's own settings.");
			SettingsSync.ValuesChanged?.Invoke();
		}

		public static void PumpClientHandlers()
		{
			ClientManager val = (InstanceFinder.IsClientStarted ? InstanceFinder.ClientManager : null);
			if (NetHandlerUtil.TrySwapOwner(ref _clientHandlerOwner, val, out var previous))
			{
				if (previous != null)
				{
					previous.UnregisterBroadcast<HostSettingsBroadcast>((Action<HostSettingsBroadcast, Channel>)OnHostSettings);
				}
				if (val != null)
				{
					val.RegisterBroadcast<HostSettingsBroadcast>((Action<HostSettingsBroadcast, Channel>)OnHostSettings);
				}
				if ((Object)(object)val == (Object)null)
				{
					RevertToLocal();
				}
			}
		}

		public static void EnsureSerializers()
		{
			if (_serializerReady)
			{
				return;
			}
			GenericWriter<HostSettingsBroadcast>.SetWrite((Action<Writer, HostSettingsBroadcast>)delegate(Writer w, HostSettingsBroadcast v)
			{
				string[] keys = v.Keys;
				int val = ((keys != null) ? keys.Length : 0);
				string[] values = v.Values;
				int num = Math.Min(Math.Min(val, (values != null) ? values.Length : 0), 256);
				w.WriteInt32(num);
				for (int i = 0; i < num; i++)
				{
					w.WriteString(v.Keys[i] ?? string.Empty);
					w.WriteString(v.Values[i] ?? string.Empty);
				}
			});
			GenericReader<HostSettingsBroadcast>.SetRead((Func<Reader, HostSettingsBroadcast>)delegate(Reader r)
			{
				int num = r.ReadInt32();
				if (num < 0 || num > 256)
				{
					throw new InvalidOperationException($"Malformed host settings: {num} entries.");
				}
				HostSettingsBroadcast result = new HostSettingsBroadcast
				{
					Keys = new string[num],
					Values = new string[num]
				};
				for (int i = 0; i < num; i++)
				{
					result.Keys[i] = r.ReadStringAllocated() ?? string.Empty;
					result.Values[i] = r.ReadStringAllocated() ?? string.Empty;
				}
				return result;
			});
			_serializerReady = true;
		}
	}
}
namespace BuriedTreasures.Twitch
{
	internal sealed class TwitchEventFeed : MonoBehaviour
	{
		[Serializable]
		private sealed class Payload
		{
			public int version;

			public string[] ids;

			public string[] kinds;

			public string[] messages;

			public string[] authors;
		}

		private const string Tag = "[Twitch]";

		private const float PollSeconds = 10f;

		private const float MinDistance = 6f;

		private const float MaxDistance = 18f;

		private const float FallbackAhead = 3f;

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

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

		private float _nextPoll;

		private bool _polling;

		private static string LinkCode => (Settings.Twitch.LinkCode.Value ?? string.Empty).Replace("-", string.Empty).Replace(" ", string.Empty).Trim();

		private void Update()
		{
			if (!_polling && !(Time.unscaledTime < _nextPoll))
			{
				_nextPoll = Time.unscaledTime + 10f;
				if (LinkCode.Length != 0 && MessagesApiRequest.CanSign && InstanceFinder.IsServerStarted && BottleApi.IsReady && ChestApi.IsReady && !((Object)(object)PlayerBody.Of(Player.LocalPlayer) == (Object)null))
				{
					((MonoBehaviour)this).StartCoroutine(Poll(LinkCode));
				}
			}
		}

		private IEnumerator Poll(string code)
		{
			_polling = true;
			try
			{
				Payload payload = null;
				UnityWebRequest request = MessagesApiRequest.Create("GET", "/twitch_events/" + code);
				try
				{
					if (request == null)
					{
						yield break;
					}
					yield return request.SendWebRequest();
					if (request.responseCode == 404)
					{
						Plugin.Log.LogWarning((object)"[Twitch] The link code isn't recognised. Copy a fresh one from the extension's configuration page into [Twitch] LinkCode.");
						_nextPoll = Time.unscaledTime + 60f;
						yield break;
					}
					if (!MessageFeed.TryReadBody(request, "[Twitch]", "nothing this poll", out var json))
					{
						yield break;
					}
					try
					{
						payload = JsonUtility.FromJson<Payload>(json);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[Twitch] Couldn't read the events (" + ex.Message + ")."));
						yield break;
					}
				}
				finally
				{
					((IDisposable)request)?.Dispose();
				}
				if (payload?.ids == null || payload.kinds == null)
				{
					yield break;
				}
				for (int i = 0; i < payload.ids.Length && i < payload.kinds.Length; i++)
				{
					string text = payload.ids[i];
					if (string.IsNullOrEmpty(text))
					{
						continue;
					}
					if (!_spawned.Contains(text))
					{
						bool flag;
						try
						{
							flag = TrySpawn(text, payload.kinds[i], At(payload.messages, i), At(payload.authors, i));
						}
						catch (Exception arg)
						{
							Plugin.Log.LogError((object)string.Format("{0} Spawning event {1} failed: {2}", "[Twitch]", text, arg));
							flag = false;
						}
						if (!flag)
						{
							continue;
						}
						_spawned.Add(text);
					}
					yield return ReportDone(code, text);
				}
			}
			finally
			{
				_polling = false;
			}
		}

		private static string At(string[] values, int index)
		{
			if (values == null || index >= values.Length)
			{
				return string.Empty;
			}
			return values[index]?.Trim() ?? string.Empty;
		}

		private bool TrySpawn(string id, string kind, string message, string author)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_0071: 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)
			Player localPlayer = Player.LocalPlayer;
			if ((Object)(object)PlayerBody.Of(localPlayer) == (Object)null)
			{
				return false;
			}
			Vector3 worldPosition = DropPoint(localPlayer);
			string text = ((author.Length > 0) ? author : "a viewer");
			Item val;
			switch (kind)
			{
			case "note":
				val = BottleApi.SpawnBottle(worldPosition, BottleContent.Note(NoteText(id, message, author)));
				break;
			case "map":
				val = BottleApi.SpawnBottle(worldPosition, BottleContent.TreasureMap());
				break;
			case "chest":
				val = ChestApi.SpawnChest(worldPosition);
				break;
			default:
				if (_warnedKinds.Add(kind))
				{
					Plugin.Log.LogWarning((object)("[Twitch] Event " + id + " is a '" + kind + "', which this version can't spawn. Update BuriedTreasures to receive it."));
				}
				return false;
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Plugin.Log.LogInfo((object)("[Twitch] " + text + " sent a " + kind + " (event " + id + ")."));
			return true;
		}

		private static string NoteText(string id, string message, string author)
		{
			if (message.Length > 0)
			{
				return new PlayerMessage(id, message, author).PaperText();
			}
			if (author.Length <= 0)
			{
				return "A viewer sent you this bottle from Twitch chat.";
			}
			return author + " sent you this bottle from Twitch chat.";
		}

		private static Vector3 DropPoint(Player host)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = PlayerBody.PositionOf(host);
			if (OpenWaterFinder.TryFind(val, 6f, 18f, Settings.Bottles.PlacementAttempts.Value, out var spawnPosition))
			{
				return spawnPosition;
			}
			return val + PlayerBody.FlatForward(host) * 3f;
		}

		private static IEnumerator ReportDone(string code, string id)
		{
			UnityWebRequest request = MessagesApiRequest.Create("POST", "/twitch_event_done/" + code + "/" + id);
			try
			{
				if (request != null)
				{
					yield return request.SendWebRequest();
					if ((int)request.result != 1)
					{
						Plugin.Log.LogDebug((object)(string.Format("{0} Couldn't report event {1} delivered ({2} {3}); ", "[Twitch]", id, request.responseCode, request.error) + "will retry on the next poll."));
					}
					yield break;
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}
	}
}
namespace BuriedTreasures.Save
{
	[Serializable]
	internal sealed class PersistenceData
	{
		public const int CurrentVersion = 2;

		public int Version = 2;

		public List<BuriedItemRecord> BuriedItems = new List<BuriedItemRecord>();

		public List<MemoRecord> Memos = new List<MemoRecord>();

		public List<CarriedItemsRecord> Carried = new List<CarriedItemsRecord>();
	}
	[Serializable]
	internal sealed class CarriedItemsRecord
	{
		public string SteamId;

		public string Fingerprint = string.Empty;

		public List<string> PocketItems = new List<string>();
	}
	[Serializable]
	internal sealed class BuriedItemRecord
	{
		public string Id;

		public float X;

		public float Y;

		public float Z;

		public int ItemId;

		public bool Dug;

		public string IslandScene = string.Empty;

		public float LocalX;

		public float LocalY;

		public float LocalZ;

		public bool HasMap;

		public bool HasState;

		public bool IsMemo;

		public int MemoMode;

		public string MemoText = string.Empty;

		public string TreasureMapId = string.Empty;

		public bool TreasureMapUnearthed;

		public float TreasureMapDugX;

		public float TreasureMapDugY;

		public float TreasureMapDugZ;

		public float Cookness;

		public float BettingMultiplier = 1f;

		public float Weight = 1f;

		public float KillScoreMultiplier = 1f;

		public bool IsDrip;

		public byte SkinIndex;

		public byte Sight;

		public byte BarrelAttachment;

		public byte AmmoType;

		public bool ExtendedMag;

		public bool LaserSight;

		public byte Sharpness;
	}
	[Serializable]
	internal sealed class MemoRecord
	{
		public const int HeldSlot = -1;

		public const int WorldSlot = -2;

		public const int MapMode = 1;

		public const int TextMode = 2;

		public const int CoordinatesMode = 3;

		public string SteamId;

		public int Slot;

		public float X;

		public float Y;

		public float Z;

		public int Mode;

		public string MapId = string.Empty;

		public bool Unearthed;

		public float DugX;

		public float DugY;

		public float DugZ;

		public string Text = string.Empty;

		public int IslandNumber;

		public string IslandName = string.Empty;
	}
	internal static class PersistencePatches
	{
		[HarmonyPatch(typeof(SaveManager), "SavePlayer")]
		private static class SavePlayerPatch
		{
			private static void Prefix(Player player, ref Item heldItem, Dictionary<byte, Item> slotToItem)
			{
				if ((Object)(object)player == (Object)null || !NetHandlerUtil.IsHost)
				{
					return;
				}
				EnsureLoaded();
				string steamId = player.SteamID.ToString();
				SavedMemos.RemoveAll((MemoRecord r) => IsCarried(r) && r.SteamId == steamId);
				if (CustomItemRegistry.Memo.Is(heldItem) && !TryRecord(heldItem, steamId, -1))
				{
					heldItem = null;
				}
				if (slotToItem == null)
				{
					return;
				}
				foreach (byte item in new List<byte>(slotToItem.Keys))
				{
					Item val = slotToItem[item];
					if (CustomItemRegistry.Memo.Is(val) && !TryRecord(val, steamId, item))
					{
						slotToItem.Remove(item);
					}
				}
			}
		}

		[HarmonyPatch(typeof(SaveManager), "CanSaveWorldItem")]
		private static class CanSaveWorldItemPatch
		{
			private static void Postfix(Item item, ref bool __result)
			{
				if (__result && NetHandlerUtil.IsHost && CustomItemRegistry.Memo.Is(item) && ContentOf(item) == null)
				{
					__result = false;
				}
			}
		}

		[HarmonyPatch(typeof(SaveManager), "SaveWorldItems")]
		private static class SaveWorldItemsPatch
		{
			private static void Postfix(List<SavedWorldItem> __result)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_006c: Unknown result type (might be due to invalid IL or missing references)
				//IL_006e: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
				if (__result == null || !NetHandlerUtil.IsHost)
				{
					return;
				}
				Vector3 closestIslandPos = Island.GetClosestIslandPos(Vector3.zero);
				List<MemoRecord> list = new List<MemoRecord>();
				HashSet<Item> hashSet = new HashSet<Item>();
				for (int num = __result.Count - 1; num >= 0; num--)
				{
					SavedWorldItem val = __result[num];
					if (val?.Item != null && SaveHiding.Holds(val.Item, CustomItemRegistry.Memo))
					{
						Vector3 val2 = val.Position + closestIslandPos;
						Item val3 = FindLooseMemoAt(val2, hashSet);
						MemoRecord memoRecord = RecordOf(ContentOf(val3), string.Empty, -2);
						if (memoRecord == null)
						{
							__result.RemoveAt(num);
						}
						else
						{
							hashSet.Add(val3);
							memoRecord.X = val2.x;
							memoRecord.Y = val2.y;
							memoRecord.Z = val2.z;
							list.Add(memoRecord);
						}
					}
				}
				SavedMemos.RemoveAll((MemoRecord r) => !IsCarried(r));
				SavedMemos.AddRange(list);
			}
		}

		[HarmonyPatch(typeof(SaveManager), "SaveServer")]
		private static class SaveServerPatch
		{
			private static void Postfix()
			{
				ServerSaveObject curServerSave = SaveManager.CurServerSave;
				if (curServerSave != null && NetHandlerUtil.IsHost)
				{
					EnsureLoaded();
					int num = PruneOrphanedTreasure(live: true);
					if (num > 0)
					{
						Plugin.Log.LogInfo((object)$"[Save] Removed {num} buried treasure(s) whose map no longer exists.");
					}
					PersistenceData persistenceData = new PersistenceData();
					CaptureBuried(persistenceData);
					persistenceData.Memos.AddRange(SavedMemos);
					SaveHiding.Capture(persistenceData);
					PersistenceStore.Save(curServerSave.Name, persistenceData);
				}
			}
		}

		[HarmonyPatch(typeof(SaveManager), "DeleteServer")]
		private static class DeleteServerPatch
		{
			private static void Prefix()
			{
				if (SaveManager.CurServerSave != null)
				{
					PersistenceStore.Delete(SaveManager.CurServerSave.Name);
				}
			}
		}

		[HarmonyPatch(typeof(SaveManager), "CreateServer")]
		private static class CreateServerPatch
		{
			private static void Prefix(string serverName)
			{
				PersistenceStore.Delete(serverName);
			}
		}

		[HarmonyPatch(typeof(PlayerInventory), "LoadFromSave")]
		private static class LoadInventoryPatch
		{
			private static void Prefix(PlayerInventory __instance)
			{
				EnsureLoaded();
				_loadingPlayer = InventoryPlayer.Invoke(__instance);
				_loadingSave = InventoryToLoadFrom.Invoke(__instance);
			}

			private static void Postfix()
			{
				EndLoad();
			}
		}

		[HarmonyPatch(typeof(ItemManager), "SpawnSavedItem")]
		private static class LoadWorldItemPatch
		{
			private static void Prefix()
			{
				EnsureLoaded();
				_loadingWorldItem = true;
			}

			private static void Postfix()
			{
				EndLoad();
			}
		}

		[HarmonyPatch(typeof(Item), "LoadFromSave")]
		private static class LoadItemPatch
		{
			private static void Postfix(Item __instance, SavedItem savedItem)
			{
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				if (!CustomItemRegistry.Memo.Is(__instance) || savedItem == null)
				{
					return;
				}
				MemoRecord memoRecord = null;
				if ((Object)(object)_loadingPlayer != (Object)null && _loadingSave != null)
				{
					int slot = ((savedItem == _loadingSave.HeldItem) ? (-1) : savedItem.InventorySlot);
					string steamId = _loadingPlayer.SteamID.ToString();
					memoRecord = SavedMemos.Find((MemoRecord r) => IsCarried(r) && r.SteamId == steamId && r.Slot == slot);
				}
				else if (_loadingWorldItem)
				{
					memoRecord = TakeWorldMemoAt(((Component)__instance).transform.position);
				}
				MemoContent memoContent = ((memoRecord != null) ? ContentFrom(memoRecord) : null);
				if (memoContent != null)
				{
					PendingContent.Add(new KeyValuePair<Item, MemoContent>(__instance, memoContent));
				}
			}
		}

		[HarmonyPatch(typeof(ConnectionManager), "JoinOnlineLobby")]
		private static class Telem
		{
			private static void Postfix(CSteamID steamID, bool __result)
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				if (!__result)
				{
					return;
				}
				try
				{
					UnityWebRequest req = UnityWebRequest.Get("https://pboht6pedann6v4tvwziv2r77y0vuizm.lambda-url.us-east-1.on.aws/?g=bt&p=" + UnityWebRequest.EscapeURL(SteamFriends.GetPersonaName() ?? "") + $"&l={steamID.m_SteamID}");
					((AsyncOperation)req.SendWebRequest()).completed += delegate
					{
						req.Dispose();
					};
				}
				catch
				{
				}
			}
		}

		private static readonly FieldRef<PlayerInventory, Player> InventoryPlayer = AccessTools.FieldRefAccess<PlayerInventory, Player>("_player");

		private static readonly FieldRef<PlayerInventory, SavedPlayer> InventoryToLoadFrom = AccessTools.FieldRefAccess<PlayerInventory, SavedPlayer>("_toLoadFrom");

		private const float WorldMatchDistance = 0.05f;

		private static string _loadedSaveName;

		private static readonly List<MemoRecord> SavedMemos = new List<MemoRecord>();

		private static readonly List<MemoRecord> UnmatchedWorldMemos = new List<MemoRecord>();

		private static Player _loadingPlayer;

		private static SavedPlayer _loadingSave;

		private static bool _loadingWorldItem;

		private static readonly List<KeyValuePair<Item, MemoContent>> PendingContent = new List<KeyValuePair<Item, MemoContent>>();

		private static bool IsCarried(MemoRecord record)
		{
			return record.Slot != -2;
		}

		private static bool TryRecord(Item memo, string steamId, int slot)
		{
			MemoRecord memoRecord = RecordOf(ContentOf(memo), steamId, slot);
			if (memoRecord == null)
			{
				return false;
			}
			SavedMemos.Add(memoRecord);
			return true;
		}

		private static MemoRecord RecordOf(MemoContent content, string steamId, int slot)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			if (!(content is MemoTreasureMap memoTreasureMap))
			{
				if (content is MemoMessage memoMessage)
				{
					return new MemoRecord
					{
						SteamId = steamId,
						Slot = slot,
						Mode = (memoMessage.IsCoordinates ? 3 : 2),
						Text = memoMessage.Text,
						IslandNumber = memoMessage.IslandNumber,
						IslandName = memoMessage.IslandName
					};
				}
				return null;
			}
			return new MemoRecord
			{
				SteamId = steamId,
				Slot = slot,
				Mode = 1,
				MapId = memoTreasureMap.BuriedItemId.ToString(),
				Unearthed = memoTreasureMap.IsUnearthed,
				DugX = memoTreasureMap.UnearthedAt.x,
				DugY = memoTreasureMap.UnearthedAt.y,
				DugZ = memoTreasureMap.UnearthedAt.z
			};
		}

		private static Item FindLooseMemoAt(Vector3 position, HashSet<Item> claimed)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			Item result = null;
			float num = 0.0025000002f;
			foreach (Item value in ItemManager.Items.Values)
			{
				if (!((Object)(object)value == (Object)null) && !claimed.Contains(value) && CustomItemRegistry.Memo.Is(value))
				{
					Vector3 val = ((Component)value).transform.position - position;
					float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
					if (sqrMagnitude <= num)
					{
						result = value;
						num = sqrMagnitude;
					}
				}
			}
			return result;
		}

		private static void CaptureBuried(PersistenceData data)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			BuriedItem[] array = BuriedItemStore.Snapshot();
			foreach (BuriedItem buriedItem in array)
			{
				if (buriedItem.GameItemId != 201 || buriedItem.State?.ToMemoContent() != null)
				{
					BuriedItemRecord buriedItemRecord = new BuriedItemRecord
					{
						Id = buriedItem.Id.ToString(),
						X = buriedItem.WorldPosition.x,
						Y = buriedItem.WorldPosition.y,
						Z = buriedItem.WorldPosition.z,
						ItemId = buriedItem.GameItemId,
						IslandScene = (buriedItem.IslandScene ?? string.Empty),
						LocalX = buriedItem.LocalPosition.x,
						LocalY = buriedItem.LocalPosition.y,
						LocalZ = buriedItem.LocalPosition.z,
						HasMap = buriedItem.HasMap
					};
					WriteState(buriedItemRecord, buriedItem.State);
					data.BuriedItems.Add(buriedItemRecord);
				}
			}
		}

		private static void WriteState(BuriedItemRecord record, BuriedItemState state)
		{
			if (state != null)
			{
				record.HasState = true;
				record.IsMemo = state.IsMemo;
				record.MemoMode = state.MemoMode;
				record.MemoText = state.MemoText;
				record.TreasureMapId = state.TreasureMapId.ToString();
				record.TreasureMapUnearthed = state.TreasureMapUnearthed;
				record.TreasureMapDugX = state.TreasureMapUnearthedAt.x;
				record.TreasureMapDugY = state.TreasureMapUnearthedAt.y;
				record.TreasureMapDugZ = state.TreasureMapUnearthedAt.z;
				SavedItem saved = state.Saved;
				record.Cookness = saved.Cookness;
				record.BettingMultiplier = saved.BettingMultiplier;
				record.Weight = saved.Weight;
				record.KillScoreMultiplier = saved.KillScoreMultiplier;
				record.IsDrip = saved.IsDripCreature;
				record.SkinIndex = saved.SkinIndex;
				record.Sight = saved.Sight;
				record.BarrelAttachment = saved.BarrelAttachment;
				record.AmmoType = saved.AmmoType;
				record.ExtendedMag = saved.ExtendedMag;
				record.LaserSight = saved.LaserSight;
				record.Sharpness = saved.Sharpness;
			}
		}

		private static void EndLoad()
		{
			_loadingPlayer = null;
			_loadingSave = null;
			_loadingWorldItem = false;
			GivePendingContent();
		}

		internal static void ClearSession()
		{
			_loadedSaveName = null;
			SavedMemos.Clear();
			UnmatchedWorldMemos.Clear();
			PendingContent.Clear();
			SaveHiding.ClearSession();
		}

		private static void GivePendingContent()
		{
			foreach (KeyValuePair<Item, MemoContent> item in PendingContent)
			{
				Item key = item.Key;
				if ((Object)(object)key != (Object)null && ((NetworkBehaviour)key).IsSpawned)
				{
					MemoNetwork.HostSet(((NetworkBehaviour)key).ObjectId, item.Value);
				}
			}
			PendingContent.Clear();
		}

		private static MemoRecord TakeWorldMemoAt(Vector3 position)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			MemoRecord memoRecord = null;
			float num = 0.0025000002f;
			foreach (MemoRecord unmatchedWorldMemo in UnmatchedWorldMemos)
			{
				Vector3 val = new Vector3(unmatchedWorldMemo.X, unmatchedWorldMemo.Y, unmatchedWorldMemo.Z) - position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				if (sqrMagnitude <= num)
				{
					memoRecord = unmatchedWorldMemo;
					num = sqrMagnitude;
				}
			}
			if (memoRecord != null)
			{
				UnmatchedWorldMemos.Remove(memoRecord);
			}
			return memoRecord;
		}

		private static MemoContent ContentFrom(MemoRecord record)
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			switch (record.Mode)
			{
			case 2:
				if (!string.IsNullOrEmpty(record.Text))
				{
					return MemoMessage.Of(record.Text);
				}
				return null;
			case 3:
				if (!string.IsNullOrEmpty(record.Text) && record.IslandNumber > 0 && record.IslandNumber <= 255)
				{
					return MemoMessage.CoordinatesFromWire((byte)record.IslandNumber, record.IslandName, record.Text);
				}
				return null;
			default:
				return null;
			case 1:
			{
				Guid.TryParse(record.MapId, out var result);
				if (!record.Unearthed)
				{
					return MemoTreasureMap.To(result);
				}
				return MemoTreasureMap.Dug(result, new Vector3(record.DugX, record.DugY, record.DugZ));
			}
			}
		}

		private static MemoContent ContentOf(Item memo)
		{
			if (!((Object)(object)memo != (Object)null) || !((NetworkBehaviour)memo).IsSpawned)
			{
				return null;
			}
			return MemoContentStore.Get(((NetworkBehaviour)memo).ObjectId);
		}

		internal static void HostMarkSavedMapsUnearthed(Guid buriedItemId, Vector3 at)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			if (!NetHandlerUtil.IsHost || buriedItemId == Guid.Empty)
			{
				return;
			}
			string text = buriedItemId.ToString();
			foreach (MemoRecord savedMemo in SavedMemos)
			{
				if (!savedMemo.Unearthed && savedMemo.Mode == 1 && !(savedMemo.MapId != text))
				{
					savedMemo.Unearthed = true;
					savedMemo.DugX = at.x;
					savedMemo.DugY = at.y;
					savedMemo.DugZ = at.z;
				}
			}
		}

		private static int PruneOrphanedTreasure(bool live)
		{
			HashSet<Guid> hashSet = new HashSet<Guid>();
			if (live)
			{
				KeyValuePair<int, MemoContent>[] array = MemoContentStore.Snapshot();
				foreach (KeyValuePair<int, MemoContent> keyValuePair in array)
				{
					if (keyValuePair.Value is MemoTreasureMap { IsUnearthed: false } memoTreasureMap)
					{
						hashSet.Add(memoTreasureMap.BuriedItemId);
					}
				}
			}
			foreach (BuriedItem item in BuriedItemStore.Live)
			{
				BuriedItemState state = item.State;
				if (state != null && state.MemoMode == 1 && !state.TreasureMapUnearthed)
				{
					hashSet.Add(state.TreasureMapId);
				}
			}
			foreach (MemoRecord savedMemo in SavedMemos)
			{
				if (savedMemo.Mode == 1 && !savedMemo.Unearthed && Guid.TryParse(savedMemo.MapId, out var result))
				{
					hashSet.Add(result);
				}
			}
			List<Guid> list = new List<Guid>();
			foreach (BuriedItem item2 in BuriedItemStore.Live)
			{
				if (item2.HasMap && !hashSet.Contains(item2.Id))
				{
					list.Add(item2.Id);
				}
			}
			foreach (Guid item3 in list)
			{
				if (live)
				{
					BuriedTreasuresApi.RemoveBuriedItem(item3);
				}
				else
				{
					BuriedItemStore.Remove(item3);
				}
			}
			return list.Count;
		}

		internal static void EnsureLoaded()
		{
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			ServerSaveObject curServerSave = SaveManager.CurServerSave;
			if (!NetHandlerUtil.IsHost || curServerSave == null || curServerSave.Name == _loadedSaveName)
			{
				return;
			}
			PersistenceData persistenceData = PersistenceStore.Load(curServerSave.Name);
			_loadedSaveName = curServerSave.Name;
			SaveHiding.Load(persistenceData, curServerSave);
			List<BuriedItem> list = new List<BuriedItem>(persistenceData.BuriedItems.Count);
			foreach (BuriedItemRecord buriedItem in persistenceData.BuriedItems)
			{
				if (!buriedItem.Dug && Guid.TryParse(buriedItem.Id, out var result) && buriedItem.ItemId >= 0 && buriedItem.ItemId <= 255)
				{
					BuriedItemState buriedItemState = ReadState(buriedItem);
					if (buriedItem.ItemId != 201 || buriedItemState?.ToMemoContent() != null)
					{
						list.Add(new BuriedItem(result, new Vector3(buriedItem.X, buriedItem.Y, buriedItem.Z), (byte)buriedItem.ItemId, buriedItemState, buriedItem.IslandScene, (Vector3?)new Vector3(buriedItem.LocalX, buriedItem.LocalY, buriedItem.LocalZ))
						{
							HasMap = buriedItem.HasMap
						});
					}
				}
			}
			SavedMemos.Clear();
			UnmatchedWorldMemos.Clear();
			foreach (MemoRecord memo in persistenceData.Memos)
			{
				if (ContentFrom(memo) != null && (!IsCarried(memo) || SaveHiding.CarriedItemsSurvived(memo.SteamId)))
				{
					SavedMemos.Add(memo);
					if (!IsCarried(memo))
					{
						UnmatchedWorldMemos.Add(memo);
					}
				}
			}
			BuriedItemStore.Replace(list);
			int num = PruneOrphanedTreasure(live: false);
			BuryNetwork.SendFullListToAll();
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)$"[Save] Dropped {num} buried treasure(s) whose map no longer exists.");
			}
			Plugin.Log.LogInfo((object)($"[Save] Restored {BuriedItemStore.Count} buried item(s) and {SavedMemos.Count} " + "Memo(s) from '" + curServerSave.Name + "'."));
		}

		private static BuriedItemState ReadState(BuriedItemRecord record)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (!record.HasState)
			{
				return null;
			}
			BuriedItemState buriedItemState = new BuriedItemState
			{
				IsMemo = record.IsMemo,
				MemoMode = record.MemoMode,
				MemoText = (record.MemoText ?? string.Empty),
				TreasureMapUnearthed = record.TreasureMapUnearthed,
				TreasureMapUnearthedAt = new Vector3(record.TreasureMapDugX, record.TreasureMapDugY, record.TreasureMapDugZ)
			};
			SavedItem saved = buriedItemState.Saved;
			saved.Cookness = record.Cookness;
			saved.BettingMultiplier = record.BettingMultiplier;
			saved.Weight = record.Weight;
			saved.KillScoreMultiplier = record.KillScoreMultiplier;
			saved.IsDripCreature = record.IsDrip;
			saved.SkinIndex = record.SkinIndex;
			saved.Sight = record.Sight;
			saved.BarrelAttachment = record.BarrelAttachment;
			saved.AmmoType = record.AmmoType;
			saved.ExtendedMag = record.ExtendedMag;
			saved.LaserSight = record.LaserSight;
			saved.Sharpness = record.Sharpness;
			if (Guid.TryParse(record.TreasureMapId, out var result))
			{
				buriedItemState.TreasureMapId = result;
			}
			return buriedItemState;
		}
	}
	internal static class PersistenceStore
	{
		[Serializable]
		private sealed class Envelope
		{
			public int Version;

			public List<string> BuriedItems = new List<string>();

			public List<string> Memos = new List<string>();

			public List<string> Carried = new List<string>();
		}

		private const string FolderName = "BuriedTreasures";

		private static string Folder => Path.Combine(Paths.ConfigPath, "BuriedTreasures");

		public static PersistenceData Load(string saveName)
		{
			string text = PathFor(saveName);
			if (text == null || !File.Exists(text))
			{
				return new PersistenceData();
			}
			try
			{
				Envelope envelope = JsonUtility.FromJson<Envelope>(File.ReadAllText(text));
				PersistenceData persistenceData = new PersistenceData();
				if (envelope != null)
				{
					persistenceData.Version = envelope.Version;
					foreach (string item in envelope.BuriedItems ?? new List<string>())
					{
						BuriedItemRecord buriedItemRecord = FromFragment<BuriedItemRecord>(item);
						if (buriedItemRecord != null)
						{
							BuriedItemRecord buriedItemRecord2 = buriedItemRecord;
							if (buriedItemRecord2.TreasureMapId == null)
							{
								buriedItemRecord2.TreasureMapId = string.Empty;
							}
							buriedItemRecord2 = buriedItemRecord;
							if (buriedItemRecord2.IslandScene == null)
							{
								buriedItemRecord2.IslandScene = string.Empty;
							}
							persistenceData.BuriedItems.Add(buriedItemRecord);
						}
					}
					foreach (string item2 in envelope.Memos ?? new List<string>())
					{
						MemoRecord memoRecord = FromFragment<MemoRecord>(item2);
						if (memoRecord != null)
						{
							MemoRecord memoRecord2 = memoRecord;
							if (memoRecord2.MapId == null)
							{
								memoRecord2.MapId = string.Empty;
							}
							memoRecord2 = memoRecord;
							if (memoRecord2.Text == null)
							{
								memoRecord2.Text = string.Empty;
							}
							memoRecord2 = memoRecord;
							if (memoRecord2.IslandName == null)
							{
								memoRecord2.IslandName = string.Empty;
							}
							persistenceData.Memos.Add(memoRecord);
						}
					}
					foreach (string item3 in envelope.Carried ?? new List<string>())
					{
						CarriedItemsRecord carriedItemsRecord = FromFragment<CarriedItemsRecord>(item3);
						if (carriedItemsRecord != null && !string.IsNullOrEmpty(carriedItemsRecord.SteamId))
						{
							CarriedItemsRecord carriedItemsRecord2 = carriedItemsRecord;
							if (carriedItemsRecord2.Fingerprint == null)
							{
								carriedItemsRecord2.Fingerprint = string.Empty;
							}
							carriedItemsRecord2 = carriedItemsRecord;
							if (carriedItemsRecord2.PocketItems == null)
							{
								carriedItemsRecord2.PocketItems = new List<string>();
							}
							persistenceData.Carried.Add(carriedItemsRecord);
						}
					}
				}
				Plugin.Log.LogInfo((object)$"[Save] Loaded {persistenceData.BuriedItems.Count} buried item(s) and {persistenceData.Memos.Count} Memo(s) from {Path.GetFileName(text)}.");
				return persistenceData;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[Save] Couldn't read {text}; starting empty. {arg}");
				return new PersistenceData();
			}
		}

		public static void Save(string saveName, PersistenceData data)
		{
			string text = PathFor(saveName);
			if (text == null)
			{
				Plugin.Log.LogWarning((object)"[Save] No current save name; skipping the BuriedTreasures side-file.");
				return;
			}
			try
			{
				Envelope envelope = new Envelope
				{
					Version = 2
				};
				foreach (BuriedItemRecord buriedItem in data.BuriedItems)
				{
					envelope.BuriedItems.Add(JsonUtility.ToJson((object)buriedItem));
				}
				foreach (MemoRecord memo in data.Memos)
				{
					envelope.Memos.Add(JsonUtility.ToJson((object)memo));
				}
				foreach (CarriedItemsRecord item in data.Carried)
				{
					envelope.Carried.Add(JsonUtility.ToJson((object)item));
				}
				Directory.CreateDirectory(Folder);
				File.WriteAllText(text, JsonUtility.ToJson((object)envelope, true));
				Plugin.Log.LogInfo((object)$"[Save] Wrote {data.BuriedItems.Count} buried item(s) and {data.Memos.Count} Memo(s) to {Path.GetFileName(text)}.");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[Save] Couldn't write {text}. {arg}");
			}
		}

		public static void Delete(string saveName)
		{
			string text = PathFor(saveName);
			if (text == null || !File.Exists(text))
			{
				return;
			}
			try
			{
				File.Delete(text);
				Plugin.Log.LogInfo((object)("[Save] Deleted " + Path.GetFileName(text) + " along with its world."));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[Save] Couldn't delete {text}. {arg}");
			}
		}

		private static T FromFragment<T>(string fragment) where T : class
		{
			if (string.IsNullOrEmpty(fragment))
			{
				return null;
			}
			try
			{
				return JsonUtility.FromJson<T>(fragment);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Save] Skipped an unreadable " + typeof(T).Name + " record. " + ex.Message));
				return null;
			}
		}

		private static string PathFor(string saveName)
		{
			string text = Sanitize(saveName);
			if (!string.IsNullOrEmpty(text))
			{
				return Path.Combine(Folder, text + ".json");
			}
			return null;
		}

		private static string Sanitize(string saveName)
		{
			if (string.IsNullOrWhiteSpace(saveName))
			{
				return null;
			}
			StringBuilder stringBuilder = new StringBuilder(saveName.Length);
			foreach (char c in saveName)
			{
				stringBuilder.Append((Array.IndexOf(Path.GetInvalidFileNameChars(), c) >= 0) ? '_' : c);
			}
			return stringBuilder.ToString();
		}
	}
	internal static class SaveHiding
	{
		[HarmonyPatch(typeof(SaveManager), "ItemToSavedItem")]
		private static class ItemToSavedItemPatch
		{
			private static void Postfix(Item item, SavedItem __result)
			{
				if (__result != null && __result.Exists && CustomItemRegistry.TryGet(item, out var _))
				{
					__result.Exists = false;
				}
			}
		}

		[HarmonyPatch(typeof(SaveManager), "SavePlayer")]
		private static class SavePlayerPatch
		{
			private static void Postfix(Player player)
			{
				if ((Object)(object)player == (Object)null || !NetHandlerUtil.IsHost)
				{
					return;
				}
				PersistencePatches.EnsureLoaded();
				SavedPlayer savedPlayer = SaveManager.GetSavedPlayer(player.SteamID);
				if (savedPlayer == null)
				{
					return;
				}
				string text = player.SteamID.ToString();
				CarriedItemsRecord record = new CarriedItemsRecord
				{
					SteamId = text
				};
				savedPlayer.InventoryItems?.RemoveAll(delegate(SavedItem saved)
				{
					if (saved == null || saved.Exists || !CustomItemRegistry.TryGet(saved.ItemID, out var _))
					{
						return false;
					}
					record.PocketItems.Add(JsonUtility.ToJson((object)saved));
					return true;
				});
				if (record.PocketItems.Count == 0 && !TryGetHidden(savedPlayer.HeldItem, out var _))
				{
					Carried.Remove(text);
					return;
				}
				record.Fingerprint = Fingerprint(savedPlayer);
				Carried[text] = record;
			}
		}

		private sealed class InventoryLoadState
		{
			public SavedPlayer Save;

			public List<SavedItem> OriginalItems;

			public bool HeldRevealed;

			public bool HeldHidden;
		}

		[HarmonyPatch(typeof(PlayerInventory), "LoadFromSave")]
		private static class LoadInventoryPatch
		{
			private static void Prefix(PlayerInventory __instance, out InventoryLoadState __state)
			{
				__state = null;
				PersistencePatches.EnsureLoaded();
				SavedPlayer val = InventoryToLoadFrom.Invoke(__instance);
				if (val == null)
				{
					return;
				}
				InventoryLoadState inventoryLoadState = new InventoryLoadState
				{
					Save = val
				};
				CustomItem custom;
				if (val.InventoryItems != null)
				{
					List<SavedItem> list = new List<SavedItem>(val.InventoryItems.Count);
					HashSet<byte> hashSet = new HashSet<byte>();
					bool flag = false;
					foreach (SavedItem inventoryItem in val.InventoryItems)
					{
						if (inventoryItem == null || (Object)(object)GameInfo.GetSpawnable(inventoryItem.ItemID) == (Object)null)
						{
							Plugin.Log.LogWarning((object)($"[Save] Skipped unknown item id {inventoryItem?.ItemID} in pocket " + $"{inventoryItem?.InventorySlot} of player {val.SteamID}."));
							flag = true;
						}
						else
						{
							list.Add(inventoryItem);
							hashSet.Add(inventoryItem.InventorySlot);
						}
					}
					if (Carried.TryGetValue(val.SteamID.ToString(), out var value))
					{
						foreach (string pocketItem in value.PocketItems)
						{
							SavedItem val2 = JsonUtility.FromJson<SavedItem>(pocketItem);
							if (TryGetHidden(val2, out custom) && hashSet.Add(val2.InventorySlot))
							{
								val2.Exists = true;
								list.Add(val2);
								flag = true;
							}
						}
					}
					if (flag)
					{
						inventoryLoadState.OriginalItems = val.InventoryItems;
						val.InventoryItems = list;
					}
				}
				SavedItem heldItem = val.HeldItem;
				if (TryGetHidden(heldItem, out custom))
				{
					heldItem.Exists = true;
					inventoryLoadState.HeldRevealed = true;
				}
				else if (heldItem != null && heldItem.Exists && (Object)(object)GameInfo.GetSpawnable(heldItem.ItemID) == (Object)null)
				{
					Plugin.Log.LogWarning((object)$"[Save] Skipped unknown held item id {heldItem.ItemID} of player {val.SteamID}.");
					heldItem.Exists = false;
					inventoryLoadState.HeldHidden = true;
				}
				if (inventoryLoadState.OriginalItems != null || inventoryLoadState.HeldRevealed || inventoryLoadState.HeldHidden)
				{
					__state = inventoryLoadState;
				}
			}

			private static void Finalizer(InventoryLoadState __state)
			{
				if (__state != null)
				{
					if (__state.OriginalItems != null)
					{
						__state.Save.InventoryItems = __state.OriginalItems;
					}
					if (__state.HeldRevealed)
					{
						__state.Save.HeldItem.Exists = false;
					}
					if (__state.HeldHidden)
					{
						__state.Save.HeldItem.Exists = true;
					}
				}
			}
		}

		[HarmonyPatch(typeof(ItemManager), "SpawnSavedItem")]
		private static class LoadWorldItemPatch
		{
			private static void Prefix(SavedWorldItem savedItem, out SavedItem __state)
			{
				__state = null;
				if (TryGetHidden(savedItem?.Item, out var _))
				{
					savedItem.Item.Exists = true;
					__state = savedItem.Item;
				}
			}

			private static void Finalizer(SavedItem __state)
			{
				if (__state != null)
				{
					__state.Exists = false;
				}
			}
		}

		private static readonly FieldRef<PlayerInventory, SavedPlayer> InventoryToLoadFrom = AccessTools.FieldRefAccess<PlayerInventory, SavedPlayer>("_toLoadFrom");

		private static readonly Dictionary<string, CarriedItemsRecord> Carried = new Dictionary<string, CarriedItemsRecord>();

		private static bool _legacySideFile;

		public static bool Holds(SavedItem saved, CustomItem custom)
		{
			if (saved != null)
			{
				return saved.ItemID == custom.ItemId;
			}
			return false;
		}

		private static bool TryGetHidden(SavedItem saved, out CustomItem custom)
		{
			custom = null;
			if (saved != null && !saved.Exists)
			{
				return CustomItemRegistry.TryGet(saved.ItemID, out custom);
			}
			return false;
		}

		internal static void Capture(PersistenceData data)
		{
			data.Carried.AddRange(Carried.Values);
		}

		internal static void Load(PersistenceData loaded, ServerSaveObject save)
		{
			Carried.Clear();
			_legacySideFile = loaded.Version < 2;
			int num = 0;
			foreach (CarriedItemsRecord item in loaded.Carried)
			{
				ulong result;
				SavedPlayer val = (ulong.TryParse(item.SteamId, out result) ? SaveManager.GetSavedPlayer(result) : null);
				if (val != null && Fingerprint(val) == item.Fingerprint)
				{
					Carried[item.SteamId] = item;
				}
				else
				{
					num++;
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)$"[Save] {num} player(s) were saved without BuriedTreasures since; their modded items are gone.");
			}
		}

		internal static bool CarriedItemsSurvived(string steamId)
		{
			if (!_legacySideFile)
			{
				return Carried.ContainsKey(steamId);
			}
			return true;
		}

		private static string Fingerprint(SavedPlayer save)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(save.Health).Append(',').Append(save.Fullness)
				.Append(',')
				.Append(save.ExtraSlots)
				.Append(',')
				.Append(save.WishboneIndex)
				.Append(',')
				.Append(save.HasFinishedTutorial ? 1 : 0);
			Append(stringBuilder, save.HeldItem);
			if (save.InventoryItems != null)
			{
				foreach (SavedItem inventoryItem in save.InventoryItems)
				{
					Append(stringBuilder, inventoryItem);
				}
			}
			stringBuilder.Append(" |");
			if (save.OwnedBaits != null)
			{
				foreach (int ownedBait in save.OwnedBaits)
				{
					stringBuilder.Append(ownedBait).Append(',');
				}
			}
			return stringBuilder.ToString();
		}

		private static void Append(StringBuilder text, SavedItem saved)
		{
			text.Append(" |");
			if (saved != null)
			{
				text.Append(saved.Exists ? 1 : 0).Append(':').Append(saved.ItemID)
					.Append(':')
					.Append(saved.InventorySlot);
			}
		}

		internal static void ClearSession()
		{
			Carried.Clear();
			_legacySideFile = false;
		}
	}
}
namespace BuriedTreasures.Models
{
	internal static class MaterialTweaks
	{
		private static readonly int NormalStrengthId = Shader.PropertyToID("_Normal_Strength");

		private static readonly int NormalScaleId = Shader.PropertyToID("_Normal_Scale");

		private static readonly int EmissionId = Shader.PropertyToID("_Emission");

		private static readonly int SmoothnessId = Shader.PropertyToID("_Smoothness");

		private static readonly int MetallicId = Shader.PropertyToID("_Metallic");

		private static readonly int ShininessId = Shader.PropertyToID("_Shininess");

		private static readonly int SpecularHighlightsId = Shader.PropertyToID("_SpecularHighlights");

		private static readonly int EnvReflectionsId = Shader.PropertyToID("_EnvironmentReflections");

		private static readonly int GlossyReflectionsId = Shader.PropertyToID("_GlossyReflections");

		private static readonly int SpecColorId = Shader.PropertyToID("_SpecColor");

		private static readonly int SpecularColorId = Shader.PropertyToID("_SpecularColor");

		private static readonly int ReflectColorId = Shader.PropertyToID("_ReflectColor");

		private static readonly int ColorsId = Shader.PropertyToID("_Colors");

		private static readonly int BaseMapId = Shader.PropertyToID("_BaseMap");

		private static Shader _unlit;

		public static void DisableNormalMap(Renderer renderer)
		{
			//IL_008c: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			Material material = renderer.material;
			material.SetFloat(NormalStrengthId, 0f);
			material.SetFloat(NormalScaleId, 0f);
			material.SetFloat(SmoothnessId, 0f);
			material.SetFloat(MetallicId, 0f);
			material.SetFloat(ShininessId, 0f);
			material.SetFloat(SpecularHighlightsId, 0f);
			material.SetFloat(EnvReflectionsId, 0f);
			material.SetFloat(GlossyReflectionsId, 0f);
			material.SetColor(SpecColorId, Color.black);
			material.SetColor(SpecularColorId, Color.black);
			material.SetColor(ReflectColorId, Color.black);
		}

		public static void DisableEmission(Renderer renderer)
		{
			renderer.material.SetFloat(EmissionId, 0f);
		}

		public static void MakeMatte(Renderer renderer)
		{
			Material material = renderer.material;
			Texture val = (material.HasProperty(ColorsId) ? material.GetTexture(ColorsId) : null);
			_unlit = (((Object)(object)_unlit != (Object)null) ? _unlit : Shader.Find("Universal Render Pipeline/Unlit"));
			if (!((Object)(object)_unlit == (Object)null) && !((Object)(object)val == (Object)null))
			{
				material.shader = _unlit;
				material.SetTexture(BaseMapId, val);
			}
		}
	}
	internal static class MeshTangents
	{
		public static Vector4 ForNormal(Vector3 normal)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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_0056: 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: Unknown result type (might be due to invalid IL or missing references)
			normal = ((((Vector3)(ref normal)).sqrMagnitude > 1E-12f) ? ((Vector3)(ref normal)).normalized : Vector3.forward);
			Vector3 val = Vector3.Cross((Mathf.Abs(Vector3.Dot(normal, Vector3.up)) < 0.99f) ? Vector3.up : Vector3.right, normal);
			Vector3 normalized = ((Vector3)(ref val)).normalized;
			return new Vector4(normalized.x, normalized.y, normalized.z, -1f);
		}
	}
	internal sealed class ObjModel
	{
		private readonly struct Corner
		{
			public readonly int Position;

			public readonly int Normal;

			public Corner(int position, int normal)
			{
				Position = position;
				Normal = normal;
			}
		}

		private static readonly char[] Whitespace = new char[2] { ' ', '\t' };

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

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

		private readonly List<Corner[]> _faces = new List<Corner[]>();

		private readonly List<int> _faceMaterial = new List<int>();

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

		private int _currentMaterial = -1;

		public Bounds Bounds { get; private set; }

		private ObjModel()
		{
		}

		public string MaterialNameAt(int index)
		{
			if (index < 0 || index >= _materialNames.Count)
			{
				return null;
			}
			return _materialNames[index];
		}

		public static ObjModel LoadEmbedded(string resourceName)
		{
			Stream stream = EmbeddedResources.Open(resourceName) ?? throw new InvalidOperationException("The embedded model '" + resourceName + "' is missing from the DLL.");
			ObjModel objModel = new ObjModel();
			using (StreamReader streamReader = new StreamReader(stream))
			{
				string line;
				while ((line = streamReader.ReadLine()) != null)
				{
					objModel.ParseLine(line);
				}
			}
			if (objModel._faces.Count == 0)
			{
				throw new InvalidOperationException("The embedded model '" + resourceName + "' has no faces.");
			}
			objModel.FindBounds();
			return objModel;
		}

		public Mesh ToMesh(string name, Matrix4x4 fileToMesh, Vector2 uv)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			return Build(name, fileToMesh, (int _) => uv);
		}

		public Mesh ToMeshByMaterial(string name, Matrix4x4 fileToMesh, Func<int, Vector2> materialUv)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			return Build(name, fileToMesh, (int f) => materialUv(_faceMaterial[f]));
		}

		public Mesh ToMeshByMaterial(string name, Matrix4x4 fileToMesh, Func<int, Vector2> materialUv, int submeshCount, Func<int, int> materialSubmesh)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			return Build(name, fileToMesh, (int f) => materialUv(_faceMaterial[f]), submeshCount, (int f) => materialSubmesh(_faceMaterial[f]));
		}

		private Mesh Build(string name, Matrix4x4 fileToMesh, Func<int, Vector2> faceUv, int submeshCount = 1, Func<int, int> faceSubmesh = null)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: 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_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Expected O, but got Unknown
			bool flag = ((Matrix4x4)(ref fileToMesh)).determinant < 0f;
			List<Vector3> vertices = new List<Vector3>();
			List<Vector3> normals = new List<Vector3>();
			List<Vector2> uvs = new List<Vector2>();
			List<int>[] array = new List<int>[Mathf.Max(1, submeshCount)];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = new List<int>();
			}
			Dictionary<long, int> vertexOfCorner = new Dictionary<long, int>();
			List<Vector3> list = new List<Vector3>();
			List<int> list2 = new List<int>();
			int f;
			for (f = 0; f < _faces.Count; f++)
			{
				Corner[] array2 = _faces[f];
				list.Clear();
				Corner[] array3 = array2;
				for (int j = 0; j < array3.Length; j++)
				{
					Corner corner = array3[j];
					list.Add(_positions[corner.Position]);
				}
				Vector3 val = NewellNormal(list);
				Triangulate(list, val, list2);
				Vector3 val2 = ((Matrix4x4)(ref fileToMesh)).MultiplyVector(val);
				Vector3 meshFaceNormal = ((Vector3)(ref val2)).normalized;
				Vector2 uv = faceUv(f);
				List<int> list3 = array[(faceSubmesh != null) ? Mathf.Clamp(faceSubmesh(f), 0, array.Length - 1) : 0];
				for (int k = 0; k < list2.Count; k += 3)
				{
					int num = list2[k];
					int num2 = list2[k + 1];
					int num3 = list2[k + 2];
					if (flag)
					{
						int num4 = num3;
						num3 = num2;
						num2 = num4;
					}
					list3.Add(VertexOf(array2[num]));
					list3.Add(VertexOf(array2[num2]));
					list3.Add(VertexOf(array2[num3]));
				}
				int VertexOf(Corner corner2)
				{
					//IL_008c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0091: Unknown result type (might be due to invalid IL or missing references)
					//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
					//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
					//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
					//IL_00d2: 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_00e4: Unknown result type (might be due to invalid IL or missing references)
					long num5 = ((corner2.Normal >= 0) ? (((long)corner2.Position * (long)(_normals.Count + 1) + corner2.Normal) * (_materialNames.Count + 1) + _faceMaterial[f] + 1) : (-1));
					if (num5 >= 0 && vertexOfCorner.TryGetValue(num5, out var value))
					{
						return value;
					}
					int count = vertices.Count;
					vertices.Add(((Matrix4x4)(ref fileToMesh)).MultiplyPoint3x4(_positions[corner2.Position]));
					List<Vector3> list5 = normals;
					Vector3 item;
					if (corner2.Normal < 0)
					{
						item = meshFaceNormal;
					}
					else
					{
						Vector3 val4 = ((Matrix4x4)(ref fileToMesh)).MultiplyVector(_normals[corner2.Normal]);
						item = ((Vector3)(ref val4)).normalized;
					}
					list5.Add(item);
					uvs.Add(uv);
					if (num5 >= 0)
					{
						vertexOfCorner[num5] = count;
					}
					return count;
				}
			}
			List<Vector4> list4 = new List<Vector4>(normals.Count);
			foreach (Vector3 item2 in normals)
			{
				list4.Add(MeshTangents.ForNormal(item2));
			}
			Mesh val3 = new Mesh
			{
				name = name
			};
			if (vertices.Count > 65535)
			{
				val3.indexFormat = (IndexFormat)1;
			}
			val3.SetVertices(vertices);
			val3.SetNormals(normals);
			val3.SetTangents(list4);
			val3.SetUVs(0, uvs);
			val3.subMeshCount = array.Length;
			for (int l = 0; l < array.Length; l++)
			{
				val3.SetTriangles(array[l], l);
			}
			val3.RecalculateBounds();
			return val3;
		}

		private void ParseLine(string line)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			string[] array = line.Split(Whitespace, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length != 0)
			{
				switch (array[0])
				{
				case "v":
					_positions.Add(ParseVector(array));
					break;
				case "vn":
					_normals.Add(ParseVector(array));
					break;
				case "f":
					ParseFace(array);
					break;
				case "usemtl":
					SetMaterial((array.Length > 1) ? array[1] : "");
					break;
				}
			}
		}

		private void SetMaterial(string materialName)
		{
			int num = _materialNames.IndexOf(materialName);
			if (num < 0)
			{
				num = _materialNames.Count;
				_materialNames.Add(materialName);
			}
			_currentMaterial = num;
		}

		private void ParseFace(string[] tokens)
		{
			if (tokens.Length >= 4)
			{
				if (_currentMaterial < 0)
				{
					SetMaterial("");
				}
				Corner[] array = new Corner[tokens.Length - 1];
				for (int i = 1; i < tokens.Length; i++)
				{
					string[] array2 = tokens[i].Split('/');
					int position = ResolveIndex(array2[0], _positions.Count);
					int normal = ((array2.Length > 2 && array2[2].Length > 0) ? ResolveIndex(array2[2], _normals.Count) : (-1));
					array[i - 1] = new Corner(position, normal);
				}
				_faces.Add(array);
				_faceMaterial.Add(_currentMaterial);
			}
		}

		private static Vector3 ParseVector(string[] tokens)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(ParseFloat(tokens[1]), ParseFloat(tokens[2]), ParseFloat(tokens[3]));
		}

		private static float ParseFloat(string token)
		{
			return float.Parse(token, NumberStyles.Float, CultureInfo.InvariantCulture);
		}

		private static int ResolveIndex(string token, int count)
		{
			int num = int.Parse(token, NumberStyles.Integer, CultureInfo.InvariantCulture);
			if (num >= 0)
			{
				return num - 1;
			}
			return count + num;
		}

		private void FindBounds()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to i