Decompiled source of Configurable Quota v1.6.0

BepInEx/plugins/ConfigurableQuota/ConfigurableQuota.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
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.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ConfigurableQuota.Compat;
using ConfigurableQuota.Patches;
using GameNetcodeStuff;
using HarmonyLib;
using LethalNetworkAPI;
using Microsoft.CodeAnalysis;
using OpenLib.Events;
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.6.0.0")]
[assembly: AssemblyInformationalVersion("1.6.0+75add316a59450188e0b11032f5d681b0094bedd")]
[assembly: AssemblyProduct("ConfigurableQuota")]
[assembly: AssemblyTitle("ConfigurableQuota")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.6.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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> OvertimeExcludesRollover;

		public static ConfigEntry<float> RolloverWipePenalty;

		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> EnableDeadlineBonus;

		public static ConfigEntry<int> DeadlineBonusPerQuota;

		public static ConfigEntry<int> DeadlineBonusMax;

		public static ConfigEntry<string> DeadlineBonusThresholds;

		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;

		public static ConfigEntry<bool> MinMaxRateEnabled;

		public static ConfigEntry<float> MinRate;

		public static ConfigEntry<float> MaxRate;

		public static ConfigEntry<bool> RandomRateEnabled;

		public static ConfigEntry<bool> LastDayRateEnabled;

		public static ConfigEntry<float> LastDayRangeChance;

		public static ConfigEntry<float> LastDayMinRate;

		public static ConfigEntry<float> LastDayMaxRate;

		public static ConfigEntry<bool> JackpotEnabled;

		public static ConfigEntry<bool> JackpotLastDayOnly;

		public static ConfigEntry<float> JackpotChance;

		public static ConfigEntry<float> JackpotMinRate;

		public static ConfigEntry<float> JackpotMaxRate;

		public static ConfigEntry<bool> BuyRateAlertEnabled;

		public static ConfigEntry<bool> JackpotAlertEnabled;

		public static ConfigEntry<float> AlertDelaySeconds;

		public static ConfigEntry<bool> DynamicInteriorSizeEnabled;

		public static ConfigEntry<float> DynamicInteriorSizeBase;

		public static ConfigEntry<int> DynamicInteriorSizePlayerThreshold;

		public static ConfigEntry<PlayerScalingDirection> DynamicInteriorSizeDirection;

		public static ConfigEntry<float> DynamicInteriorSizeMultPerPlayer;

		public static ConfigEntry<bool> DynamicScrapValueEnabled;

		public static ConfigEntry<int> DynamicScrapValueOffset;

		public static ConfigEntry<float> DynamicScrapValueMinMult;

		public static ConfigEntry<float> DynamicScrapValueMaxMult;

		public static ConfigEntry<int> DynamicScrapValuePlayerThreshold;

		public static ConfigEntry<PlayerScalingDirection> DynamicScrapValueDirection;

		public static ConfigEntry<float> DynamicScrapValueMultPerPlayer;

		public static ConfigEntry<bool> DynamicScrapAmountEnabled;

		public static ConfigEntry<int> DynamicScrapAmountValuePerItem;

		public static ConfigEntry<float> DynamicScrapAmountMinFraction;

		public static ConfigEntry<int> DynamicScrapAmountCap;

		public static ConfigEntry<int> DynamicScrapAmountPlayerThreshold;

		public static ConfigEntry<PlayerScalingDirection> DynamicScrapAmountDirection;

		public static ConfigEntry<float> DynamicScrapAmountMultPerPlayer;

		public static ConfigEntry<bool> DynamicEnemyPowerEnabled;

		public static ConfigEntry<bool> DynamicEnemyPowerScaleInside;

		public static ConfigEntry<bool> DynamicEnemyPowerScaleOutside;

		public static ConfigEntry<bool> DynamicEnemyPowerScaleDaytime;

		public static ConfigEntry<int> DynamicEnemyPowerPlayerThreshold;

		public static ConfigEntry<float> DynamicEnemyPowerMultPerPlayer;

		public static ConfigEntry<float> DynamicEnemyPowerMaxFactor;

		internal static void Initialize(ConfigFile config)
		{
			//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Expected O, but got Unknown
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			//IL_034b: Expected O, but got Unknown
			//IL_052e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0538: Expected O, but got Unknown
			//IL_0566: Unknown result type (might be due to invalid IL or missing references)
			//IL_0570: Expected O, but got Unknown
			//IL_05d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05de: Expected O, but got Unknown
			//IL_0627: Unknown result type (might be due to invalid IL or missing references)
			//IL_0631: Expected O, but got Unknown
			//IL_067a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0684: Expected O, but got Unknown
			//IL_06cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d7: Expected O, but got Unknown
			//IL_0705: Unknown result type (might be due to invalid IL or missing references)
			//IL_070f: Expected O, but got Unknown
			//IL_0773: Unknown result type (might be due to invalid IL or missing references)
			//IL_077d: Expected O, but got Unknown
			//IL_07ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b5: Expected O, but got Unknown
			//IL_07e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ed: Expected O, but got Unknown
			//IL_0851: Unknown result type (might be due to invalid IL or missing references)
			//IL_085b: Expected O, but got Unknown
			//IL_0889: Unknown result type (might be due to invalid IL or missing references)
			//IL_0893: Expected O, but got Unknown
			//IL_08c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_08cb: Expected O, but got Unknown
			//IL_092f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0939: Expected O, but got Unknown
			//IL_0982: Unknown result type (might be due to invalid IL or missing references)
			//IL_098c: Expected O, but got Unknown
			//IL_09af: Unknown result type (might be due to invalid IL or missing references)
			//IL_09b9: Expected O, but got Unknown
			//IL_0a02: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a0c: Expected O, but got Unknown
			//IL_0a70: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a7a: Expected O, but got Unknown
			//IL_0aa8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ab2: Expected O, but got Unknown
			//IL_0ad5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0adf: Expected O, but got Unknown
			//IL_0b28: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b32: Expected O, but got Unknown
			//IL_0b74: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b7e: Expected O, but got Unknown
			//IL_0bac: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bb6: Expected O, but got Unknown
			//IL_0bf4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bfe: Expected O, but got Unknown
			//IL_0c47: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c51: Expected O, but got Unknown
			//IL_0ce0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cea: Expected O, but got Unknown
			//IL_0d18: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d22: Expected O, but got Unknown
			//IL_0d50: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d5a: 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 the deadline. REQUIRES 'Randomize Deadline' SET TO TRUE.");
			DeadlineMax = config.Bind<int>("0. Basic", "DeadlineMax", 5, "Maximum days for the deadline. REQUIRES 'Randomize Deadline' SET TO TRUE.");
			DeadlineMustChange = config.Bind<bool>("0. Basic", "DeadlineMustChange", true, "After fulfilling a quota, next random deadline has to be different from the previous one. REQUIRES 'Randomize Deadline' SET TO TRUE.");
			EnableDeadlineBonus = config.Bind<bool>("0. Basic", "EnableDeadlineBonus", false, "Increase deadline as quota grows using either fixed credit interval or exact thresholds.");
			DeadlineBonusPerQuota = config.Bind<int>("0. Basic", "DeadlineBonusPerQuota", 1000, "Add one day to deadline for every this many credits of quota. 1000 means 1000 quota gets +1 day, 2000 gets +2 and so on. REQUIRES 'Enable Deadline Bonus' SET TO TRUE.");
			DeadlineBonusMax = config.Bind<int>("0. Basic", "DeadlineBonusMax", 3, "Maximum days quota bonus can add on top of deadline. 0 means no limit. Applies to both interval and thresholds. REQUIRES 'Enable Deadline Bonus' SET TO TRUE.");
			DeadlineBonusThresholds = config.Bind<string>("0. Basic", "DeadlineBonusThresholds", "", "Exact quota thresholds instead of fixed interval, comma separated. Example - 1000,2500,5000 adds one day at each one reached. Overrides 'Deadline Bonus Per Quota' whenever it is set. REQUIRES 'Enable Deadline Bonus' SET TO TRUE.");
			BaseIncrease = config.Bind<int>("0. Basic", "BaseIncrease", 100, "Base amount the quota increase per each quota. Combined with CurveSharpness as: increase ≈ BaseIncrease * (1 + quota² / Sharpness). With defaults (100, 16) at quota 3, that's 100 * (1 + 9/16) ≈ 156.");
			CurveSharpness = config.Bind<float>("0. Basic", "CurveSharpness", 16f, "Controls how fast the quota ramps up. Higher = slower growth. Formula: increase ≈ BaseIncrease * (1 + quota² / Sharpness). With BaseIncrease=100, Sharpness=16, quota 5 -> 100 * (1 + 25/16) ≈ 256.");
			RandomizerMultiplier = config.Bind<float>("0. Basic", "RandomizerMultiplier", 1f, "Random variance on each quota increase. Multiplies by a factor in [1 - 0.5*M, 1 + 0.5*M]. 0 = no randomness, 1 = ±50% (vanilla), 2 = ±100%.");
			FinalLevel = config.Bind<int>("1. Leveling", "FinalLevel", -1, "Once the previous quota hits this value, growth stops using the curve and switches to a flat Final Increase per quota. With FinalLevel=10000 and FinalIncrease=200, every quota after $10000 just adds $200. 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, "Slows quota growth down after a while. After Dampening Start At fulfilled quotas, the curve increase gets divided by (1 + (excess / DampeningSharpness)²), where excess = currentQuota - DampeningStartAt.");
			DampeningStartAt = config.Bind<int>("1. Leveling", "DampeningStartAt", 6, "How many quotas you have to clear before dampening starts. 6 means quotas 1 - 6 are untouched and quota 7 is the first one slowed down.");
			DampeningSharpness = config.Bind<float>("1. Leveling", "DampeningSharpness", 11f, "Lower values dampen harder. At Start At=6, Sharpness=11, quota 10 -> divisor = 1 + (4/11)² ≈ 1.13, so growth shrinks by ~12%.");
			EnablePlayerMultiplier = config.Bind<bool>("2. Player.Scaling", "EnablePlayerMultiplier", false, "Scale each quota increase by lobby size. Formula: (1 + extra * MultPerPlayer), where extra = clamp(playerCount - PlayerThreshold, 0, PlayerCap - PlayerThreshold).");
			PlayerThreshold = config.Bind<int>("2. Player.Scaling", "PlayerThreshold", 2, "Player count where the scaling starts. 2 means scaling starts at 3+ players.");
			PlayerCap = config.Bind<int>("2. Player.Scaling", "PlayerCap", 4, "Player count where scaling stops. 4 means player 5+ don't bump the multiplier further.");
			MultPerPlayer = config.Bind<float>("2. Player.Scaling", "MultPerPlayer", 0.25f, "Extra multiplier each player adds past the threshold. With 4 players, Threshold=2, Cap=4, MultPerPlayer=0.25, you get 1 + 2*0.25 = 1.5x on quota increases.");
			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>()));
			OvertimeExcludesRollover = config.Bind<bool>("3. Optional", "OvertimeExcludesRollover", false, "If true, overtime bonus only counts scrap you sold at the Company above the quota. Stops rollover from inflating the bonus. Only affects scrap conversion part (sold / 5), not the days-left part. Requires RolloverAmount > 0 to have any effect.");
			RolloverWipePenalty = config.Bind<float>("3. Optional", "RolloverWipePenalty", 0f, new ConfigDescription("Percentage of banked rollover that is lost when the entire crew dies. 0.5 = lose 50%. Requires RolloverAmount > 0 to have any effect.", (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. 0.15 = lose 15% of credits per death. Ignored when Dynamic is set to true.");
			CreditPenaltiesDynamic = config.Bind<bool>("4. Penalties.Credits", "Dynamic", false, "Scale credit penalty by: (dead/total) * Percent Cap. With 2 dead out of 8 and Percent Cap=0.05 -> (2/8)*0.05 = 1.25%. When false, the penalty is Percent Per Player * dead, capped at Percent Cap.");
			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. 0.1 = penalties under 10% are not applied.");
			CreditPenaltyRecoveryBonus = config.Bind<float>("4. Penalties.Credits", "RecoveryBonus", 0f, "Reduce the penalty when body is recovered. Final = base * (1 - RecoveryBonus * recovered/dead). With 4 dead, 2 recovered, and RecoveryBonus=0.5 the penalty drops by 25% (base * 0.75).");
			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. 0.1 = +10% to current quota per death. Ignored when Dynamic is true.");
			QuotaPenaltiesDynamic = config.Bind<bool>("5. Penalties.Quota", "Dynamic", false, "Scale quota increase by: (dead/total) * Percent Cap. With 2 dead out of 8 and Percent Cap=0.5 -> (2/8)*0.5 = 12.5%. When false, the penalty is Percent Per Player * dead, capped at Percent Cap.");
			QuotaPenaltyPercentCap = config.Bind<float>("5. Penalties.Quota", "PercentCap", 0.5f, "Maximum percentage the quota can increase. 0.5 = quota can increase by at most 50%.");
			QuotaPenaltyPercentThreshold = config.Bind<float>("5. Penalties.Quota", "PercentThreshold", 0f, "Ignore penalties below this percentage. 0.15 = anything under 15% are not applied.");
			QuotaPenaltyRecoveryBonus = config.Bind<float>("5. Penalties.Quota", "RecoveryBonus", 0f, "Reduce the quota penalty when bodies are recovered. Final = base * (1 - RecoveryBonus * recovered/dead). With 4 dead, 2 recovered, Recovery Bonus=0.5 penalty shrinks by 25% (base * 0.75).");
			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. Combined with Lose Each Scrap Chance, actual loss chance is (1 - SafeChance) * LoseChance. So Safe=0.5, Lose=0.1 -> 5% per item.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			LoseEachScrapChance = config.Bind<float>("6. Loss.Scrap", "LoseEachScrapChance", 0.1f, new ConfigDescription("Chance for unprotected scrap to get lost. 0.2 means 20% on the items that weren't saved by Items Safe Chance.", (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("How much of the scrap value to reduce per crew wipe. 0.25 = each item loses 25%.", (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. 0.1 = 10% 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. 1 = at most one item is lost.");
			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>()));
			MinMaxRateEnabled = config.Bind<bool>("X. Buy.Rate", "MinMaxEnabled", false, "Keep the Company's daily buy rate between Min Rate and Max Rate. Random Rate Enabled needs to be true.");
			MinRate = config.Bind<float>("X. Buy.Rate", "MinRate", 0.2f, new ConfigDescription("Minimum buy rate the Company will pay. 0.2 = 20%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			MaxRate = config.Bind<float>("X. Buy.Rate", "MaxRate", 1.2f, new ConfigDescription("Maximum buy rate the Company will pay. 1.2 = 120%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			RandomRateEnabled = config.Bind<bool>("X. Buy.Rate", "RandomRateEnabled", false, "Roll a random buy rate between Min Rate and Max Rate every day. Needs Min Max Enabled. With MinRate=0.4 and MaxRate=1.5 the daily rate lands somewhere between 40% and 150%.");
			LastDayRateEnabled = config.Bind<bool>("X. Buy.Rate", "LastDayRateEnabled", false, "On the deadline's last day, force the rate through Last Day Min Rate and Last Day Max Rate. If min and max are equal you always get that rate. If they differ, roll Last Day Range Chance: a hit picks a random value in the range, a miss falls back to 100%.");
			LastDayRangeChance = config.Bind<float>("X. Buy.Rate", "LastDayRangeChance", 0.3f, new ConfigDescription("Chance the last-day rate actually uses the range instead of falling back to 100%. 0.3 = 30%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			LastDayMinRate = config.Bind<float>("X. Buy.Rate", "LastDayMinRate", 1f, new ConfigDescription("Minimum buy rate on the last day. 1.0 = 100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			LastDayMaxRate = config.Bind<float>("X. Buy.Rate", "LastDayMaxRate", 1.2f, new ConfigDescription("Maximum buy rate on the last day. 1.2 = 120%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			JackpotEnabled = config.Bind<bool>("X. Buy.Rate", "JackpotEnabled", false, "Let the daily roll try for a jackpot rate. Checked before Last Day Rate or RandomRate. A hit picks a random value between JackpotMinRate and JackpotMaxRate (or that exact value if they match).");
			JackpotLastDayOnly = config.Bind<bool>("X. Buy.Rate", "JackpotLastDayOnly", true, "Only roll for a jackpot on the deadline's last day.");
			JackpotChance = config.Bind<float>("X. Buy.Rate", "JackpotChance", 0.01f, new ConfigDescription("Chance the daily roll lands a jackpot. 0.01 = 1% per day. If Jackpot Last Day Only is true, this only fires on the last day.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			JackpotMinRate = config.Bind<float>("X. Buy.Rate", "JackpotMinRate", 1.5f, new ConfigDescription("Minimum jackpot rate. 1.5 = 150%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			JackpotMaxRate = config.Bind<float>("X. Buy.Rate", "JackpotMaxRate", 3f, new ConfigDescription("Maximum jackpot rate. 3.0 = 300%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			BuyRateAlertEnabled = config.Bind<bool>("X. Buy.Rate", "BuyRateAlertEnabled", false, "Show a yellow on-screen alert with the new buy rate each day. Local to this client.");
			JackpotAlertEnabled = config.Bind<bool>("X. Buy.Rate", "JackpotAlertEnabled", false, "Show a red SCRAP EMERGENCY alert with sound when a jackpot is rolled. Local to this client.");
			AlertDelaySeconds = config.Bind<float>("X. Buy.Rate", "AlertDelaySeconds", 3f, new ConfigDescription("How long to wait before the buy-rate alert pops up. 3s feels fine solo. Bump to 8+ if BetterEXP or DiscountAlerts step on it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			DynamicInteriorSizeEnabled = config.Bind<bool>("A. Dynamic.Interior.Size", "Enabled", false, "Resize the moons interior based on lobby size.");
			DynamicInteriorSizeBase = config.Bind<float>("A. Dynamic.Interior.Size", "BaseSize", 1f, new ConfigDescription("factorySizeMultiplier at the PlayerThreshold. 1.0 = vanilla size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			DynamicInteriorSizePlayerThreshold = config.Bind<int>("A. Dynamic.Interior.Size", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. With PerExtraPlayer it activates above this, with PerMissingPlayer it activates below.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
			DynamicInteriorSizeDirection = config.Bind<PlayerScalingDirection>("A. Dynamic.Interior.Size", "ScalingDirection", PlayerScalingDirection.PerExtraPlayer, "PerExtraPlayer: more players = bigger dungeon. PerMissingPlayer: fewer players = bigger dungeon.");
			DynamicInteriorSizeMultPerPlayer = config.Bind<float>("A. Dynamic.Interior.Size", "MultPerPlayer", 0.1f, new ConfigDescription("How much the size multiplier changes per qualifying player. threshold=2, PerExtraPlayer, this=0.1 -> 3 players get BaseSize * 1.1.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			DynamicScrapValueEnabled = config.Bind<bool>("B. Dynamic.Scrap.Value", "Enabled", false, "Scale the moons min/max scrap value by player count.");
			DynamicScrapValueOffset = config.Bind<int>("B. Dynamic.Scrap.Value", "ScrapValueOffset", 0, "Flat credits added to both min and max after scaling. Leave at 0 to only use proportional scaling.");
			DynamicScrapValueMinMult = config.Bind<float>("B. Dynamic.Scrap.Value", "MinValueMultiplier", 1f, new ConfigDescription("minTotalScrapValue = round(baseMinTotalScrapValue * this * factor) + ScrapValueOffset.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			DynamicScrapValueMaxMult = config.Bind<float>("B. Dynamic.Scrap.Value", "MaxValueMultiplier", 1f, new ConfigDescription("maxTotalScrapValue = round(baseMaxTotalScrapValue * this * factor) + ScrapValueOffset. Keep it at or above MinValueMultiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			DynamicScrapValuePlayerThreshold = config.Bind<int>("B. Dynamic.Scrap.Value", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. With PerExtraPlayer it activates above this, with PerMissingPlayer it activates below.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
			DynamicScrapValueDirection = config.Bind<PlayerScalingDirection>("B. Dynamic.Scrap.Value", "ScalingDirection", PlayerScalingDirection.PerExtraPlayer, "PerMissingPlayer: fewer players = more scrap. PerExtraPlayer: more players = more scrap.");
			DynamicScrapValueMultPerPlayer = config.Bind<float>("B. Dynamic.Scrap.Value", "MultPerPlayer", 0.15f, new ConfigDescription("Boost per qualifying player. threshold=2, PerMissingPlayer, this=0.15 -> solo gets factor=1.15.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			DynamicScrapAmountEnabled = config.Bind<bool>("C. Dynamic.Scrap.Amount", "Enabled", false, "Scale the moons min/max scrap item count off baseMinTotalScrapValue.");
			DynamicScrapAmountValuePerItem = config.Bind<int>("C. Dynamic.Scrap.Amount", "ValuePerScrapItem", 25, new ConfigDescription("Divisor that turns scaled baseMinTotalScrapValue into item count. maxScrap = (baseMinTotalScrapValue * factor) / this. Lower numbers mean more items per moon. At baseMinTotalScrapValue=200, factor=1.15, this=25 you get maxScrap = floor(230/25) = 9.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>()));
			DynamicScrapAmountMinFraction = config.Bind<float>("C. Dynamic.Scrap.Amount", "MinScrapFraction", 0.6f, new ConfigDescription("minScrap = round(maxScrap * this). At maxScrap=9 and this=0.6 you get minScrap = round(5.4) = 5.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DynamicScrapAmountCap = config.Bind<int>("C. Dynamic.Scrap.Amount", "MaxScrapItemsCap", -1, "Hard upper bound on maxScrap no matter what the formula says. Set -1 to turn cap off.");
			DynamicScrapAmountPlayerThreshold = config.Bind<int>("C. Dynamic.Scrap.Amount", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. With PerExtraPlayer it activates above this, with PerMissingPlayer it activates below.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
			DynamicScrapAmountDirection = config.Bind<PlayerScalingDirection>("C. Dynamic.Scrap.Amount", "ScalingDirection", PlayerScalingDirection.PerExtraPlayer, "PerMissingPlayer: fewer players = more items. PerExtraPlayer: more players = more items.");
			DynamicScrapAmountMultPerPlayer = config.Bind<float>("C. Dynamic.Scrap.Amount", "MultPerPlayer", 0.15f, new ConfigDescription("Boost per qualifying player. threshold=2, PerMissingPlayer, this=0.15 -> solo gets factor=1.15.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			DynamicEnemyPowerEnabled = config.Bind<bool>("D. Dynamic.Enemy.Power", "Enabled", false, "Scale moons enemy power budget by player count. Higher budget = more / stronger enemies can spawn.");
			DynamicEnemyPowerScaleInside = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleInside", true, "Apply scaling to maxEnemyPowerCount (inside enemies).");
			DynamicEnemyPowerScaleOutside = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleOutside", true, "Apply scaling to maxOutsideEnemyPowerCount (night time outside enemies).");
			DynamicEnemyPowerScaleDaytime = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleDaytime", false, "Apply scaling to maxDaytimeEnemyPowerCount (daytime outside enemies).");
			DynamicEnemyPowerPlayerThreshold = config.Bind<int>("D. Dynamic.Enemy.Power", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. More players above this = more enemies.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
			DynamicEnemyPowerMultPerPlayer = config.Bind<float>("D. Dynamic.Enemy.Power", "MultPerPlayer", 0.15f, new ConfigDescription("Boost per qualifying player. threshold=2, PerExtraPlayer, this=0.15: 4 players -> factor = 1 + 2*0.15 = 1.30.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			DynamicEnemyPowerMaxFactor = config.Bind<float>("D. Dynamic.Enemy.Power", "MaxFactor", 3f, new ConfigDescription("Safety cap on the resolved factor. Floor is always 1.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>()));
		}
	}
	internal static class ModGUIDs
	{
		public const string LETHAL_NETWORK_API_GUID = "LethalNetworkAPI";

		public const string LETHAL_CONSTELLATIONS_GUID = "com.github.darmuh.LethalConstellations";

		public const string LETHAL_MOON_UNLOCKS_GUID = "com.xmods.lethalmoonunlocks";

		public const string OPEN_LIB_GUID = "darmuh.OpenLib";

		public const string LLL_GUID = "imabatby.lethallevelloader";

		public const string LUNAR_CONFIG_GUID = "Crafty.LunarConfig";

		public const string SELF_SORTING_STORAGE_GUID = "zigzag.SelfSortingStorage";

		public const string MORE_SHIP_UPGRADES_GUID = "com.malco.lethalcompany.moreshipupgrades";

		public const string SCRAP_INSURANCE_GUID = "com.github.WhiteSpike.ScrapInsurance";
	}
	[BepInPlugin("com.seeya.configurablequota", "Configurable Quota", "1.6.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[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);
			ConstellationDeadlineConfig.Initialize();
			NetworkSync.Initialize();
			_harmony.PatchAll();
			SelfSortingStorageWipePatch.TryPatch(_harmony);
			OpenLibEventBridge.TrySubscribe();
			Log.LogInfo((object)"Configurable Quota is loaded!");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "com.seeya.configurablequota";

		public const string PLUGIN_NAME = "Configurable Quota";

		public const string PLUGIN_VERSION = "1.6.0";
	}
}
namespace ConfigurableQuota.Patches
{
	[HarmonyPatch(typeof(TimeOfDay))]
	internal static class BuyingRatePatch
	{
		private const float ReapplyDelaySeconds = 3f;

		[HarmonyPatch("SetBuyingRateForDay")]
		[HarmonyPostfix]
		[HarmonyPriority(800)]
		private static void SetBuyingRateForDay_Postfix(TimeOfDay __instance)
		{
			try
			{
				if (((NetworkBehaviour)__instance).IsServer && !ConfigManager.DisableQuota.Value)
				{
					StartOfRound instance = StartOfRound.Instance;
					if (!((Object)(object)instance == (Object)null))
					{
						float companyBuyingRate = instance.companyBuyingRate;
						int daysUntilDeadline = __instance.daysUntilDeadline;
						(float rate, bool isJackpot, string source) tuple = ResolveRate(companyBuyingRate, daysUntilDeadline);
						float item = tuple.rate;
						bool item2 = tuple.isJackpot;
						string item3 = tuple.source;
						instance.companyBuyingRate = item;
						((MonoBehaviour)__instance).StartCoroutine(FinalizeBuyRate(item2, item3));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not apply buy rate modifiers: " + ex.Message));
			}
		}

		internal static void ApplyReceivedBuyingRate(float rate, bool isJackpot, bool silent = false)
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.companyBuyingRate = rate;
			}
			if (!silent)
			{
				DisplayBuyRateAlert(rate, isJackpot);
			}
		}

		private static (float rate, bool isJackpot, string source) ResolveRate(float vanillaRate, int daysUntilDeadline)
		{
			bool value = ConfigManager.MinMaxRateEnabled.Value;
			float value2 = ConfigManager.MinRate.Value;
			float num = Mathf.Max(value2, ConfigManager.MaxRate.Value);
			bool value3 = ConfigManager.JackpotEnabled.Value;
			bool value4 = ConfigManager.JackpotLastDayOnly.Value;
			float num2 = Mathf.Clamp01(ConfigManager.JackpotChance.Value);
			float value5 = ConfigManager.JackpotMinRate.Value;
			float num3 = Mathf.Max(value5, ConfigManager.JackpotMaxRate.Value);
			bool value6 = ConfigManager.LastDayRateEnabled.Value;
			float num4 = Mathf.Clamp01(ConfigManager.LastDayRangeChance.Value);
			float value7 = ConfigManager.LastDayMinRate.Value;
			float num5 = Mathf.Max(value7, ConfigManager.LastDayMaxRate.Value);
			bool value8 = ConfigManager.RandomRateEnabled.Value;
			bool flag = daysUntilDeadline == 0;
			if (value3 && (!value4 || flag) && Random.value <= num2)
			{
				float item = ((value5 == num3) ? value5 : Random.Range(value5, num3));
				return (rate: item, isJackpot: true, source: value4 ? "jackpot last-day" : "jackpot any-day");
			}
			if (value6 && flag)
			{
				if (value7 == num5)
				{
					return (rate: value7, isJackpot: false, source: "last-day fixed");
				}
				if (Random.value <= num4)
				{
					return (rate: Random.Range(value7, num5), isJackpot: false, source: "last-day ranged");
				}
				return (rate: 1f, isJackpot: false, source: "last-day fallback");
			}
			if (value8 && value)
			{
				return (rate: Random.Range(value2, num), isJackpot: false, source: "random in range");
			}
			if (value)
			{
				return (rate: Mathf.Clamp(vanillaRate, value2, num), isJackpot: false, source: "min/max clamp");
			}
			return (rate: vanillaRate, isJackpot: false, source: "vanilla");
		}

		private static IEnumerator FinalizeBuyRate(bool isJackpot, string source)
		{
			yield return null;
			StartOfRound instance = StartOfRound.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				float finalRate = instance.companyBuyingRate;
				Plugin.Log.LogInfo((object)$"Buy rate set to {Mathf.RoundToInt(finalRate * 100f)}% ({source}, jackpot={isJackpot}).");
				NetworkSync.SyncBuyingRateToClients(finalRate, isJackpot);
				DisplayBuyRateAlert(finalRate, isJackpot);
				yield return (object)new WaitForSeconds(3f);
				instance = StartOfRound.Instance;
				if (!((Object)(object)instance == (Object)null) && !Mathf.Approximately(instance.companyBuyingRate, finalRate))
				{
					instance.companyBuyingRate = finalRate;
					NetworkSync.SyncBuyingRateToClients(finalRate, isJackpot, silent: true);
					Plugin.Log.LogDebug((object)$"Re-applied buy rate {Mathf.RoundToInt(finalRate * 100f)}% after {3f:0.#}s.");
				}
			}
		}

		internal static void DisplayBuyRateAlert(float rate, bool isJackpot)
		{
			try
			{
				HUDManager instance = HUDManager.Instance;
				TimeOfDay instance2 = TimeOfDay.Instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && (isJackpot ? ConfigManager.JackpotAlertEnabled.Value : ConfigManager.BuyRateAlertEnabled.Value))
				{
					int rateRounded = Mathf.RoundToInt(rate * 100f);
					float delay = Mathf.Max(0f, ConfigManager.AlertDelaySeconds.Value);
					((MonoBehaviour)instance2).StartCoroutine(AlertCoroutine(rateRounded, isJackpot, delay));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("Could not schedule buy-rate alert: " + ex.Message));
			}
		}

		private static IEnumerator AlertCoroutine(int rateRounded, bool isJackpot, float delay)
		{
			yield return (object)new WaitForSecondsRealtime(delay);
			HUDManager instance = HUDManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				yield break;
			}
			if (isJackpot)
			{
				instance.DisplayTip("<color=#ffc526>SCRAP EMERGENCY</color>", $"<color=#fcbf17>\n* Buying rates have jumped to {rateRounded}%</color>", true, false, "LC_JackpotTip1");
				if ((Object)(object)instance.UIAudio != (Object)null && (Object)(object)instance.globalNotificationSFX != (Object)null)
				{
					instance.UIAudio.PlayOneShot(instance.globalNotificationSFX);
				}
			}
			else
			{
				instance.DisplayTip("New Scrap Rate", $"\n* Buying rates have changed to {rateRounded}%", false, false, "LC_JackpotTip2");
			}
		}
	}
	[HarmonyPatch(typeof(DepositItemsDesk))]
	internal static class DepositItemsDeskPatches
	{
		internal static int SoldThisQuota;

		[HarmonyPatch("SellAndDisplayItemProfits")]
		[HarmonyPostfix]
		private static void SellAndDisplayItemProfits_Postfix(int profit)
		{
			if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer)
			{
				SoldThisQuota += profit;
			}
		}
	}
	public enum PlayerScalingDirection
	{
		PerMissingPlayer,
		PerExtraPlayer
	}
	[HarmonyPatch(typeof(RoundManager))]
	internal static class DynamicLevelPatches
	{
		private static int _savedMinScrapValue;

		private static int _savedMaxScrapValue;

		private static int _savedMinScrap;

		private static int _savedMaxScrap;

		private static int _savedMaxEnemyPower;

		private static int _savedMaxOutsidePower;

		private static int _savedMaxDaytimePower;

		private static bool _enemyPowerSaved;

		[HarmonyPatch("GenerateNewFloor")]
		[HarmonyPrefix]
		[HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })]
		private static void GenerateNewFloor_Prefix(RoundManager __instance)
		{
			if (ShouldRunServer(__instance, out SelectableLevel level) && ConfigManager.DynamicInteriorSizeEnabled.Value)
			{
				float num = ComputePlayerFactor(ConfigManager.DynamicInteriorSizePlayerThreshold.Value, ConfigManager.DynamicInteriorSizeMultPerPlayer.Value, ConfigManager.DynamicInteriorSizeDirection.Value);
				float num2 = (level.factorySizeMultiplier = ConfigManager.DynamicInteriorSizeBase.Value * num);
				Plugin.Log.LogInfo((object)$"Dynamic interior size - factorySizeMultiplier = {num2:F2} (players={GetPlayerCount()}, factor={num:F2}).");
			}
		}

		[HarmonyPatch("SpawnScrapInLevel")]
		[HarmonyPrefix]
		[HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })]
		private static void SpawnScrapInLevel_Prefix(RoundManager __instance)
		{
			if (ShouldRunServer(__instance, out SelectableLevel level))
			{
				_savedMinScrapValue = level.minTotalScrapValue;
				_savedMaxScrapValue = level.maxTotalScrapValue;
				_savedMinScrap = level.minScrap;
				_savedMaxScrap = level.maxScrap;
				if (ConfigManager.DynamicScrapValueEnabled.Value)
				{
					ApplyScrapValue(level);
				}
				if (ConfigManager.DynamicScrapAmountEnabled.Value)
				{
					ApplyScrapAmount(level);
				}
			}
		}

		[HarmonyPatch("SpawnScrapInLevel")]
		[HarmonyPostfix]
		private static void SpawnScrapInLevel_Postfix(RoundManager __instance)
		{
			if (ShouldRunServer(__instance, out SelectableLevel level))
			{
				level.minTotalScrapValue = _savedMinScrapValue;
				level.maxTotalScrapValue = _savedMaxScrapValue;
				level.minScrap = _savedMinScrap;
				level.maxScrap = _savedMaxScrap;
			}
		}

		[HarmonyPatch("GenerateNewFloor")]
		[HarmonyPrefix]
		[HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })]
		private static void EnemyPower_GenerateNewFloor_Prefix(RoundManager __instance)
		{
			if (ShouldRunServer(__instance, out SelectableLevel level) && ConfigManager.DynamicEnemyPowerEnabled.Value)
			{
				_savedMaxEnemyPower = level.maxEnemyPowerCount;
				_savedMaxOutsidePower = level.maxOutsideEnemyPowerCount;
				_savedMaxDaytimePower = level.maxDaytimeEnemyPowerCount;
				_enemyPowerSaved = true;
				float num = ComputePlayerFactor(ConfigManager.DynamicEnemyPowerPlayerThreshold.Value, ConfigManager.DynamicEnemyPowerMultPerPlayer.Value, PlayerScalingDirection.PerExtraPlayer);
				num = Mathf.Min(num, Mathf.Max(1f, ConfigManager.DynamicEnemyPowerMaxFactor.Value));
				if (ConfigManager.DynamicEnemyPowerScaleInside.Value)
				{
					level.maxEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxEnemyPower * num);
				}
				if (ConfigManager.DynamicEnemyPowerScaleOutside.Value)
				{
					level.maxOutsideEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxOutsidePower * num);
				}
				if (ConfigManager.DynamicEnemyPowerScaleDaytime.Value)
				{
					level.maxDaytimeEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxDaytimePower * num);
				}
				Plugin.Log.LogInfo((object)($"Dynamic enemy power - factor={num:F2} (players={GetPlayerCount()}), " + $"inside={level.maxEnemyPowerCount}, outside={level.maxOutsideEnemyPowerCount}, daytime={level.maxDaytimeEnemyPowerCount}."));
			}
		}

		[HarmonyPatch("DespawnPropsAtEndOfRound")]
		[HarmonyPostfix]
		private static void EnemyPower_RestoreOnRoundEnd(RoundManager __instance)
		{
			if (_enemyPowerSaved)
			{
				SelectableLevel val = __instance?.currentLevel;
				if ((Object)(object)val != (Object)null)
				{
					val.maxEnemyPowerCount = _savedMaxEnemyPower;
					val.maxOutsideEnemyPowerCount = _savedMaxOutsidePower;
					val.maxDaytimeEnemyPowerCount = _savedMaxDaytimePower;
				}
				_enemyPowerSaved = false;
			}
		}

		private static bool ShouldRun(RoundManager rm, out SelectableLevel level)
		{
			level = rm?.currentLevel;
			if ((Object)(object)level == (Object)null)
			{
				return false;
			}
			if (ConfigManager.DisableQuota.Value)
			{
				return false;
			}
			if (PenaltyHelpers.IsAtCompany())
			{
				return false;
			}
			return true;
		}

		private static bool ShouldRunServer(RoundManager rm, out SelectableLevel level)
		{
			if (!PenaltyHelpers.IsServerSafe)
			{
				level = null;
				return false;
			}
			return ShouldRun(rm, out level);
		}

		private static int GetPlayerCount()
		{
			return Mathf.Max(1, (StartOfRound.Instance?.connectedPlayersAmount ?? 0) + 1);
		}

		private static float ComputePlayerFactor(int threshold, float mult, PlayerScalingDirection direction)
		{
			int num = ((direction == PlayerScalingDirection.PerMissingPlayer) ? Mathf.Max(0, threshold - GetPlayerCount()) : Mathf.Max(0, GetPlayerCount() - threshold));
			return 1f + (float)num * Mathf.Max(0f, mult);
		}

		private static void ApplyScrapValue(SelectableLevel level)
		{
			int num = Mathf.Max(1, _savedMinScrapValue);
			int num2 = Mathf.Max(num, _savedMaxScrapValue);
			float num3 = ComputePlayerFactor(ConfigManager.DynamicScrapValuePlayerThreshold.Value, ConfigManager.DynamicScrapValueMultPerPlayer.Value, ConfigManager.DynamicScrapValueDirection.Value);
			int value = ConfigManager.DynamicScrapValueOffset.Value;
			int num4 = Mathf.RoundToInt((float)num * ConfigManager.DynamicScrapValueMinMult.Value * num3) + value;
			int num5 = Mathf.RoundToInt((float)num2 * ConfigManager.DynamicScrapValueMaxMult.Value * num3) + value;
			if (num4 < 1)
			{
				num4 = 1;
			}
			if (num5 < 1)
			{
				num5 = 1;
			}
			if (num5 < num4)
			{
				num5 = num4;
			}
			level.minTotalScrapValue = num4;
			level.maxTotalScrapValue = num5;
			Plugin.Log.LogInfo((object)$"Dynamic scrap value: factor={num3:F2}, base=${num}/${num2}, min/max=${num4}/${num5}.");
		}

		private static void ApplyScrapAmount(SelectableLevel level)
		{
			float num = ComputePlayerFactor(ConfigManager.DynamicScrapAmountPlayerThreshold.Value, ConfigManager.DynamicScrapAmountMultPerPlayer.Value, ConfigManager.DynamicScrapAmountDirection.Value);
			int num2 = Mathf.Max(0, _savedMinScrapValue);
			if (num2 <= 0)
			{
				int num3 = Mathf.Max(0, _savedMinScrap);
				int num4 = Mathf.Max(num3, _savedMaxScrap);
				level.minScrap = num3;
				level.maxScrap = num4;
				Plugin.Log.LogInfo((object)$"Dynamic scrap amount - factor={num:F2}, baseline scrap value <= 0 so keeping base min/max items={num3}/{num4}.");
				return;
			}
			int num5 = Mathf.Max(1, ConfigManager.DynamicScrapAmountValuePerItem.Value);
			int num6 = Mathf.RoundToInt((float)num2 * num);
			int num7 = Mathf.Max(1, num6 / num5);
			int value = ConfigManager.DynamicScrapAmountCap.Value;
			if (value >= 0)
			{
				num7 = Mathf.Min(num7, value);
			}
			int num8 = Mathf.Max(1, Mathf.RoundToInt((float)num7 * ConfigManager.DynamicScrapAmountMinFraction.Value));
			if (num8 > num7)
			{
				num8 = num7;
			}
			level.minScrap = num8;
			level.maxScrap = num7;
			Plugin.Log.LogInfo((object)$"Dynamic scrap amount - factor={num:F2}, min/max items={num8}/{num7}.");
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager))]
	internal static class GameNetworkManagerPatches
	{
		private const float SettleTimeoutSeconds = 12f;

		private static int _firedResetGeneration;

		[HarmonyPatch("ResetSavedGameValues")]
		[HarmonyPostfix]
		[HarmonyAfter(new string[] { "com.xmods.lethalmoonunlocks" })]
		private static void ResetSavedGameValues_Postfix(GameNetworkManager __instance)
		{
			try
			{
				QuotaSaveData.Clear();
				TimeOfDay instance = TimeOfDay.Instance;
				if (!((Object)(object)instance == (Object)null) && ((NetworkBehaviour)instance).IsServer)
				{
					TimeOfDayQuotaPatch.ResetInitialDeadlineTracking();
					int generation = ++_firedResetGeneration;
					MonoBehaviour val = (MonoBehaviour)(((Object)(object)__instance != (Object)null) ? ((object)__instance) : ((object)instance));
					if (!LethalConstellationsCompat.IsInstalled)
					{
						val.StartCoroutine(ReapplyDeadlineAfterShipReset(generation));
						return;
					}
					LethalConstellationsCompat.TryGetCurrentConstellationName(out string constellationName);
					val.StartCoroutine(ReapplyDeadlineWhenConstellationSettles(generation, constellationName));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not schedule deadline reapply after firing: " + ex.Message));
			}
		}

		private static IEnumerator ReapplyDeadlineAfterShipReset(int generation)
		{
			float elapsed = 0f;
			while (elapsed < 12f && generation == _firedResetGeneration)
			{
				TimeOfDay instance = TimeOfDay.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					break;
				}
				if (instance.timesFulfilledQuota == 0)
				{
					ApplySettledDeadline(instance);
					break;
				}
				elapsed += Time.deltaTime;
				yield return null;
			}
		}

		private static IEnumerator ReapplyDeadlineWhenConstellationSettles(int generation, string staleConstellation)
		{
			bool hadStale = !string.IsNullOrEmpty(staleConstellation);
			float elapsed = 0f;
			TimeOfDay instance;
			while (true)
			{
				if (generation != _firedResetGeneration)
				{
					yield break;
				}
				instance = TimeOfDay.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					yield break;
				}
				bool flag = false;
				if (instance.timesFulfilledQuota == 0)
				{
					string constellationName;
					bool flag2 = LethalConstellationsCompat.TryGetCurrentConstellationName(out constellationName);
					if (hadStale)
					{
						flag = flag2 && !string.Equals(constellationName, staleConstellation, StringComparison.OrdinalIgnoreCase);
					}
				}
				if (flag || elapsed >= 12f)
				{
					break;
				}
				elapsed += Time.deltaTime;
				yield return null;
			}
			ApplySettledDeadline(instance);
		}

		private static void ApplySettledDeadline(TimeOfDay tod)
		{
			try
			{
				if (!((Object)(object)tod == (Object)null) && tod.timesFulfilledQuota == 0 && ((NetworkBehaviour)tod).IsServer)
				{
					ConstellationDeadlineConfig.RefreshSections();
					TimeOfDayQuotaPatch.ResetInitialDeadlineTracking();
					if (TimeOfDayQuotaPatch.TryApplyInitialDeadlineFromCurrentMode(tod, allowConstellationOverride: true, logSelection: true))
					{
						NetworkSync.SyncDeadlineToClients(tod.daysUntilDeadline);
						TimeOfDayQuotaPatch.RefreshExternalMonitors();
						Plugin.Log.LogInfo((object)$"Deadline reapplied after firing: {tod.daysUntilDeadline} days.");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not reapply deadline after firing: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager))]
	internal static class HudQuotaAnimationPatch
	{
		[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;
		}

		private static IEnumerator CustomRackUp(HUDManager hud, float speed)
		{
			yield return (object)new WaitForSeconds(3.5f / speed);
			if (!((Object)(object)TimeOfDay.Instance == (Object)null))
			{
				int quotaTextAmount = 0;
				int target = TimeOfDay.Instance.profitQuota;
				while (quotaTextAmount < target)
				{
					float num = Time.deltaTime * 250f * speed;
					quotaTextAmount = (int)Mathf.Clamp((float)quotaTextAmount + num, (float)(quotaTextAmount + 3), (float)(target + 10));
					((TMP_Text)hud.newProfitQuotaText).text = "$" + quotaTextAmount;
					yield return null;
				}
				((TMP_Text)hud.newProfitQuotaText).text = "$" + target;
				TimeOfDay.Instance.UpdateProfitQuotaCurrentTime();
				hud.UIAudio.PlayOneShot(hud.newProfitQuotaSFX);
				yield return (object)new WaitForSeconds(1.25f / Mathf.Clamp(speed, 0.5f, 2f));
				hud.displayingNewQuota = false;
				hud.reachedProfitQuotaAnimator.SetBool("display", false);
			}
		}

		[HarmonyPatch("FillEndGameStats")]
		[HarmonyPostfix]
		[HarmonyAfter(new string[] { "com.example.Advancedfeatures" })]
		private static void FillEndGameStats_Postfix()
		{
			TryApplyAdvancedFeaturesEndscreen();
		}

		internal static void TryApplyAdvancedFeaturesEndscreen()
		{
			if (!ConfigManager.ScrapLossEnabled.Value || !Chainloader.PluginInfos.ContainsKey("com.example.Advancedfeatures") || !PenaltiesOnLandingPatch.HasAllDeadSnapshot)
			{
				return;
			}
			try
			{
				HUDManager instance = HUDManager.Instance;
				if (!((Object)(object)instance?.statsUIElements?.allPlayersDeadOverlay != (Object)null) || !((Behaviour)instance.statsUIElements.allPlayersDeadOverlay).enabled)
				{
					return;
				}
				Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "AdvancedFeatures");
				if (!(assembly == null))
				{
					Type type = assembly.GetType("AdvancedFeatures.Endscreen");
					if (!(type == null))
					{
						ApplyAdvancedFeaturesEndscreen(type);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not apply Advanced Features end screen compatibility: " + ex.Message));
			}
		}

		private static void ApplyAdvancedFeaturesEndscreen(Type endscreenType)
		{
			FieldInfo fieldInfo = AccessTools.Field(endscreenType, "AreAllDead");
			FieldInfo fieldInfo2 = AccessTools.Field(endscreenType, "ScrapLost");
			FieldInfo fieldInfo3 = AccessTools.Field(endscreenType, "ScrapLostText");
			FieldInfo fieldInfo4 = AccessTools.Field(endscreenType, "CollectedText");
			FieldInfo fieldInfo5 = AccessTools.Field(endscreenType, "TotalText");
			FieldInfo fieldInfo6 = AccessTools.Field(endscreenType, "CollectedLine");
			FieldInfo fieldInfo7 = AccessTools.Field(endscreenType, "CollectedLabel");
			object? obj = fieldInfo2?.GetValue(null);
			Transform val = (Transform)((obj is Transform) ? obj : null);
			if (PenaltiesOnLandingPatch.TryGetScrapLossSummary(out var beforeValue, out var afterValue, out var lostPercent))
			{
				int num = Mathf.Max(0, beforeValue - afterValue);
				int num2 = Mathf.RoundToInt(Mathf.Clamp01(lostPercent) * 100f);
				string value = $"Lost {num2}% scrap (${num}/{beforeValue})";
				object? obj2 = fieldInfo4?.GetValue(null);
				Component val2 = (Component)((obj2 is Component) ? obj2 : null);
				object? obj3 = fieldInfo5?.GetValue(null);
				Component val3 = (Component)((obj3 is Component) ? obj3 : null);
				object? obj4 = fieldInfo6?.GetValue(null);
				Transform val4 = (Transform)((obj4 is Transform) ? obj4 : null);
				object? obj5 = fieldInfo7?.GetValue(null);
				Transform val5 = (Transform)((obj5 is Transform) ? obj5 : null);
				object obj6 = fieldInfo3?.GetValue(null);
				PropertyInfo propertyInfo = obj6?.GetType().GetProperty("text");
				if ((Object)(object)val2 != (Object)null)
				{
					val2.gameObject.SetActive(false);
				}
				if ((Object)(object)val3 != (Object)null)
				{
					val3.gameObject.SetActive(false);
				}
				if ((Object)(object)val4 != (Object)null)
				{
					((Component)val4).gameObject.SetActive(false);
				}
				if ((Object)(object)val5 != (Object)null)
				{
					((Component)val5).gameObject.SetActive(false);
				}
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).gameObject.SetActive(true);
				}
				propertyInfo?.SetValue(obj6, value);
				Plugin.Log.LogInfo((object)$"Updated Advanced Features scrap-loss text: {num2}% (${num}/{beforeValue}).");
			}
			else if ((Object)(object)val != (Object)null)
			{
				((Component)val).gameObject.SetActive(false);
			}
			fieldInfo?.SetValue(null, false);
		}

		[HarmonyPatch("ApplyPenalty")]
		[HarmonyPrefix]
		private static bool ApplyPenalty_Prefix()
		{
			return !ConfigManager.CreditPenaltiesEnabled.Value;
		}

		[HarmonyPatch("ApplyPenalty")]
		[HarmonyPostfix]
		private static void ApplyPenalty_Postfix(HUDManager __instance, int playersDead, int bodiesInsured)
		{
			try
			{
				(int dead, int total, int recovered) tuple = ResolvePenaltyCounts(playersDead, bodiesInsured);
				int item = tuple.dead;
				int item2 = tuple.total;
				int item3 = tuple.recovered;
				bool atCompany = PenaltyHelpers.IsAtCompany();
				var (num, num2) = ComputeQuotaPenalty(item, item2, item3, atCompany);
				if (!ConfigManager.CreditPenaltiesEnabled.Value)
				{
					if (num > 0f)
					{
						TextMeshProUGUI penaltyAddition = __instance.statsUIElements.penaltyAddition;
						((TMP_Text)penaltyAddition).text = ((TMP_Text)penaltyAddition).text + $"\n\nQuota: {Mathf.RoundToInt(num * 100f)}% (${num2})";
					}
				}
				else
				{
					var (creditPct, num3) = ComputeCreditPenalty(item, item2, item3, atCompany);
					((TMP_Text)__instance.statsUIElements.penaltyAddition).text = BuildPenaltyText(item, item3, creditPct, num, num2);
					((TMP_Text)__instance.statsUIElements.penaltyTotal).text = ((num3 > 0) ? $"DUE: ${num3}" : "");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not update penalty text on the end screen: " + ex.Message));
			}
		}

		private static (int dead, int total, int recovered) ResolvePenaltyCounts(int playersDead, int bodiesInsured)
		{
			if (PenaltiesOnLandingPatch.HasPenaltyCache)
			{
				return (dead: PenaltiesOnLandingPatch.CachedDead, total: PenaltiesOnLandingPatch.CachedTotal, recovered: PenaltiesOnLandingPatch.CachedRecovered);
			}
			var (num, num2, item) = PenaltyHelpers.CountDeathsAndRecovered();
			if (num == 0 && playersDead > 0)
			{
				num = playersDead;
				item = bodiesInsured;
				num2 = Mathf.Max(num + 1, num2);
			}
			return (dead: num, total: num2, recovered: item);
		}

		private static (float pct, int loss) ComputeCreditPenalty(int dead, int total, int recovered, bool atCompany)
		{
			if (!ConfigManager.CreditPenaltiesEnabled.Value || dead == 0 || (atCompany && !ConfigManager.CreditPenaltiesOnGordion.Value))
			{
				return (pct: 0f, loss: 0);
			}
			int num = Object.FindObjectOfType<Terminal>()?.groupCredits ?? 0;
			float num2 = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered);
			return (pct: num2, loss: Mathf.RoundToInt((float)num * num2));
		}

		private static (float pct, int delta) ComputeQuotaPenalty(int dead, int total, int recovered, bool atCompany)
		{
			if (!ConfigManager.QuotaPenaltiesEnabled.Value || dead == 0 || (atCompany && !ConfigManager.QuotaPenaltiesOnGordion.Value))
			{
				return (pct: 0f, delta: 0);
			}
			float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered);
			int num2 = PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta;
			if (num2 <= 0)
			{
				TimeOfDay instance = TimeOfDay.Instance;
				num2 = (((Object)(object)instance != (Object)null) ? Mathf.RoundToInt((float)Mathf.Max(1, instance.profitQuota) * num) : 0);
			}
			return (pct: num, delta: num2);
		}

		private static string BuildPenaltyText(int dead, int recovered, float creditPct, float quotaPct, int quotaDelta)
		{
			string text = ((creditPct > 0f) ? $"{dead} casualties: -{Mathf.RoundToInt(creditPct * 100f)}%" : $"{dead} casualties");
			string text2 = text + $"\n({recovered} of {dead} bodies recovered.)";
			if (quotaPct > 0f)
			{
				text2 += $"\n\nQuota: {Mathf.RoundToInt(quotaPct * 100f)}% (${quotaDelta})";
			}
			return text2;
		}
	}
	internal static class LateJoinSync
	{
		private static bool _subscribed;

		internal static void EnsureSubscribed()
		{
			if (!_subscribed)
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null))
				{
					singleton.OnClientConnectedCallback += OnClientConnected;
					_subscribed = true;
				}
			}
		}

		private static void OnClientConnected(ulong clientId)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && clientId != singleton.LocalClientId)
				{
					StartOfRound instance = StartOfRound.Instance;
					if (!((Object)(object)instance == (Object)null))
					{
						((MonoBehaviour)instance).StartCoroutine(ResyncRoutine(clientId));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not schedule latejoin resync: " + ex.Message));
			}
		}

		private static IEnumerator ResyncRoutine(ulong clientId)
		{
			yield return (object)new WaitForSeconds(4f);
			ResyncToClient(clientId);
			yield return (object)new WaitForSeconds(5f);
			ResyncToClient(clientId);
		}

		private static void ResyncToClient(ulong clientId)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && singleton.ConnectedClients.ContainsKey(clientId))
				{
					TimeOfDay instance = TimeOfDay.Instance;
					if ((Object)(object)instance != (Object)null)
					{
						NetworkSync.SyncDeadlineToClient(instance.daysUntilDeadline, clientId);
						NetworkSync.SyncQuotaToClient(instance.profitQuota, clientId);
						NetworkSync.SyncRolloverToClient(instance.quotaFulfilled, clientId);
					}
					StartOfRound instance2 = StartOfRound.Instance;
					if ((Object)(object)instance2 != (Object)null)
					{
						NetworkSync.SyncBuyingRateToClient(instance2.companyBuyingRate, isJackpot: false, clientId, silent: true);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"Latejoin resync to client {clientId} failed: {ex.Message}");
			}
		}
	}
	internal static class NetworkSync
	{
		private static LNetworkMessage<int>? _syncCreditsMessage;

		private static LNetworkMessage<SyncQuotaData>? _syncQuotaMessage;

		private static LNetworkMessage<SyncValueLossData>? _syncValueLossMessage;

		private static LNetworkMessage<int>? _syncDeadlineMessage;

		private static LNetworkMessage<int>? _syncRolloverMessage;

		private static LNetworkMessage<SyncScrapLossSummary>? _syncScrapLossSummaryMessage;

		private static LNetworkMessage<SyncBuyingRateData>? _syncBuyingRateMessage;

		public static void Initialize()
		{
			try
			{
				_syncCreditsMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncCredits", (Action<int, ulong>)null, (Action<int>)OnCreditsReceived, (Action<int, ulong>)null);
				_syncQuotaMessage = LNetworkMessage<SyncQuotaData>.Connect("ConfigurableQuota_SyncQuota", (Action<SyncQuotaData, ulong>)null, (Action<SyncQuotaData>)OnQuotaReceived, (Action<SyncQuotaData, 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);
				_syncRolloverMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncRollover", (Action<int, ulong>)null, (Action<int>)OnRolloverReceived, (Action<int, ulong>)null);
				_syncScrapLossSummaryMessage = LNetworkMessage<SyncScrapLossSummary>.Connect("ConfigurableQuota_SyncScrapLossSummary", (Action<SyncScrapLossSummary, ulong>)null, (Action<SyncScrapLossSummary>)OnScrapLossSummaryReceived, (Action<SyncScrapLossSummary, ulong>)null);
				_syncBuyingRateMessage = LNetworkMessage<SyncBuyingRateData>.Connect("ConfigurableQuota_SyncBuyingRate", (Action<SyncBuyingRateData, ulong>)null, (Action<SyncBuyingRateData>)OnBuyingRateReceived, (Action<SyncBuyingRateData, ulong>)null);
				Plugin.Log.LogInfo((object)"Network sync is ready.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not initialize network sync: " + ex.Message));
			}
		}

		public static void SyncCreditsToClients(int credits)
		{
			try
			{
				if (_syncCreditsMessage == null)
				{
					Plugin.Log.LogWarning((object)"Credits message not initialized");
				}
				else
				{
					_syncCreditsMessage.SendClients(credits);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not 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;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced credits on client: " + ex.Message));
			}
		}

		public static void SyncScrapLossSummaryToClients(int beforeValue, int afterValue)
		{
			try
			{
				if (_syncScrapLossSummaryMessage == null)
				{
					Plugin.Log.LogWarning((object)"Scrap loss summary message not initialized");
				}
				else
				{
					_syncScrapLossSummaryMessage.SendClients(new SyncScrapLossSummary(beforeValue, afterValue));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not sync scrap loss summary: " + ex.Message));
			}
		}

		private static void OnScrapLossSummaryReceived(SyncScrapLossSummary data)
		{
			try
			{
				if (data.BeforeValue > 0)
				{
					PenaltiesOnLandingPatch.CacheScrapLossSummary(data.BeforeValue, data.AfterValue);
				}
				else
				{
					PenaltiesOnLandingPatch.ClearScrapLossSummary();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced scrap loss summary on client: " + ex.Message));
			}
		}

		public static void SyncQuotaToClients(int quota, int penaltyDelta = 0)
		{
			try
			{
				if (_syncQuotaMessage == null)
				{
					Plugin.Log.LogWarning((object)"Quota message not initialized");
				}
				else
				{
					_syncQuotaMessage.SendClients(new SyncQuotaData(quota, penaltyDelta));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not sync quota: " + ex.Message));
			}
		}

		public static void SyncQuotaToClient(int quota, ulong clientId)
		{
			try
			{
				if (_syncQuotaMessage == null)
				{
					Plugin.Log.LogWarning((object)"Quota message not initialized");
				}
				else
				{
					_syncQuotaMessage.SendClient(new SyncQuotaData(quota, 0), clientId);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Could not sync quota to client {clientId}: {ex.Message}");
			}
		}

		private static void OnQuotaReceived(SyncQuotaData data)
		{
			try
			{
				TimeOfDay instance = TimeOfDay.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.profitQuota = data.Quota;
				}
				PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta = data.PenaltyDelta;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced 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);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not sync value loss: " + ex.Message));
			}
		}

		private static void OnValueLossReceived(SyncValueLossData data)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				NetworkSpawnManager val = ((singleton != null) ? singleton.SpawnManager : null);
				if (val == null || !val.SpawnedObjects.TryGetValue(data.NetworkObjectId, out var value) || (Object)(object)value == (Object)null)
				{
					return;
				}
				GrabbableObject component = ((Component)value).GetComponent<GrabbableObject>();
				if ((Object)(object)component == (Object)null)
				{
					return;
				}
				Item itemProperties = component.itemProperties;
				if (itemProperties == null || !itemProperties.isScrap)
				{
					return;
				}
				component.scrapValue = data.NewValue;
				try
				{
					component.SetScrapValue(data.NewValue);
				}
				catch
				{
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced value loss on client: " + ex.Message));
			}
		}

		public static void SyncDeadlineToClients(int days)
		{
			try
			{
				ApplyDeadline(days);
				if (_syncDeadlineMessage == null)
				{
					Plugin.Log.LogWarning((object)"Deadline message not initialized");
				}
				else
				{
					_syncDeadlineMessage.SendClients(days);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not sync deadline: " + ex.Message));
			}
		}

		public static void SyncDeadlineToClient(int days, ulong clientId)
		{
			try
			{
				if (_syncDeadlineMessage == null)
				{
					Plugin.Log.LogWarning((object)"Deadline message not initialized");
				}
				else
				{
					_syncDeadlineMessage.SendClient(days, clientId);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Could not sync deadline to client {clientId}: {ex.Message}");
			}
		}

		private static void OnDeadlineReceived(int days)
		{
			try
			{
				ApplyDeadline(days);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced deadline on client: " + ex.Message));
			}
		}

		private static void ApplyDeadline(int days)
		{
			TimeOfDay instance = TimeOfDay.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				int num = Mathf.Max(1, days);
				float num2 = instance.totalTime;
				if (num2 <= 0f && instance.daysUntilDeadline > 0 && instance.timeUntilDeadline > 0f)
				{
					num2 = instance.timeUntilDeadline / (float)instance.daysUntilDeadline;
				}
				if (instance.quotaVariables != null)
				{
					instance.quotaVariables.deadlineDaysAmount = num;
				}
				instance.daysUntilDeadline = num;
				if (num2 > 0f)
				{
					instance.timeUntilDeadline = (float)num * num2;
				}
			}
		}

		public static void SyncRolloverToClients(int rollover)
		{
			try
			{
				if (_syncRolloverMessage == null)
				{
					Plugin.Log.LogWarning((object)"Rollover message not initialized");
				}
				else
				{
					_syncRolloverMessage.SendClients(rollover);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not sync rollover: " + ex.Message));
			}
		}

		public static void SyncRolloverToClient(int rollover, ulong clientId)
		{
			try
			{
				if (_syncRolloverMessage == null)
				{
					Plugin.Log.LogWarning((object)"Rollover message not initialized");
				}
				else
				{
					_syncRolloverMessage.SendClient(rollover, clientId);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Could not sync rollover to client {clientId}: {ex.Message}");
			}
		}

		private static void OnRolloverReceived(int rollover)
		{
			try
			{
				TimeOfDay instance = TimeOfDay.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.quotaFulfilled = Math.Max(0, rollover);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced rollover on client: " + ex.Message));
			}
		}

		public static void SyncBuyingRateToClients(float rate, bool isJackpot, bool silent = false)
		{
			try
			{
				if (_syncBuyingRateMessage == null)
				{
					Plugin.Log.LogWarning((object)"Buying rate message not initialized");
				}
				else
				{
					_syncBuyingRateMessage.SendClients(new SyncBuyingRateData(rate, isJackpot, silent));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not sync buying rate: " + ex.Message));
			}
		}

		public static void SyncBuyingRateToClient(float rate, bool isJackpot, ulong clientId, bool silent = false)
		{
			try
			{
				if (_syncBuyingRateMessage == null)
				{
					Plugin.Log.LogWarning((object)"Buying rate message not initialized");
				}
				else
				{
					_syncBuyingRateMessage.SendClient(new SyncBuyingRateData(rate, isJackpot, silent), clientId);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Could not sync buying rate to client {clientId}: {ex.Message}");
			}
		}

		private static void OnBuyingRateReceived(SyncBuyingRateData data)
		{
			try
			{
				BuyingRatePatch.ApplyReceivedBuyingRate(data.Rate, data.IsJackpot, data.Silent);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not apply synced buying rate on client: " + ex.Message));
			}
		}
	}
	[Serializable]
	public struct SyncQuotaData
	{
		public int Quota;

		public int PenaltyDelta;

		public SyncQuotaData(int quota, int penaltyDelta)
		{
			Quota = quota;
			PenaltyDelta = penaltyDelta;
		}
	}
	[Serializable]
	public struct SyncBuyingRateData
	{
		public float Rate;

		public bool IsJackpot;

		public bool Silent;

		public SyncBuyingRateData(float rate, bool isJackpot, bool silent = false)
		{
			Rate = rate;
			IsJackpot = isJackpot;
			Silent = silent;
		}
	}
	[Serializable]
	public struct SyncValueLossData
	{
		public ulong NetworkObjectId;

		public int NewValue;

		public SyncValueLossData(ulong networkObjectId, int newValue)
		{
			NetworkObjectId = networkObjectId;
			NewValue = newValue;
		}
	}
	[Serializable]
	public struct SyncScrapLossSummary
	{
		public int BeforeValue;

		public int AfterValue;

		public SyncScrapLossSummary(int beforeValue, int afterValue)
		{
			BeforeValue = beforeValue;
			AfterValue = afterValue;
		}
	}
	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_0104: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return (dead: 0, total: 0, recovered: 0);
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			RagdollGrabbableObject[] array = null;
			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)
				{
					if (array == null)
					{
						array = Object.FindObjectsOfType<RagdollGrabbableObject>();
					}
					RagdollGrabbableObject[] array2 = array;
					foreach (RagdollGrabbableObject val3 in array2)
					{
						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 (dead: num, total: Math.Max(num2, 1), recovered: 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 IsAtCompany()
		{
			try
			{
				SelectableLevel val = StartOfRound.Instance?.currentLevel;
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				return val.sceneName == "CompanyBuilding" || (!val.planetHasTime && !val.spawnEnemiesAndScrap);
			}
			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 = ((cap >= 0f) ? Mathf.Clamp01(cap) : 1f);
			float num2 = (dynamicMode ? ((float)dead / (float)total * num) : ((float)dead * Mathf.Max(0f, percentPerPlayer)));
			if (recovered > 0 && dead > 0)
			{
				float num3 = Mathf.Clamp01((float)recovered / (float)dead);
				num2 *= Mathf.Clamp01(1f - Mathf.Clamp01(recoveryBonus) * num3);
			}
			if (!dynamicMode && cap >= 0f)
			{
				num2 = Mathf.Min(num2, num);
			}
			if (!(num2 < threshold))
			{
				return Mathf.Clamp01(num2);
			}
			return 0f;
		}
	}
	[HarmonyPatch(typeof(RoundManager))]
	internal static class PenaltiesOnLandingPatch
	{
		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 int CachedQuotaPenaltyDelta;

		internal static int CachedShipScrapBeforeLoss;

		internal static int CachedShipScrapAfterLoss;

		internal static bool HasScrapLossSummary;

		internal static bool HasAllDeadSnapshot;

		internal static void CachePenaltyCounts(int dead, int total, int recovered)
		{
			CachedDead = dead;
			CachedTotal = total;
			CachedRecovered = recovered;
			HasPenaltyCache = true;
		}

		internal static void CacheScrapLossSummary(int beforeValue, int afterValue)
		{
			int num = Mathf.Max(0, beforeValue);
			int cachedShipScrapAfterLoss = Mathf.Clamp(afterValue, 0, num);
			CachedShipScrapBeforeLoss = num;
			CachedShipScrapAfterLoss = cachedShipScrapAfterLoss;
			HasScrapLossSummary = num > 0;
		}

		internal static void ClearScrapLossSummary()
		{
			CachedShipScrapBeforeLoss = 0;
			CachedShipScrapAfterLoss = 0;
			HasScrapLossSummary = false;
		}

		internal static bool TryGetScrapLossSummary(out int beforeValue, out int afterValue, out float lostPercent)
		{
			beforeValue = CachedShipScrapBeforeLoss;
			afterValue = CachedShipScrapAfterLoss;
			lostPercent = 0f;
			if (!HasScrapLossSummary || beforeValue <= 0)
			{
				return false;
			}
			lostPercent = Mathf.Clamp01((float)(beforeValue - afterValue) / (float)beforeValue);
			return true;
		}

		[HarmonyPatch("DespawnPropsAtEndOfRound")]
		[HarmonyPrefix]
		private static bool DespawnPrefix(bool despawnAllItems)
		{
			try
			{
				if (!PenaltyHelpers.IsServerSafe)
				{
					return true;
				}
				HasPenaltyCache = false;
				HasAllDeadSnapshot = false;
				CachedQuotaPenaltyDelta = 0;
				ClearScrapLossSummary();
				bool flag = PenaltyHelpers.IsAtCompany();
				var (num, num2, recovered) = PenaltyHelpers.CountDeathsAndRecovered();
				if (!despawnAllItems && !flag && num >= num2 && !_lossesAppliedThisRound)
				{
					CollectVehicleItems();
					MarkBeltBagContentsAsShipItems();
					DespawnFacilityItems();
					ApplyLossesWhenAllDead();
					MarkSurvivingItemsAsPersisted();
					_lossesAppliedThisRound = true;
					HasAllDeadSnapshot = true;
					CachePenaltyCounts(num, num2, recovered);
					HudQuotaAnimationPatch.TryApplyAdvancedFeaturesEndscreen();
					if (ConfigManager.CreditPenaltiesEnabled.Value)
					{
						ScheduleCreditPenalty(num, num2, recovered);
					}
					if (ConfigManager.QuotaPenaltiesEnabled.Value)
					{
						ApplyQuotaPenalty(num, num2, recovered);
					}
					if (ConfigManager.RolloverWipePenalty.Value > 0f)
					{
						ApplyRolloverWipePenalty();
					}
					_appliedThisRound = true;
					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.IsAtCompany();
					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 creditPenaltyPercent = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered);
					creditPenaltyPercent = MoreShipUpgradesCompat.ApplyLifeInsurance(creditPenaltyPercent);
					if (!(creditPenaltyPercent <= 0f))
					{
						int desiredFinal = Mathf.Max(0, currentCredits - Mathf.RoundToInt((float)currentCredits * creditPenaltyPercent));
						_creditScheduled = true;
						((MonoBehaviour)instance).StartCoroutine(FinalizeCreditPenaltyAfterDelay(desiredFinal));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not 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 (Exception ex)
			{
				Plugin.Log.LogDebug((object)("Could not read current credits: " + ex.Message));
			}
			return 0;
		}

		private static IEnumerator FinalizeCreditPenaltyAfterDelay(int desiredFinal)
		{
			yield return (object)new WaitForSeconds(1.5f);
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					int currentCredits = GetCurrentCredits();
					SetCredits(desiredFinal);
					Plugin.Log.LogInfo((object)$"Credits penalty applied: {currentCredits} -> {desiredFinal} (-{currentCredits - desiredFinal}).");
				}
			}
			finally
			{
				_creditScheduled = false;
			}
		}

		private static void SetCredits(int value)
		{
			try
			{
				Terminal val = Object.FindObjectOfType<Terminal>();
				if ((Object)(object)val != (Object)null)
				{
					val.SyncGroupCreditsServerRpc(value, val.numberOfItemsInDropship);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("Could not sync credits to terminal: " + ex.Message));
			}
		}

		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 profitQuota = instance.profitQuota;
					int num2 = Mathf.RoundToInt((float)Math.Max(1, profitQuota) * num);
					int num3 = Mathf.Max(1, profitQuota + num2);
					CachedQuotaPenaltyDelta = num2;
					instance.profitQuota = num3;
					NetworkSync.SyncQuotaToClients(num3, num2);
					Plugin.Log.LogInfo((object)$"Quota penalty applied: {profitQuota} -> {num3} (+{num2}, {num:P0}, {dead}/{total} dead).");
				}
			}
		}

		private static void ApplyRolloverWipePenalty()
		{
			float num = Mathf.Clamp01(ConfigManager.RolloverWipePenalty.Value);
			if (num <= 0f)
			{
				return;
			}
			TimeOfDay instance = TimeOfDay.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			int quotaFulfilled = instance.quotaFulfilled;
			if (quotaFulfilled > 0)
			{
				int num2 = Mathf.RoundToInt((float)quotaFulfilled * num);
				if (num2 > 0)
				{
					int num3 = (instance.quotaFulfilled = Mathf.Max(0, quotaFulfilled - num2));
					NetworkSync.SyncRolloverToClients(num3);
					Plugin.Log.LogInfo((object)$"Rollover wipe penalty applied: {quotaFulfilled} -> {num3} (-{num2}, {num:P0}).");
				}
			}
		}

		private static void CollectVehicleItems()
		{
			try
			{
				VehicleController[] array = Object.FindObjectsOfType<VehicleController>();
				foreach (VehicleController val in array)
				{
					try
					{
						if ((Object)(object)val == (Object)null)
						{
							continue;
						}
						if (val.magnetedToShip)
						{
							val.CollectItemsInTruck();
							continue;
						}
						NetworkObject networkObject = ((NetworkBehaviour)val).NetworkObject;
						if ((Object)(object)networkObject != (Object)null && networkObject.IsSpawned)
						{
							networkObject.Despawn(false);
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogDebug((object)("Skipped vehicle cleanup: " + ex.Message));
					}
				}
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Could not collect vehicle items: " + ex2.Message));
			}
		}

		private static void MarkBeltBagContentsAsShipItems()
		{
			try
			{
				BeltBagItem[] array = Object.FindObjectsOfType<BeltBagItem>();
				foreach (BeltBagItem val in array)
				{
					try
					{
						if ((Object)(object)val == (Object)null)
						{
							continue;
						}
						if ((Object)(object)val.insideAnotherBeltBag != (Object)null && (((GrabbableObject)val.insideAnotherBeltBag).isInShipRoom || ((GrabbableObject)val.insideAnotherBeltBag).isHeld))
						{
							((GrabbableObject)val).isInElevator = true;
							((GrabbableObject)val).isInShipRoom = true;
						}
						if (!((GrabbableObject)val).isInShipRoom && !((GrabbableObject)val).isHeld)
						{
							continue;
						}
						foreach (GrabbableObject item in val.objectsInBag)
						{
							if (!((Object)(object)item == (Object)null))
							{
								item.isInElevator = true;
								item.isInShipRoom = true;
							}
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogDebug((object)("Skipped belt bag contents: " + ex.Message));
					}
				}
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Could not protect belt bag contents: " + ex2.Message));
			}
		}

		private static void MarkSurvivingItemsAsPersisted()
		{
			try
			{
				GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
				foreach (GrabbableObject val in array)
				{
					try
					{
						if ((Object)(object)val != (Object)null && IsShipItem(val))
						{
							val.scrapPersistedThroughRounds = true;
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogDebug((object)("Skipped persist flag for item: " + ex.Message));
					}
				}
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Could not flag surviving items: " + ex2.Message));
			}
		}

		private static void DespawnFacilityItems()
		{
			try
			{
				GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
				if (array == null || array.Length == 0)
				{
					return;
				}
				GrabbableObject[] array2 = array;
				foreach (GrabbableObject g in array2)
				{
					try
					{
						if (!IsShipItem(g))
						{
							DespawnObject(g);
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogDebug((object)("Skipped despawn check for facility item: " + ex.Message));
					}
				}
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Error despawning facility items: " + ex2.Message));
			}
		}

		private static void ApplyLossesWhenAllDead()
		{
			try
			{
				GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
				if (array == null || array.Length == 0)
				{
					return;
				}
				List<StorageSlot> slots = SelfSortingStorageCompat.GetSlots();
				HashSet<GrabbableObject> storedLive = new HashSet<GrabbableObject>(slots.Select((StorageSlot s) => s.Live));
				GrabbableObject[] source = array.Where((GrabbableObject g) => IsShipItem(g) && !storedLive.Contains(g)).ToArray();
				GrabbableObject[] array2 = source.Where((GrabbableObject g) => g.itemProperties.isScrap).ToArray();
				GrabbableObject[] equipItems = source.Where((GrabbableObject g) => !g.itemProperties.isScrap && !IsBodyOrBlacklisted(g)).ToArray();
				List<StorageSlot> list = slots.Where((StorageSlot s) => s.IsScrap).ToList();
				List<StorageSlot> slots2 = slots.Where((StorageSlot s) => !s.IsScrap && !IsBodyOrBlacklisted(s.Live)).ToList();
				int num = SumScrapValue(array2) + SumStoredValues(list);
				bool flag = ScrapInsuranceCompat.IsScrapInsured();
				if (flag)
				{
					Plugin.Log.LogInfo((object)"Scrap Insurance is active, collected scrap is protected from this crew wipe.");
				}
				if (ConfigManager.ValueLossEnabled.Value && !flag)
				{
					GrabbableObject[] array3 = array2.Concat(list.Select((StorageSlot s) => s.Live)).ToArray();
					if (array3.Length != 0)
					{
						ApplyValueLoss(array3);
						float multiplier = 1f - Mathf.Clamp01(ConfigManager.ValueLossPercent.Value);
						foreach (StorageSlot item in list)
						{
							SelfSortingStorageCompat.ScaleValues(item, multiplier);
						}
					}
				}
				if (ConfigManager.ScrapLossEnabled.Value && !flag)
				{
					int budget = ResolveLossBudget(ConfigManager.MaxLostScrapItems.Value);
					budget = SelectAndRemoveScrap(array2, budget);
					SelectAndRemoveStoredItems(list, budget, Mathf.Clamp01(ConfigManager.ItemsSafeChance.Value), Mathf.Clamp01(ConfigManager.LoseEachScrapChance.Value), "scrap");
				}
				if (ConfigManager.EquipmentLossEnabled.Value)
				{
					int budget2 = ResolveLossBudget(ConfigManager.MaxLostEquipmentItems.Value);
					budget2 = SelectAndRemoveEquipment(equipItems, budget2);
					SelectAndRemoveStoredItems(slots2, budget2, 0f, Mathf.Clamp01(ConfigManager.LoseEachEquipmentChance.Value), "equipment");
				}
				int num2 = SumCurrentShipScrapValue(array2) + SumStoredValues(list);
				CacheScrapLossSummary(num, num2);
				NetworkSync.SyncScrapLossSummaryToClients(num, num2);
				if (num > 0)
				{
					int num3 = Mathf.Max(0, num - num2);
					float num4 = Mathf.Clamp01((float)num3 / (float)num);
					Plugin.Log.LogInfo((object)$"Scrap lost: {Mathf.RoundToInt(num4 * 100f)}% (${num3}/${num}).");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not apply ship loss rules: " + ex.Message));
			}
		}

		private static int SumScrapValue(IEnumerable<GrabbableObject> items)
		{
			int num = 0;
			foreach (GrabbableObject item in items)
			{
				try
				{
					if (item != null && item.itemProperties?.isScrap == true)
					{
						num += Mathf.Max(0, item.scrapValue);
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogDebug((object)("Skipped scrap value sum for item: " + ex.Message));
				}
			}
			return num;
		}

		private static int SumCurrentShipScrapValue(GrabbableObject[] shipScrap)
		{
			int num = 0;
			foreach (GrabbableObject val in shipScrap)
			{
				try
				{
					if (val != null && val.itemProperties?.isScrap == true && IsShipItem(val))
					{
						num += Mathf.Max(0, val.scrapValue);
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogDebug((object)("Skipped scrap value sum for item: " + ex.Message));
				}
			}
			return num;
		}

		private static bool IsShipItem(GrabbableObject g)
		{
			if ((Object)(object)g == (Object)null || (Object)(object)g.itemProperties == (Object)null || !g.isInShipRoom)
			{
				return false;
			}
			try
			{
				NetworkObject component = ((Component)g).GetComponent<NetworkObject>();
				return (Object)(object)component != (Object)null && component.IsSpawned;
			}
			catch
			{
				return false;
			}
		}

		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 int ResolveLossBudget(int configured)
		{
			int num = Mathf.Max(0, configured);
			if (num != 0)
			{
				return num;
			}
			return int.MaxValue;
		}

		private static int SumStoredValues(IEnumerable<StorageSlot> slots)
		{
			int num = 0;
			foreach (StorageSlot slot in slots)
			{
				num += SelfSortingStorageCompat.SumValues(slot);
			}
			return num;
		}

		private static int SelectAndRemoveStoredItems(List<StorageSlot> slots, int budget, float safeChance, float loseChance, string label)
		{
			if (slots.Count == 0)
			{
				return budget;
			}
			int num = 0;
			int num2 = 0;
			List<string> list = new List<string>();
			foreach (StorageSlot slot in slots)
			{
				try
				{
					if ((Object)(object)slot?.Live == (Object)null || slot.Count == 0)
					{
						continue;
					}
					int num3 = 0;
					for (int i = 0; i < slot.Count; i++)
					{
						num++;
						if (num2 + num3 < budget && (!(safeChance > 0f) || !(Random.value < safeChance)) && Random.value < loseChance)
						{
							num3++;
						}
					}
					if (num3 != 0)
					{
						string item = (((Object)(object)slot.Live.itemProperties != (Object)null) ? slot.Live.itemProperties.itemName : ((Object)slot.Live).name);
						bool flag = SelfSortingStorageCompat.RemoveItems(slot, num3);
						num2 += num3;
						for (int j = 0; j < num3; j++)
						{
							list.Add(item);
						}
						if (flag)
						{
							DespawnObject(slot.Live);
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogDebug((object)("Skipped stored " + label + " removal: " + ex.Message));
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)string.Format("Stored {0} removed: {1}/{2} [{3}].", label, num2, num, string.Join(", ", list)));
			}
			return Mathf.Max(0, budget - num2);
		}

		private static int SelectAndRemoveScrap(GrabbableObject[] scrapItems, int budget)
		{
			float num = Mathf.Clamp01(ConfigManager.ItemsSafeChance.Value);
			float num2 = Mathf.Clamp01(ConfigManager.LoseEachScrapChance.Value);
			int num3 = 0;
			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)
					{
						continue;
					}
					num3++;
					if (num4 < budget && !(Random.value < num) && Random.value < num2)
					{
						if (MoreShipUpgradesCompat.CanKeepScrap(val))
						{
							num5++;
							continue;
						}
						DespawnObject(val);
						num4++;
						list.Add(val.itemProperties.itemName);
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogDebug((object)("Skipped scrap removal for item: " + ex.Message));
				}
			}
			Plugin.Log.LogInfo((object)string.Format("Scrap items removed: {0}/{1} [{2}].", num4, num3, string.Join(", ", list)));
			if (num5 > 0)
			{
				Plugin.Log.LogInfo((object)$"Scrap Keeper saved {num5} scrap from this crew wipe.");
			}
			return Mathf.Max(0, budget - num4);
		}

		private static int SelectAndRemoveEquipment(GrabbableObject[] equipItems, int budget)
		{
			float num = Mathf.Clamp01(ConfigManager.LoseEachEquipmentChance.Value);
			int num2 = 0;
			int num3 = 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)
					{
						num2++;
						if (num3 < budget && Random.value < num)
						{
							DespawnObject(val);
							num3++;
							list.Add(val.itemProperties.itemName);
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogDebug((object)("Skipped equipment removal for item: " + ex.Message));
				}
			}
			Plugin.Log.LogInfo((object)string.Format("Equipment items removed: {0}/{1} [{2}].", num3, num2, string.Join(", ", list)));
			return Mathf.Max(0, budget - num3);
		}

		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 == true && 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 (Exception ex)
						{
							Plugin.Log.LogDebug((object)("SetScrapValue failed: " + ex.Message));
						}
						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 ex2)
				{
					Plugin.Log.LogWarning((object)("Could not reduce a scrap item's value: " + ex2.Message));
				}
			}
			if (list.Count > 0)
			{
				NetworkSync.SyncValueLossToClients(list.ToArray());
			}
			Plugin.Log.LogInfo((o