using 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.0.0")] [assembly: AssemblyVersion("1.0.0.0")] namespace TimeControl; [BepInPlugin("drummercraig.time_control", "TimeControl", "1.0.0")] public class TimeControlPlugin : BaseUnityPlugin { public const string PluginGuid = "drummercraig.time_control"; public const string PluginName = "TimeControl"; public const string PluginVersion = "1.0.0"; private void Awake() { TimeControlConfig.Log = ((BaseUnityPlugin)this).Logger; TimeControlConfig.Bind(((BaseUnityPlugin)this).Config); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("[TimeControl] v{0} loaded. DayLengthMultiplier={1:0.###} ", "1.0.0", TimeControlConfig.DayLengthMultiplier.Value) + "(local config; server value takes over when connected to a server).")); } } public static class TimeControlConfig { public static ManualLogSource Log; public static ConfigEntry DayLengthMultiplier; public const float MinMultiplier = 0.1f; public const float MaxMultiplier = 100f; public static float ServerMultiplier = -1f; 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 DayLengthMultiplier = config.Bind("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(0.1f, 100f), Array.Empty())); DayLengthMultiplier.SettingChanged += delegate { DayLength.ApplyEffectiveMultiplier(); }; } } 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() { 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.###}).")); } } } } public static class NetworkSync { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class OnNewConnectionPatch { private static void Postfix(ZNetPeer peer, ZNet __instance) { if (__instance.IsServer()) { peer.m_rpc.Register("TimeControl_Version", (Action)OnServerReceiveClientVersion); peer.m_rpc.Invoke("TimeControl_Version", new object[1] { "1.0.0" }); } else { _serverSentVersion = false; peer.m_rpc.Register("TimeControl_Version", (Action)OnClientReceiveServerVersion); peer.m_rpc.Register("TimeControl_DayLength", (Action)OnClientReceiveDayLength); peer.m_rpc.Invoke("TimeControl_Version", new object[1] { "1.0.0" }); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class RpcPeerInfoPatch { private static bool Prefix(ZRpc rpc, ZNet __instance) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 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; } 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 _validatedPeers = new HashSet(); private static bool _serverSentVersion; private static void OnServerReceiveClientVersion(ZRpc rpc, string clientVersion) { if (string.Equals(clientVersion, "1.0.0", 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.0.0'. Will be disconnected at peer-info handshake.")); } } } private static void OnClientReceiveServerVersion(ZRpc rpc, string serverVersion) { _serverSentVersion = true; if (!string.Equals(serverVersion, "1.0.0", StringComparison.Ordinal)) { ManualLogSource log = TimeControlConfig.Log; if (log != null) { log.LogWarning((object)("[TimeControl] Server version '" + serverVersion + "' != client '1.0.0'. Disconnecting (incompatible version).")); } } else { ManualLogSource log2 = TimeControlConfig.Log; if (log2 != null) { log2.LogInfo((object)("[TimeControl] Server version " + serverVersion + " matches.")); } } } private static void OnClientReceiveDayLength(ZRpc rpc, float serverMultiplier) { 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(); } 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); } }