Decompiled source of WhySleepAlone v1.0.0

BepInEx/plugins/WhySleepAlone/WhySleepAlone.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using WhySleepAlone.Beds;
using WhySleepAlone.Model;
using WhySleepAlone.Network;
using WhySleepAlone.StatusEffects;
using WhySleepAlone.Text;
using WhySleepAlone.Visuals;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("WhySleepAlone")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("WhySleepAlone")]
[assembly: AssemblyTitle("WhySleepAlone")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 WhySleepAlone
{
	internal static class Constants
	{
		internal const string Guid = "TheLionCid.WhySleepAlone";

		internal const string Name = "Why Sleep Alone?";

		internal const string Version = "1.0.0";

		internal const string DragonBedPrefab = "piece_bed02";
	}
	internal sealed class ModConfig
	{
		internal ConfigEntry<bool> Enabled;

		internal ConfigEntry<bool> DebugLogging;

		internal ConfigEntry<bool> Buff;

		internal ConfigEntry<bool> HealthOn;

		internal ConfigEntry<bool> StaminaOn;

		internal ConfigEntry<bool> EitrOn;

		internal ConfigEntry<bool> Hearts;

		internal ConfigEntry<bool> Context;

		internal ConfigEntry<bool> AvoidRepeat;

		internal ConfigEntry<float> Duration;

		internal ConfigEntry<float> Health;

		internal ConfigEntry<float> Stamina;

		internal ConfigEntry<float> Eitr;

		internal ConfigEntry<float> HeartInterval;

		internal ConfigEntry<string> Tone;

		internal ConfigEntry<string> Intensity;

		internal ConfigEntry<string> Icon;

		internal ConfigEntry<string> AdditionalDoubleBeds;

		internal ConfigEntry<bool> IncludeCustomInMixed;

		internal ConfigEntry<int> RomanticBursts;

		internal ConfigEntry<int> BawdyBursts;

		internal ConfigEntry<KeyboardShortcut> JoinKey;

		private Gameplay? _export;

		private readonly ConfigEntry<float>[] _sleep = new ConfigEntry<float>[6];

		private readonly ConfigEntry<float>[] _spawn = new ConfigEntry<float>[6];

		internal ModConfig(ConfigFile c)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Expected O, but got Unknown
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Expected O, but got Unknown
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Expected O, but got Unknown
			//IL_0338: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Expected O, but got Unknown
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_036f: Expected O, but got Unknown
			c.SettingChanged += delegate
			{
				_export = null;
			};
			Enabled = c.Bind<bool>("General", "Enabled", true, "Enable shared Dragon Beds. Gameplay settings come from the server.");
			DebugLogging = c.Bind<bool>("General", "DebugLogging", false, "Log link and sleep lifecycle, never each frame.");
			JoinKey = c.Bind<KeyboardShortcut>("Controls", "JoinBedKey", new KeyboardShortcut((KeyCode)101, Array.Empty<KeyCode>()), "Tecla para unirse a una cama abierta / Key to join an open shared bed. Local setting; E selects the vanilla Use action (also supports remapped Use/gamepad). Other keys use a custom shortcut. The owner must invite you first.");
			Buff = c.Bind<bool>("Status Effect", "Enabled", true, "Award Satisfied after a valid shared night.");
			Duration = Number(c, "Status Effect", "DurationSeconds", 3600f, 1f, 86400f);
			HealthOn = c.Bind<bool>("Status Effect", "HealthRegenEnabled", true, "");
			StaminaOn = c.Bind<bool>("Status Effect", "StaminaRegenEnabled", true, "");
			EitrOn = c.Bind<bool>("Status Effect", "EitrRegenEnabled", true, "");
			Health = Number(c, "Status Effect", "HealthRegenMultiplier", 1.1f, 1f, 10f);
			Stamina = Number(c, "Status Effect", "StaminaRegenMultiplier", 1.1f, 1f, 10f);
			Eitr = Number(c, "Status Effect", "EitrRegenMultiplier", 1.1f, 1f, 10f);
			Tone = c.Bind<string>("Text", "SharedSleepTone", "Neutral", new ConfigDescription("Local presentation tone.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[5] { "Neutral", "Romantic", "Bawdy", "Mixed", "Custom" }), Array.Empty<object>()));
			IncludeCustomInMixed = c.Bind<bool>("Text", "IncludeCustomInMixed", false, "Include local custom text in Mixed (10% when available) / Incluir textos propios en Mixed (10% si existen). Reload config or restart after editing the UTF-8 file / Recarga la configuracion o reinicia tras editar el archivo.");
			AdditionalDoubleBeds = c.Bind<string>("Compatibility", "AdditionalDoubleBeds", "", "Exact additional bed prefab names, comma-separated; host authoritative / Prefabs exactos de camas adicionales, separados por comas; manda el anfitrion. Requires Bed, ZNetView and spawn point / Requiere Bed, ZNetView y punto de aparicion. Uses existing global position settings / Usa las posiciones globales existentes.");
			Intensity = c.Bind<string>("Text", "BawdyIntensity", "Mild", new ConfigDescription("", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Mild", "Spicy" }), Array.Empty<object>()));
			Context = c.Bind<bool>("Text", "EnableContextualMessages", true, "");
			AvoidRepeat = c.Bind<bool>("Text", "AvoidImmediateRepeats", true, "Exclude the last 10 distinct dreams and last 4 wake lines when alternatives exist. Local history resets on restart.");
			Icon = c.Bind<string>("Visual", "StatusIcon", "Auto", new ConfigDescription("Vanilla icon source.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "Auto", "bed02", "potion_pheromones", "rested" }), Array.Empty<object>()));
			Hearts = c.Bind<bool>("Visual", "EnableHeartWakeVFX", true, "");
			RomanticBursts = c.Bind<int>("Visual", "RomanticHeartBursts", 1, new ConfigDescription("", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
			BawdyBursts = c.Bind<int>("Visual", "BawdyHeartBursts", 3, new ConfigDescription("", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
			HeartInterval = Number(c, "Visual", "HeartBurstIntervalSeconds", 0.35f, 0.15f, 2f);
			string[] array = new string[6] { "A_X", "A_Y", "A_Z", "B_X", "B_Y", "B_Z" };
			float[] array2 = new float[6] { -0.45f, 0f, 0f, 0.45f, 0f, 0f };
			float[] array3 = new float[6] { -0.65f, 0.3f, 0f, 0.65f, 0.3f, 0f };
			for (int num = 0; num < 6; num++)
			{
				_sleep[num] = Number(c, "Sleep Slots", "Sleep" + array[num], array2[num], -2f, 2f);
				_spawn[num] = Number(c, "Respawn Slots", "Respawn" + array[num], array3[num], -3f, 3f);
			}
		}

		private static ConfigEntry<float> Number(ConfigFile c, string s, string k, float d, float lo, float hi)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			return c.Bind<float>(s, k, d, new ConfigDescription("Server-authoritative for gameplay; local for visuals.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(lo, hi), Array.Empty<object>()));
		}

		internal Gameplay Export()
		{
			return _export ?? (_export = new Gameplay
			{
				AdditionalDoubleBeds = AdditionalDoubleBeds.Value,
				Enabled = Enabled.Value,
				Buff = Buff.Value,
				Duration = Duration.Value,
				Health = (HealthOn.Value ? Health.Value : 1f),
				Stamina = (StaminaOn.Value ? Stamina.Value : 1f),
				Eitr = (EitrOn.Value ? Eitr.Value : 1f),
				SleepAX = _sleep[0].Value,
				SleepAY = _sleep[1].Value,
				SleepAZ = _sleep[2].Value,
				SleepBX = _sleep[3].Value,
				SleepBY = _sleep[4].Value,
				SleepBZ = _sleep[5].Value,
				SpawnAX = _spawn[0].Value,
				SpawnAY = _spawn[1].Value,
				SpawnAZ = _spawn[2].Value,
				SpawnBX = _spawn[3].Value,
				SpawnBY = _spawn[4].Value,
				SpawnBZ = _spawn[5].Value
			}.Normalize());
		}
	}
	[BepInPlugin("TheLionCid.WhySleepAlone", "Why Sleep Alone?", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private Harmony? _harmony;

		private float _nextTick;

		private bool _failed;

		internal static Plugin? Instance { get; private set; }

		internal ModConfig Settings { get; private set; }

		internal SharedBeds Beds { get; private set; }

		internal SharedNetwork Network { get; private set; }

		internal SleepMessages Messages { get; private set; }

		internal VanillaVisuals Visuals { get; private set; }

		internal RewardService Rewards { get; private set; }

		private void Awake()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			try
			{
				Settings = new ModConfig(((BaseUnityPlugin)this).Config);
				Messages = new SleepMessages(this);
				Visuals = new VanillaVisuals(this);
				((BaseUnityPlugin)this).Config.ConfigReloaded += OnConfigReloaded;
				Rewards = new RewardService(this);
				Beds = new SharedBeds(this);
				Network = new SharedNetwork(this);
				Instance = this;
				_harmony = new Harmony("TheLionCid.WhySleepAlone");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Why Sleep Alone 1.0.0: shared Dragon Beds, server links, dual slots and Satisfied. Target Valheim 1.0.17.");
			}
			catch (Exception ex)
			{
				Harmony? harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				Instance = null;
				_failed = true;
				((BaseUnityPlugin)this).Logger.LogError((object)("Plugin initialization failed: " + ex));
			}
		}

		private void Update()
		{
			if (_failed)
			{
				return;
			}
			try
			{
				Beds.UpdateJoinInput();
				if (!(Time.realtimeSinceStartup < _nextTick))
				{
					_nextTick = Time.realtimeSinceStartup + 0.25f;
					Network.Tick();
					Rewards.Tick();
				}
			}
			catch (Exception ex)
			{
				_failed = true;
				((BaseUnityPlugin)this).Logger.LogError((object)("Shared-bed update stopped after an unexpected failure; restart after inspecting this error: " + ex));
				Network.Reset();
				Harmony? harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				Instance = null;
			}
		}

		internal unsafe void ObserveBed(Bed bed)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (Settings.DebugLogging.Value && DragonBedDetector.TryIdentify(bed, out ZDO zdo))
			{
				ZDOID uid = zdo.m_uid;
				Debug("Dragon Bed identified: " + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString());
			}
		}

		internal void Debug(string text)
		{
			if (Settings.DebugLogging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			}
		}

		internal void Warn(string text)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)text);
		}

		private void OnConfigReloaded(object sender, EventArgs e)
		{
			Messages.ReloadCustomText();
		}

		private void OnDestroy()
		{
			if (Instance == this)
			{
				Network?.Reset();
				Instance = null;
			}
			((BaseUnityPlugin)this).Config.ConfigReloaded -= OnConfigReloaded;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace WhySleepAlone.Visuals
{
	internal sealed class VanillaVisuals
	{
		private readonly Plugin _plugin;

		private EffectList? _hearts;

		private bool _resolved;

		private bool _warned;

		private bool _iconWarned;

		internal VanillaVisuals(Plugin p)
		{
			_plugin = p;
		}

		internal void Reset()
		{
			_hearts = null;
			_resolved = false;
			_warned = false;
			_iconWarned = false;
		}

		internal Sprite? Icon()
		{
			ObjectDB instance = ObjectDB.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return null;
			}
			string text = _plugin.Settings.Icon.Value;
			if (text == "Auto")
			{
				text = ((_plugin.Settings.Tone.Value == "Neutral") ? "bed02" : "potion_pheromones");
			}
			Sprite val = null;
			if (text == "bed02" && Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				GameObject prefab = ZNetScene.instance.GetPrefab("piece_bed02");
				if (Object.op_Implicit((Object)(object)prefab))
				{
					val = prefab.GetComponent<Piece>()?.m_icon;
				}
			}
			if (text == "potion_pheromones")
			{
				GameObject itemPrefab = instance.GetItemPrefab("MeadTrollPheromones");
				Sprite[] array = ((!Object.op_Implicit((Object)(object)itemPrefab)) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_icons);
				if (array != null && array.Length != 0)
				{
					val = array[0];
				}
				if (!Object.op_Implicit((Object)(object)val))
				{
					val = instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("Potion_TrollPheromones"))?.m_icon;
				}
			}
			if (!Object.op_Implicit((Object)(object)val) && text != "rested" && !_iconWarned)
			{
				_iconWarned = true;
				_plugin.Warn("Preferred Satisfied icon unavailable; using Rested if present.");
			}
			if (!Object.op_Implicit((Object)(object)val))
			{
				val = instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("Rested"))?.m_icon;
			}
			return val;
		}

		internal void Wake(string tone)
		{
			if (tone == "Neutral" || !_plugin.Settings.Hearts.Value || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				return;
			}
			try
			{
				if (!_resolved)
				{
					_resolved = true;
					GameObject prefab = ZNetScene.instance.GetPrefab("Boar");
					_hearts = ((!Object.op_Implicit((Object)(object)prefab)) ? null : prefab.GetComponent<Tameable>()?.m_petEffect);
				}
				if (_hearts == null || !_hearts.HasEffects())
				{
					Warn("Boar pet effects unavailable; hearts disabled.");
					return;
				}
				int val = ((tone == "Bawdy") ? _plugin.Settings.BawdyBursts.Value : _plugin.Settings.RomanticBursts.Value);
				((MonoBehaviour)_plugin).StartCoroutine(Emit(Player.m_localPlayer, Math.Min(3, val), _plugin.Network.World));
			}
			catch (Exception ex)
			{
				Warn("Hearts disabled: " + ex.Message);
			}
		}

		private IEnumerator Emit(Player player, int bursts, long world)
		{
			for (int i = 0; i < bursts; i++)
			{
				if (!Object.op_Implicit((Object)(object)player))
				{
					break;
				}
				if (((Character)player).IsDead())
				{
					break;
				}
				if (world != _plugin.Network.World)
				{
					break;
				}
				bool flag = false;
				try
				{
					_hearts.Create(((Component)player).transform.position + Vector3.up, ((Component)player).transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
				}
				catch (Exception ex)
				{
					Warn("Hearts disabled: " + ex.Message);
					flag = true;
				}
				if (flag)
				{
					break;
				}
				yield return (object)new WaitForSeconds(_plugin.Settings.HeartInterval.Value);
			}
		}

		private void Warn(string message)
		{
			_hearts = null;
			_resolved = true;
			if (!_warned)
			{
				_warned = true;
				_plugin.Warn(message);
			}
		}
	}
}
namespace WhySleepAlone.Text
{
	internal sealed class SleepMessages
	{
		private readonly Plugin _plugin;

		private string _oneKey = "";

		private string _oneText = "";

		private readonly Dictionary<string, List<string>> _es = Load("es");

		private readonly Dictionary<string, List<string>> _en = Load("en");

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

		private readonly DreamHistory _dreamHistory = new DreamHistory(10);

		private readonly DreamHistory _wakeHistory = new DreamHistory(4);

		private static readonly Dictionary<string, string[]> NarrativeKeys = BuildNarrativeKeys();

		private static readonly string[] ComfortNeutral = new string[3] { "sleep.Neutral.01", "sleep.Neutral.06", "sleep.Neutral.17" };

		private static readonly string[] ComfortRomantic = new string[3] { "sleep.Romantic.04", "sleep.Romantic.11", "sleep.Romantic.17" };

		private readonly PendingDreamSession _pendingDream = new PendingDreamSession();

		private bool _customWarned;

		private CustomTextBank _custom = new CustomTextBank();

		internal bool Spanish
		{
			get
			{
				if (Localization.instance != null)
				{
					return Localization.instance.GetSelectedLanguage().StartsWith("Spanish", StringComparison.OrdinalIgnoreCase);
				}
				return false;
			}
		}

		private static Dictionary<string, string[]> BuildNarrativeKeys()
		{
			Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>();
			string[] array = new string[2] { "sleep", "wake" };
			foreach (string text in array)
			{
				string[] array2 = new string[4] { "Neutral", "Romantic", "Bawdy Mild", "Bawdy Spicy" };
				foreach (string text2 in array2)
				{
					string[] array3 = new string[(text == "sleep") ? 20 : 5];
					for (int k = 0; k < array3.Length; k++)
					{
						array3[k] = text + "." + text2 + "." + (k + 1).ToString("00", CultureInfo.InvariantCulture);
					}
					dictionary[text + "." + text2] = array3;
				}
			}
			return dictionary;
		}

		private string Bucket(int roll)
		{
			return DreamSelection.Bucket(_plugin.Settings.Tone.Value, _plugin.Settings.Intensity.Value, roll);
		}

		private string Narrative(string category, string bucket, DreamHistory history)
		{
			string[] array = ((category == "sleep") ? _custom.Dreams : _custom.Wake);
			string value = _plugin.Settings.Tone.Value;
			int roll = ((value == "Mixed" && _plugin.Settings.IncludeCustomInMixed.Value) ? Random.Range(0, 100) : 100);
			if (CustomTextBank.UseCustom(value, _plugin.Settings.IncludeCustomInMixed.Value, roll, array.Length))
			{
				string key = history.Pick(array, (int n) => Random.Range(0, n), _plugin.Settings.AvoidRepeat.Value);
				return _custom.Text[key];
			}
			if (value == "Custom")
			{
				bucket = "Neutral";
				WarnCustomOnce("Custom " + category + " bank empty; Neutral fallback / Banco personalizado vacio; se usa Neutral.");
			}
			string[] keys = NarrativeKeys[category + "." + bucket];
			if (category == "sleep" && _plugin.Settings.Context.Value && Object.op_Implicit((Object)(object)Player.m_localPlayer) && Player.m_localPlayer.GetComfortLevel() >= 12 && Random.Range(0, 4) == 0)
			{
				string[] array2 = ((bucket == "Neutral") ? ComfortNeutral : ((bucket == "Romantic") ? ComfortRomantic : null));
				if (array2 != null && (!_plugin.Settings.AvoidRepeat.Value || array2.Any((string key2) => !history.Contains(key2))))
				{
					keys = array2;
				}
			}
			string text = history.Pick(keys, (int n) => Random.Range(0, n), _plugin.Settings.AvoidRepeat.Value);
			if ((Spanish ? _es : _en).TryGetValue(text, out List<string> value2) || _en.TryGetValue(text, out value2))
			{
				return value2[0];
			}
			throw new InvalidDataException("Missing narrative translation: " + text);
		}

		private void WarnCustomOnce(string warning)
		{
			if (!_customWarned && warning.Length != 0)
			{
				_customWarned = true;
				_plugin.Warn(warning);
			}
		}

		internal SleepMessages(Plugin p)
		{
			_plugin = p;
			ReloadCustomText();
		}

		internal void ReloadCustomText()
		{
			_custom = CustomTextBank.Load(Path.Combine(Paths.ConfigPath, "WhySleepAlone_CustomText.txt"));
			_customWarned = false;
			string text = _custom.Warning;
			if ((_plugin.Settings.Tone.Value == "Custom" || _plugin.Settings.IncludeCustomInMixed.Value) && _custom.Dreams.Length == 0)
			{
				text = text + ((text.Length == 0) ? "" : " ") + "No custom dreams; Neutral fallback / Sin suenos personalizados; se usa Neutral.";
			}
			WarnCustomOnce(text);
			_plugin.Debug("Loaded custom text: dreams=" + _custom.Dreams.Length + ", wake=" + _custom.Wake.Length);
		}

		private static Dictionary<string, List<string>> Load(string language)
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("WhySleepAlone.Text.Resources." + language + ".txt");
			if (stream == null)
			{
				return dictionary;
			}
			using StreamReader streamReader = new StreamReader(stream);
			while (!streamReader.EndOfStream)
			{
				string text = streamReader.ReadLine();
				int num = text.IndexOf('=');
				if (num >= 0)
				{
					string key = text.Substring(0, num);
					if (!dictionary.TryGetValue(key, out var value))
					{
						value = (dictionary[key] = new List<string>());
					}
					value.Add(text.Substring(num + 1));
				}
			}
			return dictionary;
		}

		internal string Get(string key)
		{
			switch (key)
			{
			case "server":
				if (!Spanish)
				{
					return "The server needs Why Sleep Alone; wait for synchronization.";
				}
				return "El servidor debe tener Why Sleep Alone y terminar la sincronización.";
			case "characterpending":
				if (!Spanish)
				{
					return "The server cannot validate your character yet. Check the Why Sleep Alone log.";
				}
				return "El servidor todavía no puede validar tu personaje. Consulta el log de Why Sleep Alone.";
			case "claim":
				if (!Spanish)
				{
					return "Claim this bed with Use first.";
				}
				return "Reclama primero la cama con Usar.";
			case "waiting":
				if (!Spanish)
				{
					return "Waiting for the bed…";
				}
				return "Esperando respuesta de la cama…";
			case "timeout":
				if (!Spanish)
				{
					return "No response. Please try again.";
				}
				return "Sin respuesta. Inténtalo de nuevo.";
			case "stale":
			case "invalid":
				if (!Spanish)
				{
					return "The bed or link is no longer valid.";
				}
				return "La cama o el vínculo ya no son válidos.";
			case "open":
				if (!Spanish)
				{
					return "Bed open to sharing. Your companion can link using the key shown on the bed.";
				}
				return "Cama abierta para compartir. Tu compañero puede vincularse con la tecla indicada en la cama.";
			case "closed":
				if (!Spanish)
				{
					return "This bed is not open for sharing.";
				}
				return "Esta cama no está abierta para compartir.";
			case "unlinked":
				if (!Spanish)
				{
					return "Shared bed link removed.";
				}
				return "Vínculo compartido retirado.";
			case "full":
			case "occupied":
				if (!Spanish)
				{
					return "This bed already has two linked Vikings.";
				}
				return "Esta cama ya tiene dos vikingos vinculados.";
			case "savefailed":
				if (!Spanish)
				{
					return "The link could not be saved. Check the server log.";
				}
				return "No se pudo guardar el vínculo. Consulta el log del servidor.";
			case "selected":
				if (!Spanish)
				{
					return "Shared spawn point set.";
				}
				return "Punto de aparición compartido establecido.";
			case "invite":
				if (!Spanish)
				{
					return "Open bed for sharing";
				}
				return "Abrir cama para compartir";
			case "closeinvite":
				if (!Spanish)
				{
					return "Close invitation";
				}
				return "Cerrar invitación";
			case "unlinkpair":
				if (!Spanish)
				{
					return "Remove shared link";
				}
				return "Retirar vínculo compartido";
			case "unlink":
				if (!Spanish)
				{
					return "Leave shared bed";
				}
				return "Dejar de compartir cama";
			case "join":
				if (!Spanish)
				{
					return "Link to this bed";
				}
				return "Vincularse a esta cama";
			case "sleepaction":
				if (!Spanish)
				{
					return "Sleep together";
				}
				return "Dormir juntos";
			case "selectaction":
				if (!Spanish)
				{
					return "Set spawn point";
				}
				return "Elegir punto de aparición";
			case "name":
				if (!Spanish)
				{
					return "Satisfied";
				}
				return "Satisfecho";
			case "tooltip":
				if (_plugin.Settings.Tone.Value == "Romantic")
				{
					if (!Spanish)
					{
						return "Some nights leave more than fond memories.";
					}
					return "Algunas noches dejan algo más que buenos recuerdos.";
				}
				if (_plugin.Settings.Tone.Value == "Bawdy" || _plugin.Settings.Tone.Value == "Mixed")
				{
					if (!Spanish)
					{
						return "You slept exceptionally well. No further details needed.";
					}
					return "Has dormido excepcionalmente bien. No hace falta entrar en detalles.";
				}
				if (!Spanish)
				{
					return "You shared an especially restful night.";
				}
				return "Has compartido una noche especialmente reparadora.";
			default:
				return key;
			}
		}

		internal string Tone(string token = "")
		{
			string text = _plugin.Settings.Tone.Value;
			if (text == "Mixed")
			{
				text = (new string[3] { "Neutral", "Romantic", "Bawdy" })[(token.Length == 0) ? Random.Range(0, 3) : ((int)((uint)StringExtensionMethods.GetStableHashCode(token) % 3u))];
			}
			return text;
		}

		private string PickKey(string category, string tone)
		{
			return category + "." + ((tone == "Bawdy") ? ("Bawdy " + _plugin.Settings.Intensity.Value) : tone);
		}

		private string Pick(string category, string tone)
		{
			Dictionary<string, List<string>> dictionary = (Spanish ? _es : _en);
			string key = PickKey(category, tone);
			if (!dictionary.TryGetValue(key, out var value) && !dictionary.TryGetValue(category + "." + tone, out value) && !dictionary.TryGetValue(category + ".Neutral", out value))
			{
				return Get(category);
			}
			int num = Random.Range(0, value.Count);
			if (_plugin.Settings.AvoidRepeat.Value && value.Count > 1 && _last.TryGetValue(key, out var value2) && value2 == num)
			{
				num = (num + 1) % value.Count;
			}
			_last[key] = num;
			return value[num];
		}

		internal string OneOccupant()
		{
			string text = Tone("hover");
			string text2 = Spanish + text + _plugin.Settings.Intensity.Value;
			if (text2 != _oneKey)
			{
				_oneKey = text2;
				_oneText = Pick("one", text);
			}
			return _oneText;
		}

		internal void Show(string key)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, (key == "linked") ? Pick("link", Tone()) : Get(key), 0, (Sprite)null, false);
			}
		}

		internal void PrepareDream(long session)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && session != 0L && _pendingDream.CanPrepare(session))
			{
				string bucket = Bucket(Random.Range(0, 100));
				_pendingDream.Prepare(session, Narrative("sleep", bucket, _dreamHistory));
			}
		}

		internal bool TryConsumeDream(long session, out string text)
		{
			return _pendingDream.TryConsume(session, out text);
		}

		internal void ClearPendingDream(long session = 0L)
		{
			_pendingDream.Clear(session);
		}

		internal string Wake(string token)
		{
			string bucket = Bucket((int)((uint)StringExtensionMethods.GetStableHashCode(token) % 100u));
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !CinematicsManager.IsStartedPlaying())
			{
				((Character)Player.m_localPlayer).Message((MessageType)1, Narrative("wake", bucket, _wakeHistory), 0, (Sprite)null, false);
			}
			return DreamSelection.Tone(bucket);
		}
	}
	internal static class VanillaDreamGuard
	{
		private static readonly FieldInfo? PendingCinematic = AccessTools.Field(typeof(CinematicsManager), "m_dreamCinematic");

		internal static DreamTexts? Source;

		internal static bool Allows(DreamTexts source, DreamText? result)
		{
			try
			{
				if ((Object)(object)Source != (Object)(object)source || result == null || PendingCinematic == null || !Object.op_Implicit((Object)(object)CinematicsManager.s_instance))
				{
					return false;
				}
				if (CinematicsManager.IsStartedPlaying() || CinematicsManager.IsPlaying() || !string.IsNullOrEmpty(PendingCinematic.GetValue(null) as string))
				{
					return false;
				}
				if (result.m_trueKeys == null || result.m_falseKeys == null || source.m_texts == null)
				{
					return false;
				}
				if (!VanillaDreamPolicy.IsOrdinary(result.m_text, result.m_chanceToDream, result.m_trueKeys.Count, result.m_falseKeys.Count))
				{
					return false;
				}
				foreach (DreamText text in source.m_texts)
				{
					if (text == result)
					{
						return true;
					}
				}
			}
			catch (Exception)
			{
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(SleepText), "ShowDreamText")]
	internal static class DreamPresentationPatch
	{
		private static void Prefix(SleepText __instance, out DreamTexts? __state)
		{
			__state = VanillaDreamGuard.Source;
			VanillaDreamGuard.Source = __instance.m_dreamTexts;
		}

		private static Exception? Finalizer(Exception? __exception, DreamTexts? __state)
		{
			VanillaDreamGuard.Source = __state;
			return __exception;
		}
	}
}
namespace WhySleepAlone.StatusEffects
{
	public sealed class SatisfiedStatusEffect : SE_Stats
	{
	}
	internal sealed class RewardService
	{
		internal const string EffectName = "SE_Satisfied";

		private static readonly int Hash = StringExtensionMethods.GetStableHashCode("SE_Satisfied");

		private readonly Plugin _plugin;

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

		private Packet? _pending;

		private double _received;

		private bool _warned;

		internal RewardService(Plugin p)
		{
			_plugin = p;
		}

		internal void Reset()
		{
			_pending = null;
			_applied.Clear();
			_warned = false;
			_plugin.Visuals.Reset();
		}

		internal void Register(ObjectDB db)
		{
			if (Object.op_Implicit((Object)(object)db) && !Object.op_Implicit((Object)(object)db.GetStatusEffect(Hash)))
			{
				SatisfiedStatusEffect satisfiedStatusEffect = ScriptableObject.CreateInstance<SatisfiedStatusEffect>();
				((Object)satisfiedStatusEffect).name = "SE_Satisfied";
				((StatusEffect)satisfiedStatusEffect).m_name = _plugin.Messages.Get("name");
				((StatusEffect)satisfiedStatusEffect).m_tooltip = _plugin.Messages.Get("tooltip");
				((StatusEffect)satisfiedStatusEffect).m_ttl = 3600f;
				((SE_Stats)satisfiedStatusEffect).m_healthRegenMultiplier = 1.1f;
				((SE_Stats)satisfiedStatusEffect).m_staminaRegenMultiplier = 1.1f;
				((SE_Stats)satisfiedStatusEffect).m_eitrRegenMultiplier = 1.1f;
				db.m_StatusEffects.Add((StatusEffect)(object)satisfiedStatusEffect);
			}
		}

		internal void Queue(Packet packet)
		{
			if (packet.World == _plugin.Network.World && !string.IsNullOrEmpty(packet.Token) && !_applied.Contains(packet.Token) && !(packet.Remaining <= 0f) && !(packet.Remaining > 86401f) && !(_pending?.Token == packet.Token))
			{
				_pending = packet;
				_received = _plugin.Network.Now;
			}
		}

		internal void Tick()
		{
			if (_pending == null || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (((Character)localPlayer).IsDead())
			{
				_applied.Add(_pending.Token);
				_pending = null;
			}
			else
			{
				if (localPlayer.IsSleeping() || ((Character)localPlayer).InBed())
				{
					return;
				}
				float num = _pending.Remaining - (float)(_plugin.Network.Now - _received);
				if (num <= 0f)
				{
					_pending = null;
					return;
				}
				Register(ObjectDB.instance);
				SatisfiedStatusEffect satisfiedStatusEffect = ObjectDB.instance.GetStatusEffect(Hash) as SatisfiedStatusEffect;
				if (!Object.op_Implicit((Object)(object)satisfiedStatusEffect))
				{
					if (!_warned)
					{
						_warned = true;
						_plugin.Warn("SE_Satisfied name conflicts with another effect; reward disabled.");
					}
					_pending = null;
					return;
				}
				((StatusEffect)satisfiedStatusEffect).m_name = _plugin.Messages.Get("name");
				((StatusEffect)satisfiedStatusEffect).m_tooltip = _plugin.Messages.Get("tooltip");
				try
				{
					((StatusEffect)satisfiedStatusEffect).m_icon = _plugin.Visuals.Icon();
				}
				catch (Exception ex)
				{
					if (!_warned)
					{
						_warned = true;
						_plugin.Warn("Icon fallback: " + ex.Message);
					}
				}
				((StatusEffect)satisfiedStatusEffect).m_ttl = num;
				((SE_Stats)satisfiedStatusEffect).m_healthRegenMultiplier = _pending.Health;
				((SE_Stats)satisfiedStatusEffect).m_staminaRegenMultiplier = _pending.Stamina;
				((SE_Stats)satisfiedStatusEffect).m_eitrRegenMultiplier = _pending.Eitr;
				SEMan sEMan = ((Character)localPlayer).GetSEMan();
				sEMan.AddStatusEffect(Hash, true, 0, 0f, (short)(-1));
				SatisfiedStatusEffect satisfiedStatusEffect2 = sEMan.GetStatusEffect(Hash) as SatisfiedStatusEffect;
				if (Object.op_Implicit((Object)(object)satisfiedStatusEffect2))
				{
					((StatusEffect)satisfiedStatusEffect2).m_ttl = num;
					((StatusEffect)satisfiedStatusEffect2).ResetTime();
					((StatusEffect)satisfiedStatusEffect2).m_icon = ((StatusEffect)satisfiedStatusEffect).m_icon;
					((StatusEffect)satisfiedStatusEffect2).m_name = ((StatusEffect)satisfiedStatusEffect).m_name;
					((SE_Stats)satisfiedStatusEffect2).m_healthRegenMultiplier = _pending.Health;
					((SE_Stats)satisfiedStatusEffect2).m_staminaRegenMultiplier = _pending.Stamina;
					((SE_Stats)satisfiedStatusEffect2).m_eitrRegenMultiplier = _pending.Eitr;
					string token = _pending.Token;
					bool num2 = _pending.Action == "wake";
					_applied.Add(token);
					_pending = null;
					if (num2)
					{
						string tone = _plugin.Messages.Wake(token);
						_plugin.Visuals.Wake(tone);
					}
					_plugin.Debug("Satisfied awarded once: " + token);
				}
			}
		}
	}
}
namespace WhySleepAlone.Patches
{
	[HarmonyPatch(typeof(Bed), "Awake")]
	internal static class BedLifecyclePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Bed __instance)
		{
			Plugin.Instance?.ObserveBed(__instance);
		}
	}
	[HarmonyPatch(typeof(Bed), "Interact")]
	internal static class InteractionPatch
	{
		private static bool Prefix(Bed __instance, Humanoid human, bool repeat, bool alt, ref bool __result)
		{
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return true;
			}
			try
			{
				bool num = instance.Beds.Interact(__instance, human, repeat, alt);
				if (!num)
				{
					__result = false;
				}
				return num;
			}
			catch (Exception ex)
			{
				instance.Warn("Shared interaction rejected: " + ex.Message);
				__result = false;
				return false;
			}
		}
	}
	[HarmonyPatch(typeof(Bed), "GetHoverText")]
	internal static class HoverPatch
	{
		private static void Postfix(Bed __instance, ref string __result)
		{
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				__result = instance.Beds.Hover(__instance, __result);
			}
		}
	}
	[HarmonyPatch(typeof(Bed), "IsCurrent")]
	internal static class CurrentPatch
	{
		private static void Postfix(Bed __instance, ref bool __result)
		{
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				__result = instance.Beds.IsCurrent(__instance, __result);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "SetCustomSpawnPoint")]
	internal static class SpawnSetPatch
	{
		private static void Postfix()
		{
			Plugin.Instance?.Beds.SpawnChanged();
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "ClearCustomSpawnPoint")]
	internal static class SpawnClearPatch
	{
		private static void Postfix()
		{
			Plugin.Instance?.Beds.SpawnChanged();
		}
	}
	[HarmonyPatch(typeof(Game), "FindSpawnPoint")]
	internal static class SpawnResolvePatch
	{
		private static bool Prefix(ref Vector3 point, ref bool usedLogoutPoint, ref bool __result)
		{
			//IL_0037: 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)
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return true;
			}
			instance.Beds.RespawnBed = null;
			instance.Beds.FindingSpawn = false;
			if (instance.Beds.WaitForSnapshot())
			{
				point = Vector3.zero;
				usedLogoutPoint = false;
				__result = false;
				return false;
			}
			instance.Beds.FindingSpawn = true;
			return true;
		}

		private static void Postfix(ref Vector3 point, bool usedLogoutPoint, bool __result)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance != (Object)null && __result && !usedLogoutPoint && Object.op_Implicit((Object)(object)instance.Beds.RespawnBed))
			{
				point = instance.Beds.RespawnPoint(instance.Beds.RespawnBed, point);
			}
		}

		private static Exception? Finalizer(Exception? __exception)
		{
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.Beds.FindingSpawn = false;
				instance.Beds.RespawnBed = null;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Game), "FindBedNearby")]
	internal static class FindBedPatch
	{
		private static void Postfix(ref Bed? __result)
		{
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance != (Object)null && instance.Beds.FindingSpawn)
			{
				__result = instance.Beds.ResolveLinkedBed(__result);
				instance.Beds.RespawnBed = __result;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "AttachStop")]
	internal static class DetachPatch
	{
		private static void Postfix(Player __instance)
		{
			Plugin.Instance?.Beds.Detached(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class DeathPatch
	{
		private static void Prefix(Player __instance)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				Plugin.Instance?.Beds.Release();
			}
		}
	}
	[HarmonyPatch(typeof(Player), "SetSleeping")]
	internal static class SleepStatePatch
	{
		private static void Prefix(Player __instance, out bool __state)
		{
			__state = __instance.IsSleeping();
		}

		private static void Postfix(Player __instance, bool sleep, bool __state)
		{
			if (sleep != __state)
			{
				Plugin.Instance?.Beds.SleepStateChanged(__instance, sleep);
			}
		}
	}
	[HarmonyPatch(typeof(DreamTexts), "GetRandomDreamText")]
	internal static class DreamTextPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(DreamTexts __instance, ref DreamText __result)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			string text;
			if (!VanillaDreamGuard.Allows(__instance, __result))
			{
				if ((Object)(object)VanillaDreamGuard.Source == (Object)(object)__instance)
				{
					instance.Beds.CancelDream();
				}
			}
			else if (instance.Beds.TryConsumeDream(out text))
			{
				__result = new DreamText
				{
					m_text = text,
					m_chanceToDream = 1f
				};
			}
		}
	}
	[HarmonyPatch(typeof(Game), "UpdateSleeping")]
	internal static class NightPatch
	{
		internal struct NightState
		{
			internal bool Sleeping;

			internal List<long>? Players;
		}

		private static void Prefix(bool ___m_sleeping, out NightState __state)
		{
			__state = new NightState
			{
				Sleeping = ___m_sleeping
			};
			Plugin instance = Plugin.Instance;
			if (!((Object)(object)instance == (Object)null) && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
			{
				instance.Network.Completing = ___m_sleeping && !EnvMan.instance.IsTimeSkipping() && !CinematicsManager.IsPlaying();
				if (instance.Network.Completing)
				{
					instance.Network.Sweep();
					__state.Players = instance.Network.CaptureNightRewards();
				}
			}
		}

		private static void Postfix(bool ___m_sleeping, NightState __state)
		{
			Plugin instance = Plugin.Instance;
			if (!((Object)(object)instance == (Object)null) && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
			{
				if (!__state.Sleeping && ___m_sleeping)
				{
					instance.Network.BeginNight();
				}
				if (__state.Sleeping && !___m_sleeping)
				{
					instance.Network.CompleteNight(__state.Players ?? new List<long>());
				}
			}
		}

		private static Exception? Finalizer(Exception? __exception)
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.Network.Completing = false;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	internal static class ConnectPatch
	{
		private static void Postfix(ZNetPeer peer)
		{
			Plugin.Instance?.Network.Register(peer);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Disconnect")]
	internal static class DisconnectPatch
	{
		private static void Prefix(ZNetPeer peer)
		{
			Plugin instance = Plugin.Instance;
			instance?.Network.Disconnected(peer);
			if ((Object)(object)instance != (Object)null && Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer() && peer == ZNet.instance.GetServerPeer())
			{
				instance.Beds.CancelDream();
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Shutdown")]
	internal static class ShutdownPatch
	{
		private static void Prefix()
		{
			Plugin.Instance?.Network.StopWorld();
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	internal static class ObjectDBPatch
	{
		private static void Postfix(ObjectDB __instance)
		{
			Plugin.Instance?.Rewards.Register(__instance);
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	internal static class ObjectDBCopyPatch
	{
		private static void Postfix(ObjectDB __instance)
		{
			Plugin.Instance?.Rewards.Register(__instance);
		}
	}
}
namespace WhySleepAlone.Network
{
	public sealed class Gameplay
	{
		public bool Enabled = true;

		public bool Buff = true;

		public string AdditionalDoubleBeds = "";

		public float Duration = 3600f;

		public float Health = 1.1f;

		public float Stamina = 1.1f;

		public float Eitr = 1.1f;

		public float SleepAX = -0.45f;

		public float SleepAY;

		public float SleepAZ;

		public float SleepBX = 0.45f;

		public float SleepBY;

		public float SleepBZ;

		public float SpawnAX = -0.65f;

		public float SpawnAY = 0.3f;

		public float SpawnAZ;

		public float SpawnBX = 0.65f;

		public float SpawnBY = 0.3f;

		public float SpawnBZ;

		public Gameplay Normalize()
		{
			Duration = Safe(Duration, 1f, 86400f, 3600f);
			Health = Safe(Health, 1f, 10f, 1.1f);
			Stamina = Safe(Stamina, 1f, 10f, 1.1f);
			Eitr = Safe(Eitr, 1f, 10f, 1.1f);
			SleepAX = Safe(SleepAX, -2f, 2f, -0.45f);
			SleepAY = Safe(SleepAY, -2f, 2f, 0f);
			SleepAZ = Safe(SleepAZ, -2f, 2f, 0f);
			SleepBX = Safe(SleepBX, -2f, 2f, 0.45f);
			SleepBY = Safe(SleepBY, -2f, 2f, 0f);
			SleepBZ = Safe(SleepBZ, -2f, 2f, 0f);
			SpawnAX = Safe(SpawnAX, -3f, 3f, -0.65f);
			SpawnAY = Safe(SpawnAY, -3f, 3f, 0.3f);
			SpawnAZ = Safe(SpawnAZ, -3f, 3f, 0f);
			SpawnBX = Safe(SpawnBX, -3f, 3f, 0.65f);
			SpawnBY = Safe(SpawnBY, -3f, 3f, 0.3f);
			SpawnBZ = Safe(SpawnBZ, -3f, 3f, 0f);
			if (Squared(SleepAX - SleepBX, SleepAY - SleepBY, SleepAZ - SleepBZ) < 0.09f)
			{
				SleepAX = -0.45f;
				SleepBX = 0.45f;
			}
			if (Squared(SpawnAX - SpawnBX, SpawnAY - SpawnBY, SpawnAZ - SpawnBZ) < 0.09f)
			{
				SpawnAX = -0.65f;
				SpawnBX = 0.65f;
			}
			return this;
		}

		private static float Squared(float x, float y, float z)
		{
			return x * x + y * y + z * z;
		}

		private static float Safe(float value, float min, float max, float fallback)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Max(min, Math.Min(max, value));
			}
			return fallback;
		}
	}
	public sealed class SeatCount
	{
		public string Bed = "";

		public int Count;
	}
	public sealed class Packet
	{
		public int Protocol = 1;

		public long World;

		public string Kind = "";

		public string Action = "";

		public string Message = "";

		public string Token = "";

		public long Request;

		public long User;

		public uint Id;

		public float X;

		public float Y;

		public float Z;

		public List<SeatCount> Seats = new List<SeatCount>();

		public WorldData? State;

		public Gameplay? Settings;

		public float Remaining;

		public float Health;

		public float Stamina;

		public float Eitr;
	}
	internal sealed class SharedNetwork
	{
		private const string RpcName = "WSA_Protocol1";

		private readonly Plugin _plugin;

		private ZNet? _net;

		private ZNet? _stoppedNet;

		private long _restoredLocal;

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

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

		private readonly Dictionary<ZRpc, double> _lastRequest = new Dictionary<ZRpc, double>();

		private readonly Dictionary<ZRpc, long> _peerPlayers = new Dictionary<ZRpc, long>();

		private readonly Dictionary<long, double> _lastPulse = new Dictionary<long, double>();

		private readonly Dictionary<ZRpc, RequestJournal<Packet>> _journals = new Dictionary<ZRpc, RequestJournal<Packet>>();

		private RequestJournal<Packet> _localJournal = new RequestJournal<Packet>();

		private readonly ClientSyncRetry _syncRetry = new ClientSyncRetry();

		private List<SeatCount> _seats = new List<SeatCount>();

		private string _seatSignature = "";

		private string _file = "";

		private ZDOMan? _zdoMan;

		private bool _failed;

		internal bool Ready { get; private set; }

		internal long World { get; private set; }

		internal WorldData Snapshot { get; private set; } = new WorldData();

		internal Gameplay Settings { get; private set; } = new Gameplay
		{
			Enabled = false
		};

		internal Authority? Server { get; private set; }

		internal bool Completing { get; set; }

		internal double Now => Time.realtimeSinceStartup;

		internal SharedNetwork(Plugin plugin)
		{
			_plugin = plugin;
		}

		internal static string Key(ZDOID id)
		{
			return ((ZDOID)(ref id)).UserID.ToString(CultureInfo.InvariantCulture) + ":" + ((ZDOID)(ref id)).ID.ToString(CultureInfo.InvariantCulture);
		}

		internal Link? LinkFor(ZDOID id)
		{
			//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)
			return Snapshot.Links.FirstOrDefault((Link x) => x.User == ((ZDOID)(ref id)).UserID && x.Id == ((ZDOID)(ref id)).ID);
		}

		internal int Occupancy(string bed)
		{
			if (Server == null)
			{
				return _seats.FirstOrDefault((SeatCount x) => x.Bed == bed)?.Count ?? 0;
			}
			return Server.Occupants.Values.Count((Reservation x) => x.Bed == bed && x.Verified);
		}

		internal Link? LinkForPlayer(long player)
		{
			return Snapshot.Links.FirstOrDefault((Link x) => x.Contains(player));
		}

		internal void Register(ZNetPeer peer)
		{
			if (_registered.Add(peer.m_rpc))
			{
				peer.m_rpc.Register<string>("WSA_Protocol1", (Action<ZRpc, string>)Receive);
			}
		}

		internal void Reset()
		{
			if (_zdoMan != null)
			{
				ZDOMan? zdoMan = _zdoMan;
				zdoMan.m_onZDODestroyed = (Action<ZDO>)Delegate.Remove(zdoMan.m_onZDODestroyed, new Action<ZDO>(Destroyed));
			}
			_zdoMan = null;
			_net = null;
			World = 0L;
			Server = null;
			Ready = false;
			_failed = false;
			Completing = false;
			_seats.Clear();
			_seatSignature = "";
			Snapshot = new WorldData();
			Settings = new Gameplay
			{
				Enabled = false
			};
			foreach (ZRpc item in _registered)
			{
				item.Unregister("WSA_Protocol1");
			}
			_restoredLocal = 0L;
			_journals.Clear();
			_localJournal = new RequestJournal<Packet>();
			_peerPlayers.Clear();
			_registered.Clear();
			_clients.Clear();
			_lastRequest.Clear();
			_lastPulse.Clear();
			_syncRetry.Reset();
			_plugin.Beds.Reset();
			_plugin.Rewards.Reset();
		}

		internal void StopWorld()
		{
			_stoppedNet = ZNet.instance;
			Reset();
		}

		internal void Tick()
		{
			ZNet instance = ZNet.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				if ((Object)(object)_net != (Object)null || World != 0L)
				{
					Reset();
				}
			}
			else
			{
				if (instance == _stoppedNet || instance.GetWorld() == null)
				{
					return;
				}
				if (!instance.IsServer())
				{
					ZNetPeer serverPeer = instance.GetServerPeer();
					if (serverPeer == null || !serverPeer.IsReady())
					{
						return;
					}
				}
				long worldUID = instance.GetWorldUID();
				if (worldUID == 0L || ZDOMan.instance == null)
				{
					return;
				}
				if (instance != _net || worldUID != World)
				{
					Reset();
					_net = instance;
					World = worldUID;
					if (instance.IsServer())
					{
						Load();
					}
				}
				foreach (ZNetPeer peer in instance.GetPeers())
				{
					Register(peer);
				}
				if (instance.IsServer())
				{
					if (_failed || Server == null)
					{
						return;
					}
					Gameplay gameplay = _plugin.Settings.Export();
					bool num = Settings != gameplay;
					Settings = gameplay;
					Sweep();
					string text = string.Join("|", from x in Server.Occupants.Values
						where x.Verified
						orderby x.Player
						select x.Bed + ":" + x.Player);
					if (num || text != _seatSignature)
					{
						_seatSignature = text;
						Broadcast();
					}
					if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && LocalId() != _restoredLocal)
					{
						_restoredLocal = LocalId();
						DeliverExisting(null, _restoredLocal);
					}
				}
				else
				{
					ZNetPeer serverPeer2 = instance.GetServerPeer();
					if (_syncRetry.ShouldSend(Now, serverPeer2 != null && serverPeer2.IsReady(), Ready))
					{
						if (!Ready && _syncRetry.Attempts == 4)
						{
							_plugin.Warn("Shared-bed synchronization is still pending. Check Why Sleep Alone on server and clients; retrying.");
						}
						Send(serverPeer2.m_rpc, new Packet
						{
							Kind = "hello",
							World = World
						});
					}
				}
				DragonBedDetector.CheckConfiguration();
				_plugin.Beds.Tick();
			}
		}

		private void Load()
		{
			_file = Path.Combine(Paths.ConfigPath, "WhySleepAlone", "world_" + World + ".xml");
			try
			{
				WorldData worldData = (File.Exists(_file) ? XmlStore.Decode<WorldData>(File.ReadAllText(_file)) : new WorldData
				{
					World = World
				});
				if (worldData.Schema != 1 || worldData.World != World || worldData.Links.Any((Link x) => x.A == 0) || (from x in worldData.Links
					group x by x.Key).Any((IGrouping<string, Link> x) => x.Count() > 1) || (from x in worldData.Links.SelectMany((Link x) => new long[2] { x.A, x.B })
					where x != 0
					group x by x).Any((IGrouping<long, long> x) => x.Count() > 1))
				{
					throw new InvalidDataException("Invalid or duplicate link data");
				}
				Server = new Authority(worldData);
				Snapshot = worldData;
				Settings = _plugin.Settings.Export();
				Ready = true;
				_zdoMan = ZDOMan.instance;
				ZDOMan? zdoMan = _zdoMan;
				zdoMan.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(zdoMan.m_onZDODestroyed, new Action<ZDO>(Destroyed));
				_plugin.Debug("Loaded " + worldData.Links.Count + " links for world " + World);
				DeliverExisting(null, LocalId());
			}
			catch (Exception e)
			{
				Fail("Cannot load shared-bed registry; original file preserved", e);
			}
		}

		private void Fail(string message, Exception e)
		{
			_failed = true;
			Ready = true;
			Settings = new Gameplay
			{
				Enabled = false
			};
			Snapshot.World = World;
			_plugin.Warn(message + ": " + e.Message);
			Broadcast();
		}

		private bool Commit()
		{
			if (Server == null || _failed)
			{
				return false;
			}
			try
			{
				XmlStore.Save(_file, Server.Data);
				Snapshot = Server.Data;
				Broadcast();
				return true;
			}
			catch (Exception e)
			{
				Fail("Shared-bed save failed; sharing disabled to avoid acknowledging an unsaved link", e);
				return false;
			}
		}

		private Packet StatePacket()
		{
			return new Packet
			{
				Kind = "state",
				World = World,
				State = Snapshot,
				Settings = Settings,
				Seats = ((Server == null) ? new List<SeatCount>() : (from x in Server.Occupants.Values
					where x.Verified
					group x by x.Bed into x
					select new SeatCount
					{
						Bed = x.Key,
						Count = x.Count()
					}).ToList())
			};
		}

		private void Broadcast()
		{
			if ((Object)(object)_net == (Object)null || !_net.IsServer() || _clients.Count == 0)
			{
				return;
			}
			try
			{
				string payload = XmlStore.Encode(StatePacket());
				ZRpc[] array = _clients.ToArray();
				foreach (ZRpc rpc in array)
				{
					SendPayload(rpc, payload);
				}
			}
			catch (Exception ex)
			{
				_plugin.Debug("RPC serialization failed: " + ex.Message);
			}
		}

		private void Send(ZRpc rpc, Packet packet)
		{
			try
			{
				SendPayload(rpc, XmlStore.Encode(packet));
			}
			catch (Exception ex)
			{
				_plugin.Debug("RPC serialization failed: " + ex.Message);
			}
		}

		private void SendPayload(ZRpc rpc, string payload)
		{
			try
			{
				rpc.Invoke("WSA_Protocol1", new object[1] { payload });
			}
			catch (Exception ex)
			{
				_plugin.Debug("RPC delivery failed: " + ex.Message);
			}
		}

		private void Reply(ZRpc? rpc, Packet packet)
		{
			packet.World = World;
			if (rpc == null)
			{
				_plugin.Beds.Receive(packet);
			}
			else
			{
				Send(rpc, packet);
			}
		}

		private void Receive(ZRpc rpc, string text)
		{
			try
			{
				if (text.Length > 2000000 || (Object)(object)_net == (Object)null)
				{
					return;
				}
				Packet packet = XmlStore.Decode<Packet>(text);
				if (packet.Protocol != 1 || packet.World != World)
				{
					return;
				}
				if (_net.IsServer())
				{
					ZNetPeer val = ((IEnumerable<ZNetPeer>)_net.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer x) => x.m_rpc == rpc));
					if (val == null || !val.IsReady())
					{
						return;
					}
					if (packet.Kind == "hello")
					{
						if (!_lastRequest.TryGetValue(rpc, out var value) || !(Now - value < 1.0))
						{
							_lastRequest[rpc] = Now;
							_clients.Add(rpc);
							Send(rpc, StatePacket());
							DeliverExisting(rpc, PeerPlayer(val));
						}
					}
					else
					{
						if (!_clients.Contains(rpc) || _failed || !Settings.Enabled || !(packet.Kind == "request"))
						{
							return;
						}
						if (packet.Action != "release" && packet.Action != "pulse" && packet.Action != "other" && _lastRequest.TryGetValue(rpc, out var value2) && Now - value2 < 0.15)
						{
							Reply(rpc, new Packet
							{
								Kind = "reply",
								Request = packet.Request,
								User = packet.User,
								Id = packet.Id,
								Message = "waiting"
							});
						}
						else
						{
							if (packet.Action != "pulse" && packet.Action != "release")
							{
								_lastRequest[rpc] = Now;
							}
							Handle(val, packet);
						}
					}
					return;
				}
				ZNetPeer serverPeer = _net.GetServerPeer();
				if (serverPeer == null || serverPeer.m_rpc != rpc)
				{
					return;
				}
				if (packet.Kind == "state" && packet.State != null && packet.Settings != null && packet.State.World == World)
				{
					bool num = !Ready;
					Snapshot = packet.State;
					Settings = packet.Settings;
					_seats = packet.Seats;
					Ready = true;
					if (num)
					{
						_plugin.Warn("Shared-bed synchronization complete; sharing enabled=" + Settings.Enabled + ".");
					}
					_plugin.Beds.Reconcile();
				}
				else if (packet.Kind == "reply")
				{
					_plugin.Beds.Receive(packet);
				}
				else if (packet.Kind == "reward")
				{
					_plugin.Rewards.Queue(packet);
				}
			}
			catch (Exception ex)
			{
				_plugin.Debug("Rejected malformed shared-bed message: " + ex.Message);
			}
		}

		internal void Request(Packet p)
		{
			if (!Ready || !Settings.Enabled || (Object)(object)_net == (Object)null)
			{
				return;
			}
			p.Kind = "request";
			p.World = World;
			if (_net.IsServer())
			{
				Handle(null, p);
				return;
			}
			ZNetPeer serverPeer = _net.GetServerPeer();
			if (serverPeer != null)
			{
				Send(serverPeer.m_rpc, p);
			}
		}

		internal static long LocalId()
		{
			if (!Object.op_Implicit((Object)(object)Game.instance))
			{
				return 0L;
			}
			return Game.instance.GetPlayerProfile().GetPlayerID();
		}

		private ZDO? Character(ZNetPeer? peer, out long id)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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_0027: Unknown result type (might be due to invalid IL or missing references)
			id = 0L;
			if (ZDOMan.instance == null || (peer != null && !peer.IsReady()))
			{
				return null;
			}
			ZDOID val = peer?.m_characterID ?? (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? ((Character)Player.m_localPlayer).GetZDOID() : ZDOID.None);
			ZDO zDO = ZDOMan.instance.GetZDO(val);
			if (zDO == null)
			{
				return null;
			}
			long connection = peer?.m_uid ?? ZNet.GetUID();
			long localProfile = ((peer != null) ? 0 : LocalId());
			id = CharacterIdentity.Resolve(connection, zDO.GetOwner(), zDO.GetLong(ZDOVars.s_playerID, 0L), localProfile);
			if (id == 0L)
			{
				return null;
			}
			if (peer != null)
			{
				_peerPlayers[peer.m_rpc] = id;
			}
			return zDO;
		}

		private long PeerPlayer(ZNetPeer peer)
		{
			Character(peer, out var id);
			return id;
		}

		private unsafe void Handle(ZNetPeer? peer, Packet p)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: 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_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0457: Unknown result type (might be due to invalid IL or missing references)
			//IL_045b: Unknown result type (might be due to invalid IL or missing references)
			if (Server == null || _failed || !Settings.Enabled)
			{
				return;
			}
			long id;
			ZDO val = Character(peer, out id);
			if (val == null)
			{
				if (p.Action != "pulse" && p.Action != "release")
				{
					ZDOID val2 = peer?.m_characterID ?? ZDOID.None;
					ZDOMan instance = ZDOMan.instance;
					ZDO val3 = ((instance != null) ? instance.GetZDO(val2) : null);
					Plugin plugin = _plugin;
					string[] obj = new string[10]
					{
						"Bed character rejected: peer=",
						(peer?.m_uid).ToString(),
						", character=",
						null,
						null,
						null,
						null,
						null,
						null,
						null
					};
					ZDOID val4 = val2;
					obj[3] = ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString();
					obj[4] = ", zdoPresent=";
					obj[5] = (val3 != null).ToString();
					obj[6] = ", owner=";
					obj[7] = ((val3 != null) ? new long?(val3.GetOwner()) : ((long?)null)).ToString();
					obj[8] = ", player=";
					obj[9] = ((val3 != null) ? new long?(val3.GetLong(ZDOVars.s_playerID, 0L)) : ((long?)null)).ToString();
					plugin.Warn(string.Concat(obj));
					Reply(peer?.m_rpc, new Packet
					{
						Kind = "reply",
						Request = p.Request,
						User = p.User,
						Id = p.Id,
						Message = "characterpending"
					});
				}
				return;
			}
			if (p.Action == "release")
			{
				if (!Completing)
				{
					Server.Release(id);
				}
				return;
			}
			if (p.Action == "other")
			{
				if (Server.ForPlayer(id) != null)
				{
					Server.Leave(id);
					Commit();
				}
				return;
			}
			if (val.GetBool(ZDOVars.s_dead, false))
			{
				Server.Release(id);
				return;
			}
			RequestJournal<Packet> value = _localJournal;
			if (peer != null && !_journals.TryGetValue(peer.m_rpc, out value))
			{
				value = (_journals[peer.m_rpc] = new RequestJournal<Packet>());
			}
			if (p.Action != "pulse" && !value.IsNew(p.Request))
			{
				Packet packet = value.Replay(p.Request);
				if (packet != null)
				{
					Reply(peer?.m_rpc, packet);
				}
				return;
			}
			ZDOID val5 = default(ZDOID);
			((ZDOID)(ref val5))..ctor(p.User, p.Id);
			string bed = Key(val5);
			ZDO zDO = ZDOMan.instance.GetZDO(val5);
			if (zDO == null || !DragonBedDetector.IsSupported(zDO) || Vector3.Distance(zDO.GetPosition(), val.GetPosition()) > 6f)
			{
				Reply(peer?.m_rpc, new Packet
				{
					Kind = "reply",
					Request = p.Request,
					Message = "invalid"
				});
				return;
			}
			long num = zDO.GetLong(ZDOVars.s_owner, 0L);
			Link link = Server.Find(bed);
			if (link != null && link.A != num)
			{
				Server.Remove(bed);
				Commit();
				link = null;
			}
			if (p.Action == "pulse")
			{
				_lastPulse[id] = Now;
				if (val.GetBool(ZDOVars.s_inBed, false))
				{
					Server.Confirm(id, bed, Now);
				}
				return;
			}
			string text = "invalid";
			if (p.Action == "invite" && num == id)
			{
				if (link != null && link.B != 0L)
				{
					Server.Remove(bed);
					text = "unlinked";
				}
				else
				{
					Vector3 val6 = default(Vector3);
					((Vector3)(ref val6))..ctor(p.X, p.Y, p.Z);
					if (float.IsNaN(p.X) || float.IsNaN(p.Y) || float.IsNaN(p.Z) || Vector3.Distance(val6, zDO.GetPosition()) > 6f)
					{
						return;
					}
					text = Server.Invite(p.User, p.Id, id);
					Link link2 = Server.Find(bed);
					if (link2 != null)
					{
						link2.SpawnX = p.X;
						link2.SpawnY = p.Y;
						link2.SpawnZ = p.Z;
					}
				}
				if (!Commit())
				{
					text = "savefailed";
				}
			}
			else if (p.Action == "join" && num != 0L && num != id)
			{
				text = Server.Join(bed, id);
				if (text == "linked" && !Commit())
				{
					text = "savefailed";
				}
			}
			else if (p.Action == "unlink" && link != null && link.B == id)
			{
				Server.Leave(id);
				text = (Commit() ? "unlinked" : "savefailed");
			}
			else if (p.Action == "select" && link != null && link.Contains(id))
			{
				text = "selected";
			}
			else if (p.Action == "sleep" && link != null && link.Contains(id) && EnvMan.CanSleep())
			{
				text = (Server.Reserve(bed, id, Now) ? "sleep" : "full");
				if (text == "sleep")
				{
					_lastPulse[id] = Now;
				}
			}
			Packet packet2 = new Packet
			{
				Kind = "reply",
				Request = p.Request,
				User = p.User,
				Id = p.Id,
				Message = text
			};
			value.Remember(p.Request, packet2);
			Reply(peer?.m_rpc, packet2);
			if (p.Action == "join")
			{
				_plugin.Warn("Join processed: request=" + p.Request + ", result=" + text);
			}
			_plugin.Debug("Request " + p.Action + " -> " + text + " for " + id);
		}

		internal void Sweep()
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			if (Server == null || (Object)(object)_net == (Object)null)
			{
				return;
			}
			Reservation[] array = Server.Occupants.Values.ToArray();
			foreach (Reservation r in array)
			{
				ZNetPeer val = ((IEnumerable<ZNetPeer>)_net.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer x) => PeerPlayer(x) == r.Player));
				if (val == null && r.Player != LocalId())
				{
					Server.Release(r.Player);
					continue;
				}
				long id;
				ZDO val2 = Character(val, out id);
				Link link = Server.Find(r.Bed);
				ZDO val3 = ((link == null) ? null : ZDOMan.instance.GetZDO(new ZDOID(link.User, link.Id)));
				double value;
				if (val2 == null || val2.GetBool(ZDOVars.s_dead, false) || val3 == null || val3.GetLong(ZDOVars.s_owner, 0L) != link.A || !DragonBedDetector.IsSupported(val3) || Vector3.Distance(val2.GetPosition(), val3.GetPosition()) > 6f)
				{
					Server.Release(r.Player);
				}
				else if (val2.GetBool(ZDOVars.s_inBed, false) && _lastPulse.TryGetValue(id, out value) && Now - value < 4.0)
				{
					Server.Confirm(id, r.Bed, Now);
				}
				else if (r.Verified)
				{
					Server.Release(id);
				}
			}
			Server.Expire(Now);
			bool flag = false;
			foreach (Receipt receipt in Server.Data.Receipts.Where((Receipt x) => x.Expires > 0))
			{
				ZNetPeer val4 = ((IEnumerable<ZNetPeer>)_net.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer x) => PeerPlayer(x) == receipt.Player));
				if (val4 != null || receipt.Player == LocalId())
				{
					long id2;
					ZDO val5 = Character(val4, out id2);
					if (val5 != null && val5.GetBool(ZDOVars.s_dead, false))
					{
						receipt.Expires = 0L;
						flag = true;
					}
				}
			}
			if (flag)
			{
				Commit();
			}
		}

		internal void BeginNight()
		{
			if (Server != null && Settings.Enabled)
			{
				Sweep();
				Server.BeginNight(Now);
			}
		}

		internal List<long> CaptureNightRewards()
		{
			if (Server == null || !Settings.Enabled)
			{
				return new List<long>();
			}
			return Server.CaptureNightRewards(Now, EnvMan.instance.GetDay());
		}

		internal void CompleteNight(List<long> players)
		{
			if (Server == null || (Object)(object)_net == (Object)null || !Settings.Enabled)
			{
				return;
			}
			Server.EndNight();
			_plugin.Debug("Shared night completed: eligible=" + players.Count + ", buffEnabled=" + Settings.Buff);
			if (!Settings.Buff || players.Count == 0)
			{
				return;
			}
			string token = Guid.NewGuid().ToString("N");
			foreach (long player in players)
			{
				Server.Data.Receipts.RemoveAll((Receipt x) => x.Player == player);
				Server.Data.Receipts.Add(new Receipt
				{
					Player = player,
					Day = EnvMan.instance.GetDay(),
					Expires = DateTime.UtcNow.AddSeconds(Settings.Duration).Ticks,
					Token = token,
					Health = Settings.Health,
					Stamina = Settings.Stamina,
					Eitr = Settings.Eitr
				});
			}
			if (!Commit())
			{
				return;
			}
			foreach (long player2 in players)
			{
				ZNetPeer val = ((IEnumerable<ZNetPeer>)_net.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer x) => PeerPlayer(x) == player2));
				if (val != null)
				{
					DeliverExisting(val.m_rpc, player2, fresh: true);
				}
				else if (player2 == LocalId())
				{
					DeliverExisting(null, player2, fresh: true);
				}
			}
		}

		private void DeliverExisting(ZRpc? rpc, long player, bool fresh = false)
		{
			if (Server == null || player == 0L || !Settings.Enabled || !Settings.Buff)
			{
				return;
			}
			Receipt receipt = Server.Data.Receipts.FirstOrDefault((Receipt x) => x.Player == player);
			if (receipt == null)
			{
				return;
			}
			float num = (float)TimeSpan.FromTicks(receipt.Expires - DateTime.UtcNow.Ticks).TotalSeconds;
			if (!(num <= 0f))
			{
				Packet packet = new Packet
				{
					Kind = "reward",
					Action = (fresh ? "wake" : "restore"),
					World = World,
					Token = receipt.Token,
					Remaining = num,
					Health = receipt.Health,
					Stamina = receipt.Stamina,
					Eitr = receipt.Eitr
				};
				if (rpc == null)
				{
					_plugin.Rewards.Queue(packet);
				}
				else
				{
					Send(rpc, packet);
				}
			}
		}

		private void Destroyed(ZDO zdo)
		{
			//IL_000e: 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)
			if (Server?.Find(Key(zdo.m_uid)) != null)
			{
				Server.Remove(Key(zdo.m_uid));
				Commit();
			}
		}

		internal void Disconnected(ZNetPeer peer)
		{
			if (_peerPlayers.TryGetValue(peer.m_rpc, out var value))
			{
				Server?.Release(value);
			}
			_peerPlayers.Remove(peer.m_rpc);
			_journals.Remove(peer.m_rpc);
			_clients.Remove(peer.m_rpc);
			_registered.Remove(peer.m_rpc);
			_lastRequest.Remove(peer.m_rpc);
		}
	}
}
namespace WhySleepAlone.Model
{
	public sealed class Link
	{
		public long User;

		public uint Id;

		public long A;

		public long B;

		public bool Open;

		public long Revision;

		public float SpawnX;

		public float SpawnY;

		public float SpawnZ;

		[XmlIgnore]
		public string Key => User.ToString(CultureInfo.InvariantCulture) + ":" + Id.ToString(CultureInfo.InvariantCulture);

		public bool Contains(long player)
		{
			if (player != 0L)
			{
				if (A != player)
				{
					return B == player;
				}
				return true;
			}
			return false;
		}
	}
	public sealed class Receipt
	{
		public long Player;

		public int Day;

		public long Expires;

		public string Token = "";

		public float Health = 1.1f;

		public float Stamina = 1.1f;

		public float Eitr = 1.1f;
	}
	public sealed class WorldData
	{
		public int Schema = 1;

		public long World;

		public List<Link> Links = new List<Link>();

		public List<Receipt> Receipts = new List<Receipt>();
	}
	public sealed class Reservation
	{
		public long Player;

		public string Bed = "";

		public double Until;

		public bool Verified;
	}
	public sealed class Pair
	{
		public string Bed = "";

		public long A;

		public long B;

		public long Revision;
	}
	public sealed class Authority
	{
		public WorldData Data;

		public readonly Dictionary<long, Reservation> Occupants = new Dictionary<long, Reservation>();

		private readonly List<Pair> _night = new List<Pair>();

		public Authority(WorldData data)
		{
			Data = data;
		}

		public Link? Find(string bed)
		{
			return Data.Links.FirstOrDefault((Link x) => x.Key == bed);
		}

		public Link? ForPlayer(long player)
		{
			return Data.Links.FirstOrDefault((Link x) => x.Contains(player));
		}

		public string Invite(long user, uint id, long owner)
		{
			if (owner == 0L)
			{
				return "invalid";
			}
			string text = user.ToString(CultureInfo.InvariantCulture) + ":" + id.ToString(CultureInfo.InvariantCulture);
			Link link = Find(text);
			if (link != null && link.A != owner)
			{
				return "stale";
			}
			if (link != null && link.B != 0L)
			{
				return "occupied";
			}
			Link link2 = ForPlayer(owner);
			if (link2 != null && link2.Key != text)
			{
				Leave(owner);
			}
			if (link == null)
			{
				link = new Link
				{
					User = user,
					Id = id,
					A = owner
				};
				Data.Links.Add(link);
			}
			link.Open = !link.Open;
			link.Revision++;
			if (!link.Open)
			{
				return "closed";
			}
			return "open";
		}

		public string Join(string bed, long player)
		{
			Link link = Find(bed);
			if (link == null || player == 0L || link.A == player)
			{
				return "invalid";
			}
			if (link.B == player)
			{
				return "linked";
			}
			if (link.B != 0L)
			{
				return "full";
			}
			if (!link.Open)
			{
				return "closed";
			}
			if (ForPlayer(player) != null)
			{
				Leave(player);
			}
			link.B = player;
			link.Open = false;
			link.Revision++;
			return "linked";
		}

		public void Leave(long player)
		{
			Release(player);
			Link[] array = Data.Links.Where((Link x) => x.Contains(player)).ToArray();
			foreach (Link link in array)
			{
				Invalidate(link.Key);
				if (link.A == player)
				{
					Release(link.B);
					Data.Links.Remove(link);
				}
				else
				{
					link.B = 0L;
					link.Open = false;
					link.Revision++;
				}
			}
		}

		public void Remove(string bed)
		{
			Link link = Find(bed);
			if (link != null)
			{
				Release(link.A);
				Release(link.B);
				Data.Links.Remove(link);
				Invalidate(bed);
			}
		}

		public bool Reserve(string bed, long player, double now)
		{
			Expire(now);
			Link link = Find(bed);
			if (link == null || !link.Contains(player))
			{
				return false;
			}
			if (Occupants.TryGetValue(player, out Reservation value))
			{
				return value.Bed == bed;
			}
			if (Occupants.Values.Count((Reservation x) => x.Bed == bed) >= 2)
			{
				return false;
			}
			Occupants[player] = new Reservation
			{
				Player = player,
				Bed = bed,
				Until = now + 8.0
			};
			return true;
		}

		public void Confirm(long player, string bed, double now)
		{
			if (Occupants.TryGetValue(player, out Reservation value) && value.Bed == bed && value.Until >= now)
			{
				value.Verified = true;
				value.Until = now + 5.0;
			}
		}

		public void Release(long player)
		{
			if (Occupants.TryGetValue(player, out Reservation value))
			{
				Invalidate(value.Bed);
			}
			Occupants.Remove(player);
		}

		public void Expire(double now)
		{
			long[] array = (from x in Occupants
				where x.Value.Until < now
				select x.Key).ToArray();
			foreach (long player in array)
			{
				Release(player);
			}
		}

		private void Invalidate(string bed)
		{
			_night.RemoveAll((Pair x) => x.Bed == bed);
		}

		private bool Present(long player, string bed, double now)
		{
			if (Occupants.TryGetValue(player, out Reservation value) && value.Bed == bed && value.Verified)
			{
				return value.Until >= now;
			}
			return false;
		}

		public void BeginNight(double now)
		{
			_night.Clear();
			foreach (Link link in Data.Links)
			{
				if (link.B != 0L && Present(link.A, link.Key, now) && Present(link.B, link.Key, now))
				{
					_night.Add(new Pair
					{
						Bed = link.Key,
						A = link.A,
						B = link.B,
						Revision = link.Revision
					});
				}
			}
		}

		public List<long> CompleteNight(double now, int day)
		{
			List<long> result = CaptureNightRewards(now, day);
			EndNight();
			return result;
		}

		public void EndNight()
		{
			_night.Clear();
		}

		public List<long> CaptureNightRewards(double now, int day)
		{
			List<long> list = new List<long>();
			foreach (Pair item in _night)
			{
				Link link = Find(item.Bed);
				if (link == null || link.Revision != item.Revision || link.A != item.A || link.B != item.B || !Present(item.A, item.Bed, now) || !Present(item.B, item.Bed, now))
				{
					continue;
				}
				long[] array = new long[2] { item.A, item.B };
				foreach (long player in array)
				{
					if (!Data.Receipts.Any((Receipt x) => x.Player == player && x.Day >= day))
					{
						list.Add(player);
					}
				}
			}
			return list.Distinct().ToList();
		}
	}
	public sealed class BedPrefabNames
	{
		private readonly HashSet<string> _names = new HashSet<string>(StringComparer.Ordinal);

		public IEnumerable<string> Names => _names;

		public BedPrefabNames(string? csv)
		{
			_names.Add("piece_bed02");
			string[] array = (csv ?? "").Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					_names.Add(text);
				}
			}
		}

		public bool Contains(string name)
		{
			return _names.Contains(name);
		}
	}
	public static class CharacterIdentity
	{
		public static long Resolve(long connection, long owner, long characterPlayer, long localProfile = 0L)
		{
			if (connection == 0L || owner != connection || characterPlayer == 0L)
			{
				return 0L;
			}
			if (localProfile != 0L && localProfile != characterPlayer)
			{
				return 0L;
			}
			return characterPlayer;
		}
	}
	public sealed class ClientSyncRetry
	{
		private double _next;

		public int Attempts { get; private set; }

		public void Reset()
		{
			Attempts = 0;
			_next = 0.0;
		}

		public bool ShouldSend(double now, bool peerReady, bool synchronized)
		{
			if (!peerReady || now < _next)
			{
				return false;
			}
			if (!synchronized && Attempts < int.MaxValue)
			{
				Attempts++;
			}
			_next = now + (double)((synchronized || Attempts >= 4) ? 15 : 3);
			return true;
		}
	}
	public sealed class CustomTextBank
	{
		public const int MaxFileBytes = 1048576;

		public const int MaxLineLength = 4096;

		public const int MaxEntries = 512;

		public const string Example = "# Why Sleep Alone? custom text / textos personalizados (UTF-8)\n# One entry per line. Una frase por línea. Comments start with #.\n# Edit, then restart the game or reload the BepInEx config.\n# Edita y reinicia el juego o recarga la configuración BepInEx.\n\n[Dreams]\n# You dream of the first house you built together...\n# Sueñas con la primera casa que construisteis juntos...\n\n[Wake]\n# You wake beside the reason you keep coming home.\n# Despiertas junto a la razón por la que siempre vuelves a casa.\n";

		public string[] Dreams = Array.Empty<string>();

		public string[] Wake = Array.Empty<string>();

		public readonly Dictionary<string, string> Text = new Dictionary<string, string>(StringComparer.Ordinal);

		public string Warning = "";

		public static CustomTextBank Parse(string source)
		{
			CustomTextBank customTextBank = new CustomTextBank();
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			string text = "";
			using (SHA256 sHA = SHA256.Create())
			{
				using StringReader stringReader = new StringReader(source.TrimStart(new char[1] { '\ufeff' }));
				string text2;
				while ((text2 = stringReader.ReadLine()) != null)
				{
					string text3 = text2.Trim();
					if (text3.Length == 0 || text3.StartsWith("#", StringComparison.Ordinal))
					{
						continue;
					}
					if (text3.StartsWith("[", StringComparison.Ordinal))
					{
						if (string.Equals(text3, "[Dreams]", StringComparison.OrdinalIgnoreCase))
						{
							text = "sleep";
							continue;
						}
						if (string.Equals(text3, "[Wake]", StringComparison.OrdinalIgnoreCase))
						{
							text = "wake";
							continue;
						}
						text = "";
						customTextBank.Warning = "Unknown or malformed section ignored / Seccion desconocida ignorada.";
						continue;
					}
					if (text.Length == 0)
					{
						customTextBank.Warning = "Text outside a known section ignored / Texto fuera de seccion ignorado.";
						continue;
					}
					List<string> list3 = ((text == "sleep") ? list : list2);
					if (text3.Length > 4096 || list3.Count >= 512)
					{
						customTextBank.Warning = "Custom text limits exceeded; excess ignored / Limites excedidos; exceso ignorado.";
						continue;
					}
					string text4 = "custom." + text + "." + BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(text3))).Replace("-", "");
					if (!customTextBank.Text.ContainsKey(text4))
					{
						customTextBank.Text.Add(text4, text3);
						list3.Add(text4);
					}
				}
			}
			customTextBank.Dreams = list.ToArray();
			customTextBank.Wake = list2.ToArray();
			return customTextBank;
		}

		public static CustomTextBank Load(string path)
		{
			try
			{
				if (!File.Exists(path))
				{
					try
					{
						using FileStream stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
						using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
						streamWriter.Write("# Why Sleep Alone? custom text / textos personalizados (UTF-8)\n# One entry per line. Una frase por línea. Comments start with #.\n# Edit, then restart the game or reload the BepInEx config.\n# Edita y reinicia el juego o recarga la configuración BepInEx.\n\n[Dreams]\n# You dream of the first house you built together...\n# Sueñas con la primera casa que construisteis juntos...\n\n[Wake]\n# You wake beside the reason you keep coming home.\n# Despiertas junto a la razón por la que siempre vuelves a casa.\n");
					}
					catch (IOException)
					{
						if (!File.Exists(path))
						{
							throw;
						}
					}
				}
				using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				if (fileStream.Length > 1048576)
				{
					return new CustomTextBank
					{
						Warning = "Custom file exceeds 1 MiB / Archivo supera 1 MiB."
					};
				}
				using StreamReader streamReader = new StreamReader(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: false);
				char[] array = new char[1048577];
				int num = streamReader.ReadBlock(array, 0, array.Length);
				if (num > 1048576)
				{
					return new CustomTextBank
					{
						Warning = "Custom file too large / Archivo demasiado grande."
					};
				}
				return Parse(new string(array, 0, num));
			}
			catch (Exception ex2) when (ex2 is IOException || ex2 is UnauthorizedAccessException || ex2 is SecurityException || ex2 is DecoderFallbackException || ex2 is ArgumentException || ex2 is NotSupportedException)
			{
				return new CustomTextBank
				{
					Warning = "Cannot read custom text; using built-in fallback / No se puede leer texto personalizado: " + ex2.Message
				};
			}
		}

		public static bool UseCustom(string mode, bool includeMixed, int roll, int available)
		{
			if (available > 0)
			{
				if (!(mode == "Custom"))
				{
					if (mode == "Mixed" && includeMixed)
					{
						return roll < 10;
					}
					return false;
				}
				return true;
			}
			return false;
		}
	}
	public static class DreamSelection
	{
		public static string Bucket(string tone, string intensity, int roll)
		{
			if (roll < 0 || roll >= 100)
			{
				throw new ArgumentOutOfRangeException("roll");
			}
			switch (tone)
			{
			case "Mixed":
				if (roll >= 45)
				{
					if (roll >= 75)
					{
						if (roll >= 95)
						{
							return "Bawdy Spicy";
						}
						return "Bawdy Mild";
					}
					return "Romantic";
				}
				return "Neutral";
			case "Bawdy":
				if (!(intensity == "Spicy") || roll < 35)
				{
					return "Bawdy Mild";
				}
				return "Bawdy Spicy";
			default:
				return "Neutral";
			case "Romantic":
				return "Romantic";
			}
		}

		public static string Tone(string bucket)
		{
			if (!bucket.StartsWith("Bawdy", StringComparison.Ordinal))
			{
				return bucket;
			}
			return "Bawdy";
		}
	}
	public sealed class DreamHistory
	{
		private readonly int _capacity;

		private readonly Queue<string> _recent = new Queue<string>();

		public DreamHistory(int capacity)
		{
			_capacity = Math.Max(0, capacity);
		}

		public bool Contains(string key)
		{
			return _recent.Contains(key);
		}

		public string Pick(IReadOnlyList<string> keys, Func<int, int> random, bool avoidRepeat)
		{
			if (keys.Count == 0)
			{
				return "";
			}
			int num = 0;
			for (int i = 0; i < keys.Count; i++)
			{
				if (!avoidRepeat || !Contains(keys[i]))
				{
					num++;
				}
			}
			bool flag = num == 0;
			int num2 = random(flag ? keys.Count : num);
			for (int j = 0; j < keys.Count; j++)
			{
				if ((!(!flag && avoidRepeat) || !Contains(keys[j])) && num2-- == 0)
				{
					if (flag)
					{
						_recent.Clear();
					}
					Remember(keys[j]);
					return keys[j];
				}
			}
			throw new InvalidOperationException("Random selection out of range");
		}

		private void Remember(string key)
		{
			if (_recent.Contains(key))
			{
				int count = _recent.Count;
				while (count-- > 0)
				{
					string text = _recent.Dequeue();
					if (text != key)
					{
						_recent.Enqueue(text);
					}
				}
			}
			_recent.Enqueue(key);
			while (_recent.Count > _capacity)
			{
				_recent.Dequeue();
			}
		}
	}
	public sealed class PendingDreamSession
	{
		private long _session;

		private string _text = "";

		private bool _consumed;

		public bool CanPrepare(long session)
		{
			if (session != 0L)
			{
				return session != _session;
			}
			return false;
		}

		public void Prepare(long session, string text)
		{
			if (CanPrepare(session))
			{
				_session = session;
				_text = text;
				_consumed = false;
			}
		}

		public bool TryConsume(long session, out string text)
		{
			text = "";
			if (session == 0L || session != _session || _consumed || string.IsNullOrEmpty(_text))
			{
				return false;
			}
			text = _text;
			_text = "";
			_consumed = true;
			return true;
		}

		public void Clear(long session = 0L)
		{
			if (session == 0L || session == _session)
			{
				_session = 0L;
				_text = "";
				_consumed = false;
			}
		}
	}
	public sealed class RequestJournal<T> where T : class
	{
		private long _latest;

		private T? _reply;

		public bool IsNew(long request)
		{
			return request > _latest;
		}

		public T? Replay(long request)
		{
			if (request != _latest)
			{
				return null;
			}
			return _reply;
		}

		public void Remember(long request, T reply)
		{
			if (request > _latest)
			{
				_latest = request;
				_reply = reply;
			}
		}
	}
	public static class VanillaDreamPolicy
	{
		public static bool IsOrdinary(string? text, float chance, int trueKeys, int falseKeys)
		{
			if (trueKeys != 0 || falseKeys != 0 || chance != 0.5f || text == null)
			{
				return false;
			}
			switch (text)
			{
			case "$dream_random01":
			case "$dream_random11":
			case "$dream_random02":
			case "$dream_random12":
			case "$dream_random03":
			case "$dream_random13":
			case "$dream_random04":
			case "$dream_random14":
			case "$dream_random05":
			case "$dream_random15":
			case "$dream_random06":
			case "$dream_random16":
			case "$dream_random07":
			case "$dream_random17":
			case "$dream_random08":
			case "$dream_random09":
			case "$dream_random10":
				return true;
			default:
				return false;
			}
		}
	}
	public static class XmlStore
	{
		private static class Serializer<T>
		{
			internal static readonly XmlSerializer Instance = new XmlSerializer(typeof(T));
		}

		private sealed class Utf8Writer : StringWriter
		{
			public override Encoding Encoding => System.Text.Encoding.UTF8;
		}

		public static string Encode<T>(T value)
		{
			using Utf8Writer utf8Writer = new Utf8Writer();
			Serializer<T>.Instance.Serialize((TextWriter)utf8Writer, (object)value);
			return utf8Writer.ToString();
		}

		public static T Decode<T>(string text)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_0042: Expected O, but got Unknown
			if (text.Length > 2000000)
			{
				throw new InvalidDataException("Data too large");
			}
			XmlReader val = XmlReader.Create((TextReader)new StringReader(text), new XmlReaderSettings
			{
				DtdProcessing = (DtdProcessing)0,
				XmlResolver = null,
				MaxCharactersInDocument = 2000000L
			});
			try
			{
				return (T)(Serializer<T>.Instance.Deserialize(val) ?? throw new InvalidDataException("Empty XML"));
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		}

		public static void Save(string path, WorldData data)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			string text = path + ".tmp";
			byte[] bytes = Encoding.UTF8.GetBytes(Encode(data));
			using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				fileStream.Write(bytes, 0, bytes.Length);
				fileStream.Flush(flushToDisk: true);
			}
			if (File.Exists(path))
			{
				File.Replace(text, path, path + ".bak");
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
}
namespace WhySleepAlone.Beds
{
	internal static class DragonBedDetector
	{
		private static string _csv = "";

		private static BedPrefabNames _names = new BedPrefabNames("");

		private static ZNetScene? _scene;

		private static readonly HashSet<string> Warned = new HashSet<string>(StringComparer.Ordinal);

		internal static void CheckConfiguration()
		{
			ZNetScene instance = ZNetScene.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return;
			}
			string text = Plugin.Instance?.Network.Settings.AdditionalDoubleBeds ?? "";
			if ((Object)(object)instance == (Object)(object)_scene && string.Equals(text, _csv, StringComparison.Ordinal))
			{
				return;
			}
			_scene = instance;
			_csv = text;
			_names = new BedPrefabNames(text);
			Warned.Clear();
			foreach (string name in _names.Names)
			{
				if (!(name == "piece_bed02"))
				{
					GameObject prefab = instance.GetPrefab(name);
					if (!Object.op_Implicit((Object)(object)prefab) || !Compatible(prefab.GetComponent<Bed>()))
					{
						Warn(name);
					}
				}
			}
		}

		private static void Warn(string name)
		{
			if (Warned.Add(name))
			{
				Plugin.Instance?.Warn("Configured custom bed '" + name + "' is missing or lacks a compatible Bed/ZNetView/spawn point; ignoring / Cama ausente o incompatible.");
			}
		}

		private static bool Compatible(Bed bed)
		{
			if (Object.op_Implicit((Object)(object)bed) && Object.op_Implicit((Object)(object)bed.m_spawnPoint))
			{
				return Object.op_Implicit((Object)(object)((Component)bed).GetComponent<ZNetView>());
			}
			return false;
		}

		internal static bool IsSupported(ZDO? candidate)
		{
			if (candidate == null || !Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				return false;
			}
			CheckConfiguration();
			GameObject prefab = ZNetScene.instance.GetPrefab(candidate.GetPrefab());
			if (!Object.op_Implicit((Object)(object)prefab) || !_names.Contains(((Object)prefab).name))
			{
				return false;
			}
			if (!Compatible(prefab.GetComponent<Bed>()))
			{
				if (((Object)prefab).name != "piece_bed02")
				{
					Warn(((Object)prefab).name);
				}
				return false;
			}
			return true;
		}

		internal static bool TryIdentify(Bed bed, out ZDO? zdo)
		{
			zdo = null;
			if (!Compatible(bed))
			{
				return false;
			}
			ZNetView component = ((Component)bed).GetComponent<ZNetView>();
			if (!Object.op_Implicit((Object)(object)component) || !component.IsValid())
			{
				return false;
			}
			ZDO zDO = component.GetZDO();
			if (!IsSupported(zDO))
			{
				return false;
			}
			zdo = zDO;
			return true;
		}
	}
	internal sealed class SharedBeds
	{
		private readonly Plugin _plugin;

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

		private readonly MethodInfo[] _checks = new MethodInfo[4]
		{
			AccessTools.Method(typeof(Bed), "CheckEnemies", new Type[1] { typeof(Player) }, (Type[])null),
			AccessTools.Method(typeof(Bed), "CheckExposure", new Type[1] { typeof(Player) }, (Type[])null),
			AccessTools.Method(typeof(Bed), "CheckFire", new Type[1] { typeof(Player) }, (Type[])null),
			AccessTools.Method(typeof(Bed), "CheckWet", new Type[1] { typeof(Player) }, (Type[])null)
		};

		private long _sequence;

		private long _request;

		private long _sleepSequence;

		private long _sleepSession;

		private long _dreamWorld;

		private long _dreamPlayer;

		private long _dreamRevision;

		private bool _dreamPrepared;

		private Bed? _pending;

		private Bed? _sleepBed;

		private Transform? _anchor;

		private double _deadline;

		private double _pulseAt;

		private double _waitingSpawnSince = -1.0;

		private bool _writingSpawn;

		private bool _holding;

		internal Bed? RespawnBed;

		internal bool FindingSpawn;

		internal bool Attaching { get; private set; }

		internal bool SharedSleepActive { get; private set; }

		private SharedNetwork Net => _plugin.Network;

		private Player? Local => Player.m_localPlayer;

		private bool UsesDefaultJoin
		{
			get
			{
				//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_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Invalid comparison between Unknown and I4
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0036: Unknown result type (might be due to invalid IL or missing references)
				KeyboardShortcut value = _plugin.Settings.JoinKey.Value;
				if ((int)((KeyboardShortcut)(ref value)).MainKey == 101)
				{
					value = _plugin.Settings.JoinKey.Value;
					return !((KeyboardShortcut)(ref value)).Modifiers.Any();
				}
				return false;
			}
		}

		internal SharedBeds(Plugin plugin)
		{
			_plugin = plugin;
			if (Array.Exists(_checks, (MethodInfo x) => x == null))
			{
				throw new MissingMethodException("Bed sleep validation contract changed");
			}
		}

		internal void Reset()
		{
			EndSharedSleep();
			_holding = false;
			_pending = null;
			_sleepBed = null;
			_request = 0L;
			_pulseAt = 0.0;
			_waitingSpawnSince = -1.0;
			RespawnBed = null;
			FindingSpawn = false;
			if (Object.op_Implicit((Object)(object)_anchor))
			{
				Object.Destroy((Object)(object)((Component)_anchor).gameObject);
			}
			_anchor = null;
		}

		internal unsafe bool Interact(Bed bed, Humanoid human, bool repeat, bool alt, bool joining = false)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			//IL_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a1: Unknown result type (might be due to invalid IL or missing references)
			if (!DragonBedDetector.TryIdentify(bed, out ZDO zdo) || (Object)(object)human != (Object)(object)Local)
			{
				return true;
			}
			if (repeat)
			{
				return false;
			}
			if (!Net.Ready || !Net.Settings.Enabled)
			{
				long num = zdo.GetLong(ZDOVars.s_owner, 0L);
				if (alt || joining || (num != 0L && num != Local.GetPlayerID()))
				{
					Say("server");
					return false;
				}
				return true;
			}
			Player local = Local;
			if (((Character)local).IsDead() || ((Character)local).IsAttached())
			{
				return false;
			}
			long playerID = local.GetPlayerID();
			long num2 = zdo.GetLong(ZDOVars.s_owner, 0L);
			Link link = Net.LinkFor(zdo.m_uid);
			if (num2 == 0L)
			{
				if (alt)
				{
					Say("claim");
				}
				return !alt;
			}
			string text;
			if (num2 != playerID && (link == null || link.B != playerID))
			{
				if (!joining && !UsesDefaultJoin && !CustomJoinPressed())
				{
					return true;
				}
				if (Object.op_Implicit((Object)(object)_pending))
				{
					return false;
				}
				text = "join";
			}
			else if (alt)
			{
				text = ((num2 == playerID) ? "invite" : ((link == null || link.B != playerID) ? "join" : "unlink"));
			}
			else
			{
				if (link == null || !link.Contains(playerID))
				{
					return true;
				}
				PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
				text = ((playerProfile.HaveCustomSpawnPoint() && Vector3.Distance(playerProfile.GetCustomSpawnPoint(), bed.GetSpawnPoint()) < 1f) ? "sleep" : "select");
				if (!Validate(bed, local, text == "sleep"))
				{
					return false;
				}
			}
			if ((text == "join" || (text == "invite" && (link == null || (link.B == 0L && !link.Open)))) && !Validate(bed, local, sleep: false))
			{
				return false;
			}
			if (Object.op_Implicit((Object)(object)_pending))
			{
				Say("waiting");
				return false;
			}
			_pending = bed;
			_request = ++_sequence;
			_deadline = Net.Now + 8.0;
			_holding = text == "sleep";
			Vector3 spawnPoint = bed.GetSpawnPoint();
			if (text == "join")
			{
				Say("waiting");
				Plugin plugin = _plugin;
				string text2 = _request.ToString();
				ZDOID uid = zdo.m_uid;
				plugin.Warn("Join requested: request=" + text2 + ", bed=" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString());
			}
			Net.Request(new Packet
			{
				Action = text,
				User = ((ZDOID)(ref zdo.m_uid)).UserID,
				Id = ((ZDOID)(ref zdo.m_uid)).ID,
				Request = _request,
				X = spawnPoint.x,
				Y = spawnPoint.y,
				Z = spawnPoint.z
			});
			return false;
		}

		private bool CustomJoinPressed()
		{
			//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_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = _plugin.Settings.JoinKey.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey != 0 && ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, true))
			{
				return ((KeyboardShortcut)(ref value)).Modifiers.All((KeyCode modifier) => ZInput.GetKey(modifier, true));
			}
			return false;
		}

		internal void UpdateJoinInput()
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			Player local = Local;
			if (!Object.op_Implicit((Object)(object)local) || !Object.op_Implicit((Object)(object)Hud.instance) || Object.op_Implicit((Object)(object)_pending) || UsesDefaultJoin || !CustomJoinPressed() || Hud.InRadial() || ((Character)local).InAttack() || ((Character)local).InDodge() || !(bool)TakeInput.Invoke(local, new object[0]) || ((Character)local).IsAttached())
			{
				return;
			}
			GameObject hoverObject = ((Humanoid)local).GetHoverObject();
			Bed val = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<Bed>() : null);
			if (!Object.op_Implicit((Object)(object)val) || !DragonBedDetector.TryIdentify(val, out ZDO zdo))
			{
				return;
			}
			long playerID = local.GetPlayerID();
			long num = zdo.GetLong(ZDOVars.s_owner, 0L);
			if (num != 0L && num != playerID)
			{
				Link? link = Net.LinkFor(zdo.m_uid);
				if (link == null || link.B != playerID)
				{
					Interact(val, (Humanoid)(object)local, repeat: false, alt: false, joining: true);
				}
			}
		}

		private bool Validate(Bed bed, Player player, bool sleep)
		{
			//IL_0026: 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)
			if (!Object.op_Implicit((Object)(object)bed) || !Object.op_Implicit((Object)(object)player) || ((Character)player).IsDead() || ((Character)player).IsAttached() || Vector3.Distance(((Component)player).transform.position, ((Component)bed).transform.position) > 6f)
			{
				return false;
			}
			if (!sleep)
			{
				return (bool)_checks[1].Invoke(bed, new object[1] { player });
			}
			if (!EnvMan.CanSleep())
			{
				((Character)player).Message((MessageType)2, "$msg_cantsleep", 0, (Sprite)null, false);
				return false;
			}
			MethodInfo[] checks = _checks;
			for (int i = 0; i < checks.Length; i++)
			{
				if (!(bool)checks[i].Invoke(bed, new object[1] { player }))
				{
					return false;
				}
			}
			return true;
		}

		internal string Hover(Bed bed, string vanilla)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			if (!DragonBedDetector.TryIdentify(bed, out ZDO zdo) || !Object.op_Implicit((Object)(object)Local))
			{
				return vanilla;
			}
			if (!Net.Ready)
			{
				return vanilla + "\n" + _plugin.Messages.Get("server");
			}
			if (!Net.Settings.Enabled)
			{
				return vanilla;
			}
			long playerID = Local.GetPlayerID();
			long num = zdo.GetLong(ZDOVars.s_owner, 0L);
			if (num == 0L)
			{
				return vanilla;
			}
			Link link = Net.LinkFor(zdo.m_uid);
			string text = ((num != playerID) ? ((link != null && link.B == playerID) ? "unlink" : "join") : (((link == null || link.B != 0) && link != null) ? "unlinkpair" : ((link != null && link.Open) ? "closeinvite" : "invite")));
			string text2 = vanilla;
			if (link != null && link.B == playerID)
			{
				text2 = text2 + "\n[<color=yellow><b>$KEY_Use</b></color>] " + _plugin.Messages.Get(bed.IsCurrent() ? "sleepaction" : "selectaction");
			}
			string text3 = ((!(text == "join")) ? "$KEY_AltPlace + $KEY_Use" : (UsesDefaultJoin ? "$KEY_Use" : ((object)_plugin.Settings.JoinKey.Value/*cast due to .constrained prefix*/).ToString()));
			text2 = text2 + "\n[<color=yellow><b>" + text3 + "</b></color>] " + _plugin.Messages.Get(text);
			if (link != null && link.B != 0L && !link.Contains(playerID))
			{
				text2 = text2 + "\n" + _plugin.Messages.Get("full