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 TimeControl v1.2.1
plugins/TimeControl.dll
Decompiled 2 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TimeControl")] [assembly: AssemblyDescription("Adjusts the Valheim day/night cycle length via a multiplier, with server-authoritative sync and an exact mod-version gate.")] [assembly: AssemblyProduct("TimeControl")] [assembly: AssemblyCompany("drummercraig")] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.2.1")] [assembly: AssemblyVersion("1.2.1.0")] namespace TimeControl; [BepInPlugin("drummercraig.time_control", "TimeControl", "1.2.1")] public class TimeControlPlugin : BaseUnityPlugin { public const string PluginGuid = "drummercraig.time_control"; public const string PluginName = "TimeControl"; public const string PluginVersion = "1.2.1"; private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown TimeControlConfig.Log = ((BaseUnityPlugin)this).Logger; TimeControlConfig.Bind(((BaseUnityPlugin)this).Config); ResilientPatcher.ApplyAll(new Harmony("drummercraig.time_control"), Assembly.GetExecutingAssembly(), ((BaseUnityPlugin)this).Logger); ((Component)this).gameObject.AddComponent<ClockHud>(); ((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("[TimeControl] v{0} loaded. DayLengthMultiplier={1:0.###} ", "1.2.1", TimeControlConfig.DayLengthMultiplier.Value) + "(local config; server value takes over when connected to a server).")); } } public enum ClockTextColor { White, Yellow, Orange, Red, Green, Cyan, Blue, Pink, Black, Gray } public static class TimeControlConfig { public static ManualLogSource Log; public static ConfigEntry<float> DayLengthMultiplier; public const float MinMultiplier = 0.1f; public const float MaxMultiplier = 100f; public static ConfigEntry<bool> ShowClock; public static ConfigEntry<ClockTextColor> ClockColor; public static ConfigEntry<string> ClockColorHex; public static ConfigEntry<int> ClockFontSize; public static ConfigEntry<FontStyle> ClockFontStyle; public static ConfigEntry<string> ClockFont; public const string DefaultFontName = "Default"; public static float ServerMultiplier = -1f; private static readonly string[] FontChoices = new string[7] { "Default", "Arial", "Verdana", "Tahoma", "Georgia", "Times New Roman", "Courier New" }; public static float EffectiveMultiplier => Mathf.Clamp((ServerMultiplier >= 0f) ? ServerMultiplier : DayLengthMultiplier.Value, 0.1f, 100f); public static void Bind(ConfigFile config) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown DayLengthMultiplier = config.Bind<float>("General", "DayLengthMultiplier", 4f, new ConfigDescription("Multiplier applied to the in-game day/night cycle length. 1.0 = vanilla (a full day is ~30 real minutes). 2.0 = days take twice as long; 4.0 = ~2 real hours per full day. Day and night both scale together (their ratio is fixed by Valheim and is preserved). When you join a server running TimeControl, the server's value is used instead of this one for that session, so everyone shares the same clock. Range: " + 0.1f + " to " + 100f + ".", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>())); DayLengthMultiplier.SettingChanged += delegate { DayLength.ApplyEffectiveMultiplier(); }; ShowClock = config.Bind<bool>("General", "ShowClock", true, "Show an on-screen clock in the top-center of the screen that displays the current time of day as a word (Midnight, Night, Dawn, Morning, Midday, Afternoon, Dusk). Display-only and per-client; does not affect the day/night cycle."); ClockColor = config.Bind<ClockTextColor>("Clock Appearance", "TextColor", ClockTextColor.White, "Color of the on-screen clock text. Ignored when TextColorHex below is set to a valid hex color."); ClockColorHex = config.Bind<string>("Clock Appearance", "TextColorHex", "", "Optional custom clock text color as a hex string (e.g. #FF8800). When set to a valid value this overrides TextColor above. Leave blank to use the TextColor palette."); ClockFontSize = config.Bind<int>("Clock Appearance", "FontSize", 16, new ConfigDescription("Font size of the on-screen clock text.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[7] { 12, 14, 16, 18, 20, 24, 28 }), Array.Empty<object>())); ClockFontStyle = config.Bind<FontStyle>("Clock Appearance", "FontStyle", (FontStyle)1, "Style of the on-screen clock text: Normal, Bold, Italic, or BoldAndItalic."); ClockFont = config.Bind<string>("Clock Appearance", "Font", "Default", new ConfigDescription("Font family for the on-screen clock. \"Default\" uses the built-in game UI font. The other entries are common fonts that exist on most systems; if a chosen font is not installed on your machine, the clock falls back to the default font.", (AcceptableValueBase)(object)new AcceptableValueList<string>(FontChoices), Array.Empty<object>())); } } internal class ClockHud : MonoBehaviour { private static readonly float[] Thresholds = new float[9] { 0.03f, 0.2f, 0.3f, 0.45f, 0.55f, 0.7f, 0.8f, 0.97f, 1.01f }; private static readonly string[] Words = new string[9] { "Midnight", "Night", "Dawn", "Morning", "Midday", "Afternoon", "Dusk", "Night", "Midnight" }; private const float LabelWidth = 300f; private const float LabelTop = 12f; private const float LabelHeight = 30f; private GUIStyle _style; private int _lastBucket = -1; private string _cachedText = ""; private bool _errorLogged; private Color _resolvedColor = Color.white; private Font _customFont; private ClockTextColor _lastColor = (ClockTextColor)(-1); private string _lastHex; private int _lastSize = -1; private FontStyle _lastStyle = (FontStyle)(-1); private string _lastFont; private void OnGUI() { //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) try { if (!TimeControlConfig.ShowClock.Value) { return; } EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null) { return; } if (_style == null) { BuildStyle(); } if (AppearanceChanged()) { ApplyAppearance(); } int num = BucketFor(instance.GetDayFraction()); if (num != _lastBucket) { _lastBucket = num; _cachedText = Words[num]; } Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - 300f) * 0.5f, 12f, 300f, 30f); Color color = GUI.color; try { GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), _cachedText, _style); GUI.color = _resolvedColor; GUI.Label(val, _cachedText, _style); } finally { GUI.color = color; } } catch (Exception arg) { if (!_errorLogged) { _errorLogged = true; ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogError((object)$"[TimeControl] ClockHud.OnGUI failed (further errors suppressed): {arg}"); } } } } private static int BucketFor(float fraction) { float num = Mathf.Clamp(fraction, 0f, 0.99999f); for (int i = 0; i < Thresholds.Length; i++) { if (num < Thresholds[i]) { return i; } } return Words.Length - 1; } private void BuildStyle() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) _style = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)4 }; _style.normal.textColor = Color.white; } private bool AppearanceChanged() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) ClockTextColor value = TimeControlConfig.ClockColor.Value; string value2 = TimeControlConfig.ClockColorHex.Value; int value3 = TimeControlConfig.ClockFontSize.Value; FontStyle value4 = TimeControlConfig.ClockFontStyle.Value; string value5 = TimeControlConfig.ClockFont.Value; if (value == _lastColor && value2 == _lastHex && value3 == _lastSize && value4 == _lastStyle && value5 == _lastFont) { return false; } _lastColor = value; _lastHex = value2; _lastSize = value3; _lastStyle = value4; _lastFont = value5; return true; } private void ApplyAppearance() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0024: Unknown result type (might be due to invalid IL or missing references) string value = TimeControlConfig.ClockColorHex.Value; Color resolvedColor = default(Color); if (!string.IsNullOrEmpty(value) && ColorUtility.TryParseHtmlString(value.Trim(), ref resolvedColor)) { _resolvedColor = resolvedColor; } else { _resolvedColor = MapColor(TimeControlConfig.ClockColor.Value); } int value2 = TimeControlConfig.ClockFontSize.Value; _style.fontSize = value2; _style.fontStyle = TimeControlConfig.ClockFontStyle.Value; if ((Object)(object)_customFont != (Object)null) { Object.Destroy((Object)(object)_customFont); _customFont = null; } string value3 = TimeControlConfig.ClockFont.Value; if (string.IsNullOrEmpty(value3) || value3 == "Default") { _style.font = null; return; } Font val = Font.CreateDynamicFontFromOSFont(value3, value2); if ((Object)(object)val == (Object)null) { ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogWarning((object)("[TimeControl] Clock font '" + value3 + "' unavailable on this machine; using default.")); } _style.font = null; } else { _customFont = val; _style.font = val; } } private static Color MapColor(ClockTextColor c) { //IL_002e: 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) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) return (Color)(c switch { ClockTextColor.Yellow => Color.yellow, ClockTextColor.Orange => new Color(1f, 0.55f, 0f), ClockTextColor.Red => Color.red, ClockTextColor.Green => Color.green, ClockTextColor.Cyan => Color.cyan, ClockTextColor.Blue => new Color(0.3f, 0.5f, 1f), ClockTextColor.Pink => new Color(1f, 0.45f, 0.75f), ClockTextColor.Black => Color.black, ClockTextColor.Gray => Color.gray, _ => Color.white, }); } } public static class DayLength { [HarmonyPatch(typeof(EnvMan), "Awake")] private static class EnvManAwakePatch { private static void Postfix() { ApplyEffectiveMultiplier(); } } private static long _vanillaBaseSec = -1L; public static bool HasBase => _vanillaBaseSec > 0; public static long VanillaBaseSec => _vanillaBaseSec; public static void ApplyEffectiveMultiplier() { try { EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null) { return; } if (_vanillaBaseSec < 0) { _vanillaBaseSec = instance.m_dayLengthSec; } float effectiveMultiplier = TimeControlConfig.EffectiveMultiplier; long num = (long)((float)_vanillaBaseSec * effectiveMultiplier); if (num < 1) { num = 1L; } if (instance.m_dayLengthSec != num) { instance.m_dayLengthSec = num; ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogInfo((object)($"[TimeControl] Day length set to {num}s " + $"(vanilla base {_vanillaBaseSec}s x {effectiveMultiplier:0.###}).")); } } } catch (Exception arg) { ManualLogSource log2 = TimeControlConfig.Log; if (log2 != null) { log2.LogError((object)$"[TimeControl] ApplyEffectiveMultiplier failed: {arg}"); } } } } public static class NetworkSync { [HarmonyPatch(typeof(ZNet), "OnNewConnection", new Type[] { typeof(ZNetPeer) })] private static class OnNewConnectionPatch { private static void Postfix(ZNetPeer peer, ZNet __instance) { try { if (__instance.IsServer()) { peer.m_rpc.Register<string>("TimeControl_Version", (Action<ZRpc, string>)OnServerReceiveClientVersion); peer.m_rpc.Invoke("TimeControl_Version", new object[1] { "1.2.1" }); } else { _serverSentVersion = false; peer.m_rpc.Register<string>("TimeControl_Version", (Action<ZRpc, string>)OnClientReceiveServerVersion); peer.m_rpc.Register<float>("TimeControl_DayLength", (Action<ZRpc, float>)OnClientReceiveDayLength); peer.m_rpc.Invoke("TimeControl_Version", new object[1] { "1.2.1" }); } } catch (Exception arg) { ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogError((object)$"[TimeControl] OnNewConnection handling failed: {arg}"); } } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class RpcPeerInfoPatch { private static bool Prefix(ZRpc rpc, ZNet __instance) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Invalid comparison between Unknown and I4 try { if (__instance.IsServer()) { if (!_validatedPeers.Contains(rpc)) { ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogWarning((object)"[TimeControl] Disconnecting client without a matching TimeControl version."); } rpc.Invoke("Error", new object[1] { 3 }); return false; } SendDayLengthToPeer(rpc); return true; } if (!_serverSentVersion) { ManualLogSource log2 = TimeControlConfig.Log; if (log2 != null) { log2.LogWarning((object)"[TimeControl] Server is not running TimeControl. Disconnecting (incompatible version)."); } if ((int)ZNet.GetConnectionStatus() != 3) { SetClientConnectionError((ConnectionStatus)3); } if ((Object)(object)Game.instance != (Object)null) { Game.instance.Logout(true, true); } return false; } return true; } catch (Exception arg) { ManualLogSource log3 = TimeControlConfig.Log; if (log3 != null) { log3.LogError((object)$"[TimeControl] RPC_PeerInfo gate failed, allowing vanilla handshake: {arg}"); } return true; } } private static void SendDayLengthToPeer(ZRpc rpc) { try { rpc.Invoke("TimeControl_DayLength", new object[1] { TimeControlConfig.EffectiveMultiplier }); } catch (Exception ex) { ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogWarning((object)("[TimeControl] Failed to push day length: " + ex.Message)); } } } } private const string RpcVersion = "TimeControl_Version"; private const string RpcDayLength = "TimeControl_DayLength"; private static readonly HashSet<ZRpc> _validatedPeers = new HashSet<ZRpc>(); private static bool _serverSentVersion; private static void OnServerReceiveClientVersion(ZRpc rpc, string clientVersion) { try { if (string.Equals(clientVersion, "1.2.1", StringComparison.Ordinal)) { _validatedPeers.Add(rpc); ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogInfo((object)("[TimeControl] Client presented matching version " + clientVersion + ".")); } } else { ManualLogSource log2 = TimeControlConfig.Log; if (log2 != null) { log2.LogWarning((object)("[TimeControl] Client version '" + clientVersion + "' != server '1.2.1'. Will be disconnected at peer-info handshake.")); } } } catch (Exception arg) { ManualLogSource log3 = TimeControlConfig.Log; if (log3 != null) { log3.LogError((object)$"[TimeControl] OnServerReceiveClientVersion failed: {arg}"); } } } private static void OnClientReceiveServerVersion(ZRpc rpc, string serverVersion) { try { _serverSentVersion = true; if (!string.Equals(serverVersion, "1.2.1", StringComparison.Ordinal)) { ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogWarning((object)("[TimeControl] Server version '" + serverVersion + "' != client '1.2.1'. Disconnecting (incompatible version).")); } } else { ManualLogSource log2 = TimeControlConfig.Log; if (log2 != null) { log2.LogInfo((object)("[TimeControl] Server version " + serverVersion + " matches.")); } } } catch (Exception arg) { ManualLogSource log3 = TimeControlConfig.Log; if (log3 != null) { log3.LogError((object)$"[TimeControl] OnClientReceiveServerVersion failed: {arg}"); } } } private static void OnClientReceiveDayLength(ZRpc rpc, float serverMultiplier) { try { TimeControlConfig.ServerMultiplier = serverMultiplier; ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogInfo((object)$"[TimeControl] Adopting server day-length multiplier {serverMultiplier:0.###} for this session."); } DayLength.ApplyEffectiveMultiplier(); } catch (Exception arg) { ManualLogSource log2 = TimeControlConfig.Log; if (log2 != null) { log2.LogError((object)$"[TimeControl] OnClientReceiveDayLength failed: {arg}"); } } } private static void SetClientConnectionError(ConnectionStatus status) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) AccessTools.Field(typeof(ZNet), "m_connectionStatus")?.SetValue(null, status); } } internal static class ResilientPatcher { internal static void ApplyAll(Harmony harmony, Assembly assembly, ManualLogSource log) { if (harmony == null || assembly == null) { return; } int num = 0; int num2 = 0; List<string> list = null; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly); foreach (Type type in typesFromAssembly) { try { List<MethodInfo> list2 = harmony.CreateClassProcessor(type).Patch(); if (list2 != null && list2.Count > 0) { num++; if (log != null) { log.LogDebug((object)$"[TimeControl] patched {type.Name} ({list2.Count} method(s))."); } } } catch (Exception ex) { num2++; (list ?? (list = new List<string>())).Add(type.Name); if (log != null) { log.LogWarning((object)("[TimeControl] SKIP patch class " + type.Name + ": " + ex.Message)); } } } if (num2 == 0) { if (log != null) { log.LogInfo((object)$"[TimeControl] patches: {num} applied, 0 skipped."); } } else if (log != null) { log.LogWarning((object)(string.Format("[TimeControl] patches: {0} applied, {1} skipped ({2}). ", num, num2, string.Join(", ", list)) + "A skipped patch usually means a Valheim update changed a hooked method; that feature is disabled but the mod is still running.")); } } }