using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Landoria.SharedLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Landoria.DecayControl")] [assembly: AssemblyDescription("Controls rain wear and fuel consumption for player-built pieces.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Landoria")] [assembly: AssemblyProduct("Landoria.DecayControl")] [assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("0F691AC2-F9E1-45CB-B34E-7B57171ADCE7")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")] [assembly: AssemblyVersion("1.0.0.40036")] namespace Landoria.DecayControl { internal static class CreatorActivityPolicy { internal static HashSet GetActiveCreators(IEnumerable onlinePlayers) { return new HashSet(onlinePlayers); } internal static bool IsCreatorActive(long creator, ISet onlinePlayers) { return onlinePlayers.Contains(creator); } } internal static class DecayControlArgumentPolicy { internal static DecayControlMode Resolve(string[] arguments, string name, DecayControlMode configured) { string text = ReadValue(arguments, name); if (text == null) { return configured; } if (string.Equals(text, "default", StringComparison.OrdinalIgnoreCase)) { return DecayControlMode.Default; } if (string.Equals(text, "player-online", StringComparison.OrdinalIgnoreCase)) { return DecayControlMode.PlayerOnline; } if (string.Equals(text, "disabled", StringComparison.OrdinalIgnoreCase)) { return DecayControlMode.Disabled; } throw new InvalidOperationException("Command-line switch " + name + " requires default, player-online, or disabled."); } private static string ReadValue(string[] arguments, string name) { string text = null; for (int i = 0; i < arguments.Length; i++) { if (string.Equals(arguments[i], name, StringComparison.OrdinalIgnoreCase)) { if (text != null || i + 1 >= arguments.Length) { throw new InvalidOperationException("Command-line switch " + name + " is missing or duplicated."); } text = arguments[++i]; } } return text; } } internal enum DecayControlMode { Default, PlayerOnline, Disabled } internal static class DecayEffectPolicy { internal static bool ShouldApplyEnvironmentalWear(bool isVanillaRainTick, bool isPlayerBuilt, DecayControlMode mode, float activityMultiplier) { if (isVanillaRainTick && isPlayerBuilt) { switch (mode) { case DecayControlMode.Default: break; case DecayControlMode.PlayerOnline: return activityMultiplier > 0f; default: return false; } } return true; } internal static bool ShouldPauseFuel(bool isPlayerBuilt, bool firstUpdate, DecayControlMode mode, float activityMultiplier) { if (!isPlayerBuilt || mode == DecayControlMode.Default) { return false; } if (!(mode == DecayControlMode.Disabled || firstUpdate)) { return activityMultiplier <= 0f; } return true; } } internal static class DecayProtection { [HarmonyPatch(typeof(WearNTear), "ApplyDamage")] private static class RainDamagePatch { private static bool Prefix(WearNTear __instance, float damage, HitData hitData) { float num = __instance.m_health * 0.05f; bool isVanillaRainTick = hitData == null && __instance.IsWet() && __instance.GetHealthPercentage() > 0.5f && Mathf.Approximately(damage, num); Piece component = ((Component)__instance).GetComponent(); return DecayEffectPolicy.ShouldApplyEnvironmentalWear(isPlayerBuilt: (Object)(object)component != (Object)null && component.IsPlacedByPlayer(), activityMultiplier: GetActivityMultiplier(component), isVanillaRainTick: isVanillaRainTick, mode: DecayControlPlugin.Settings.EnvironmentalBuildingWear); } } [HarmonyPatch(typeof(Fireplace), "UpdateFireplace")] private static class FireplaceFuelPatch { private static void Prefix(Fireplace __instance, out float __state) { __state = __instance.m_secPerFuel; Piece component = ((Component)__instance).GetComponent(); int num; int num2; if ((Object)(object)component != (Object)null) { num = (component.IsPlacedByPlayer() ? 1 : 0); if (num != 0) { num2 = ((!initializedFireplaces.TryGetValue(__instance, out var _)) ? 1 : 0); goto IL_0037; } } else { num = 0; } num2 = 0; goto IL_0037; IL_0037: bool flag = (byte)num2 != 0; if (flag) { initializedFireplaces.Add(__instance, new object()); } if (DecayEffectPolicy.ShouldPauseFuel(activityMultiplier: GetActivityMultiplier(component), isPlayerBuilt: (byte)num != 0, firstUpdate: flag, mode: DecayControlPlugin.Settings.FuelConsumption)) { __instance.m_secPerFuel = float.PositiveInfinity; } } private static void Postfix(Fireplace __instance, float __state) { __instance.m_secPerFuel = __state; } } private const float VanillaRainDamageFraction = 0.05f; private static readonly HashSet ActiveCreators = new HashSet(); private static ConditionalWeakTable initializedFireplaces = new ConditionalWeakTable(); private static bool hasServerState; internal static void Reset() { ActiveCreators.Clear(); hasServerState = false; initializedFireplaces = new ConditionalWeakTable(); } internal static void WriteState(ZPackage package) { HashSet activeCreators = GetActiveCreators(); package.Write(activeCreators.Count); foreach (long item in activeCreators) { package.Write(item); } } internal static void ReadState(ZPackage package) { ActiveCreators.Clear(); int num = package.ReadInt(); for (int i = 0; i < num; i++) { ActiveCreators.Add(package.ReadLong()); } hasServerState = true; } internal static float GetActivityMultiplier(Piece piece) { if ((Object)(object)piece == (Object)null || !piece.IsPlacedByPlayer()) { return 1f; } long creator = piece.GetCreator(); if (creator == 0L) { return 1f; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { if (!IsCreatorActive(creator, GetOnlinePlayers())) { return 0f; } return 1f; } if (hasServerState && !ActiveCreators.Contains(creator)) { return 0f; } return 1f; } private static HashSet GetActiveCreators() { return CreatorActivityPolicy.GetActiveCreators(GetOnlinePlayers()); } private static bool IsCreatorActive(long creator, HashSet onlinePlayers) { return CreatorActivityPolicy.IsCreatorActive(creator, onlinePlayers); } private static HashSet GetOnlinePlayers() { HashSet hashSet = new HashSet(); if ((Object)(object)ZNet.instance == (Object)null) { return hashSet; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && !((ZDOID)(ref peer.m_characterID)).IsNone()) { hashSet.Add(((ZDOID)(ref peer.m_characterID)).UserID); } } if (!ServerRole.IsDedicatedServer && (Object)(object)Game.instance != (Object)null) { hashSet.Add(Game.instance.GetPlayerProfile().GetPlayerID()); } return hashSet; } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class DecayStateOnSpawnPatch { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { DecayStateRpc.RequestOnSpawn(); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] internal static class DecayControlDisconnectPatch { private static void Prefix() { DecayStateRpc.ResetSession(); } } internal static class DecayStateRpc { private const string RequestRpc = "Landoria_DecayControl_Request"; private const string ResponseRpc = "Landoria_DecayControl_Response"; private static ZRoutedRpc registeredRpc; private static ZNetPeer serverPeer; private static bool requestSent; internal static void Update() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != registeredRpc) { instance.Register("Landoria_DecayControl_Request", (Action)ReceiveRequest); instance.Register("Landoria_DecayControl_Response", (Action)ReceiveResponse); registeredRpc = instance; } } internal static void RequestOnSpawn() { Update(); if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && ZRoutedRpc.instance != null && !requestSent) { serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { requestSent = true; ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "Landoria_DecayControl_Request", Array.Empty()); } } } internal static void ResetSession() { serverPeer = null; requestSent = false; DecayControlPlugin.Settings?.ResetState(); DecayProtection.Reset(); } private static void ReceiveRequest(long sender) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if (ServerRole.IsDedicatedServer && ZNet.instance.GetPeer(sender) != null && ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); DecayControlPlugin.Settings.WriteState(val); DecayProtection.WriteState(val); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "Landoria_DecayControl_Response", new object[1] { val }); } } private static void ReceiveResponse(long sender, ZPackage package) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && serverPeer != null && serverPeer.m_uid == sender) { DecayControlPlugin.Settings.ReadState(package); DecayProtection.ReadState(package); } } } [BepInPlugin("Landoria.DecayControl", "Landoria.DecayControl", "1.0.0")] public sealed class DecayControlPlugin : LandoriaPlugin { private const string PluginGuid = "Landoria.DecayControl"; private const string PluginName = "Landoria.DecayControl"; private const string PluginVersion = "1.0.0"; internal static ModLog Log { get; private set; } internal static DecayControlSettings Settings { get; private set; } private void Awake() { Log = InitializePlugin("Landoria.DecayControl"); Settings = new DecayControlSettings(); Settings.InitializeServer(Log); Log.LogInfo("Landoria.DecayControl 1.0.0 is loaded."); } private void Update() { DecayStateRpc.Update(); } private void OnDestroy() { DecayStateRpc.ResetSession(); DecayProtection.Reset(); Log?.LogInfo("Landoria.DecayControl 1.0.0 is unloaded."); ShutdownPlugin(); Settings = null; Log = null; } } internal sealed class DecayControlServerConfiguration { internal DecayControlMode FuelConsumption { get; private set; } internal DecayControlMode EnvironmentalBuildingWear { get; private set; } internal static DecayControlServerConfiguration FromArguments(string[] arguments) { return new DecayControlServerConfiguration { FuelConsumption = DecayControlArgumentPolicy.Resolve(arguments, "--decay-control-fuel-consumption", DecayControlMode.Default), EnvironmentalBuildingWear = DecayControlArgumentPolicy.Resolve(arguments, "--decay-control-environmental-building-wear", DecayControlMode.Default) }; } } internal sealed class DecayControlSettings { private bool serverInitialized; internal DecayControlMode FuelConsumption { get; private set; } internal DecayControlMode EnvironmentalBuildingWear { get; private set; } internal DecayControlSettings() { ResetState(); } internal void InitializeServer(ModLog logger) { if (!serverInitialized && ServerRole.IsDedicatedServer) { DecayControlServerConfiguration decayControlServerConfiguration = DecayControlServerConfiguration.FromArguments(Environment.GetCommandLineArgs()); FuelConsumption = decayControlServerConfiguration.FuelConsumption; EnvironmentalBuildingWear = decayControlServerConfiguration.EnvironmentalBuildingWear; serverInitialized = true; logger.LogInfo($"Effective decay settings: fuelConsumption={FuelConsumption}, " + $"environmentalBuildingWear={EnvironmentalBuildingWear}."); } } internal void WriteState(ZPackage package) { package.Write((int)FuelConsumption); package.Write((int)EnvironmentalBuildingWear); } internal void ReadState(ZPackage package) { FuelConsumption = (DecayControlMode)package.ReadInt(); EnvironmentalBuildingWear = (DecayControlMode)package.ReadInt(); } internal void ResetState() { if (!serverInitialized) { FuelConsumption = DecayControlMode.Default; EnvironmentalBuildingWear = DecayControlMode.Default; } } } } namespace Landoria.SharedLib { public abstract class LandoriaPlugin : BaseUnityPlugin { private Harmony _harmony; private bool _patchesApplied; protected ModLog InitializePlugin(string pluginGuid) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown ModLog modLog = new ModLog(((BaseUnityPlugin)this).Logger); Version version = ((object)this).GetType().Assembly.GetName().Version; modLog.LogInfo($"AssemblyVersion: {version}."); _harmony = new Harmony(pluginGuid); PatchOwnNamespace(modLog); return modLog; } protected void PatchOwnNamespace(ModLog log) { if (_patchesApplied) { log.LogDebug("Harmony patches are already active; skipping registration."); return; } string text = ((object)this).GetType().Namespace; Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (type.Namespace == text) { _harmony.CreateClassProcessor(type).Patch(); } } _patchesApplied = true; log.LogDebug("Harmony patches were applied for the plugin namespace."); } protected void ShutdownPlugin() { if (_patchesApplied) { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _patchesApplied = false; } } } public sealed class ModLog { private readonly ManualLogSource _logger; public ModLog(ManualLogSource logger) { _logger = logger; } public void LogFatal(object message) { Write((LogLevel)1, message); } public void LogError(object message) { Write((LogLevel)2, message); } public void LogWarning(object message) { Write((LogLevel)4, message); } public void LogMessage(object message) { Write((LogLevel)8, message); } public void LogInfo(object message) { Write((LogLevel)16, message); } public void LogDebug(object message) { Write((LogLevel)32, message); } public void Log(LogLevel level, object message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Write(level, message); } private void Write(LogLevel level, object message) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) string arg = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); _logger.Log(level, (object)$"[{arg}] {message}"); } } public static class ServerRole { public static bool IsDedicatedServer { get { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return ZNet.instance.IsDedicated(); } return false; } } } }