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 Configurable Quota v1.0.1
BepInEx/plugins/ConfigurableQuota/ConfigurableQuota.dll
Decompiled a day agousing 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<int> StartingCredits; public static ConfigEntry<int> StartingQuota; public static ConfigEntry<int> DaysToDeadline; public static ConfigEntry<int> BaseIncrease; public static ConfigEntry<float> CurveSharpness; public static ConfigEntry<float> RandomizerMultiplier; public static ConfigEntry<int> FinalLevel; public static ConfigEntry<int> FinalIncrease; public static ConfigEntry<int> QuotaCap; public static ConfigEntry<bool> EnablePlayerMultiplier; public static ConfigEntry<int> PlayerThreshold; public static ConfigEntry<int> PlayerCap; public static ConfigEntry<float> MultPerPlayer; public static ConfigEntry<bool> DisableQuota; public static ConfigEntry<float> RolloverAmount; public static ConfigEntry<bool> CreditPenaltiesEnabled; public static ConfigEntry<bool> CreditPenaltiesOnGordion; public static ConfigEntry<float> CreditPenaltyPercentPerPlayer; public static ConfigEntry<bool> CreditPenaltiesDynamic; public static ConfigEntry<float> CreditPenaltyPercentCap; public static ConfigEntry<float> CreditPenaltyPercentThreshold; public static ConfigEntry<float> CreditPenaltyRecoveryBonus; public static ConfigEntry<bool> QuotaPenaltiesEnabled; public static ConfigEntry<bool> QuotaPenaltiesOnGordion; public static ConfigEntry<float> QuotaPenaltyPercentPerPlayer; public static ConfigEntry<bool> QuotaPenaltiesDynamic; public static ConfigEntry<float> QuotaPenaltyPercentCap; public static ConfigEntry<float> QuotaPenaltyPercentThreshold; public static ConfigEntry<float> QuotaPenaltyRecoveryBonus; public static ConfigEntry<bool> ScrapLossEnabled; public static ConfigEntry<float> ItemsSafeChance; public static ConfigEntry<float> LoseEachScrapChance; public static ConfigEntry<int> MaxLostScrapItems; public static ConfigEntry<bool> ValueLossEnabled; public static ConfigEntry<float> ValueLossPercent; public static ConfigEntry<bool> RandomizeDeadline; public static ConfigEntry<int> DeadlineMin; public static ConfigEntry<int> DeadlineMax; public static ConfigEntry<bool> DeadlineMustChange; public static ConfigEntry<bool> EnableGrowthDampening; public static ConfigEntry<int> DampeningStartAt; public static ConfigEntry<float> DampeningSharpness; public static ConfigEntry<bool> EquipmentLossEnabled; public static ConfigEntry<float> LoseEachEquipmentChance; public static ConfigEntry<int> MaxLostEquipmentItems; public static ConfigEntry<float> 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<int>("0. Basic", "StartingCredits", 60, "Starting credits for a new lobby."); StartingQuota = config.Bind<int>("0. Basic", "StartingQuota", 130, "Starting quota for a new lobby."); DaysToDeadline = config.Bind<int>("0. Basic", "DaysToDeadline", 3, "Number of days to meet each quota. Ignored if RandomizeDeadline is enabled."); RandomizeDeadline = config.Bind<bool>("0. Basic", "RandomizeDeadline", false, "Randomize the deadline each quota using Deadline Min/Max instead of a fixed Days To Deadline."); DeadlineMin = config.Bind<int>("0. Basic", "DeadlineMin", 3, "Minimum days for deadline. REQUIRES 'Randomize Deadline' SET TO TRUE"); DeadlineMax = config.Bind<int>("0. Basic", "DeadlineMax", 5, "Maximum days for deadline. REQUIRES 'Randomize Deadline' SET TO TRUE"); DeadlineMustChange = config.Bind<bool>("0. Basic", "DeadlineMustChange", true, "New deadline after fulfilling quota must differ from the previous one. REQUIRES 'Randomize Deadline' SET TO TRUE"); BaseIncrease = config.Bind<int>("0. Basic", "BaseIncrease", 100, "Base quota increase. Combined with 'Curve Sharpness' to calculate growth."); CurveSharpness = config.Bind<float>("0. Basic", "CurveSharpness", 16f, "Quota growth curve. Higher = slower growth. Formula: increase ≈ BaseIncrease x (1 + quotaCount²/Sharpness)"); RandomizerMultiplier = config.Bind<float>("0. Basic", "RandomizerMultiplier", 1f, "Adds variation to quota increases. 1 = ±50% variance (vanilla), 0 = no randomness, 2 = ±100% variance."); FinalLevel = config.Bind<int>("1. Leveling", "FinalLevel", -1, "When quota reaches this value, the Base Increase and Curve Sharpness are ignored. Set -1 to disable."); FinalIncrease = config.Bind<int>("1. Leveling", "FinalIncrease", 200, "Fixed increase amount used after reaching Final Level value."); QuotaCap = config.Bind<int>("1. Leveling", "QuotaCap", -1, "Maximum quota value, quota will never increase more than this amount. Set -1 for no limit."); EnableGrowthDampening = config.Bind<bool>("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<int>("1. Leveling", "DampeningStartAt", 6, "Number of quota cycles before dampening begins."); DampeningSharpness = config.Bind<float>("1. Leveling", "DampeningSharpness", 11f, "Controls dampening intensity. Lower values reduce growth more aggressively."); EnablePlayerMultiplier = config.Bind<bool>("2. PlayerScaling", "EnablePlayerMultiplier", false, "Scale quota increases based on player count."); PlayerThreshold = config.Bind<int>("2. PlayerScaling", "PlayerThreshold", 2, "Player count where scaling begins. Example: 2 means scaling starts at 3+ players."); PlayerCap = config.Bind<int>("2. PlayerScaling", "PlayerCap", 4, "Maximum players counted for scaling. Example: 4 means player 5+ will not increase the quota multiplier."); MultPerPlayer = config.Bind<float>("2. PlayerScaling", "MultPerPlayer", 0.25f, "Quota increase multiplier per extra player. Example: 0.5 = +50% increase per player above threshold."); DisableQuota = config.Bind<bool>("3. Optional", "DisableQuota", false, "Completely disables the quota system."); RolloverAmount = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); CreditPenaltiesEnabled = config.Bind<bool>("4. Penalties.Credits", "Enabled", false, "Reduce credits when crew members die."); CreditPenaltiesOnGordion = config.Bind<bool>("4. Penalties.Credits", "OnGordion", false, "Apply credit penalties even when visiting The Company."); CreditPenaltyPercentPerPlayer = config.Bind<float>("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<bool>("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<float>("4. Penalties.Credits", "PercentCap", 0.8f, "Maximum percentage of credits that can be lost."); CreditPenaltyPercentThreshold = config.Bind<float>("4. Penalties.Credits", "PercentThreshold", 0f, "Ignore penalties below this percentage. Example: 0.1 = penalties under 10% are not applied."); CreditPenaltyRecoveryBonus = config.Bind<float>("4. Penalties.Credits", "RecoveryBonus", 0f, "Reduce penalty if you recover bodies. Example: 0.5 = 50% penalty forgiveness if bodies brought back."); QuotaPenaltiesEnabled = config.Bind<bool>("5. Penalties.Quota", "Enabled", false, "Increase the current quota when crew members die."); QuotaPenaltiesOnGordion = config.Bind<bool>("5. Penalties.Quota", "OnGordion", false, "Apply quota penalties even when visiting The Company."); QuotaPenaltyPercentPerPlayer = config.Bind<float>("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<bool>("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<float>("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<float>("5. Penalties.Quota", "PercentThreshold", 0f, "Ignore penalties below this percentage. Example: 0.15 = increases under 15% are not applied."); QuotaPenaltyRecoveryBonus = config.Bind<float>("5. Penalties.Quota", "RecoveryBonus", 0f, "Reduce penalty if you recover bodies. Example: 0.5 = 50% penalty forgiveness if bodies brought back."); ScrapLossEnabled = config.Bind<bool>("6. Loss.Scrap", "Enabled", false, "Randomly lose collected scrap when all crew dies."); ItemsSafeChance = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); LoseEachScrapChance = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); MaxLostScrapItems = config.Bind<int>("6. Loss.Scrap", "MaxLostScrapItems", 2, "Maximum scrap that can be lost per round."); ValueLossEnabled = config.Bind<bool>("7. Loss.Value", "Enabled", false, "Reduce the scrap value of all ship items when the entire crew is wiped."); ValueLossPercent = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); EquipmentLossEnabled = config.Bind<bool>("8. Loss.Equipment", "Enabled", false, "Randomly lose purchased equipment when all crew dies."); LoseEachEquipmentChance = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); MaxLostEquipmentItems = config.Bind<int>("8. Loss.Equipment", "MaxLostEquipmentItems", 1, "Maximum equipment items that can be lost per round. Example: 1 = lose at most 1 item."); QuotaAnimationSpeed = config.Bind<float>("9. UI", "QuotaAnimationSpeed", 1f, new ConfigDescription("Speed multiplier for the new quota animation. Higher = faster.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>())); } } 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 <CustomRackUp>d__1 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float speed; public HUDManager hud; private int <quotaTextAmount>5__2; private int <target>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CustomRackUp>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; <quotaTextAmount>5__2 = 0; <target>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 (<quotaTextAmount>5__2 < <target>5__3) { float num = Time.deltaTime * 250f * speed; <quotaTextAmount>5__2 = (int)Mathf.Clamp((float)<quotaTextAmount>5__2 + num, (float)(<quotaTextAmount>5__2 + 3), (float)(<target>5__3 + 10)); ((TMP_Text)hud.newProfitQuotaText).text = "$" + <quotaTextAmount>5__2; <>2__current = null; <>1__state = 2; return true; } ((TMP_Text)hud.newProfitQuotaText).text = "$" + <target>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(<CustomRackUp>d__1))] private static IEnumerator CustomRackUp(HUDManager hud, float speed) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <CustomRackUp>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<Terminal>()?.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<int>? _syncCreditsMessage; private static LNetworkMessage<int>? _syncQuotaMessage; private static LNetworkMessage<SyncValueLossData>? _syncValueLossMessage; private static LNetworkMessage<int>? _syncDeadlineMessage; public static void Initialize() { try { _syncCreditsMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncCredits", (Action<int, ulong>)null, (Action<int>)OnCreditsReceived, (Action<int, ulong>)null); _syncQuotaMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncQuota", (Action<int, ulong>)null, (Action<int>)OnQuotaReceived, (Action<int, ulong>)null); _syncValueLossMessage = LNetworkMessage<SyncValueLossData>.Connect("ConfigurableQuota_SyncValueLoss", (Action<SyncValueLossData, ulong>)null, (Action<SyncValueLossData>)OnValueLossReceived, (Action<SyncValueLossData, ulong>)null); _syncDeadlineMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncDeadline", (Action<int, ulong>)null, (Action<int>)OnDeadlineReceived, (Action<int, ulong>)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<Terminal>(); 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>(); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { try { NetworkObject component = ((Component)val).GetComponent<NetworkObject>(); 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<RagdollGrabbableObject>(); foreach (RagdollGrabbableObject val3 in array) { if ((Object)(object)((val3 == null) ? null : ((Component)val3).GetComponent<DeadBodyInfo>()?.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 <FinalizeCreditPenaltyAfterDelay>d__12 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int desiredFinal; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FinalizeCreditPenaltyAfterDelay>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<Terminal>(); if ((Object)(object)val != (Object)null) { return val.groupCredits; } } catch { } return 0; } [IteratorStateMachine(typeof(<FinalizeCreditPenaltyAfterDelay>d__12))] private static IEnumerator FinalizeCreditPenaltyAfterDelay(int desiredFinal) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FinalizeCreditPenaltyAfterDelay>d__12(0) { desiredFinal = desiredFinal }; } private static void SetCredits(int value) { try { Terminal val = Object.FindObjectOfType<Terminal>(); 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<GrabbableObject>(); 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<GrabbableObject>(); 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<NetworkObject>(); 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<string> list = new List<string>(); 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<string> list = new List<string>(); 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<SyncValueLossData> list = new List<SyncValueLossData>(); 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<NetworkObject>(); 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<NetworkObject>(); 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)); } } } }