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 SleepCall v1.0.3
plugins/SleepCall.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [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: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("SleepCall")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3+d77508339d29be45d76add4d20eca3c190e5ca9f")] [assembly: AssemblyProduct("SleepCall")] [assembly: AssemblyTitle("SleepCall")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SleepCall { [BepInPlugin("liekos47.sleepcall", "SleepCall", "1.0.3")] public class SleepCallPlugin : BaseUnityPlugin { public const string Guid = "liekos47.sleepcall"; public const string Name = "SleepCall"; public const string Version = "1.0.3"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<int> CountdownSeconds; internal static ConfigEntry<string> ReminderSeconds; internal static ConfigEntry<int> BannerIntervalSeconds; internal static ConfigEntry<int> FinalCountdownSeconds; internal static ConfigEntry<bool> TopLeftFeed; internal static ConfigEntry<bool> ChatAnnounce; internal static ConfigEntry<bool> WaitForCombat; internal static ConfigEntry<float> RecentDamageSeconds; internal static ConfigEntry<float> AlertedMonsterRange; internal static ConfigEntry<bool> WaitForTravel; internal static ConfigEntry<float> TravelSpeed; internal static ConfigEntry<int> MaxWaitSeconds; internal static ConfigEntry<int> WaitReminderSeconds; internal static ConfigEntry<bool> DebugLog; private Harmony harmony; private DateTime configStamp; private void Awake() { //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the mod. When disabled the vanilla rule applies: everyone must be in bed."); CountdownSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Countdown", "CountdownSeconds", 60, "Seconds between the first player lying down and the night skipping, so everyone is warned."); ReminderSeconds = ((BaseUnityPlugin)this).Config.Bind<string>("Countdown", "ReminderSeconds", "30,10,5", "Comma-separated seconds-remaining at which a reminder is also posted to the top-left feed."); BannerIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Countdown", "BannerIntervalSeconds", 10, "Re-show the centre-screen countdown banner this often, with the time left. Centre messages fade after about 4 seconds, so a single one is easy to miss. 0 = only at the start and the reminders."); FinalCountdownSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Countdown", "FinalCountdownSeconds", 5, "Count the last seconds down one by one in the centre of the screen (\"Sleeping in 5\", 4, 3, 2, 1, \"Sleeping now\"). It always runs straight before the night passes, including after waiting for a fight or a journey to end. 0 = off."); TopLeftFeed = ((BaseUnityPlugin)this).Config.Bind<bool>("Countdown", "TopLeftFeed", true, "Also post the countdown start, reminders, waiting reasons and the outcome to the top-left message feed, which stays on screen longer, queues instead of replacing, and is kept in the Compendium's message log."); ChatAnnounce = ((BaseUnityPlugin)this).Config.Bind<bool>("Countdown", "ChatAnnounce", false, "EXPERIMENTAL, untested: also post the countdown start and outcome as a chat shout from 'SleepCall'. Clients run a platform permission check on the sender of every chat line, and a server-made sender has no platform identity, so it may be silently dropped. Safe to try live: the config reloads every 30 s."); WaitForCombat = ((BaseUnityPlugin)this).Config.Bind<bool>("Waiting", "WaitForCombat", true, "After the countdown, keep waiting while any awake player is fighting."); RecentDamageSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Waiting", "RecentDamageSeconds", 15f, "A player counts as fighting if their health dropped within this many seconds."); AlertedMonsterRange = ((BaseUnityPlugin)this).Config.Bind<float>("Waiting", "AlertedMonsterRange", 40f, "A player counts as fighting if an alerted, untamed monster is within this many metres."); WaitForTravel = ((BaseUnityPlugin)this).Config.Bind<bool>("Waiting", "WaitForTravel", true, "After the countdown, keep waiting while any awake player is travelling."); TravelSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Waiting", "TravelSpeed", 3f, "A player counts as travelling above this average speed in m/s (walking is about 2, running 7, sailing 4-8)."); MaxWaitSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Waiting", "MaxWaitSeconds", 300, "Give up waiting after this many seconds and skip the night anyway, with a final warning. 0 = wait forever."); WaitReminderSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Waiting", "WaitReminderSeconds", 15, "While waiting, repeat who is holding things up every this many seconds."); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, "Log every decision to the server log."); harmony = new Harmony("liekos47.sleepcall"); harmony.PatchAll(); Log.LogInfo((object)"SleepCall 1.0.3 loaded"); configStamp = ConfigStamp(); ((MonoBehaviour)this).InvokeRepeating("ReloadConfig", 30f, 30f); } private DateTime ConfigStamp() { return File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath); } private void ReloadConfig() { try { if (!(ConfigStamp() == configStamp)) { ((BaseUnityPlugin)this).Config.Reload(); configStamp = ConfigStamp(); Log.LogInfo((object)"config file changed, settings re-read"); } } catch (Exception ex) { Log.LogWarning((object)("config reload failed: " + ex.Message)); } } private void OnDestroy() { ((MonoBehaviour)this).CancelInvoke(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } [HarmonyPatch(typeof(Game), "EverybodyIsTryingToSleep")] internal static class Game_EverybodyIsTryingToSleep_Patch { private static bool Prefix(ref bool __result) { if (!SleepCallPlugin.Enabled.Value) { return true; } __result = SleepCoordinator.Tick(); return false; } } internal static class SleepCoordinator { private enum State { Idle, Counting, Waiting, Final, Skipping } private struct Sample { public Vector3 pos; public float health; public float time; public float lastDamageTime; public float speed; } private static State state = State.Idle; private static float lastTick; private static float startedAt; private static float lastBanner; private static float lastWaitReminder; private static float finalStartedAt; private static int finalShown; private static bool chatFailed; private static float lastSample; private static readonly HashSet<int> firedReminders = new HashSet<int>(); private static readonly Dictionary<ZDOID, Sample> samples = new Dictionary<ZDOID, Sample>(); private static readonly Dictionary<int, bool> monsterPrefabs = new Dictionary<int, bool>(); private static readonly List<ZDO> scratch = new List<ZDO>(); private static readonly List<ZDO> sleepers = new List<ZDO>(); private static readonly List<ZDO> awake = new List<ZDO>(); private static string reminderSource; private static int[] reminderMarks = new int[0]; private const float SampleInterval = 1f; private const float StaleAfter = 10f; private const long Everybody = 0L; public static bool Tick() { float unscaledTime = Time.unscaledTime; if (unscaledTime - lastTick > 10f) { if (state == State.Counting || state == State.Waiting || state == State.Final) { Info($"dropped a countdown left over from {(int)(unscaledTime - lastTick)}s ago"); } Reset(); } lastTick = unscaledTime; List<ZDO> allCharacterZDOS = ZNet.instance.GetAllCharacterZDOS(); if (allCharacterZDOS.Count == 0) { Reset(); return false; } if (unscaledTime - lastSample >= 1f) { UpdateSamples(allCharacterZDOS, unscaledTime); lastSample = unscaledTime; } sleepers.Clear(); awake.Clear(); foreach (ZDO item in allCharacterZDOS) { if (item.GetBool(ZDOVars.s_inBed, false)) { sleepers.Add(item); } else { awake.Add(item); } } if (sleepers.Count == 0) { if (state == State.Counting || state == State.Waiting || state == State.Final) { Info("cancelled: nobody in bed any more"); Broadcast("Nobody is in bed any more - the night goes on.", feed: true, chat: true); } Reset(); return false; } if (awake.Count == 0) { if (state != State.Skipping) { Info($"everyone in bed ({allCharacterZDOS.Count} of {allCharacterZDOS.Count}), skipping at once"); } state = State.Skipping; return true; } if (state == State.Idle || state == State.Skipping) { state = State.Counting; startedAt = unscaledTime; lastBanner = unscaledTime; firedReminders.Clear(); lastWaitReminder = -1000f; string text = Names(sleepers); Info($"countdown started: {text} in bed, {awake.Count} awake ({Names(awake)}), {SleepCallPlugin.CountdownSeconds.Value}s"); Broadcast($"{text} went to bed. The night will pass in {SleepCallPlugin.CountdownSeconds.Value} seconds.", feed: true, chat: true); Feed("Fighting or travelling delays it. Go to bed too to skip sooner."); return false; } float num = unscaledTime - startedAt; int value = SleepCallPlugin.CountdownSeconds.Value; int num2 = Mathf.Clamp(SleepCallPlugin.FinalCountdownSeconds.Value, 0, value); int num3 = value - (int)num; if (state == State.Counting && num3 > num2) { bool flag = false; int[] array = ReminderMarks(); foreach (int num4 in array) { if (num4 > num2 && num3 <= num4 && firedReminders.Add(num4)) { Broadcast($"The night passes in {num4} seconds.", feed: true, chat: false); lastBanner = unscaledTime; flag = true; break; } } int value2 = SleepCallPlugin.BannerIntervalSeconds.Value; if (!flag && value2 > 0 && unscaledTime - lastBanner >= (float)value2) { lastBanner = unscaledTime; string arg = Names(sleepers); Center(string.Format("{0} {1} in bed - the night passes in {2} seconds.", arg, (sleepers.Count == 1) ? "is" : "are", num3)); } return false; } if (state == State.Final) { int num5 = num2 - (int)(unscaledTime - finalStartedAt); if (num5 > 0) { if (num5 != finalShown) { finalShown = num5; Center($"Sleeping in {num5}"); } return false; } } List<string> list = Blockers(awake, unscaledTime); int num6 = (int)num - (value - num2); bool flag2 = SleepCallPlugin.MaxWaitSeconds.Value > 0 && num6 >= SleepCallPlugin.MaxWaitSeconds.Value; if (list.Count == 0 || flag2) { if (state != State.Final && num2 > 0) { if (state == State.Waiting) { Feed(flag2 ? "Waited long enough - the night passes anyway." : "Everyone is clear."); } state = State.Final; finalStartedAt = unscaledTime; finalShown = num2; Center($"Sleeping in {num2}"); return false; } Broadcast(flag2 ? "Waited long enough - sleeping now." : "Sleeping now.", feed: true, chat: true); Info(flag2 ? string.Format("skipping: gave up after waiting {0}s, still blocked by: {1}", num6, string.Join("; ", list)) : $"skipping: countdown done after {(int)num}s, nobody fighting or travelling"); state = State.Skipping; return true; } bool num7 = state == State.Final; state = State.Waiting; if (num7 || unscaledTime - lastWaitReminder >= (float)SleepCallPlugin.WaitReminderSeconds.Value) { bool flag3 = lastWaitReminder < 0f; lastWaitReminder = unscaledTime; Broadcast("Waiting to sleep: " + string.Join(", ", list), feed: true, flag3); if (flag3) { Info("waiting: " + string.Join("; ", list)); } else { Debug("waiting: " + string.Join("; ", list)); } } return false; } private static void Reset() { state = State.Idle; firedReminders.Clear(); } private static int[] ReminderMarks() { string value = SleepCallPlugin.ReminderSeconds.Value; if (value != reminderSource) { reminderSource = value; reminderMarks = (from part in value.Split(new char[1] { ',' }) select int.TryParse(part.Trim(), out var result) ? result : 0 into v where v > 0 select v).ToArray(); } return reminderMarks; } private static void UpdateSamples(List<ZDO> characters, float now) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) HashSet<ZDOID> seen = new HashSet<ZDOID>(); foreach (ZDO character in characters) { seen.Add(character.m_uid); Vector3 position = character.GetPosition(); float num = character.GetFloat(ZDOVars.s_health, 0f); if (samples.TryGetValue(character.m_uid, out var value)) { float num2 = Mathf.Max(now - value.time, 0.001f); float num3 = Vector3.Distance(position, value.pos) / num2; value.speed = Mathf.Lerp(value.speed, Mathf.Min(num3, 30f), 0.5f); if (num < value.health - 0.5f) { value.lastDamageTime = now; } value.pos = position; value.health = num; value.time = now; samples[character.m_uid] = value; } else { samples[character.m_uid] = new Sample { pos = position, health = num, time = now, lastDamageTime = -1000f, speed = 0f }; } } foreach (ZDOID item in samples.Keys.Where((ZDOID k) => !seen.Contains(k)).ToList()) { samples.Remove(item); } } private static List<string> Blockers(List<ZDO> awake, float now) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); foreach (ZDO item in awake) { if (item.GetBool(ZDOVars.s_dead, false)) { continue; } string text = NameOf(item); samples.TryGetValue(item.m_uid, out var value); if (SleepCallPlugin.WaitForCombat.Value) { if (now - value.lastDamageTime <= SleepCallPlugin.RecentDamageSeconds.Value) { list.Add(text + " is taking damage"); continue; } if (AlertedMonsterNear(item.GetPosition())) { list.Add(text + " is in combat"); continue; } } if (SleepCallPlugin.WaitForTravel.Value && value.speed > SleepCallPlugin.TravelSpeed.Value) { list.Add(text + " is travelling"); } } return list; } private static bool AlertedMonsterNear(Vector3 point) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) float value = SleepCallPlugin.AlertedMonsterRange.Value; scratch.Clear(); ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(point), new SimulationDistance(1, 0, true), scratch, (List<ZDO>)null); float num = value * value; foreach (ZDO item in scratch) { if (item.GetBool(ZDOVars.s_alert, false) && !item.GetBool(ZDOVars.s_tamed, false)) { Vector3 val = item.GetPosition() - point; if (!(((Vector3)(ref val)).sqrMagnitude > num) && IsMonster(item.GetPrefab())) { return true; } } } return false; } private static bool IsMonster(int prefabHash) { if (!monsterPrefabs.TryGetValue(prefabHash, out var value)) { GameObject val = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(prefabHash) : null); value = Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.GetComponent<MonsterAI>()); monsterPrefabs[prefabHash] = value; } return value; } private static string NameOf(ZDO zdo) { string text = zdo.GetString(ZDOVars.s_playerName, ""); if (text.Length <= 0) { return "Someone"; } return text; } private static string Names(List<ZDO> zdos) { List<string> list = zdos.Select(NameOf).Distinct().ToList(); if (list.Count == 1) { return list[0]; } return string.Join(", ", list.Take(list.Count - 1)) + " and " + list[list.Count - 1]; } private static void Broadcast(string text, bool feed, bool chat) { Center(text); if (feed) { Feed(text); } if (chat) { Chat(text); } } private static void Center(string text) { Announce((MessageType)2, text); } private static void Feed(string text) { if (SleepCallPlugin.TopLeftFeed.Value) { Announce((MessageType)1, text); } } private static void Announce(MessageType type, string text) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected I4, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { (int)type, text }); Debug($"announce [{type}] {text}"); } private static void Chat(string text) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!SleepCallPlugin.ChatAnnounce.Value || chatFailed) { return; } try { UserInfo val = new UserInfo { Name = "SleepCall" }; ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ChatMessage", new object[4] { Vector3.zero, 2, val, text }); Debug("chat " + text); } catch (Exception ex) { chatFailed = true; SleepCallPlugin.Log.LogWarning((object)("chat announce failed, disabled until restart: " + ex.GetType().Name + ": " + ex.Message)); } } private static void Info(string text) { SleepCallPlugin.Log.LogInfo((object)text); } private static void Debug(string text) { if (SleepCallPlugin.DebugLog.Value) { SleepCallPlugin.Log.LogInfo((object)text); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }