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 Sono v1.5.0
Sono.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace Sono; internal sealed class ServerRules { internal bool Enabled; internal double SleepFromHour; internal bool DelayEnabled; internal double DelaySeconds; internal bool BedPlayersOnly; internal bool PermissionAllowed; internal double AfkMinutes; internal bool CapSpeed; internal double MaxSpeed; internal bool EffectsAsleep; internal bool EffectsAwake; internal bool FoodAsleep; internal bool FoodAwake; internal bool ShowWaitingFor; internal int MaxAwake; internal bool PermissionUntilDawn; internal static ServerRules Defaults() { ServerRules serverRules = new ServerRules(); serverRules.Enabled = true; serverRules.SleepFromHour = 18.0; serverRules.DelayEnabled = false; serverRules.DelaySeconds = 10.0; serverRules.BedPlayersOnly = true; serverRules.PermissionAllowed = true; serverRules.AfkMinutes = 1.0; serverRules.CapSpeed = true; serverRules.MaxSpeed = 60.0; serverRules.EffectsAsleep = true; serverRules.EffectsAwake = true; serverRules.FoodAsleep = false; serverRules.FoodAwake = false; serverRules.ShowWaitingFor = true; serverRules.MaxAwake = 1; serverRules.PermissionUntilDawn = true; return serverRules; } internal string Serialize() { string[] value = new string[17] { "7", B(Enabled), D(SleepFromHour), B(DelayEnabled), D(DelaySeconds), B(BedPlayersOnly), B(PermissionAllowed), D(AfkMinutes), B(CapSpeed), D(MaxSpeed), B(EffectsAsleep), B(EffectsAwake), B(FoodAsleep), B(FoodAwake), B(ShowWaitingFor), MaxAwake.ToString(CultureInfo.InvariantCulture), B(PermissionUntilDawn) }; return string.Join("|", value); } internal static bool TryDeserialize(string payload, out ServerRules rules) { rules = null; if (string.IsNullOrEmpty(payload)) { return false; } string[] array = payload.Split('|'); if (array.Length != 17 || array[0] != "7") { return false; } if (!bool.TryParse(array[1], out var result) || !TryDouble(array[2], out var value) || !bool.TryParse(array[3], out var result2) || !TryDouble(array[4], out var value2) || !bool.TryParse(array[5], out var result3) || !bool.TryParse(array[6], out var result4) || !TryDouble(array[7], out var value3) || !bool.TryParse(array[8], out var result5) || !TryDouble(array[9], out var value4) || !bool.TryParse(array[10], out var result6) || !bool.TryParse(array[11], out var result7) || !bool.TryParse(array[12], out var result8) || !bool.TryParse(array[13], out var result9) || !bool.TryParse(array[14], out var result10) || !int.TryParse(array[15], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result11) || !bool.TryParse(array[16], out var result12)) { return false; } rules = new ServerRules { Enabled = result, SleepFromHour = ClampHour(value), DelayEnabled = result2, DelaySeconds = Math.Max(0.0, Math.Min(120.0, value2)), BedPlayersOnly = result3, PermissionAllowed = result4, AfkMinutes = Math.Max(0.0, Math.Min(60.0, value3)), CapSpeed = result5, MaxSpeed = Math.Max(1.0, Math.Min(240.0, value4)), EffectsAsleep = result6, EffectsAwake = result7, FoodAsleep = result8, FoodAwake = result9, ShowWaitingFor = result10, MaxAwake = ClampMaxAwake(result11), PermissionUntilDawn = result12 }; return true; } internal static double ClampHour(double hour) { return Math.Max(0.0, Math.Min(23.99, hour)); } internal static int ClampMaxAwake(int value) { return Math.Max(0, Math.Min(64, value)); } internal static string B(bool value) { if (!value) { return "false"; } return "true"; } internal static string D(double value) { return value.ToString("R", CultureInfo.InvariantCulture); } internal static bool TryDouble(string text, out double value) { return double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value); } } internal struct SleeperState { internal long Owner; internal bool InBed; internal string Name; internal bool IgnoresSleep; internal SleeperState(long owner, bool inBed, string name, bool ignoresSleep) { Owner = owner; InBed = inBed; Name = name; IgnoresSleep = ignoresSleep; } } internal static class SleepCore { internal const double VanillaSleepDelay = 10.0; internal const int MaxSubsteps = 240; internal const double DawnHour = 6.0; internal const double MaxHour = 23.99; internal const int MaxAwakeLimit = 64; internal static double RescaleDayFraction(double fraction) { if (fraction >= 0.15 && fraction <= 0.85) { return 0.25 + (fraction - 0.15) / 0.7 * 0.5; } if (fraction < 0.5) { return fraction / 0.15 * 0.25; } return 0.75 + (fraction - 0.85) / 0.15 * 0.25; } internal static long DawnIndex(double worldSeconds, double dayLengthSeconds) { return (long)Math.Floor((worldSeconds - dayLengthSeconds * 0.15) / dayLengthSeconds); } internal static double ClockHour(double worldSeconds, double dayLengthSeconds) { double num = worldSeconds % dayLengthSeconds / dayLengthSeconds; if (num < 0.0) { num += 1.0; } return RescaleDayFraction(Math.Max(0.0, Math.Min(1.0, num))) * 24.0; } internal static bool SleepAllowedAt(double hour, double fromHour) { if (fromHour >= 6.0) { if (!(hour >= fromHour)) { return hour < 6.0; } return true; } if (hour >= fromHour) { return hour < 6.0; } return false; } internal static bool EveryoneReady(IList<SleeperState> players, bool permissionAllowed, IDictionary<long, bool> permits, List<long> blockers, List<string> sleepers) { return EveryoneReady(players, permissionAllowed, 0, permits, blockers, sleepers, null); } internal static bool EveryoneReady(IList<SleeperState> players, bool permissionAllowed, IDictionary<long, bool> permits, List<long> blockers, List<string> sleepers, List<string> blockerNames) { return EveryoneReady(players, permissionAllowed, 0, permits, blockers, sleepers, blockerNames); } internal static bool EveryoneReady(IList<SleeperState> players, bool permissionAllowed, int maxAwake, IDictionary<long, bool> permits, List<long> blockers, List<string> sleepers, List<string> blockerNames) { List<int> list = new List<int>(); blockers.Clear(); sleepers.Clear(); blockerNames?.Clear(); for (int i = 0; i < players.Count; i++) { SleeperState sleeperState = players[i]; if (sleeperState.InBed) { sleepers.Add(sleeperState.Name); } else if (!sleeperState.IgnoresSleep) { if (!permissionAllowed || !permits.TryGetValue(sleeperState.Owner, out var value) || !value) { blockers.Add(sleeperState.Owner); blockerNames?.Add(sleeperState.Name); } else { list.Add(i); } } } if (maxAwake > 0 && list.Count > maxAwake) { for (int j = 0; j < list.Count; j++) { blockers.Add(players[list[j]].Owner); blockerNames?.Add(players[list[j]].Name); } } if (sleepers.Count > 0) { return blockers.Count == 0; } return false; } internal static double CountdownStartValue(double now, ServerRules rules) { double num = (rules.DelayEnabled ? rules.DelaySeconds : 0.0); return now - (10.0 - num); } internal static bool IsAfk(double now, double lastActivity, double afkMinutes) { if (afkMinutes > 0.0) { return now - lastActivity >= afkMinutes * 60.0; } return false; } internal static double CappedSkipSpeed(double vanillaSpeed, ServerRules rules) { if (!rules.Enabled || !rules.CapSpeed) { return vanillaSpeed; } return Math.Max(1.0, Math.Min(vanillaSpeed, rules.MaxSpeed)); } internal static float EffectMultiplier(ServerRules rules, bool asleep, bool skipActive, double skipSpeed, double skipEnd, double now) { if (!(asleep ? rules.EffectsAsleep : rules.EffectsAwake)) { return 1f; } return SkipMultiplier(rules, skipActive, skipSpeed, skipEnd, now); } internal static float FoodMultiplier(ServerRules rules, bool asleep, bool skipActive, double skipSpeed, double skipEnd, double now) { if (!(asleep ? rules.FoodAsleep : rules.FoodAwake)) { return 1f; } return SkipMultiplier(rules, skipActive, skipSpeed, skipEnd, now); } private static float SkipMultiplier(ServerRules rules, bool skipActive, double skipSpeed, double skipEnd, double now) { if (!rules.Enabled || !skipActive || now >= skipEnd) { return 1f; } double val = (rules.CapSpeed ? rules.MaxSpeed : skipSpeed); return (float)(1.0 + Math.Max(0.0, Math.Min(skipSpeed, val))); } internal static bool HoldsRealTime(bool asleep, bool isRest, int substep) { if (asleep && isRest) { return substep > 0; } return false; } internal static void RunSubsteps(float dt, float multiplier, Action<float> step) { float num = dt * multiplier; int num2 = Math.Min(240, Math.Max(1, (int)Math.Ceiling(multiplier))); float obj = num / (float)num2; for (int i = 0; i < num2; i++) { step(obj); } } } internal sealed class WaitNotice { private readonly HashSet<long> _inBed = new HashSet<long>(); private string _lastNames = ""; internal List<long> Step(IList<SleeperState> players, List<string> blockerNames, bool active, out string names) { List<string> list = new List<string>(blockerNames); list.Sort(StringComparer.OrdinalIgnoreCase); names = string.Join(", ", list.ToArray()); bool flag = active && names.Length > 0; if (!flag) { names = ""; } bool flag2 = names != _lastNames; List<long> list2 = new List<long>(); List<long> list3 = new List<long>(); for (int i = 0; i < players.Count; i++) { if (players[i].InBed) { long owner = players[i].Owner; list3.Add(owner); if ((flag2 || (flag && !_inBed.Contains(owner))) && !list2.Contains(owner)) { list2.Add(owner); } } } _inBed.Clear(); for (int j = 0; j < list3.Count; j++) { _inBed.Add(list3[j]); } _lastNames = names; return list2; } } internal sealed class SleepGate { private bool _wasEligible; internal void Reset() { _wasEligible = false; } internal int Step(bool eligible) { int result = 0; if (eligible && !_wasEligible) { result = 1; } else if (!eligible && _wasEligible) { result = -1; } _wasEligible = eligible; return result; } } internal sealed class PermissionState { internal bool Manual; private double _lastActivity; private bool _sent; private bool _sentValue; private bool _seenDawn; private long _dawn; internal PermissionState(double now) { _lastActivity = now; } internal void Activity(double now) { _lastActivity = now; } internal bool ExpireAtDawn(ServerRules rules, long dawn) { bool flag = _seenDawn && dawn > _dawn; _seenDawn = true; _dawn = dawn; if (!flag || !Manual || !rules.Enabled || !rules.PermissionUntilDawn) { return false; } Manual = false; return true; } internal void Forget() { _sent = false; } internal bool Effective(ServerRules rules, double now) { if (rules.Enabled && rules.PermissionAllowed) { if (!Manual) { return SleepCore.IsAfk(now, _lastActivity, rules.AfkMinutes); } return true; } return false; } internal bool NeedsSend(ServerRules rules, double now, out bool value) { value = Effective(rules, now); if (_sent && value == _sentValue) { return false; } _sent = true; _sentValue = value; return true; } } [BepInPlugin("razaotium.sono", "Sono", "1.5.0")] public sealed class SonoPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Game), "UpdateSleeping")] private static class UpdateSleepingPatch { [HarmonyPriority(800)] private static bool Prefix(Game __instance) { ServerRules rules = Rules; if (!rules.Enabled || SleepingField == null || LastSleepTimeField == null) { return true; } if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)EnvMan.instance == (Object)null) { return false; } double timeSeconds = ZNet.instance.GetTimeSeconds(); if ((bool)SleepingField.GetValue(__instance)) { if (EnvMan.instance.IsTimeSkipping() || CinematicsManager.IsPlaying()) { return false; } LastSleepTimeField.SetValue(__instance, timeSeconds); SleepingField.SetValue(__instance, false); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "SleepStop", new object[0]); return false; } if (EnvMan.instance.IsTimeSkipping()) { return false; } string text = WhyNotSleeping(rules); bool flag = text.Length == 0; if (text != _lastReason) { if (text.Length > 0 && SleeperNames.Count > 0) { Log.LogInfo((object)("Not sleeping yet: " + text + ".")); } _lastReason = ((SleeperNames.Count > 0) ? text : ""); } int num = Gate.Step(flag); if (num > 0) { LastSleepTimeField.SetValue(__instance, SleepCore.CountdownStartValue(timeSeconds, rules)); Log.LogInfo((object)("Everyone is ready: " + SleeperNames.Count + "/" + Players.Count + " in bed.")); } else if (num < 0) { Log.LogInfo((object)"Sleep cancelled: conditions no longer met."); } if (!flag) { return false; } if (timeSeconds - (double)LastSleepTimeField.GetValue(__instance) < 10.0) { return false; } EnvMan.instance.SkipToMorning(); SleepingField.SetValue(__instance, true); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "SleepStart", new object[0]); return false; } } [HarmonyPatch(typeof(Game), "SleepStart")] private static class SleepStartPatch { [HarmonyPriority(800)] private static bool Prefix() { Gate.Reset(); _waitingFor = new string[0]; ServerRules rules = Rules; if (!rules.Enabled || !rules.BedPlayersOnly) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).InBed()) { localPlayer.SetSleeping(true); } return false; } } [HarmonyPatch(typeof(Game), "SleepStop")] private static class SleepStopPatch { [HarmonyPriority(800)] private static void Prefix() { _skipActive = false; } } [HarmonyPatch(typeof(EnvMan), "SkipToMorning")] private static class SkipToMorningPatch { private static void Postfix(EnvMan __instance) { if (!(TimeSkipSpeedField == null) && !(SkipToTimeField == null)) { double num = (double)TimeSkipSpeedField.GetValue(__instance); double num2 = SleepCore.CappedSkipSpeed(num, Rules); if (num2 != num) { TimeSkipSpeedField.SetValue(__instance, num2); } Log.LogInfo((object)("Skipping to morning at " + num2.ToString("0.##") + "x.")); _skipActive = true; _skipSpeed = num2; _skipEnd = (double)SkipToTimeField.GetValue(__instance); BroadcastSkipState(0L); } } } [HarmonyPatch(typeof(SEMan), "Update")] private static class StatusEffectTimePatch { private static bool _inSubstep; internal static bool HoldRest; private static bool Prefix(SEMan __instance, ZDO zdo, float dt) { if (_inSubstep) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !object.ReferenceEquals(__instance, ((Character)localPlayer).GetSEMan()) || (Object)(object)ZNet.instance == (Object)null) { return true; } bool asleep = localPlayer.IsSleeping(); float num = SleepCore.EffectMultiplier(Rules, asleep, _skipActive, _skipSpeed, _skipEnd, ZNet.instance.GetTimeSeconds()); if (num <= 1f) { return true; } _inSubstep = true; try { int substep = 0; SleepCore.RunSubsteps(dt, num, delegate(float step) { HoldRest = SleepCore.HoldsRealTime(asleep, isRest: true, substep++); __instance.Update(zdo, step); }); } finally { _inSubstep = false; HoldRest = false; } return false; } } [HarmonyPatch(typeof(StatusEffect), "UpdateStatusEffect")] private static class RestTimePatch { private static bool Prefix(StatusEffect __instance) { if (!StatusEffectTimePatch.HoldRest) { return true; } int num = __instance.NameHash(); if (num != SEMan.s_statusEffectRested) { return num != SEMan.s_statusEffectResting; } return false; } } [HarmonyPatch(typeof(Player), "UpdateFood")] private static class FoodTimePatch { private static readonly Action<Player, float, bool> UpdateFood = AccessTools.MethodDelegate<Action<Player, float, bool>>(AccessTools.Method(typeof(Player), "UpdateFood", (Type[])null, (Type[])null), (object)null, true); private static bool _inSubstep; private static bool Prefix(Player __instance, float dt, bool forceUpdate) { if (_inSubstep || forceUpdate || !object.ReferenceEquals(__instance, Player.m_localPlayer) || (Object)(object)ZNet.instance == (Object)null) { return true; } float num = SleepCore.FoodMultiplier(Rules, __instance.IsSleeping(), _skipActive, _skipSpeed, _skipEnd, ZNet.instance.GetTimeSeconds()); if (num <= 1f) { return true; } _inSubstep = true; try { SleepCore.RunSubsteps(dt, num, delegate(float step) { UpdateFood(__instance, step, arg3: false); }); } finally { _inSubstep = false; } return false; } } public const string PluginGuid = "razaotium.sono"; public const string PluginName = "Sono"; public const string PluginVersion = "1.5.0"; private const string PermitRpc = "Sono_SetPermit"; private const string RulesRequestRpc = "Sono_RequestRules"; private const string RulesSyncRpc = "Sono_SyncRules"; private const string SkipStateRpc = "Sono_SkipState"; private const string WaitingForRpc = "Sono_WaitingFor"; private const float RulesRequestInterval = 3f; private static readonly Dictionary<long, bool> PermittedByPeer = new Dictionary<long, bool>(); private static readonly List<SleeperState> Players = new List<SleeperState>(); private static readonly List<long> Blockers = new List<long>(); private static readonly List<string> SleeperNames = new List<string>(); private static readonly List<string> BlockerNames = new List<string>(); private static readonly SleepGate Gate = new SleepGate(); private static readonly WaitNotice Notice = new WaitNotice(); private static readonly int GhostIgnoreSleepHash = "DEV_GhostIgnoreSleep".GetHashCode(); private static string _lastReason = ""; private static readonly FieldInfo LastSleepTimeField = AccessTools.Field(typeof(Game), "m_lastSleepTime"); private static readonly FieldInfo SleepingField = AccessTools.Field(typeof(Game), "m_sleeping"); private static readonly FieldInfo TimeSkipSpeedField = AccessTools.Field(typeof(EnvMan), "m_timeSkipSpeed"); private static readonly FieldInfo SkipToTimeField = AccessTools.Field(typeof(EnvMan), "m_skipToTime"); private static readonly FieldInfo DayLengthField = AccessTools.Field(typeof(EnvMan), "m_dayLengthSec"); private static readonly MethodInfo GetServerPeerIdMethod = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null); internal static ManualLogSource Log; internal static ConfigEntry<bool> ModEnabled; internal static ConfigEntry<double> SleepFromHour; internal static ConfigEntry<bool> UseSleepDelay; internal static ConfigEntry<double> SleepDelaySeconds; internal static ConfigEntry<bool> OnlyBedPlayersSleep; internal static ConfigEntry<bool> AllowPermission; internal static ConfigEntry<double> AfkMinutes; internal static ConfigEntry<int> MaxPlayersAwake; internal static ConfigEntry<bool> PermissionUntilDawn; internal static ConfigEntry<KeyCode> ToggleKey; internal static ConfigEntry<bool> CapSkipSpeed; internal static ConfigEntry<double> MaximumSkipSpeed; internal static ConfigEntry<bool> EffectsWhileAsleep; internal static ConfigEntry<bool> EffectsWhileAwake; internal static ConfigEntry<bool> FoodWhileAsleep; internal static ConfigEntry<bool> FoodWhileAwake; internal static ConfigEntry<bool> ShowWaitingFor; private static readonly ServerRules DefaultRules = ServerRules.Defaults(); private static ServerRules _rules = ServerRules.Defaults(); private static bool _receivedServerRules; private static bool _skipActive; private static double _skipSpeed = 1.0; private static double _skipEnd; private Harmony _harmony; private float _nextRulesRequest; private ZRoutedRpc _registeredRpc; private FileSystemWatcher _configWatcher; private volatile bool _configReloadRequested; private PermissionState _permission; private Vector3 _lastMousePosition; private static string[] _waitingFor = new string[0]; private static GUIStyle _panelStyle; private static GUIStyle _panelShadow; private static ServerRules Rules { get { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return _rules; } if (!_receivedServerRules) { return DefaultRules; } return _rules; } } private void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindConfiguration(); ReadRulesFromConfiguration(); SubscribeToRuleChanges(); StartConfigWatcher(); _permission = new PermissionState(Time.unscaledTime); _harmony = new Harmony("razaotium.sono"); _harmony.PatchAll(typeof(SonoPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Sono 1.5.0 loaded; waiting for the server rules."); } private void BindConfiguration() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Expected O, but got Unknown //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Expected O, but got Unknown ModEnabled = BindServer("1 - General", "Enabled", value: true, "Turns every Sono change on or off. False restores vanilla sleep. Editing the server CFG while it runs applies to everyone immediately."); SleepFromHour = ((BaseUnityPlugin)this).Config.Bind<double>("2 - Starting Sleep", "SleepFromHour", 18.0, new ConfigDescription("Earliest clock hour (24 h, as MyLittleUI shows it) at which the night can be skipped; sleep is allowed from this hour until dawn at 6:00. 18 = nightfall (only at night), 12 = vanilla (afternoon too), 21.5 = 21:30. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 23.99), new object[0])); UseSleepDelay = BindServer("2 - Starting Sleep", "UseSleepDelay", value: false, "Waits SleepDelaySeconds before skipping to morning. Off by default: like vanilla, the night skips as soon as everyone is ready."); SleepDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<double>("2 - Starting Sleep", "SleepDelaySeconds", 10.0, new ConfigDescription("Seconds between the conditions being met and the skip to morning. Vanilla has no such wait: sleep starts almost as soon as everyone lies down. 0 matches vanilla. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 120.0), new object[0])); OnlyBedPlayersSleep = BindServer("3 - Experience", "OnlyPlayersInBedSleep", value: true, "Only players in bed get the sleep screen, text, dream and Rested bonus. False uses vanilla SleepStart for everyone."); AllowPermission = BindServer("4 - Permission", "AllowPermission", value: true, "Like vanilla, the night only skips when every player is in bed, but a player can press the key to let the others sleep without them. False requires everyone in bed, exactly like vanilla."); AfkMinutes = ((BaseUnityPlugin)this).Config.Bind<double>("4 - Permission", "AfkMinutes", 1.0, new ConfigDescription("A player with no mouse or keyboard input for this many minutes counts as letting the others sleep, until they move again. 0 turns it off. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 60.0), new object[0])); MaxPlayersAwake = ((BaseUnityPlugin)this).Config.Bind<int>("4 - Permission", "MaxPlayersAwake", 1, new ConfigDescription("How many players may stay awake (F7 or AFK) while the others sleep. With more awake than this, the night waits until enough of them go to bed. 0 = no limit. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64), new object[0])); PermissionUntilDawn = BindServer("4 - Permission", "PermissionUntilDawn", value: true, "The key's permission lasts one night: at dawn it is dropped and the player must press the key again the next night. False keeps it until pressed again. AFK is not affected."); ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("4 - Permission", "TogglePermissionKey", (KeyCode)288, "Local client setting, not enforced by the server. Toggles whether the others may sleep without you."); CapSkipSpeed = BindServer("5 - Time", "CapSkipSpeed", value: true, "Caps the skip-to-morning speed at MaximumSkipSpeed. Vanilla skips the rest of the night in 12 real seconds at a constant speed and stops at dawn."); MaximumSkipSpeed = ((BaseUnityPlugin)this).Config.Bind<double>("5 - Time", "MaximumSkipSpeed", 60.0, new ConfigDescription("Maximum world seconds per real second when CapSkipSpeed=true. Night is 30% of the day, so from nightfall vanilla skips at about 45x with a 30-minute day; only a lower cap makes the skip longer. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(1.0, 240.0), new object[0])); EffectsWhileAsleep = BindServer("6 - Consequences", "StatusEffectsWhileAsleep", value: true, "Players asleep through the skip: their buffs, debuffs and ticks run down with the skipped night. Rested and Resting never do while asleep, like vanilla, and Rested is refreshed on waking."); EffectsWhileAwake = BindServer("6 - Consequences", "StatusEffectsWhileAwake", value: true, "Players awake during the skip (F7 or AFK): their buffs, debuffs and ticks run down with the skipped night, Rested included. They get no new Rested."); FoodWhileAsleep = BindServer("6 - Consequences", "FoodWhileAsleep", value: false, "Players asleep through the skip: their food runs down with the skipped night (food regen included). False leaves it untouched, like vanilla."); FoodWhileAwake = BindServer("6 - Consequences", "FoodWhileAwake", value: false, "Players awake during the skip (F7 or AFK): their food runs down with the skipped night (food regen included). False leaves it untouched, like vanilla."); ShowWaitingFor = BindServer("7 - Messages", "ShowWaitingFor", value: true, "While a player lies in bed at sleep time, a small panel in the centre, just below the crosshair, lists who is still awake without permission. It updates as people lie down or press the permission key."); } private ConfigEntry<bool> BindServer(string section, string key, bool value, string description) { return ((BaseUnityPlugin)this).Config.Bind<bool>(section, key, value, description + " [SERVER RULE]"); } private void SubscribeToRuleChanges() { ((BaseUnityPlugin)this).Config.SettingChanged += OnServerRuleChanged; ((BaseUnityPlugin)this).Config.ConfigReloaded += OnServerConfigReloaded; } private void StartConfigWatcher() { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; string directoryName = Path.GetDirectoryName(configFilePath); string fileName = Path.GetFileName(configFilePath); if (!string.IsNullOrEmpty(directoryName) && !string.IsNullOrEmpty(fileName)) { _configWatcher = new FileSystemWatcher(directoryName, fileName); _configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite; _configWatcher.Changed += OnConfigFileChanged; _configWatcher.Created += OnConfigFileChanged; _configWatcher.Renamed += OnConfigFileRenamed; _configWatcher.EnableRaisingEvents = true; } } private void OnConfigFileChanged(object sender, FileSystemEventArgs args) { _configReloadRequested = true; } private void OnConfigFileRenamed(object sender, RenamedEventArgs args) { _configReloadRequested = true; } private void OnServerRuleChanged(object sender, SettingChangedEventArgs args) { ApplyAndBroadcastServerRules(); } private void OnServerConfigReloaded(object sender, EventArgs args) { ApplyAndBroadcastServerRules(); } private static void ApplyAndBroadcastServerRules() { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ReadRulesFromConfiguration(); BroadcastRules(); } } private static void ReadRulesFromConfiguration() { ServerRules serverRules = new ServerRules(); serverRules.Enabled = ModEnabled.Value; serverRules.SleepFromHour = SleepFromHour.Value; serverRules.DelayEnabled = UseSleepDelay.Value; serverRules.DelaySeconds = SleepDelaySeconds.Value; serverRules.BedPlayersOnly = OnlyBedPlayersSleep.Value; serverRules.PermissionAllowed = AllowPermission.Value; serverRules.AfkMinutes = AfkMinutes.Value; serverRules.CapSpeed = CapSkipSpeed.Value; serverRules.MaxSpeed = MaximumSkipSpeed.Value; serverRules.EffectsAsleep = EffectsWhileAsleep.Value; serverRules.EffectsAwake = EffectsWhileAwake.Value; serverRules.FoodAsleep = FoodWhileAsleep.Value; serverRules.FoodAwake = FoodWhileAwake.Value; serverRules.ShowWaitingFor = ShowWaitingFor.Value; serverRules.MaxAwake = ServerRules.ClampMaxAwake(MaxPlayersAwake.Value); serverRules.PermissionUntilDawn = PermissionUntilDawn.Value; _rules = serverRules; _receivedServerRules = true; } private void OnDestroy() { if (_configWatcher != null) { _configWatcher.EnableRaisingEvents = false; _configWatcher.Dispose(); _configWatcher = null; } if (_harmony != null) { _harmony.UnpatchSelf(); } } private void Update() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) RegisterRpcIfNeeded(); if (_configReloadRequested) { _configReloadRequested = false; if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { try { ((BaseUnityPlugin)this).Config.Reload(); Log.LogInfo((object)"CFG reloaded live; rules sent to clients."); } catch (Exception ex) { Log.LogWarning((object)("Failed to reload CFG live: " + ex.Message)); } } } UpdateServerSkipState(); RequestRulesWhenConnected(); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { TrackActivity(); ServerRules rules = Rules; if (ToggleKey != null && Input.GetKeyDown(ToggleKey.Value)) { TogglePermission(rules); } ExpirePermissionAtDawn(rules); SyncPermission(rules); } } private void TrackActivity() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Vector3 mousePosition = Input.mousePosition; if (!Input.anyKey && !(mousePosition != _lastMousePosition)) { Vector2 mouseScrollDelta = Input.mouseScrollDelta; if (!(((Vector2)(ref mouseScrollDelta)).sqrMagnitude > 0f)) { goto IL_0040; } } _permission.Activity(Time.unscaledTime); goto IL_0040; IL_0040: _lastMousePosition = mousePosition; } private static string T(string english, string portuguese) { if (!IsPortuguese()) { return english; } return portuguese; } private static bool IsPortuguese() { if (Localization.instance == null) { return false; } return Localization.instance.GetSelectedLanguage()?.StartsWith("Portuguese", StringComparison.OrdinalIgnoreCase) ?? false; } private void TogglePermission(ServerRules rules) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (!rules.Enabled || !rules.PermissionAllowed) { ((Character)Player.m_localPlayer).Message((MessageType)2, T("The server requires everyone in bed; there is nothing to toggle.", "O servidor exige todos na cama; não há o que alternar."), 0, (Sprite)null, false); return; } _permission.Manual = !_permission.Manual; string text = (_permission.Manual ? T("the others MAY sleep without you", "os outros PODEM dormir sem você") : T("the others must WAIT for you", "os outros devem ESPERAR por você")); ((Character)Player.m_localPlayer).Message((MessageType)2, string.Concat(T("Sleep: ", "Sono: "), text, T(". Press ", ". Aperte "), ToggleKey.Value, T(" to change.", " para mudar.")), 0, (Sprite)null, false); } private void ExpirePermissionAtDawn(ServerRules rules) { if (!((Object)(object)EnvMan.instance == (Object)null) && !((Object)(object)ZNet.instance == (Object)null) && !(DayLengthField == null)) { long num = (long)DayLengthField.GetValue(EnvMan.instance); if (num > 0) { _permission.ExpireAtDawn(rules, SleepCore.DawnIndex(ZNet.instance.GetTimeSeconds(), num)); } } } private void SyncPermission(ServerRules rules) { if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && (ZNet.instance.IsServer() || _receivedServerRules) && _permission.NeedsSend(rules, Time.unscaledTime, out var value)) { if (ZNet.instance.IsServer()) { SetPermitted(ZNet.GetUID(), value); return; } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Sono_SetPermit", new object[1] { value }); } } private void RegisterRpcIfNeeded() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && !object.ReferenceEquals(instance, _registeredRpc)) { instance.Register<bool>("Sono_SetPermit", (Action<long, bool>)RpcSetPermit); instance.Register("Sono_RequestRules", (Action<long>)RpcRequestRules); instance.Register<string>("Sono_SyncRules", (Action<long, string>)RpcReceiveRules); instance.Register<string>("Sono_SkipState", (Action<long, string>)RpcReceiveSkipState); instance.Register<string>("Sono_WaitingFor", (Action<long, string>)RpcReceiveWaitingFor); _registeredRpc = instance; _skipActive = false; _permission.Forget(); PermittedByPeer.Clear(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ReadRulesFromConfiguration(); BroadcastRules(); } else { _receivedServerRules = false; _nextRulesRequest = 0f; } } } private void RequestRulesWhenConnected() { if (!_receivedServerRules && ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.IsReady() && !(Time.unscaledTime < _nextRulesRequest)) { _nextRulesRequest = Time.unscaledTime + 3f; ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Sono_RequestRules", new object[0]); } } } private static void UpdateServerSkipState() { if (_skipActive && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && (!((Object)(object)EnvMan.instance != (Object)null) || !EnvMan.instance.IsTimeSkipping())) { _skipActive = false; BroadcastSkipState(0L); } } private static void RpcSetPermit(long sender, bool permit) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { SetPermitted(sender, permit); } } private static void SetPermitted(long peerId, bool permit) { PermittedByPeer[peerId] = permit; Log.LogInfo((object)("Sleep permission from " + peerId + ": " + (permit ? "may sleep without them" : "wait for them"))); } private static void RpcRequestRules(long sender) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "Sono_SyncRules", new object[1] { _rules.Serialize() }); if (_skipActive) { BroadcastSkipState(sender); } } } private static void BroadcastSkipState(long target) { if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { string text = (_skipActive ? "1" : "0") + "|" + ServerRules.D(_skipSpeed) + "|" + ServerRules.D(_skipEnd); ZRoutedRpc.instance.InvokeRoutedRPC(target, "Sono_SkipState", new object[1] { text }); } } private static bool IsFromServer(long sender) { long serverPeerId = GetServerPeerId(); if (serverPeerId != 0) { return sender == serverPeerId; } return true; } private static void RpcReceiveSkipState(long sender, string payload) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && IsFromServer(sender)) { string[] array = (payload ?? "").Split('|'); if (array.Length != 3 || !ServerRules.TryDouble(array[1], out var value) || !ServerRules.TryDouble(array[2], out var value2)) { Log.LogWarning((object)"Invalid time-skip packet; ignored."); return; } _skipActive = array[0] == "1"; _skipSpeed = Math.Max(1.0, Math.Min(240.0, value)); _skipEnd = value2; } } private static void RpcReceiveWaitingFor(long sender, string names) { if (IsFromServer(sender)) { _waitingFor = (string.IsNullOrEmpty(names) ? new string[0] : names.Split(new string[1] { ", " }, StringSplitOptions.RemoveEmptyEntries)); } } private void OnGUI() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Expected O, but got Unknown //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) if (_waitingFor.Length == 0) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Character)localPlayer).InBed() && !localPlayer.IsSleeping() && Rules.Enabled && Rules.ShowWaitingFor && (!((Object)(object)Hud.instance != (Object)null) || Hud.instance.m_rootObject.activeSelf)) { float num = (float)Screen.height / 1080f; int num2 = Mathf.Max(10, Mathf.RoundToInt(19f * num)); if (_panelStyle == null || _panelStyle.fontSize != num2) { GUIStyle val = new GUIStyle(GUI.skin.label); val.fontSize = num2; val.wordWrap = false; val.richText = true; val.alignment = (TextAnchor)1; _panelStyle = val; _panelStyle.normal.textColor = new Color(0.92f, 0.88f, 0.78f); _panelShadow = new GUIStyle(_panelStyle); _panelShadow.normal.textColor = new Color(0f, 0f, 0f, 0.85f); } string text = "<b>" + T("Waiting for:", "Esperando por:") + "</b>"; for (int i = 0; i < _waitingFor.Length; i++) { text = text + "\n" + _waitingFor[i]; } GUIContent val2 = new GUIContent(text); float num3 = 600f * num; float num4 = _panelStyle.CalcHeight(val2, num3); Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(((float)Screen.width - num3) * 0.5f, (float)Screen.height * 0.57f, num3, num4); GUI.Label(new Rect(((Rect)(ref val3)).x + 2f, ((Rect)(ref val3)).y + 2f, ((Rect)(ref val3)).width, ((Rect)(ref val3)).height), val2, _panelShadow); GUI.Label(val3, val2, _panelStyle); } } private static void TellSleepers(ServerRules rules, bool sleepTime) { string names; List<long> list = Notice.Step(Players, BlockerNames, rules.ShowWaitingFor && sleepTime, out names); if (list.Count != 0 && ZRoutedRpc.instance != null) { for (int i = 0; i < list.Count; i++) { ZRoutedRpc.instance.InvokeRoutedRPC(list[i], "Sono_WaitingFor", new object[1] { names }); } if (names.Length > 0) { Log.LogInfo((object)("Told " + list.Count + " player(s) in bed they are waiting for: " + names + ".")); } } } private static void BroadcastRules() { if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Sono_SyncRules", new object[1] { _rules.Serialize() }); } } private static void RpcReceiveRules(long sender, string payload) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } if (!IsFromServer(sender)) { Log.LogWarning((object)"Rules packet ignored: sender is not the server."); return; } if (!ServerRules.TryDeserialize(payload, out var rules)) { Log.LogWarning((object)"Invalid rules packet from the server; keeping the previous rules."); return; } bool flag = _receivedServerRules && _rules.Enabled != rules.Enabled; _rules = rules; _receivedServerRules = true; if (flag && (Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, T("Sono " + (rules.Enabled ? "ENABLED" : "DISABLED") + " for the whole server.", "Sono " + (rules.Enabled ? "LIGADO" : "DESLIGADO") + " para o servidor todo."), 0, (Sprite)null, false); } Log.LogInfo((object)"Sono rules received from the server."); } private static long GetServerPeerId() { if (ZRoutedRpc.instance == null || GetServerPeerIdMethod == null) { return 0L; } return (long)GetServerPeerIdMethod.Invoke(ZRoutedRpc.instance, null); } private static string WhyNotSleeping(ServerRules rules) { Players.Clear(); List<ZDO> allCharacterZDOS = ZNet.instance.GetAllCharacterZDOS(); for (int i = 0; i < allCharacterZDOS.Count; i++) { ZDO val = allCharacterZDOS[i]; Players.Add(new SleeperState(val.GetOwner(), val.GetBool(ZDOVars.s_inBed, false), val.GetString(ZDOVars.s_playerName, "?"), val.GetBool(GhostIgnoreSleepHash, false))); } bool flag = SleepCore.EveryoneReady(Players, rules.PermissionAllowed, rules.MaxAwake, PermittedByPeer, Blockers, SleeperNames, BlockerNames); double hour; bool flag2 = (TryGetClockHour(out hour) ? SleepCore.SleepAllowedAt(hour, rules.SleepFromHour) : EnvMan.IsNight()); TellSleepers(rules, flag2); if (!flag2) { return "too early (sleep from " + FormatHour(rules.SleepFromHour) + ")"; } if (!flag) { if (SleeperNames.Count != 0) { return "awake without permission or over MaxPlayersAwake: " + string.Join(", ", BlockerNames.ToArray()); } return "nobody in bed"; } return ""; } private static bool TryGetClockHour(out double hour) { hour = 0.0; if ((Object)(object)EnvMan.instance == (Object)null || (Object)(object)ZNet.instance == (Object)null || DayLengthField == null) { return false; } long num = (long)DayLengthField.GetValue(EnvMan.instance); if (num <= 0) { return false; } hour = SleepCore.ClockHour(ZNet.instance.GetTimeSeconds(), num); return true; } private static string FormatHour(double hour) { int num = (int)Math.Floor(hour * 60.0 + 0.5) % 1440; return (num / 60).ToString("00") + ":" + (num % 60).ToString("00"); } }