Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of WhySleepAlone v1.0.0
BepInEx/plugins/WhySleepAlone/WhySleepAlone.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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