using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ConfigurableQuota.Patches; using GameNetcodeStuff; using HarmonyLib; using LethalNetworkAPI; using Microsoft.CodeAnalysis; using TMPro; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ConfigurableQuota")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Allows users to configure every aspect of the quota the way they want.")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+88ee3608198092b05327c7d4d2ee35ee56a73a45")] [assembly: AssemblyProduct("ConfigurableQuota")] [assembly: AssemblyTitle("ConfigurableQuota")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ConfigurableQuota { public static class ConfigManager { public static ConfigEntry StartingCredits; public static ConfigEntry StartingQuota; public static ConfigEntry DaysToDeadline; public static ConfigEntry BaseIncrease; public static ConfigEntry CurveSharpness; public static ConfigEntry RandomizerMultiplier; public static ConfigEntry FinalLevel; public static ConfigEntry FinalIncrease; public static ConfigEntry QuotaCap; public static ConfigEntry EnablePlayerMultiplier; public static ConfigEntry PlayerThreshold; public static ConfigEntry PlayerCap; public static ConfigEntry MultPerPlayer; public static ConfigEntry DisableQuota; public static ConfigEntry RolloverAmount; public static ConfigEntry CreditPenaltiesEnabled; public static ConfigEntry CreditPenaltiesOnGordion; public static ConfigEntry CreditPenaltyPercentPerPlayer; public static ConfigEntry CreditPenaltiesDynamic; public static ConfigEntry CreditPenaltyPercentCap; public static ConfigEntry CreditPenaltyPercentThreshold; public static ConfigEntry CreditPenaltyRecoveryBonus; public static ConfigEntry QuotaPenaltiesEnabled; public static ConfigEntry QuotaPenaltiesOnGordion; public static ConfigEntry QuotaPenaltyPercentPerPlayer; public static ConfigEntry QuotaPenaltiesDynamic; public static ConfigEntry QuotaPenaltyPercentCap; public static ConfigEntry QuotaPenaltyPercentThreshold; public static ConfigEntry QuotaPenaltyRecoveryBonus; public static ConfigEntry ScrapLossEnabled; public static ConfigEntry ItemsSafeChance; public static ConfigEntry LoseEachScrapChance; public static ConfigEntry MaxLostScrapItems; public static ConfigEntry ValueLossEnabled; public static ConfigEntry ValueLossPercent; public static ConfigEntry RandomizeDeadline; public static ConfigEntry DeadlineMin; public static ConfigEntry DeadlineMax; public static ConfigEntry DeadlineMustChange; public static ConfigEntry EnableGrowthDampening; public static ConfigEntry DampeningStartAt; public static ConfigEntry DampeningSharpness; public static ConfigEntry EquipmentLossEnabled; public static ConfigEntry LoseEachEquipmentChance; public static ConfigEntry MaxLostEquipmentItems; public static ConfigEntry QuotaAnimationSpeed; internal static void Initialize(ConfigFile config) { //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_0471: Expected O, but got Unknown //IL_049f: Unknown result type (might be due to invalid IL or missing references) //IL_04a9: Expected O, but got Unknown //IL_050d: Unknown result type (might be due to invalid IL or missing references) //IL_0517: Expected O, but got Unknown //IL_0560: Unknown result type (might be due to invalid IL or missing references) //IL_056a: Expected O, but got Unknown //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Expected O, but got Unknown StartingCredits = config.Bind("0. Basic", "StartingCredits", 60, "Starting credits for a new lobby."); StartingQuota = config.Bind("0. Basic", "StartingQuota", 130, "Starting quota for a new lobby."); DaysToDeadline = config.Bind("0. Basic", "DaysToDeadline", 3, "Number of days to meet each quota. Ignored if RandomizeDeadline is enabled."); RandomizeDeadline = config.Bind("0. Basic", "RandomizeDeadline", false, "Randomize the deadline each quota using Deadline Min/Max instead of a fixed Days To Deadline."); DeadlineMin = config.Bind("0. Basic", "DeadlineMin", 3, "Minimum days for deadline. REQUIRES 'Randomize Deadline' SET TO TRUE"); DeadlineMax = config.Bind("0. Basic", "DeadlineMax", 5, "Maximum days for deadline. REQUIRES 'Randomize Deadline' SET TO TRUE"); DeadlineMustChange = config.Bind("0. Basic", "DeadlineMustChange", true, "New deadline after fulfilling quota must differ from the previous one. REQUIRES 'Randomize Deadline' SET TO TRUE"); BaseIncrease = config.Bind("0. Basic", "BaseIncrease", 100, "Base quota increase. Combined with 'Curve Sharpness' to calculate growth."); CurveSharpness = config.Bind("0. Basic", "CurveSharpness", 16f, "Quota growth curve. Higher = slower growth. Formula: increase ≈ BaseIncrease x (1 + quotaCount²/Sharpness)"); RandomizerMultiplier = config.Bind("0. Basic", "RandomizerMultiplier", 1f, "Adds variation to quota increases. 1 = ±50% variance (vanilla), 0 = no randomness, 2 = ±100% variance."); FinalLevel = config.Bind("1. Leveling", "FinalLevel", -1, "When quota reaches this value, the Base Increase and Curve Sharpness are ignored. Set -1 to disable."); FinalIncrease = config.Bind("1. Leveling", "FinalIncrease", 200, "Fixed increase amount used after reaching Final Level value."); QuotaCap = config.Bind("1. Leveling", "QuotaCap", -1, "Maximum quota value, quota will never increase more than this amount. Set -1 for no limit."); EnableGrowthDampening = config.Bind("1. Leveling", "EnableGrowthDampening", false, "Gradually reduces quota growth after a number of fulfilled cycles, growth will slow down the longer you play."); DampeningStartAt = config.Bind("1. Leveling", "DampeningStartAt", 6, "Number of quota cycles before dampening begins."); DampeningSharpness = config.Bind("1. Leveling", "DampeningSharpness", 11f, "Controls dampening intensity. Lower values reduce growth more aggressively."); EnablePlayerMultiplier = config.Bind("2. PlayerScaling", "EnablePlayerMultiplier", false, "Scale quota increases based on player count."); PlayerThreshold = config.Bind("2. PlayerScaling", "PlayerThreshold", 2, "Player count where scaling begins. Example: 2 means scaling starts at 3+ players."); PlayerCap = config.Bind("2. PlayerScaling", "PlayerCap", 4, "Maximum players counted for scaling. Example: 4 means player 5+ will not increase the quota multiplier."); MultPerPlayer = config.Bind("2. PlayerScaling", "MultPerPlayer", 0.25f, "Quota increase multiplier per extra player. Example: 0.5 = +50% increase per player above threshold."); DisableQuota = config.Bind("3. Optional", "DisableQuota", false, "Completely disables the quota system."); RolloverAmount = config.Bind("3. Optional", "RolloverAmount", 0f, new ConfigDescription("Percentage of extra scrap value that goes above the set limit and is added to the next quota. 0 = none (vanilla), 0.5 = 50%, 1.0 = 100%.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); CreditPenaltiesEnabled = config.Bind("4. Penalties.Credits", "Enabled", false, "Reduce credits when crew members die."); CreditPenaltiesOnGordion = config.Bind("4. Penalties.Credits", "OnGordion", false, "Apply credit penalties even when visiting The Company."); CreditPenaltyPercentPerPlayer = config.Bind("4. Penalties.Credits", "PercentPerPlayer", 0.15f, "Credits lost per dead player. Example: 0.15 = lose 15% of credits per death. Ignored if 'Dynamic' is true."); CreditPenaltiesDynamic = config.Bind("4. Penalties.Credits", "Dynamic", false, "Use team death ratio instead of per-player. Example: 2 dead out of 4 total = 50% penalty, not 30% (2x15%)."); CreditPenaltyPercentCap = config.Bind("4. Penalties.Credits", "PercentCap", 0.8f, "Maximum percentage of credits that can be lost."); CreditPenaltyPercentThreshold = config.Bind("4. Penalties.Credits", "PercentThreshold", 0f, "Ignore penalties below this percentage. Example: 0.1 = penalties under 10% are not applied."); CreditPenaltyRecoveryBonus = config.Bind("4. Penalties.Credits", "RecoveryBonus", 0f, "Reduce penalty if you recover bodies. Example: 0.5 = 50% penalty forgiveness if bodies brought back."); QuotaPenaltiesEnabled = config.Bind("5. Penalties.Quota", "Enabled", false, "Increase the current quota when crew members die."); QuotaPenaltiesOnGordion = config.Bind("5. Penalties.Quota", "OnGordion", false, "Apply quota penalties even when visiting The Company."); QuotaPenaltyPercentPerPlayer = config.Bind("5. Penalties.Quota", "PercentPerPlayer", 0.1f, "Quota increase per dead player. Example: 0.1 = +10% to current quota per death. Ignored if 'Dynamic' is true."); QuotaPenaltiesDynamic = config.Bind("5. Penalties.Quota", "Dynamic", false, "Use team death ratio instead of per-player. Example: 2 dead out of 4 total = 50% quota increase."); QuotaPenaltyPercentCap = config.Bind("5. Penalties.Quota", "PercentCap", 0.5f, "Maximum percentage the quota can increase. Example: 0.5 = quota can increase by at most 50%."); QuotaPenaltyPercentThreshold = config.Bind("5. Penalties.Quota", "PercentThreshold", 0f, "Ignore penalties below this percentage. Example: 0.15 = increases under 15% are not applied."); QuotaPenaltyRecoveryBonus = config.Bind("5. Penalties.Quota", "RecoveryBonus", 0f, "Reduce penalty if you recover bodies. Example: 0.5 = 50% penalty forgiveness if bodies brought back."); ScrapLossEnabled = config.Bind("6. Loss.Scrap", "Enabled", false, "Randomly lose collected scrap when all crew dies."); ItemsSafeChance = config.Bind("6. Loss.Scrap", "ItemsSafeChance", 0.5f, new ConfigDescription("Chance for each scrap to be protected from loss. Example: 0.7 = 70% chance each scrap is safe.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); LoseEachScrapChance = config.Bind("6. Loss.Scrap", "LoseEachScrapChance", 0.1f, new ConfigDescription("If scrap is not safe, this is the chance it gets lost. Example: 0.2 = 20% chance to lose unprotected scrap.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); MaxLostScrapItems = config.Bind("6. Loss.Scrap", "MaxLostScrapItems", 2, "Maximum scrap that can be lost per round."); ValueLossEnabled = config.Bind("7. Loss.Value", "Enabled", false, "Reduce the scrap value of all ship items when the entire crew is wiped."); ValueLossPercent = config.Bind("7. Loss.Value", "Percent", 0.2f, new ConfigDescription("Percentage to reduce scrap value. Example: 0.25 = all scrap items lose 25% of their value, stacks on repeated wipes.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); EquipmentLossEnabled = config.Bind("8. Loss.Equipment", "Enabled", false, "Randomly lose purchased equipment when all crew dies."); LoseEachEquipmentChance = config.Bind("8. Loss.Equipment", "LoseEachEquipmentChance", 0.05f, new ConfigDescription("Chance for each equipment item to be lost. Example: 0.1 = 10% chance per item.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); MaxLostEquipmentItems = config.Bind("8. Loss.Equipment", "MaxLostEquipmentItems", 1, "Maximum equipment items that can be lost per round. Example: 1 = lose at most 1 item."); QuotaAnimationSpeed = config.Bind("9. UI", "QuotaAnimationSpeed", 1f, new ConfigDescription("Speed multiplier for the new quota animation. Higher = faster.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 2f), Array.Empty())); } } internal static class Metadata { public const string GUID = "com.seeya.configurablequota"; public const string PLUGIN_NAME = "Configurable Quota"; public const string VERSION = "1.0.0"; } [BepInPlugin("com.seeya.configurablequota", "Configurable Quota", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("com.seeya.configurablequota"); public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"Initializing Configurable Quota"); ConfigManager.Initialize(((BaseUnityPlugin)this).Config); NetworkSync.Initialize(); _harmony.PatchAll(); Log.LogInfo((object)"Configurable Quota is loaded!"); } } } namespace ConfigurableQuota.Patches { [HarmonyPatch(typeof(HUDManager))] internal static class HudQuotaAnimationPatch { [CompilerGenerated] private sealed class d__1 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float speed; public HUDManager hud; private int 5__2; private int 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(3.5f / speed); <>1__state = 1; return true; case 1: <>1__state = -1; 5__2 = 0; 5__3 = TimeOfDay.Instance.profitQuota; goto IL_00dd; case 2: <>1__state = -1; goto IL_00dd; case 3: { <>1__state = -1; hud.displayingNewQuota = false; hud.reachedProfitQuotaAnimator.SetBool("display", false); return false; } IL_00dd: if (5__2 < 5__3) { float num = Time.deltaTime * 250f * speed; 5__2 = (int)Mathf.Clamp((float)5__2 + num, (float)(5__2 + 3), (float)(5__3 + 10)); ((TMP_Text)hud.newProfitQuotaText).text = "$" + 5__2; <>2__current = null; <>1__state = 2; return true; } ((TMP_Text)hud.newProfitQuotaText).text = "$" + 5__3; TimeOfDay.Instance.UpdateProfitQuotaCurrentTime(); hud.UIAudio.PlayOneShot(hud.newProfitQuotaSFX); <>2__current = (object)new WaitForSeconds(1.25f / Mathf.Clamp(speed, 0.5f, 2f)); <>1__state = 3; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [HarmonyPatch("rackUpNewQuotaText")] [HarmonyPrefix] private static bool RackUpNewQuotaText_Prefix(HUDManager __instance, ref IEnumerator __result) { float speed = Mathf.Clamp(ConfigManager.QuotaAnimationSpeed.Value, 0.1f, 2f); __result = CustomRackUp(__instance, speed); return false; } [IteratorStateMachine(typeof(d__1))] private static IEnumerator CustomRackUp(HUDManager hud, float speed) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(0) { hud = hud, speed = speed }; } [HarmonyPatch("ApplyPenalty")] [HarmonyPrefix] private static bool ApplyPenalty_Prefix() { return false; } [HarmonyPatch("ApplyPenalty")] [HarmonyPostfix] private static void ApplyPenalty_Postfix(HUDManager __instance, int playersDead, int bodiesInsured) { try { int num; int num2; int num3; if (PenaltiesOnLandingPatch.HasPenaltyCache) { num = PenaltiesOnLandingPatch.CachedDead; num2 = PenaltiesOnLandingPatch.CachedTotal; num3 = PenaltiesOnLandingPatch.CachedRecovered; } else { (num, num2, num3) = PenaltyHelpers.CountDeathsAndRecovered(); if (num == 0 && playersDead > 0) { num = playersDead; num3 = bodiesInsured; num2 = Mathf.Max(num + 1, num2); } } bool flag = PenaltyHelpers.IsOnGordion(); float num4 = 0f; int num5 = 0; if (ConfigManager.CreditPenaltiesEnabled.Value && num > 0 && (!flag || ConfigManager.CreditPenaltiesOnGordion.Value)) { int num6 = Object.FindObjectOfType()?.groupCredits ?? 0; num4 = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, num, num2, num3); num5 = Mathf.RoundToInt((float)num6 * num4); } float num7 = 0f; int num8 = 0; if (ConfigManager.QuotaPenaltiesEnabled.Value && num > 0 && (!flag || ConfigManager.QuotaPenaltiesOnGordion.Value)) { num7 = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, num, num2, num3); TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { num8 = Mathf.RoundToInt((float)Mathf.Max(1, instance.profitQuota) * num7); } } string text = ((num4 > 0f) ? $"{num} casualties: -{Mathf.RoundToInt(num4 * 100f)}%" : $"{num} casualties"); string text2 = $"({num3} of {num} bodies recovered.)"; string text3 = text + "\n" + text2; if (num7 > 0f) { text3 += $"\n\nQuota: {Mathf.RoundToInt(num7 * 100f)}% (${num8})"; } ((TMP_Text)__instance.statsUIElements.penaltyAddition).text = text3; ((TMP_Text)__instance.statsUIElements.penaltyTotal).text = ((num5 > 0) ? $"DUE: ${num5}" : ""); } catch (Exception ex) { Plugin.Log.LogWarning((object)("ApplyPenalty display patch failed: " + ex.Message)); } } } internal static class NetworkSync { private static LNetworkMessage? _syncCreditsMessage; private static LNetworkMessage? _syncQuotaMessage; private static LNetworkMessage? _syncValueLossMessage; private static LNetworkMessage? _syncDeadlineMessage; public static void Initialize() { try { _syncCreditsMessage = LNetworkMessage.Connect("ConfigurableQuota_SyncCredits", (Action)null, (Action)OnCreditsReceived, (Action)null); _syncQuotaMessage = LNetworkMessage.Connect("ConfigurableQuota_SyncQuota", (Action)null, (Action)OnQuotaReceived, (Action)null); _syncValueLossMessage = LNetworkMessage.Connect("ConfigurableQuota_SyncValueLoss", (Action)null, (Action)OnValueLossReceived, (Action)null); _syncDeadlineMessage = LNetworkMessage.Connect("ConfigurableQuota_SyncDeadline", (Action)null, (Action)OnDeadlineReceived, (Action)null); Plugin.Log.LogInfo((object)"Network initialized"); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to initialize: " + ex.Message)); } } public static void SyncCreditsToClients(int credits) { try { if (_syncCreditsMessage == null) { Plugin.Log.LogWarning((object)"Credits message not initialized"); return; } _syncCreditsMessage.SendClients(credits); Plugin.Log.LogDebug((object)$"Sent credits sync to clients: {credits}"); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to sync credits: " + ex.Message)); } } private static void OnCreditsReceived(int credits) { try { Terminal val = Object.FindObjectOfType(); if ((Object)(object)val != (Object)null) { val.groupCredits = credits; } Plugin.Log.LogDebug((object)$"Client received credits sync: {credits}"); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply credits on client: " + ex.Message)); } } public static void SyncQuotaToClients(int quota) { try { if (_syncQuotaMessage == null) { Plugin.Log.LogWarning((object)"Quota message not initialized"); return; } _syncQuotaMessage.SendClients(quota); Plugin.Log.LogDebug((object)$"Sent quota sync to clients: {quota}"); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to sync quota: " + ex.Message)); } } private static void OnQuotaReceived(int quota) { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { instance.profitQuota = quota; Plugin.Log.LogDebug((object)$"Client received quota sync: {quota}"); } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply quota on client: " + ex.Message)); } } public static void SyncValueLossToClients(SyncValueLossData[] items) { try { if (_syncValueLossMessage == null) { Plugin.Log.LogWarning((object)"Value loss message not initialized"); return; } foreach (SyncValueLossData syncValueLossData in items) { _syncValueLossMessage.SendClients(syncValueLossData); } Plugin.Log.LogDebug((object)$"Sent {items.Length} value loss updates to clients"); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to sync value loss: " + ex.Message)); } } private static void OnValueLossReceived(SyncValueLossData data) { try { GrabbableObject[] array = Object.FindObjectsOfType(); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { try { NetworkObject component = ((Component)val).GetComponent(); if (!((Object)(object)val != (Object)null) || !((Object)(object)component != (Object)null) || component.NetworkObjectId != data.NetworkObjectId) { continue; } Item itemProperties = val.itemProperties; if (itemProperties != null && itemProperties.isScrap) { val.scrapValue = data.NewValue; try { val.SetScrapValue(data.NewValue); } catch { } Plugin.Log.LogDebug((object)$"Client updated scrap value for {val.itemProperties.itemName}: {data.NewValue}"); break; } } catch { } } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply value loss on client: " + ex.Message)); } } public static void SyncDeadlineToClients(int days) { try { if (_syncDeadlineMessage == null) { Plugin.Log.LogWarning((object)"Deadline message not initialized"); return; } _syncDeadlineMessage.SendClients(days); Plugin.Log.LogDebug((object)$"Sent deadline sync to clients: {days} days"); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to sync deadline: " + ex.Message)); } } private static void OnDeadlineReceived(int days) { try { TimeOfDay instance = TimeOfDay.Instance; if (!((Object)(object)instance == (Object)null)) { if (instance.quotaVariables != null) { instance.quotaVariables.deadlineDaysAmount = days; } instance.daysUntilDeadline = days; instance.timeUntilDeadline = (float)days * instance.totalTime; Plugin.Log.LogDebug((object)$"Client updated deadline: {days} days"); } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply deadline on client: " + ex.Message)); } } } [Serializable] public struct SyncValueLossData { public ulong NetworkObjectId; public int NewValue; public SyncValueLossData(ulong networkObjectId, int newValue) { NetworkObjectId = networkObjectId; NewValue = newValue; } } internal static class PenaltyHelpers { public static bool IsServerSafe { get { if ((Object)(object)NetworkManager.Singleton != (Object)null) { return NetworkManager.Singleton.IsServer; } return false; } } public static (int dead, int total, int recovered) CountDeathsAndRecovered() { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return (0, 0, 0); } int num = 0; int num2 = 0; int num3 = 0; PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val == (Object)null) { continue; } bool isPlayerControlled = val.isPlayerControlled; bool isPlayerDead = val.isPlayerDead; if (isPlayerControlled || isPlayerDead) { num2++; } if (!isPlayerDead) { continue; } num++; GrabbableObject obj = val.deadBody?.grabBodyObject; RagdollGrabbableObject val2 = (RagdollGrabbableObject)(object)((obj is RagdollGrabbableObject) ? obj : null); if ((Object)(object)val2 == (Object)null) { RagdollGrabbableObject[] array = Object.FindObjectsOfType(); foreach (RagdollGrabbableObject val3 in array) { if ((Object)(object)((val3 == null) ? null : ((Component)val3).GetComponent()?.playerScript) == (Object)(object)val) { val2 = val3; break; } } } bool flag = false; if ((Object)(object)val2 != (Object)null) { bool isInShipRoom = ((GrabbableObject)val2).isInShipRoom; bool flag2 = IsPositionInsideShip(((Component)val2).transform.position); flag = isInShipRoom || flag2; } if (flag) { num3++; } } return (num, Math.Max(num2, 1), Mathf.Clamp(num3, 0, num)); } public static bool IsPositionInsideShip(Vector3 pos) { //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) //IL_0024: Unknown result type (might be due to invalid IL or missing references) try { Collider val = StartOfRound.Instance?.shipBounds; int result; if ((Object)(object)val != (Object)null) { Bounds bounds = val.bounds; result = (((Bounds)(ref bounds)).Contains(pos) ? 1 : 0); } else { result = 0; } return (byte)result != 0; } catch { return false; } } public static bool IsOnGordion() { try { SelectableLevel val = StartOfRound.Instance?.currentLevel; if ((Object)(object)val == (Object)null) { return false; } return val.sceneName == "CompanyBuilding"; } catch { return false; } } public static float ComputePenaltyPercent(bool dynamicMode, float percentPerPlayer, float cap, float threshold, float recoveryBonus, int dead, int total, int recovered) { if (dead <= 0 || total <= 0) { return 0f; } float num = (dynamicMode ? ((float)dead / (float)total) : ((float)dead * Mathf.Max(0f, percentPerPlayer))); if (recovered > 0 && dead > 0) { float num2 = Mathf.Clamp01((float)recovered / (float)dead); num *= Mathf.Clamp01(1f - Mathf.Clamp01(recoveryBonus) * num2); } if (cap >= 0f) { num = Mathf.Min(num, Mathf.Clamp01(cap)); } if (!(num < threshold)) { return Mathf.Clamp01(num); } return 0f; } } [HarmonyPatch(typeof(RoundManager))] internal static class PenaltiesOnLandingPatch { [CompilerGenerated] private sealed class d__12 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int desiredFinal; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__12(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 1; return true; case 1: <>1__state = -1; try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } int currentCredits = GetCurrentCredits(); SetCredits(desiredFinal); Plugin.Log.LogInfo((object)$"[Penalty] Credits: {currentCredits} → {desiredFinal} (-{currentCredits - desiredFinal})"); } finally { _creditScheduled = false; } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static bool _appliedThisRound; private static bool _creditScheduled; internal static bool _lossesAppliedThisRound; internal static int CachedDead; internal static int CachedTotal; internal static int CachedRecovered; internal static bool HasPenaltyCache; internal static void CachePenaltyCounts(int dead, int total, int recovered) { CachedDead = dead; CachedTotal = total; CachedRecovered = recovered; HasPenaltyCache = true; } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPrefix] private static bool DespawnPrefix(bool despawnAllItems) { try { if (!PenaltyHelpers.IsServerSafe) { return true; } bool flag = PenaltyHelpers.IsOnGordion(); var (num, num2, recovered) = PenaltyHelpers.CountDeathsAndRecovered(); if (!despawnAllItems && !flag && num >= num2 && !_lossesAppliedThisRound) { DespawnFacilityItems(); ApplyLossesWhenAllDead(); _lossesAppliedThisRound = true; CachePenaltyCounts(num, num2, recovered); if (ConfigManager.CreditPenaltiesEnabled.Value) { ScheduleCreditPenalty(num, num2, recovered); } if (ConfigManager.QuotaPenaltiesEnabled.Value) { ApplyQuotaPenalty(num, num2, recovered); } _appliedThisRound = true; Plugin.Log.LogDebug((object)"Bypassed vanilla despawn to preserve kept items"); return false; } return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error in despawn prefix: " + ex.Message)); return true; } } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPostfix] private static void DespawnPostfix(bool despawnAllItems) { try { if (despawnAllItems || !PenaltyHelpers.IsServerSafe || _appliedThisRound) { return; } var (num, total, recovered) = PenaltyHelpers.CountDeathsAndRecovered(); if (num > 0) { CachePenaltyCounts(num, total, recovered); bool flag = PenaltyHelpers.IsOnGordion(); if (ConfigManager.CreditPenaltiesEnabled.Value && (!flag || ConfigManager.CreditPenaltiesOnGordion.Value)) { ScheduleCreditPenalty(num, total, recovered); } if (ConfigManager.QuotaPenaltiesEnabled.Value && (!flag || ConfigManager.QuotaPenaltiesOnGordion.Value)) { ApplyQuotaPenalty(num, total, recovered); } _appliedThisRound = true; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error in despawn postfix: " + ex.Message)); } } private static void ScheduleCreditPenalty(int dead, int total, int recovered) { try { if (_creditScheduled) { return; } StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance == (Object)null)) { int currentCredits = GetCurrentCredits(); float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered); if (!(num <= 0f)) { int desiredFinal = Mathf.Max(0, currentCredits - Mathf.RoundToInt((float)currentCredits * num)); _creditScheduled = true; ((MonoBehaviour)instance).StartCoroutine(FinalizeCreditPenaltyAfterDelay(desiredFinal)); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to schedule credit penalty: " + ex.Message)); } } private static int GetCurrentCredits() { try { Terminal val = Object.FindObjectOfType(); if ((Object)(object)val != (Object)null) { return val.groupCredits; } } catch { } return 0; } [IteratorStateMachine(typeof(d__12))] private static IEnumerator FinalizeCreditPenaltyAfterDelay(int desiredFinal) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__12(0) { desiredFinal = desiredFinal }; } private static void SetCredits(int value) { try { Terminal val = Object.FindObjectOfType(); if ((Object)(object)val != (Object)null) { val.SyncGroupCreditsServerRpc(value, val.numberOfItemsInDropship); } } catch { } } private static void ApplyQuotaPenalty(int dead, int total, int recovered) { float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered); if (!(num <= 0f)) { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { int num2 = Mathf.RoundToInt((float)Math.Max(1, instance.profitQuota) * num); int num3 = (instance.profitQuota = Mathf.Max(1, instance.profitQuota + num2)); NetworkSync.SyncQuotaToClients(num3); int num4 = instance.profitQuota - num2; Plugin.Log.LogInfo((object)$"[Penalty] Quota: {num4} → {num3} (+{num2}, {num:P0} penalty, {dead}/{total} dead)"); } } } private static void DespawnFacilityItems() { try { GrabbableObject[] array = Object.FindObjectsOfType(); if (array == null || array.Length == 0) { return; } Transform shipRoot = null; try { StartOfRound instance = StartOfRound.Instance; object obj; if (instance == null) { obj = null; } else { Collider shipBounds = instance.shipBounds; obj = ((shipBounds != null) ? ((Component)shipBounds).transform : null); } shipRoot = (Transform)obj; } catch { } int num = 0; GrabbableObject[] array2 = array; foreach (GrabbableObject g in array2) { try { if (!IsShipItem(g, shipRoot)) { DespawnObject(g); num++; } } catch { } } Plugin.Log.LogDebug((object)$"Despawned {num} facility items"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error despawning facility items: " + ex.Message)); } } private static void ApplyLossesWhenAllDead() { try { GrabbableObject[] array = Object.FindObjectsOfType(); if (array == null || array.Length == 0) { return; } Transform shipRoot = null; try { StartOfRound instance = StartOfRound.Instance; object obj; if (instance == null) { obj = null; } else { Collider shipBounds = instance.shipBounds; obj = ((shipBounds != null) ? ((Component)shipBounds).transform : null); } shipRoot = (Transform)obj; } catch { } GrabbableObject[] source = array.Where((GrabbableObject g) => IsShipItem(g, shipRoot)).ToArray(); GrabbableObject[] array2 = source.Where((GrabbableObject g) => g.itemProperties.isScrap).ToArray(); GrabbableObject[] array3 = source.Where((GrabbableObject g) => !g.itemProperties.isScrap && !IsBodyOrBlacklisted(g)).ToArray(); Plugin.Log.LogDebug((object)$"Ship items: {array2.Length} scrap, {array3.Length} equipment"); if (ConfigManager.ValueLossEnabled.Value && array2.Length != 0) { ApplyValueLoss(array2); } if (ConfigManager.ScrapLossEnabled.Value && array2.Length != 0) { SelectAndRemoveScrap(array2); } if (ConfigManager.EquipmentLossEnabled.Value && array3.Length != 0) { SelectAndRemoveEquipment(array3); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error applying losses: " + ex.Message)); } } private static bool IsShipItem(GrabbableObject g, Transform? shipRoot) { if ((Object)(object)g == (Object)null || (Object)(object)g.itemProperties == (Object)null || !g.isInShipRoom) { return false; } try { NetworkObject component = ((Component)g).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { return false; } } catch { return false; } if ((Object)(object)shipRoot == (Object)null) { return true; } Transform val = ((Component)g).transform; for (int i = 0; i < 8; i++) { if (!((Object)(object)val != (Object)null)) { break; } if ((Object)(object)val == (Object)(object)shipRoot) { return true; } val = val.parent; } return true; } private static bool IsBodyOrBlacklisted(GrabbableObject g) { if ((Object)(object)g == (Object)null) { return true; } if (g is RagdollGrabbableObject) { return true; } if (g is ClipboardItem) { return true; } try { string text = g.itemProperties?.itemName ?? ((Object)g).name; return text.IndexOf("sticky note", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } private static void SelectAndRemoveScrap(GrabbableObject[] scrapItems) { int num = Mathf.Max(0, ConfigManager.MaxLostScrapItems.Value); float num2 = Mathf.Clamp01(ConfigManager.ItemsSafeChance.Value); float num3 = Mathf.Clamp01(ConfigManager.LoseEachScrapChance.Value); int num4 = 0; int num5 = 0; List list = new List(); foreach (GrabbableObject val in scrapItems) { try { if ((Object)(object)val == (Object)null) { continue; } Item itemProperties = val.itemProperties; if (itemProperties != null && itemProperties.isScrap) { num4++; if ((num <= 0 || num5 < num) && !(Random.value < num2) && Random.value < num3) { DespawnObject(val); num5++; list.Add(val.itemProperties.itemName); } } } catch { } } Plugin.Log.LogInfo((object)string.Format("Scrap: {0}/{1} removed [{2}]", num5, num4, string.Join(", ", list))); } private static void SelectAndRemoveEquipment(GrabbableObject[] equipItems) { int num = Mathf.Max(0, ConfigManager.MaxLostEquipmentItems.Value); float num2 = Mathf.Clamp01(ConfigManager.LoseEachEquipmentChance.Value); int num3 = 0; int num4 = 0; List list = new List(); foreach (GrabbableObject val in equipItems) { try { if ((Object)(object)val == (Object)null) { continue; } Item itemProperties = val.itemProperties; if (itemProperties != null && !itemProperties.isScrap) { num3++; if ((num <= 0 || num4 < num) && Random.value < num2) { DespawnObject(val); num4++; list.Add(val.itemProperties.itemName); } } } catch { } } Plugin.Log.LogInfo((object)string.Format("[Losses] Equipment: {0}/{1} removed [{2}]", num4, num3, string.Join(", ", list))); } private static void ApplyValueLoss(GrabbableObject[] scrapItems) { float num = Mathf.Clamp01(ConfigManager.ValueLossPercent.Value); if (num <= 0f) { return; } float num2 = 1f - num; int num3 = 0; int num4 = 0; int num5 = 0; List list = new List(); foreach (GrabbableObject val in scrapItems) { try { if (val != null && (val.itemProperties?.isScrap).GetValueOrDefault() && val.scrapValue > 0) { int scrapValue = val.scrapValue; int num6 = (val.scrapValue = Mathf.Max(0, Mathf.RoundToInt((float)val.scrapValue * num2))); try { val.SetScrapValue(num6); } catch { } NetworkObject component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { list.Add(new SyncValueLossData(component.NetworkObjectId, num6)); } num4 += scrapValue; num5 += num6; num3++; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error reducing value for item: " + ex.Message)); } } if (list.Count > 0) { NetworkSync.SyncValueLossToClients(list.ToArray()); } Plugin.Log.LogInfo((object)$"Value: {num3} items reduced by {num:P0} (${num4} → ${num5})"); } private static void DespawnObject(GrabbableObject g) { try { NetworkObject component = ((Component)g).GetComponent(); if ((Object)(object)component != (Object)null && component.IsSpawned) { component.Despawn(true); } else { Object.Destroy((Object)(object)((Component)g).gameObject); } } catch { } } } [HarmonyPatch(typeof(StartOfRound))] internal static class ResetPenaltyFlags { [HarmonyPatch("StartGame")] [HarmonyPostfix] private static void ResetFlagsOnNewGame() { PenaltiesOnLandingPatch._appliedThisRound = false; PenaltiesOnLandingPatch._lossesAppliedThisRound = false; PenaltiesOnLandingPatch.HasPenaltyCache = false; } } [HarmonyPatch(typeof(Terminal))] internal static class StartOfRoundPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void ApplyStartingCredits(Terminal __instance) { try { int value = ConfigManager.StartingCredits.Value; if (value >= 0) { __instance.groupCredits = value; Plugin.Log.LogInfo((object)$"Applied starting credits: {value}"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to apply StartingCredits: " + ex.Message)); } try { TimeOfDay instance = TimeOfDay.Instance; if (!((Object)(object)instance == (Object)null) && instance.timesFulfilledQuota == 0 && ((NetworkBehaviour)instance).IsServer && ConfigManager.RandomizeDeadline.Value) { NetworkSync.SyncDeadlineToClients(instance.daysUntilDeadline); Plugin.Log.LogInfo((object)$"[Lobby] Initial deadline synced: {instance.daysUntilDeadline}d"); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Failed to sync initial deadline: " + ex2.Message)); } } } [HarmonyPatch(typeof(TimeOfDay))] internal static class TimeOfDayQuotaPatch { [HarmonyPatch("SetNewProfitQuota")] [HarmonyPrefix] [HarmonyPriority(200)] [HarmonyAfter(new string[] { "Jade.ChocoQuota", "luciusoflegend.lethalcompany.quotaoverhaul" })] private static bool SetNewProfitQuota_Prefix(TimeOfDay __instance, ref int ___timesFulfilledQuota, ref int ___profitQuota, ref float ___timeUntilDeadline, ref int ___quotaFulfilled, ref int ___daysUntilDeadline, ref float ___totalTime) { try { if (!((NetworkBehaviour)__instance).IsServer) { return false; } if (ConfigManager.DisableQuota.Value) { ___profitQuota = Mathf.Max(0, ConfigManager.StartingQuota.Value); SetDeadlineTimer(___totalTime, ref ___daysUntilDeadline, ref ___timeUntilDeadline); return false; } int num = ___profitQuota; ___timesFulfilledQuota++; int num2 = CalculateNewQuota(num, ___timesFulfilledQuota); int num3 = ___daysUntilDeadline; int num4 = ___quotaFulfilled - num; int num5 = num4 / 5 + 15 * num3; ___profitQuota = num2; ___quotaFulfilled = CalculateRollover(num4); int num6 = SetDeadlineTimer(___totalTime, ref ___daysUntilDeadline, ref ___timeUntilDeadline, num3); __instance.quotaVariables.deadlineDaysAmount = num6; __instance.SyncNewProfitQuotaClientRpc(___profitQuota, num5, ___timesFulfilledQuota); ___daysUntilDeadline = num6; ___timeUntilDeadline = ___totalTime * (float)num6; NetworkSync.SyncDeadlineToClients(num6); Plugin.Log.LogInfo((object)($"[Quota #{___timesFulfilledQuota}] {num} → {num2}" + $" | Deadline: {num6}d" + $" | Rollover: {___quotaFulfilled}" + $" | Overtime: {num5}cr")); return false; } catch (Exception arg) { Plugin.Log.LogError((object)$"Quota SetNewProfitQuota patch error: {arg}"); return true; } } private static int CalculateNewQuota(int previousQuota, int timesFulfilled) { int value = ConfigManager.FinalLevel.Value; int num; if (value != -1 && previousQuota >= value) { num = previousQuota + Math.Max(0, ConfigManager.FinalIncrease.Value); } else { float num2 = Mathf.Max(0.1f, ConfigManager.CurveSharpness.Value); float num3 = timesFulfilled; float num4 = Mathf.Clamp(1f + num3 * (num3 / num2), 0f, 10000f); float num5 = 1f; float value2 = ConfigManager.RandomizerMultiplier.Value; if (value2 > 0f) { num5 = 1f + Random.Range(-0.5f, 0.5f) * value2; } float num6 = (float)ConfigManager.BaseIncrease.Value * num4 * num5; if (ConfigManager.EnablePlayerMultiplier.Value) { num6 *= CalculatePlayerMultiplier(); } if (ConfigManager.EnableGrowthDampening.Value) { int value3 = ConfigManager.DampeningStartAt.Value; if (timesFulfilled > value3) { float num7 = timesFulfilled - value3; float num8 = Mathf.Max(0.1f, ConfigManager.DampeningSharpness.Value); num6 /= 1f + Mathf.Pow(num7 / num8, 2f); } } num = Mathf.RoundToInt(Mathf.Clamp((float)previousQuota + num6, 0f, 1E+09f)); } int value4 = ConfigManager.QuotaCap.Value; if (value4 == -1) { return num; } return Mathf.Min(num, value4); } private static float CalculatePlayerMultiplier() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer) { return 1f; } int num = Mathf.Max(1, singleton.ConnectedClientsList?.Count ?? 1); int value = ConfigManager.PlayerThreshold.Value; int num2 = num - value; if (num2 <= 0) { return 1f; } int value2 = ConfigManager.PlayerCap.Value; int num3 = Mathf.Max(0, value2 - value); num2 = Mathf.Clamp(num2, 0, num3); return 1f + (float)num2 * Mathf.Max(0f, ConfigManager.MultPerPlayer.Value); } private static int CalculateRollover(int overage) { float value = ConfigManager.RolloverAmount.Value; if (value <= 0f || overage <= 0) { return 0; } return Mathf.RoundToInt((float)overage * Mathf.Clamp01(value)); } private static int SetDeadlineTimer(float dayDuration, ref int days, ref float timeUntilDeadline, int prevDays = -1) { int num3; if (ConfigManager.RandomizeDeadline.Value) { int num = Math.Max(1, ConfigManager.DeadlineMin.Value); int num2 = Math.Max(num, ConfigManager.DeadlineMax.Value); num3 = Random.Range(num, num2 + 1); if (ConfigManager.DeadlineMustChange.Value && num3 == prevDays && num != num2) { num3 = Random.Range(num, num2 + 1); } } else { num3 = Math.Max(1, ConfigManager.DaysToDeadline.Value); } days = num3; timeUntilDeadline = (float)num3 * dayDuration; return num3; } [HarmonyPatch("Awake")] [HarmonyPostfix] private static void TimeOfDay_Awake_Postfix(TimeOfDay __instance) { ApplyQuotaVariables(__instance); } [HarmonyPatch("Start")] [HarmonyPostfix] private static void TimeOfDay_Start_Postfix(TimeOfDay __instance) { if (__instance.timesFulfilledQuota == 0) { if (((NetworkBehaviour)__instance).IsServer && ConfigManager.RandomizeDeadline.Value) { NetworkSync.SyncDeadlineToClients(__instance.daysUntilDeadline); } string arg = (ConfigManager.RandomizeDeadline.Value ? $"randomized {ConfigManager.DeadlineMin.Value}-{ConfigManager.DeadlineMax.Value}d (first: {__instance.daysUntilDeadline}d)" : $"fixed {ConfigManager.DaysToDeadline.Value}d"); string text = ((ConfigManager.QuotaCap.Value != -1) ? $", cap={ConfigManager.QuotaCap.Value}" : ""); string text2 = ((ConfigManager.FinalLevel.Value != -1) ? $", finalLevel={ConfigManager.FinalLevel.Value} (+{ConfigManager.FinalIncrease.Value} flat)" : ""); string text3 = (ConfigManager.CreditPenaltiesEnabled.Value ? $"credits={ConfigManager.CreditPenaltyPercentPerPlayer.Value:P0}/player (cap {ConfigManager.CreditPenaltyPercentCap.Value:P0})" : "credits=off"); string text4 = (ConfigManager.QuotaPenaltiesEnabled.Value ? $"quota={ConfigManager.QuotaPenaltyPercentPerPlayer.Value:P0}/player (cap {ConfigManager.QuotaPenaltyPercentCap.Value:P0})" : "quota=off"); string text5 = $"scrap={ConfigManager.ScrapLossEnabled.Value}" + $", value={ConfigManager.ValueLossEnabled.Value}({ConfigManager.ValueLossPercent.Value:P0})" + $", equip={ConfigManager.EquipmentLossEnabled.Value}"; Plugin.Log.LogInfo((object)$"Quota: start={ConfigManager.StartingQuota.Value}, base+{ConfigManager.BaseIncrease.Value}/cycle, sharpness={ConfigManager.CurveSharpness.Value}{text}{text2}"); Plugin.Log.LogInfo((object)$"Deadline: {arg} | Credits start: {ConfigManager.StartingCredits.Value}"); Plugin.Log.LogInfo((object)("Penalties: " + text3 + " | " + text4)); Plugin.Log.LogInfo((object)("Losses: " + text5)); } } private static void ApplyQuotaVariables(TimeOfDay instance) { try { if (instance.quotaVariables != null) { instance.quotaVariables.startingQuota = ConfigManager.StartingQuota.Value; instance.quotaVariables.startingCredits = ConfigManager.StartingCredits.Value; if (ConfigManager.RandomizeDeadline.Value) { int num = Math.Max(1, ConfigManager.DeadlineMin.Value); int num2 = Math.Max(num, ConfigManager.DeadlineMax.Value); instance.quotaVariables.deadlineDaysAmount = Random.Range(num, num2 + 1); } else { instance.quotaVariables.deadlineDaysAmount = ConfigManager.DaysToDeadline.Value; } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to apply quota variables: {arg}"); } } [HarmonyPatch("UpdateProfitQuotaCurrentTime")] [HarmonyPostfix] private static void UpdateProfitQuotaCurrentTime_Postfix(TimeOfDay __instance, ref float ___timeUntilDeadline, ref float ___totalTime, ref int ___daysUntilDeadline) { if (ConfigManager.DisableQuota.Value) { ApplyDisableQuotaState(ref ___daysUntilDeadline, ref ___totalTime, ref ___timeUntilDeadline); } } [HarmonyPatch("SetBuyingRateForDay")] [HarmonyPostfix] private static void SetBuyingRateForDay_Postfix(TimeOfDay __instance, ref float ___timeUntilDeadline, ref float ___totalTime, ref int ___daysUntilDeadline) { if (ConfigManager.DisableQuota.Value) { ApplyDisableQuotaState(ref ___daysUntilDeadline, ref ___totalTime, ref ___timeUntilDeadline); } } private static void ApplyDisableQuotaState(ref int days, ref float totalTime, ref float timeUntilDeadline) { try { SetDeadlineTimer(totalTime, ref days, ref timeUntilDeadline); StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null) { instance.companyBuyingRate = 1f; if ((Object)(object)instance.deadlineMonitorText != (Object)null) { ((TMP_Text)instance.deadlineMonitorText).text = "DEADLINE:\nNEVER"; } if ((Object)(object)instance.profitQuotaMonitorText != (Object)null) { ((TMP_Text)instance.profitQuotaMonitorText).text = "QUOTA:\nDISABLED"; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("DisableQuota state update failed: " + ex.Message)); } } } }