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 LiveComfort v1.0.0
LiveComfort.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; 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: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("LiveComfort")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Confort, abri et effet En forme recalcules en temps reel.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0ebe48a0d3cb31344ad9cc189d2f30b6aca504c1")] [assembly: AssemblyProduct("Live Comfort")] [assembly: AssemblyTitle("LiveComfort")] [assembly: AssemblyVersion("1.0.0.0")] namespace LiveComfort { internal static class CoverScheduler { private static Player _player; private static Vector3 _lastCoverPos; private static bool _primed; private static bool _lastDecisionWasMoving; private static float _comfortTimer; private static int _coverCount; private static int _comfortCount; private static long _comfortTicks; private static float _statsTimer; private static readonly Stopwatch Watch = new Stopwatch(); private static void EnsurePlayer(Player p) { //IL_001c: 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) if (!_primed || _player != p) { _player = p; _lastCoverPos = ((Component)p).transform.position; _primed = true; _lastDecisionWasMoving = true; _comfortTimer = 0f; } } internal static float GetCoverInterval(Player player) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || ModConfig.Enabled == null || !ModConfig.Enabled.Value) { _lastDecisionWasMoving = false; return 1f; } EnsurePlayer(player); switch (ModConfig.Mode.Value) { case ReactivityMode.EveryFrame: _lastDecisionWasMoving = true; return 0f; case ReactivityMode.FixedInterval: _lastDecisionWasMoving = true; return Mathf.Max(0f, ModConfig.FixedCoverInterval.Value); default: { Vector3 val = ((Component)player).transform.position - _lastCoverPos; float value = ModConfig.MoveThreshold.Value; if (!(_lastDecisionWasMoving = ((Vector3)(ref val)).sqrMagnitude >= value * value)) { return Mathf.Max(0.05f, ModConfig.IdleCoverInterval.Value); } return Mathf.Max(0f, ModConfig.MovingCoverInterval.Value); } } } internal static void OnCoverRecomputed(Player player) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) EnsurePlayer(player); _lastCoverPos = ((Component)player).transform.position; _coverCount++; } internal static bool ShouldRecomputeComfort(float dt) { if (!ModConfig.LiveComfortLevel.Value) { return false; } _comfortTimer += dt; float num = (_lastDecisionWasMoving ? Mathf.Max(0f, ModConfig.MovingComfortInterval.Value) : ModConfig.IdleComfortInterval.Value); if (!_lastDecisionWasMoving && num <= 0f) { return false; } if (_comfortTimer < num) { return false; } _comfortTimer = 0f; return true; } internal static void BeginComfort() { if (ModConfig.DebugStatsInterval.Value > 0f) { Watch.Restart(); } } internal static void EndComfort() { if (!(ModConfig.DebugStatsInterval.Value <= 0f)) { Watch.Stop(); _comfortTicks += Watch.ElapsedTicks; _comfortCount++; } } internal static void TickStats(float dt) { float value = ModConfig.DebugStatsInterval.Value; if (value <= 0f) { return; } _statsTimer += dt; if (!(_statsTimer < value)) { double num = ((_comfortCount > 0) ? ((double)_comfortTicks * 1000000.0 / (double)Stopwatch.Frequency / (double)_comfortCount) : 0.0); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[stats] abri {(float)_coverCount / _statsTimer:0.0}/s, confort {(float)_comfortCount / _statsTimer:0.0}/s, cout confort moyen {num:0.0} us"); } _statsTimer = 0f; _coverCount = 0; _comfortCount = 0; _comfortTicks = 0L; } } } public enum ReactivityMode { Adaptive, FixedInterval, EveryFrame } internal static class ModConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<ReactivityMode> Mode; internal static ConfigEntry<float> MovingCoverInterval; internal static ConfigEntry<float> IdleCoverInterval; internal static ConfigEntry<float> FixedCoverInterval; internal static ConfigEntry<float> MoveThreshold; internal static ConfigEntry<bool> LiveComfortLevel; internal static ConfigEntry<float> MovingComfortInterval; internal static ConfigEntry<float> IdleComfortInterval; internal static ConfigEntry<bool> InstantRested; internal static ConfigEntry<float> CozyDelay; internal static ConfigEntry<float> ShelterLossGrace; internal static ConfigEntry<bool> GradualFill; internal static ConfigEntry<float> FillSpeed; internal static ConfigEntry<bool> CapOnComfortDrop; internal static ConfigEntry<bool> HideSeconds; internal static ConfigEntry<bool> DebugLogging; internal static ConfigEntry<float> DebugStatsInterval; internal static void Init(ConfigFile cfg) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Expected O, but got Unknown //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Expected O, but got Unknown //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Expected O, but got Unknown //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Expected O, but got Unknown Enabled = cfg.Bind<bool>("1 - General", "Enabled", true, "Enables every change made by the mod. Unchecking restores the base game behaviour without restarting."); Mode = cfg.Bind<ReactivityMode>("2 - Shelter", "Mode", ReactivityMode.Adaptive, "Adaptive: high frequency only while the player moves, base game rate when standing still. FixedInterval: constant interval no matter what. EveryFrame: recomputed every physics frame (~50 Hz), the most reactive and the most expensive."); MovingCoverInterval = cfg.Bind<float>("2 - Shelter", "MovingInterval", 0.05f, new ConfigDescription("Minimum seconds between two shelter checks while the player moves. 0 = every physics frame (~50 Hz). This check allocates no memory.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); IdleCoverInterval = cfg.Bind<float>("2 - Shelter", "IdleInterval", 1f, new ConfigDescription("Seconds between two shelter checks while the player stands still. 1.0 = exactly the base game. Raising it to 2 or 3 makes the mod cheaper than the base game when standing still, at the cost of a slower detection if a roof is built above you.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), Array.Empty<object>())); FixedCoverInterval = cfg.Bind<float>("2 - Shelter", "FixedInterval", 0.1f, new ConfigDescription("Used only in FixedInterval mode. 0.1 = 10 Hz.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); MoveThreshold = cfg.Bind<float>("2 - Shelter", "MoveThreshold", 0.1f, new ConfigDescription("Distance in metres travelled since the last check beyond which the player is considered moving (Adaptive mode).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 5f), Array.Empty<object>())); LiveComfortLevel = cfg.Bind<bool>("3 - Comfort", "LiveComfortLevel", true, "Recomputes the comfort level outside the base game's 2 second cycle."); MovingComfortInterval = cfg.Bind<float>("3 - Comfort", "MovingInterval", 0.1f, new ConfigDescription("Minimum seconds between two comfort recomputations while the player moves. 0.1 = 10 Hz. Do not go below 0.05: the game's furniture sort boxes an enum on every comparison, which puts the garbage collector under pressure.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); IdleComfortInterval = cfg.Bind<float>("3 - Comfort", "IdleInterval", 0.25f, new ConfigDescription("Seconds between two comfort recomputations while the player stands still. Keeps placing a piece of furniture reactive even when not moving. 0 = no recomputation, left entirely to the base game, which already does it every 2 seconds: the cheapest setting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); InstantRested = cfg.Bind<bool>("4 - Rested", "InstantRested", true, "Removes the 10 second delay of SE_Cozy before the Rested effect is granted."); CozyDelay = cfg.Bind<float>("4 - Rested", "RestedDelay", 0f, new ConfigDescription("Seconds to spend in comfort before getting the Rested effect. The base game uses 10. 0 = immediate (on the next frame).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>())); ShelterLossGrace = cfg.Bind<float>("4 - Rested", "ShelterLossGrace", 0.5f, new ConfigDescription("Grace time before the player is considered out of shelter. Prevents the Resting effect from flickering while standing on a doorstep, which would repeat its message and instantiate its effect every frame. Also affects wind, cloth and indoor audio, which read the same InShelter(). 0 = disables this fix.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); GradualFill = cfg.Bind<bool>("5 - Timer", "GradualFill", true, "Fills the Rested timer gradually instead of making it jump to its target value. Unchecking restores the base game behaviour."); FillSpeed = cfg.Bind<float>("5 - Timer", "FillSpeed", 1f, new ConfigDescription("Fill speed multiplier. 1.0 = a full fill, from zero to the target value, takes exactly the base game's waiting time (10 s). 2.0 = twice as fast.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 20f), Array.Empty<object>())); CapOnComfortDrop = cfg.Bind<bool>("5 - Timer", "CapOnComfortDrop", true, "When comfort drops, the timer runs down naturally to the new target, then stays there. Unchecking keeps the best value reached, like the base game."); HideSeconds = cfg.Bind<bool>("5 - Timer", "HideSeconds", false, "Shows the Rested timer in minutes only, rounded up to the next minute (13m instead of 12:34). Unchecked, the timer keeps the base game display."); DebugLogging = cfg.Bind<bool>("9 - Debug", "VerboseLogging", false, "Logs every comfort level change. Used to measure latency."); DebugStatsInterval = cfg.Bind<float>("9 - Debug", "StatsInterval", 0f, new ConfigDescription("Seconds between two performance summaries in the log (recomputations per second, average cost of the comfort computation). 0 = disabled.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>())); } } [BepInPlugin("grogui.LiveComfort", "Live Comfort", "1.0.0")] [BepInProcess("valheim.exe")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "grogui.LiveComfort"; public const string PluginName = "Live Comfort"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; private readonly Harmony _harmony = new Harmony("grogui.LiveComfort"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; ModConfig.Init(((BaseUnityPlugin)this).Config); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)"Live Comfort v1.0.0 charge."); } private void OnDestroy() { _harmony.UnpatchSelf(); } internal static bool IsLocalPlayer(Player p) { if ((Object)(object)p == (Object)null) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } return p == localPlayer; } } } namespace LiveComfort.Patches { [HarmonyPatch(typeof(Player), "InShelter")] internal static class Player_InShelter_Patch { private static Player _player; private static float _lastTrueAt = -999f; [HarmonyPostfix] private static void Postfix(Player __instance, ref bool __result) { if (!ModConfig.Enabled.Value) { return; } float value = ModConfig.ShelterLossGrace.Value; if (!(value <= 0f) && Plugin.IsLocalPlayer(__instance)) { if (_player != __instance) { _player = __instance; _lastTrueAt = -999f; } if (__result) { _lastTrueAt = Time.time; } else if (Time.time - _lastTrueAt < value) { __result = true; } } } } [HarmonyPatch(typeof(Player), "UpdateCover")] internal static class Player_UpdateCover_Patch { private static readonly MethodInfo IntervalGetter = AccessTools.Method(typeof(CoverScheduler), "GetCoverInterval", (Type[])null, (Type[])null); [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); for (int i = 0; i < list.Count; i++) { if (!(list[i].opcode != OpCodes.Ldc_R4) && list[i].operand is float num && num == 1f) { list[i].opcode = OpCodes.Ldarg_0; list[i].operand = null; list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)IntervalGetter)); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Player.UpdateCover : seuil d'abri rendu configurable."); } return list; } } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)"Player.UpdateCover : constante 1f introuvable, le seuil reste a 1 s. Le jeu a probablement ete mis a jour ; le reste du mod continue de fonctionner."); } return list; } [HarmonyPostfix] private static void Postfix(Player __instance, float dt, float ___m_updateCoverTimer, ref int ___m_comfortLevel) { if (!ModConfig.Enabled.Value || !Plugin.IsLocalPlayer(__instance)) { return; } if (___m_updateCoverTimer == 0f) { CoverScheduler.OnCoverRecomputed(__instance); } CoverScheduler.TickStats(dt); if (!CoverScheduler.ShouldRecomputeComfort(dt)) { return; } CoverScheduler.BeginComfort(); int num = SE_Rested.CalculateComfortLevel(__instance); CoverScheduler.EndComfort(); if (ModConfig.DebugLogging.Value && num != ___m_comfortLevel) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"confort {___m_comfortLevel} -> {num} | abri={__instance.InShelter()}"); } } ___m_comfortLevel = num; } } [HarmonyPatch(typeof(SE_Cozy), "UpdateStatusEffect")] internal static class SE_Cozy_Patch { internal static float VanillaDelay = 10f; private static bool _captured; [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(SE_Cozy __instance) { if (!_captured) { if (__instance.m_delay > 0f) { VanillaDelay = __instance.m_delay; } _captured = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("SE_Cozy : delai du jeu de base = " + VanillaDelay + " s (sert aussi de duree de remplissage du minuteur).")); } } float num = ((ModConfig.Enabled.Value && ModConfig.InstantRested.Value) ? Mathf.Max(0f, ModConfig.CozyDelay.Value) : VanillaDelay); if (__instance.m_delay != num) { __instance.m_delay = num; } } } internal static class RestedTimer { internal static bool ShouldManage(StatusEffect se) { if (!ModConfig.Enabled.Value || !ModConfig.GradualFill.Value) { return false; } Character character = se.m_character; Player val = (Player)(object)((character is Player) ? character : null); if (!Plugin.IsLocalPlayer(val)) { return false; } return ((Character)val).GetSEMan().HaveStatusEffect(SEMan.s_statusEffectResting); } } [HarmonyPatch(typeof(SE_Rested), "UpdateTTL")] internal static class SE_Rested_UpdateTTL_Patch { [HarmonyPrefix] private static bool Prefix(SE_Rested __instance) { return !RestedTimer.ShouldManage((StatusEffect)(object)__instance); } } [HarmonyPatch(typeof(SE_Rested), "Setup")] internal static class SE_Rested_Setup_Patch { [HarmonyPostfix] private static void Postfix(SE_Rested __instance, ref float ___m_time) { if (RestedTimer.ShouldManage((StatusEffect)(object)__instance)) { ((StatusEffect)__instance).m_ttl = 1f; ___m_time = 0f; } } } [HarmonyPatch(typeof(SE_Rested), "UpdateStatusEffect")] internal static class SE_Rested_UpdateStatusEffect_Patch { [HarmonyPostfix] private static void Postfix(SE_Rested __instance, float dt, ref float ___m_time) { if (!RestedTimer.ShouldManage((StatusEffect)(object)__instance)) { return; } Character character = ((StatusEffect)__instance).m_character; Player val = (Player)(object)((character is Player) ? character : null); if (!((Object)(object)val == (Object)null)) { int num = Mathf.Max(1, val.GetComfortLevel()); float num2 = __instance.m_baseTTL + (float)(num - 1) * __instance.m_TTLPerComfortLevel; float num3 = ((StatusEffect)__instance).m_ttl - ___m_time; if (num3 < num2) { float num4 = Mathf.Max(0.05f, SE_Cozy_Patch.VanillaDelay / ModConfig.FillSpeed.Value); float num5 = num2 / num4; num3 = Mathf.Min(num2, num3 + (num5 + 1f) * dt); } else if (!ModConfig.CapOnComfortDrop.Value) { return; } ((StatusEffect)__instance).m_ttl = num3; ___m_time = 0f; } } } [HarmonyPatch(typeof(StatusEffect), "GetIconText")] internal static class StatusEffect_GetIconText_Patch { [HarmonyPostfix] private static void Postfix(StatusEffect __instance, ref string __result) { if (__instance is SE_Rested && ModConfig.Enabled.Value && ModConfig.HideSeconds.Value && !string.IsNullOrEmpty(__result) && Plugin.IsLocalPlayer((Player)/*isinst with value type is only supported in some contexts*/)) { __result = Mathf.CeilToInt(__instance.GetRemaningTime() / 60f) + "m"; } } } }