Decompiled source of QuotaBasedDeadline v1.0.0

QuotaBasedDeadline.dll

Decompiled 3 days ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("QuotaBasedDeadline")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1")]
[assembly: AssemblyProduct("QuotaBasedDeadline")]
[assembly: AssemblyTitle("QuotaBasedDeadline")]
[assembly: AssemblyVersion("1.3.1.0")]
namespace Hibyhii.QuotaBasedDeadline;

internal sealed class DeadlineCalculationResult
{
	public int OriginalQuota;

	public int RolloverCredits;

	public int EffectiveQuota;

	public int CompletedQuotas;

	public int BaseDays;

	public int MultipliedDays;

	public int OffsetDays;

	public int DifficultyReduction;

	public int BeforeClampDays;

	public int FinalDays;

	public bool UsedConstant;

	public bool ClampedToMinimum;

	public bool ClampedToMaximum;

	public string BuildLogExplanation(QuotaDeadlineConfig config)
	{
		StringBuilder stringBuilder = new StringBuilder();
		if (UsedConstant)
		{
			stringBuilder.Append("Constant Deadline is enabled; all other settings are ignored. FINAL = ");
			stringBuilder.Append(FinalDays);
			stringBuilder.Append(" day(s).");
			return stringBuilder.ToString();
		}
		stringBuilder.Append("quota=");
		stringBuilder.Append(OriginalQuota);
		if (config.AccountForQuotaRollover.Value)
		{
			stringBuilder.Append("; rollover=");
			stringBuilder.Append(RolloverCredits);
			stringBuilder.Append("; effectiveQuota=");
			stringBuilder.Append(EffectiveQuota);
		}
		else
		{
			stringBuilder.Append("; rollover accounting is OFF");
		}
		stringBuilder.Append("; ceil(");
		stringBuilder.Append(EffectiveQuota);
		stringBuilder.Append(" / ");
		stringBuilder.Append(Math.Max(1, config.QuotaPerDay.Value));
		stringBuilder.Append(") = ");
		stringBuilder.Append(BaseDays);
		stringBuilder.Append(" base day(s); multiplier ");
		stringBuilder.Append(config.DeadlineMultiplier.Value.ToString("0.###"));
		stringBuilder.Append(" -> ");
		stringBuilder.Append(MultipliedDays);
		stringBuilder.Append(" day(s); offset ");
		stringBuilder.Append((OffsetDays >= 0) ? "+" : "");
		stringBuilder.Append(OffsetDays);
		stringBuilder.Append(" -> ");
		stringBuilder.Append(BeforeClampDays);
		if (config.RaiseDifficultyPerQuota.Value)
		{
			stringBuilder.Append(" before limits; difficulty reduction=-");
			stringBuilder.Append(DifficultyReduction);
			stringBuilder.Append(" from completedQuotas=");
			stringBuilder.Append(CompletedQuotas);
		}
		else
		{
			stringBuilder.Append(" before limits; difficulty scaling is OFF");
		}
		if (ClampedToMinimum)
		{
			stringBuilder.Append("; clamped to minimum ");
			stringBuilder.Append(config.MinimumDeadlineDays.Value);
		}
		else if (ClampedToMaximum)
		{
			stringBuilder.Append("; clamped to maximum ");
			stringBuilder.Append(config.MaximumDeadlineDays.Value);
		}
		stringBuilder.Append("; FINAL=");
		stringBuilder.Append(FinalDays);
		stringBuilder.Append(" day(s).");
		return stringBuilder.ToString();
	}
}
internal static class DeadlineCalculator
{
	public static DeadlineCalculationResult Calculate(QuotaDeadlineConfig config, int quota, int completedQuotas, int rolloverCredits)
	{
		DeadlineCalculationResult deadlineCalculationResult = new DeadlineCalculationResult();
		deadlineCalculationResult.OriginalQuota = Math.Max(0, quota);
		deadlineCalculationResult.CompletedQuotas = Math.Max(0, completedQuotas);
		deadlineCalculationResult.RolloverCredits = Math.Max(0, rolloverCredits);
		if (config.ConstantDeadline.Value)
		{
			deadlineCalculationResult.UsedConstant = true;
			deadlineCalculationResult.EffectiveQuota = deadlineCalculationResult.OriginalQuota;
			deadlineCalculationResult.FinalDays = Math.Max(1, config.ConstantDeadlineDays.Value);
			return deadlineCalculationResult;
		}
		deadlineCalculationResult.EffectiveQuota = (config.AccountForQuotaRollover.Value ? Math.Max(0, deadlineCalculationResult.OriginalQuota - deadlineCalculationResult.RolloverCredits) : deadlineCalculationResult.OriginalQuota);
		int num = Math.Max(1, config.QuotaPerDay.Value);
		deadlineCalculationResult.BaseDays = (int)Math.Ceiling((double)deadlineCalculationResult.EffectiveQuota / (double)num);
		if (deadlineCalculationResult.BaseDays < 1)
		{
			deadlineCalculationResult.BaseDays = 1;
		}
		double num2 = config.DeadlineMultiplier.Value;
		if (double.IsNaN(num2) || double.IsInfinity(num2))
		{
			num2 = 1.0;
		}
		if (num2 < 0.0)
		{
			num2 = 0.0;
		}
		deadlineCalculationResult.MultipliedDays = SafeRoundedInt((double)deadlineCalculationResult.BaseDays * num2, 1);
		deadlineCalculationResult.OffsetDays = config.OffsetDays.Value;
		deadlineCalculationResult.DifficultyReduction = CalculateDifficultyReduction(config, deadlineCalculationResult.CompletedQuotas);
		long num3 = (long)deadlineCalculationResult.MultipliedDays + (long)deadlineCalculationResult.OffsetDays - deadlineCalculationResult.DifficultyReduction;
		deadlineCalculationResult.BeforeClampDays = (int)((num3 < int.MinValue) ? int.MinValue : ((num3 > int.MaxValue) ? int.MaxValue : num3));
		int num4 = Math.Max(1, config.MinimumDeadlineDays.Value);
		int num5 = Math.Max(num4, config.MaximumDeadlineDays.Value);
		if (deadlineCalculationResult.BeforeClampDays < num4)
		{
			deadlineCalculationResult.FinalDays = num4;
			deadlineCalculationResult.ClampedToMinimum = true;
		}
		else if (deadlineCalculationResult.BeforeClampDays > num5)
		{
			deadlineCalculationResult.FinalDays = num5;
			deadlineCalculationResult.ClampedToMaximum = true;
		}
		else
		{
			deadlineCalculationResult.FinalDays = deadlineCalculationResult.BeforeClampDays;
		}
		if (deadlineCalculationResult.FinalDays < 1)
		{
			deadlineCalculationResult.FinalDays = 1;
		}
		return deadlineCalculationResult;
	}

	public static int CalculateDifficultyReduction(QuotaDeadlineConfig config, int completedQuotas)
	{
		if (!config.RaiseDifficultyPerQuota.Value)
		{
			return 0;
		}
		int num = Math.Max(1, config.QuotasBeforeFirstReduction.Value);
		int num2 = Math.Max(1, config.QuotasBetweenDifficultyRaises.Value);
		int num3 = Math.Max(0, config.DaysRemovedPerDifficultyRaise.Value);
		if (completedQuotas < num || num3 == 0)
		{
			return 0;
		}
		int num4 = 1 + (completedQuotas - num) / num2;
		long num5 = (long)num4 * (long)num3;
		return (int)((num5 > int.MaxValue) ? int.MaxValue : num5);
	}

	private static int SafeRoundedInt(double value, int fallback)
	{
		if (double.IsNaN(value) || double.IsInfinity(value))
		{
			return fallback;
		}
		double num = Math.Round(value, 0, MidpointRounding.AwayFromZero);
		if (num > 2147483647.0)
		{
			return int.MaxValue;
		}
		if (num < -2147483648.0)
		{
			return int.MinValue;
		}
		return (int)num;
	}
}
internal sealed class DeadlineController : IDisposable
{
	private readonly QuotaDeadlineConfig config;

	private readonly ConfigFile configFile;

	private string activeSave;

	private bool initializedForSave;

	private bool initializedForQuota;

	private bool dirty;

	private string dirtyReason;

	private bool pendingSaveLoad;

	private bool saveLoadHookSeen;

	private int lastAppliedQuota = -1;

	private int lastAppliedCompleted = -1;

	private int rolloverCredits;

	private int appliedDeadlineDays = -1;

	private float configPollTimer;

	private DateTime lastConfigWriteUtc;

	private bool lastHost;

	private bool disposed;

	public DeadlineController(QuotaDeadlineConfig settings)
	{
		config = settings;
		configFile = settings.GetConfigFile();
		try
		{
			lastConfigWriteUtc = GetConfigWriteTime();
		}
		catch
		{
			lastConfigWriteUtc = DateTime.MinValue;
		}
	}

	public void MarkConfigurationDirty(string name, object value)
	{
		dirty = true;
		dirtyReason = "configuration changed: " + name + " = " + Convert.ToString(value);
		Plugin.Log.LogInfo((object)("Config changed: " + name + " = " + Convert.ToString(value) + ". Current quota will be recalculated live; saved rollover is retained."));
		try
		{
			lastConfigWriteUtc = GetConfigWriteTime();
		}
		catch
		{
		}
	}

	public void Tick()
	{
		if (disposed)
		{
			return;
		}
		PollConfigFileIfNeeded();
		GameNetworkManager instance = GameNetworkManager.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		if (!instance.isHostingGame)
		{
			if (lastHost)
			{
				ResetSession("no longer host");
			}
			lastHost = false;
			return;
		}
		if (!lastHost)
		{
			lastHost = true;
			ResetSession("new host session");
		}
		string currentSaveFileName = instance.currentSaveFileName;
		if (!string.Equals(activeSave, currentSaveFileName, StringComparison.Ordinal))
		{
			activeSave = currentSaveFileName;
			initializedForSave = false;
			initializedForQuota = false;
			lastAppliedQuota = -1;
			lastAppliedCompleted = -1;
			appliedDeadlineDays = -1;
			rolloverCredits = 0;
			pendingSaveLoad = !string.IsNullOrEmpty(currentSaveFileName);
			saveLoadHookSeen = false;
			dirty = false;
			dirtyReason = null;
			if (!string.IsNullOrEmpty(currentSaveFileName))
			{
				Plugin.Log.LogInfo((object)("Active host save changed to '" + currentSaveFileName + "'. Waiting for vanilla save-load to finish before initializing this quota."));
			}
		}
		TimeOfDay instance2 = TimeOfDay.Instance;
		if ((Object)(object)instance2 == (Object)null || instance2.profitQuota <= 0)
		{
			return;
		}
		if (!initializedForSave && pendingSaveLoad)
		{
			if (saveLoadHookSeen)
			{
				InitializeAfterSaveLoad(instance2, currentSaveFileName, "save-load hook");
			}
			else
			{
				if (!((Object)(object)StartOfRound.Instance != (Object)null) || StartOfRound.Instance.fullyLoadedPlayers == null || StartOfRound.Instance.fullyLoadedPlayers.Count <= 0)
				{
					return;
				}
				InitializeAfterSaveLoad(instance2, currentSaveFileName, "safe load fallback");
			}
		}
		if (initializedForSave)
		{
			if (!initializedForQuota)
			{
				ApplyDeadline(instance2, dirtyReason ?? "initial quota initialization");
			}
			else if (instance2.profitQuota != lastAppliedQuota || instance2.timesFulfilledQuota != lastAppliedCompleted)
			{
				rolloverCredits = 0;
				dirty = true;
				dirtyReason = "quota identity changed without a tracked transition";
				ApplyDeadline(instance2, dirtyReason);
			}
			else if (dirty)
			{
				ApplyDeadline(instance2, dirtyReason);
			}
		}
	}

	public void OnSaveLoaded()
	{
		if (!disposed)
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if (!((Object)(object)instance == (Object)null) && instance.isHostingGame)
			{
				activeSave = instance.currentSaveFileName;
				saveLoadHookSeen = true;
				pendingSaveLoad = true;
				Plugin.Log.LogDebug((object)("Vanilla save-load hook fired for '" + activeSave + "'. Waiting one controller tick to initialize the restored state once."));
			}
		}
	}

	public void NotifyNewQuota(TimeOfDay tod, int capturedRollover)
	{
		if (!disposed && !((Object)(object)tod == (Object)null) && !((Object)(object)GameNetworkManager.Instance == (Object)null) && GameNetworkManager.Instance.isHostingGame)
		{
			if (string.IsNullOrEmpty(activeSave))
			{
				activeSave = GameNetworkManager.Instance.currentSaveFileName;
			}
			rolloverCredits = Math.Max(0, capturedRollover);
			initializedForSave = true;
			initializedForQuota = false;
			dirty = false;
			dirtyReason = "new quota created; captured rollover=" + rolloverCredits;
			Plugin.Log.LogInfo((object)("New quota transition detected: quota=" + tod.profitQuota + ", completedQuotas=" + tod.timesFulfilledQuota + ", captured rollover=" + rolloverCredits + ". Rollover is calculation-only; no game credits were changed."));
			ApplyDeadline(tod, dirtyReason);
		}
	}

	public void SaveCurrentState(string reason)
	{
		if (!disposed && initializedForQuota && !string.IsNullOrEmpty(activeSave))
		{
			TimeOfDay instance = TimeOfDay.Instance;
			if (!((Object)(object)instance == (Object)null) && instance.profitQuota > 0 && appliedDeadlineDays >= 1)
			{
				QuotaDeadlineSaveData.Save(activeSave, instance.profitQuota, instance.timesFulfilledQuota, appliedDeadlineDays, Sanitize(instance.timeUntilDeadline), rolloverCredits);
				Plugin.Log.LogDebug((object)("Saved deadline state (" + reason + "): quota=" + instance.profitQuota + ", completedQuotas=" + instance.timesFulfilledQuota + ", days=" + appliedDeadlineDays + ", remaining=" + instance.timeUntilDeadline.ToString("0.##") + ", rollover=" + rolloverCredits + "."));
			}
		}
	}

	public void ResetRuntimeState(string reason)
	{
		ResetSession(reason);
		lastHost = false;
	}

	private void InitializeAfterSaveLoad(TimeOfDay tod, string save, string reason)
	{
		if (initializedForSave || (Object)(object)tod == (Object)null || string.IsNullOrEmpty(save))
		{
			return;
		}
		pendingSaveLoad = false;
		initializedForSave = true;
		if (QuotaDeadlineSaveData.TryLoad(save, out var state))
		{
			if (state.Quota == tod.profitQuota && state.CompletedQuotas == tod.timesFulfilledQuota)
			{
				rolloverCredits = state.RolloverCredits;
				appliedDeadlineDays = state.AssignedDeadlineDays;
				lastAppliedQuota = state.Quota;
				lastAppliedCompleted = state.CompletedQuotas;
				initializedForQuota = true;
				dirty = false;
				dirtyReason = null;
				if (tod.quotaVariables != null)
				{
					tod.quotaVariables.deadlineDaysAmount = appliedDeadlineDays;
				}
				float num = (tod.timeUntilDeadline = Sanitize(state.RemainingTime));
				tod.UpdateProfitQuotaCurrentTime();
				tod.SyncTimeClientRpc(tod.globalTime, Mathf.RoundToInt(num));
				Plugin.Log.LogInfo((object)("Restored QuotaBasedDeadline state ONCE: quota=" + state.Quota + ", completedQuotas=" + state.CompletedQuotas + ", assigned=" + appliedDeadlineDays + " day(s), remaining=" + num.ToString("0.##") + ", rollover=" + rolloverCredits + ". No recalculation was performed."));
				return;
			}
			Plugin.Log.LogInfo((object)("Saved QuotaBasedDeadline state does not belong to the loaded quota (saved quota=" + state.Quota + ", completed=" + state.CompletedQuotas + "; current quota=" + tod.profitQuota + ", completed=" + tod.timesFulfilledQuota + "). Recalculating once for the current quota."));
			rolloverCredits = 0;
		}
		else
		{
			rolloverCredits = 0;
			Plugin.Log.LogInfo((object)("No current QuotaBasedDeadline state exists for '" + save + "'. Calculating the current quota once."));
		}
		dirty = true;
		dirtyReason = "new save/no matching saved deadline (" + reason + ")";
		ApplyDeadline(tod, dirtyReason);
	}

	private void ApplyDeadline(TimeOfDay tod, string reason)
	{
		if ((Object)(object)tod == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || !GameNetworkManager.Instance.isHostingGame)
		{
			return;
		}
		DeadlineCalculationResult deadlineCalculationResult = DeadlineCalculator.Calculate(config, tod.profitQuota, tod.timesFulfilledQuota, rolloverCredits);
		float dayDuration = GetDayDuration(tod);
		if (!(dayDuration <= 0f))
		{
			if (tod.quotaVariables != null)
			{
				tod.quotaVariables.deadlineDaysAmount = deadlineCalculationResult.FinalDays;
			}
			tod.timeUntilDeadline = dayDuration * (float)deadlineCalculationResult.FinalDays;
			tod.UpdateProfitQuotaCurrentTime();
			tod.SyncTimeClientRpc(tod.globalTime, Mathf.RoundToInt(tod.timeUntilDeadline));
			appliedDeadlineDays = deadlineCalculationResult.FinalDays;
			lastAppliedQuota = tod.profitQuota;
			lastAppliedCompleted = tod.timesFulfilledQuota;
			initializedForQuota = true;
			dirty = false;
			dirtyReason = null;
			Plugin.Log.LogInfo((object)("Applied deadline: quota=" + tod.profitQuota + ", completedQuotas=" + tod.timesFulfilledQuota + ", days=" + deadlineCalculationResult.FinalDays + ", rollover=" + rolloverCredits + ", reason=" + reason + ". " + deadlineCalculationResult.BuildLogExplanation(config)));
			SaveCurrentState("deadline assignment");
		}
	}

	private void PollConfigFileIfNeeded()
	{
		configPollTimer -= Time.unscaledDeltaTime;
		if (configPollTimer > 0f)
		{
			return;
		}
		configPollTimer = 1f;
		try
		{
			DateTime configWriteTime = GetConfigWriteTime();
			if (!(configWriteTime == DateTime.MinValue) && !(configWriteTime == lastConfigWriteUtc))
			{
				lastConfigWriteUtc = configWriteTime;
				configFile.Reload();
				dirty = true;
				dirtyReason = "external config edit";
				Plugin.Log.LogInfo((object)"External config change detected. Current quota will be recalculated live.");
			}
		}
		catch (IOException)
		{
		}
		catch (UnauthorizedAccessException)
		{
		}
		catch (Exception ex3)
		{
			Plugin.Log.LogWarning((object)("Could not reload config after external edit: " + ex3.Message));
		}
	}

	private DateTime GetConfigWriteTime()
	{
		if (configFile == null || string.IsNullOrEmpty(configFile.ConfigFilePath) || !File.Exists(configFile.ConfigFilePath))
		{
			return DateTime.MinValue;
		}
		return File.GetLastWriteTimeUtc(configFile.ConfigFilePath);
	}

	private static float GetDayDuration(TimeOfDay tod)
	{
		if (tod.totalTime > 0f)
		{
			return tod.totalTime;
		}
		if (tod.lengthOfHours > 0f && tod.numberOfHours > 0)
		{
			return tod.lengthOfHours * (float)tod.numberOfHours;
		}
		return 0f;
	}

	private static float Sanitize(float value)
	{
		return (float.IsNaN(value) || float.IsInfinity(value) || value < 0f) ? 0f : value;
	}

	private void ResetSession(string reason)
	{
		activeSave = null;
		initializedForSave = false;
		initializedForQuota = false;
		pendingSaveLoad = false;
		saveLoadHookSeen = false;
		lastAppliedQuota = -1;
		lastAppliedCompleted = -1;
		rolloverCredits = 0;
		appliedDeadlineDays = -1;
		dirty = false;
		dirtyReason = reason;
	}

	public void Dispose()
	{
		disposed = true;
	}
}
[BepInPlugin("Hibyhii.QuotaBasedDeadline", "QuotaBasedDeadline", "1.3.1")]
[BepInProcess("Lethal Company.exe")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "Hibyhii.QuotaBasedDeadline";

	public const string PluginName = "QuotaBasedDeadline";

	public const string PluginVersion = "1.3.1";

	internal static ManualLogSource Log;

	internal static QuotaDeadlineConfig Settings;

	internal static DeadlineController Controller;

	private Harmony harmony;

	private void Awake()
	{
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Log.LogInfo((object)"QuotaBasedDeadline 1.3.1 LOADED.");
		try
		{
			Settings = new QuotaDeadlineConfig(((BaseUnityPlugin)this).Config);
			Controller = new DeadlineController(Settings);
			Settings.BindChangeHandlers(delegate(string settingName, object value)
			{
				if (Controller != null)
				{
					Controller.MarkConfigurationDirty(settingName, value);
				}
			});
			harmony = new Harmony("Hibyhii.QuotaBasedDeadline");
			QuotaDeadlinePatches.Install(harmony);
			Log.LogInfo((object)"Standalone host-only mode active. No DynamicDeadline or DeadlineManager code/dependency is used.");
			Log.LogInfo((object)("Configuration file: " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
			Log.LogInfo((object)"Waiting for the host's active save/quota...");
		}
		catch (Exception ex)
		{
			Log.LogError((object)("QuotaBasedDeadline failed during startup: " + ex));
			if (harmony != null)
			{
				try
				{
					harmony.UnpatchSelf();
				}
				catch
				{
				}
				harmony = null;
			}
		}
	}

	private void Update()
	{
		try
		{
			if (Controller != null)
			{
				Controller.Tick();
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("Unexpected QuotaBasedDeadline update error: " + ex));
		}
	}

	internal static void LogPatchResult(string methodName, bool installed)
	{
		if (installed)
		{
			Log.LogInfo((object)("Installed patch: " + methodName));
		}
		else
		{
			Log.LogWarning((object)("Could not find patch target: " + methodName + ". That feature will use its safe fallback if available."));
		}
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			try
			{
				harmony.UnpatchSelf();
			}
			catch
			{
			}
			harmony = null;
		}
		if (Controller != null)
		{
			Controller.Dispose();
			Controller = null;
		}
		if (Log != null)
		{
			Log.LogInfo((object)"QuotaBasedDeadline unloaded.");
		}
	}
}
internal sealed class QuotaDeadlineConfig
{
	public const string CalculationSection = "Deadline Calculation";

	public const string DifficultySection = "Difficulty Scaling";

	public const string ConstantSection = "Constant Deadline";

	public readonly ConfigEntry<int> QuotaPerDay;

	public readonly ConfigEntry<bool> AccountForQuotaRollover;

	public readonly ConfigEntry<int> OffsetDays;

	public readonly ConfigEntry<double> DeadlineMultiplier;

	public readonly ConfigEntry<int> MinimumDeadlineDays;

	public readonly ConfigEntry<int> MaximumDeadlineDays;

	public readonly ConfigEntry<bool> RaiseDifficultyPerQuota;

	public readonly ConfigEntry<int> QuotasBeforeFirstReduction;

	public readonly ConfigEntry<int> QuotasBetweenDifficultyRaises;

	public readonly ConfigEntry<int> DaysRemovedPerDifficultyRaise;

	public readonly ConfigEntry<bool> ConstantDeadline;

	public readonly ConfigEntry<int> ConstantDeadlineDays;

	private readonly ConfigFile configFile;

	public QuotaDeadlineConfig(ConfigFile config)
	{
		configFile = config;
		QuotaPerDay = config.Bind<int>("Deadline Calculation", "Quota Per Day", 200, "Quota amount represented by one deadline day. Example: 400 / 200 = 2 days. Division is rounded UP, so 401 / 200 = 3. Values below 1 are treated as 1.");
		AccountForQuotaRollover = config.Bind<bool>("Deadline Calculation", "Account for Quota Rollover", false, "OFF by default. When enabled, credits earned above the previous quota are subtracted from the NEW quota for deadline calculation only. Example: previous quota 100, crew earns 150, rollover 50; new quota 200 is calculated as 200 - 50 = 150. Actual credits are NEVER removed or changed. Ignored when Constant Deadline is enabled.");
		OffsetDays = config.Bind<int>("Deadline Calculation", "Offset Days", 0, "Fixed number of days added after the quota/day calculation. Positive values add days; negative values remove days. Example: 400 / 200 = 2, then Offset Days = 1 makes 3 days. Ignored when Constant Deadline is enabled.");
		DeadlineMultiplier = config.Bind<double>("Deadline Calculation", "Deadline Multiplier", 1.0, "Multiplies the calculated deadline before Offset Days are applied. The result is rounded to the nearest whole day. Example: 3 x 1.5 = 4.5, which becomes 5 days. Negative values are treated as 0. Ignored when Constant Deadline is enabled.");
		MinimumDeadlineDays = config.Bind<int>("Deadline Calculation", "Minimum Deadline Days", 1, "The minimum number of days the normal calculation can produce. Results below this value are raised to this value. Ignored when Constant Deadline is enabled.");
		MaximumDeadlineDays = config.Bind<int>("Deadline Calculation", "Maximum Deadline Days", 10, "The maximum number of days the normal calculation can produce. Results above this value are lowered to this value. Ignored when Constant Deadline is enabled.");
		RaiseDifficultyPerQuota = config.Bind<bool>("Difficulty Scaling", "Progressive Difficulty Scaling", false, "OFF by default. When enabled, completed quotas progressively remove more deadline days from the normal calculation. The first reduction occurs at Quotas Before First Reduction, then another reduction tier occurs every Quotas Between Difficulty Raises completed quotas.");
		QuotasBeforeFirstReduction = config.Bind<int>("Difficulty Scaling", "First Reduction At Quota", 3, "The completed quota number at which the first deadline reduction occurs. Default 3 means quotas 1 and 2 receive no reduction, while quota 3 receives the first reduction.");
		QuotasBetweenDifficultyRaises = config.Bind<int>("Difficulty Scaling", "Quotas Between Reductions", 2, "The number of completed quotas between each additional deadline reduction. Default 2 produces reductions at quota 3, 5, 7, 9, etc. With the default settings, quota 3 removes 1 day, quota 5 removes 2 days, quota 7 removes 3 days, and so on.");
		DaysRemovedPerDifficultyRaise = config.Bind<int>("Difficulty Scaling", "Days Removed Per Reduction", 1, "How many additional deadline days are removed at each reduction tier. Default 1 means the first reduction removes 1 day, the second removes 2 days, the third removes 3 days, and so on. Only applies when Progressive Difficulty Scaling is enabled.");
		ConstantDeadline = config.Bind<bool>("Constant Deadline", "Constant Deadline", false, "OFF by default. When enabled, every quota receives exactly Constant Deadline Days. All other deadline settings, including rollover, multiplier, offset, difficulty scaling, and minimum/maximum limits, are ignored.");
		ConstantDeadlineDays = config.Bind<int>("Constant Deadline", "Constant Deadline Days", 3, "The number of days used for every quota when Constant Deadline is enabled. Values below 1 are treated as 1.");
	}

	public void BindChangeHandlers(Action<string, object> onChanged)
	{
		BindChangeHandler<int>(QuotaPerDay, onChanged);
		BindChangeHandler<bool>(AccountForQuotaRollover, onChanged);
		BindChangeHandler<int>(OffsetDays, onChanged);
		BindChangeHandler<double>(DeadlineMultiplier, onChanged);
		BindChangeHandler<int>(MinimumDeadlineDays, onChanged);
		BindChangeHandler<int>(MaximumDeadlineDays, onChanged);
		BindChangeHandler<bool>(RaiseDifficultyPerQuota, onChanged);
		BindChangeHandler<int>(QuotasBeforeFirstReduction, onChanged);
		BindChangeHandler<int>(QuotasBetweenDifficultyRaises, onChanged);
		BindChangeHandler<int>(DaysRemovedPerDifficultyRaise, onChanged);
		BindChangeHandler<bool>(ConstantDeadline, onChanged);
		BindChangeHandler<int>(ConstantDeadlineDays, onChanged);
	}

	private static void BindChangeHandler<T>(ConfigEntry<T> entry, Action<string, object> onChanged)
	{
		string key = ((ConfigEntryBase)entry).Definition.Key;
		entry.SettingChanged += delegate
		{
			onChanged(key, entry.Value);
		};
	}

	public ConfigFile GetConfigFile()
	{
		return configFile;
	}
}
internal static class QuotaDeadlinePatches
{
	internal sealed class QuotaTransitionState
	{
		public int Rollover;
	}

	public static void Install(Harmony harmony)
	{
		Patch(harmony, typeof(TimeOfDay), "SetNewProfitQuota", "QuotaPrefix", "QuotaPostfix");
		Patch(harmony, typeof(StartOfRound), "SetTimeAndPlanetToSavedSettings", null, "SaveLoadPostfix");
		Patch(harmony, typeof(GameNetworkManager), "SaveGameValues", "SavePrefix", null);
		Patch(harmony, typeof(GameNetworkManager), "ResetSavedGameValues", null, "ResetPostfix");
	}

	private static void QuotaPrefix(TimeOfDay __instance, out QuotaTransitionState __state)
	{
		__state = new QuotaTransitionState();
		try
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)GameNetworkManager.Instance == (Object)null) && GameNetworkManager.Instance.isHostingGame)
			{
				__state.Rollover = Math.Max(0, __instance.quotaFulfilled - __instance.profitQuota);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Could not capture quota rollover before vanilla quota transition: " + ex.Message));
		}
	}

	private static void QuotaPostfix(TimeOfDay __instance, QuotaTransitionState __state)
	{
		try
		{
			if (Plugin.Controller != null && (Object)(object)__instance != (Object)null && (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame)
			{
				Plugin.Controller.NotifyNewQuota(__instance, __state?.Rollover ?? 0);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Error handling new quota transition: " + ex));
		}
	}

	private static void SaveLoadPostfix()
	{
		try
		{
			if (Plugin.Controller != null)
			{
				Plugin.Controller.OnSaveLoaded();
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Error handling saved-game load: " + ex));
		}
	}

	private static void SavePrefix(GameNetworkManager __instance)
	{
		try
		{
			if ((Object)(object)__instance != (Object)null && __instance.isHostingGame && Plugin.Controller != null)
			{
				Plugin.Controller.SaveCurrentState("vanilla save event");
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Error saving QuotaBasedDeadline state: " + ex));
		}
	}

	private static void ResetPostfix(GameNetworkManager __instance)
	{
		try
		{
			if (!((Object)(object)__instance == (Object)null) && __instance.isHostingGame)
			{
				QuotaDeadlineSaveData.Delete(__instance.currentSaveFileName);
				if (Plugin.Controller != null)
				{
					Plugin.Controller.ResetRuntimeState("vanilla save reset");
				}
				Plugin.Log.LogInfo((object)"Cleared QuotaBasedDeadline custom save data because the vanilla save was reset.");
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Error clearing QuotaBasedDeadline save data: " + ex));
		}
	}

	private static void Patch(Harmony harmony, Type type, string methodName, string prefixName, string postfixName)
	{
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.LogPatchResult(type.Name + "." + methodName, installed: false);
				return;
			}
			HarmonyMethod val = ((prefixName == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(QuotaDeadlinePatches), prefixName, (Type[])null));
			HarmonyMethod val2 = ((postfixName == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(QuotaDeadlinePatches), postfixName, (Type[])null));
			harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.LogPatchResult(type.Name + "." + methodName, installed: true);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Failed to install patch " + type.Name + "." + methodName + ": " + ex));
		}
	}
}
internal static class QuotaDeadlineSaveData
{
	internal sealed class State
	{
		public int Quota;

		public int CompletedQuotas;

		public int AssignedDeadlineDays;

		public float RemainingTime;

		public int RolloverCredits;
	}

	private const string Prefix = "Hibyhii.QuotaBasedDeadline.";

	private const string VersionKey = "Hibyhii.QuotaBasedDeadline.Version";

	private const string QuotaKey = "Hibyhii.QuotaBasedDeadline.Quota";

	private const string CompletedKey = "Hibyhii.QuotaBasedDeadline.CompletedQuotas";

	private const string DeadlineDaysKey = "Hibyhii.QuotaBasedDeadline.AssignedDeadlineDays";

	private const string RemainingTimeKey = "Hibyhii.QuotaBasedDeadline.RemainingTime";

	private const string RolloverKey = "Hibyhii.QuotaBasedDeadline.RolloverCredits";

	private const int CurrentVersion = 3;

	public static bool TryLoad(string save, out State state)
	{
		state = null;
		if (string.IsNullOrEmpty(save))
		{
			return false;
		}
		try
		{
			if (!ES3.KeyExists("Hibyhii.QuotaBasedDeadline.Version", save) || !ES3.KeyExists("Hibyhii.QuotaBasedDeadline.Quota", save))
			{
				return false;
			}
			int num = ES3.Load<int>("Hibyhii.QuotaBasedDeadline.Version", save, 0);
			if (num != 3)
			{
				Plugin.Log.LogInfo((object)("Ignoring old/incompatible QuotaBasedDeadline save data version " + num + "; this build uses version " + 3 + "."));
				return false;
			}
			State state2 = new State();
			state2.Quota = ES3.Load<int>("Hibyhii.QuotaBasedDeadline.Quota", save, -1);
			state2.CompletedQuotas = ES3.Load<int>("Hibyhii.QuotaBasedDeadline.CompletedQuotas", save, -1);
			state2.AssignedDeadlineDays = ES3.Load<int>("Hibyhii.QuotaBasedDeadline.AssignedDeadlineDays", save, -1);
			state2.RemainingTime = ES3.Load<float>("Hibyhii.QuotaBasedDeadline.RemainingTime", save, -1f);
			state2.RolloverCredits = ES3.Load<int>("Hibyhii.QuotaBasedDeadline.RolloverCredits", save, 0);
			if (state2.Quota < 0 || state2.CompletedQuotas < 0 || state2.AssignedDeadlineDays < 1 || float.IsNaN(state2.RemainingTime) || float.IsInfinity(state2.RemainingTime))
			{
				return false;
			}
			state2.RemainingTime = Math.Max(0f, state2.RemainingTime);
			state2.RolloverCredits = Math.Max(0, state2.RolloverCredits);
			state = state2;
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Could not read QuotaBasedDeadline save data: " + ex.Message));
			return false;
		}
	}

	public static void Save(string save, int quota, int completed, int deadlineDays, float remaining, int rollover)
	{
		if (string.IsNullOrEmpty(save))
		{
			return;
		}
		try
		{
			ES3.Save<int>("Hibyhii.QuotaBasedDeadline.Version", 3, save);
			ES3.Save<int>("Hibyhii.QuotaBasedDeadline.Quota", Math.Max(0, quota), save);
			ES3.Save<int>("Hibyhii.QuotaBasedDeadline.CompletedQuotas", Math.Max(0, completed), save);
			ES3.Save<int>("Hibyhii.QuotaBasedDeadline.AssignedDeadlineDays", Math.Max(1, deadlineDays), save);
			ES3.Save<float>("Hibyhii.QuotaBasedDeadline.RemainingTime", Math.Max(0f, remaining), save);
			ES3.Save<int>("Hibyhii.QuotaBasedDeadline.RolloverCredits", Math.Max(0, rollover), save);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Could not write QuotaBasedDeadline save data: " + ex));
		}
	}

	public static void Delete(string save)
	{
		if (string.IsNullOrEmpty(save))
		{
			return;
		}
		try
		{
			DeleteKey("Hibyhii.QuotaBasedDeadline.Version", save);
			DeleteKey("Hibyhii.QuotaBasedDeadline.Quota", save);
			DeleteKey("Hibyhii.QuotaBasedDeadline.CompletedQuotas", save);
			DeleteKey("Hibyhii.QuotaBasedDeadline.AssignedDeadlineDays", save);
			DeleteKey("Hibyhii.QuotaBasedDeadline.RemainingTime", save);
			DeleteKey("Hibyhii.QuotaBasedDeadline.RolloverCredits", save);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Could not clear QuotaBasedDeadline save data: " + ex.Message));
		}
	}

	private static void DeleteKey(string key, string save)
	{
		if (ES3.KeyExists(key, save))
		{
			ES3.DeleteKey(key, save);
		}
	}
}