Decompiled source of TimeConfig v0.2.0

plugins/TimeConfig/TimeConfig.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using TimeConfig.Configuration;
using TimeConfig.Models;
using TimeConfig.Network;
using TimeConfig.Runtime;
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: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TimeConfig
{
	[BepInPlugin("com.lncinteractive", "TimeConfig", "0.2.0")]
	public sealed class PluginMain : BaseUnityPlugin
	{
		public const string PluginGuid = "com.lncinteractive";

		public const string PluginName = "TimeConfig";

		public const string PluginVersion = "0.2.0";

		private Harmony? _harmony;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			TimingCoordinator.Initialize(ActiveSettings.Bind(((BaseUnityPlugin)this).Config), Log);
			TimingCoordinator.TryApplyFromResources("PluginMain.Awake");
			LobbyVisibility.Initialize(Log);
			LobbyVisibility.RegisterMessageDelegates();
			_harmony = new Harmony("com.lncinteractive");
			_harmony.PatchAll();
			Log.LogInfo((object)"TimeConfig 0.2.0 initialized.");
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace TimeConfig.Runtime
{
	public static class NativeLobbySettingsMenu
	{
		private const int MaxEditableQuotaMultipliers = 6;

		private static readonly List<string> QuotaScalingModeOptions = new List<string> { "Vanilla scaling", "Custom pattern" };

		public const string SectionKey = "timeconfig.section";

		public const string TimeSectionKey = "timeconfig.time.section";

		public const string QuotaSectionKey = "timeconfig.quota.section";

		public const string DayDurationMinutesKey = "timeconfig.day-duration-minutes";

		public const string StartingQuotaKey = "timeconfig.starting-quota";

		public const string CatchUpFactorKey = "timeconfig.catch-up-factor";

		public const string QuotaScalingModeKey = "timeconfig.quota-scaling-mode";

		public const string QuotaPatternLengthKey = "timeconfig.quota-pattern-length";

		private static SettingsLayout? _lobbySettingsLayout;

		private static bool _isSynchronizing;

		public static void RegisterLobbyLayout(SettingsLayout layout, string source)
		{
			if (!((Object)(object)layout == (Object)null))
			{
				_lobbySettingsLayout = layout;
				PluginMain.Log.LogDebug((object)("[NativeLobbySettingsMenu] Registered lobby settings layout from " + source + "."));
			}
		}

		public static bool EnsureInjected(SettingsLayout layout, string source)
		{
			if ((Object)(object)layout == (Object)null)
			{
				return false;
			}
			if (!IsLobbySettingsLayout(layout))
			{
				return false;
			}
			RegisterLobbyLayout(layout, source);
			if (layout.tabs == null || layout.tabs.Count == 0)
			{
				PluginMain.Log.LogDebug((object)("[NativeLobbySettingsMenu] Skipped injection from " + source + " because the layout had no tabs."));
				return false;
			}
			Tab val = FindTargetTab(layout);
			if (val == null)
			{
				PluginMain.Log.LogDebug((object)("[NativeLobbySettingsMenu] Skipped injection from " + source + " because no lobby settings tab was found."));
				return false;
			}
			Tab val2 = val;
			if (val2.entries == null)
			{
				val2.entries = new List<SettingItemBase>();
			}
			int num = 0;
			num += EnsureTitleEntry(val.entries, "timeconfig.section", "TimeConfig");
			num += EnsureTitleEntry(val.entries, "timeconfig.time.section", "Time");
			num += EnsureSliderEntry(val.entries, "timeconfig.day-duration-minutes", "Day duration (minutes)", 1f, 1440f, wholeNumbers: true);
			num += EnsureTitleEntry(val.entries, "timeconfig.quota.section", "Quota");
			num += EnsureSliderEntry(val.entries, "timeconfig.starting-quota", "Starting quota", 0f, 100000f, wholeNumbers: true);
			num += EnsureSliderEntry(val.entries, "timeconfig.catch-up-factor", "Catch-up factor", 0f, 5f, wholeNumbers: false);
			num += EnsureDropdownEntry(val.entries, "timeconfig.quota-scaling-mode", "Quota scaling", QuotaScalingModeOptions, 0);
			num += EnsureSliderEntry(val.entries, "timeconfig.quota-pattern-length", "Custom pattern length", 1f, 6f, wholeNumbers: true);
			for (int i = 0; i < 6; i++)
			{
				num += EnsureSliderEntry(val.entries, GetQuotaMultiplierKey(i), $"Pattern multiplier {i + 1}", 0.01f, 100f, wholeNumbers: false);
			}
			SyncFromCurrentState();
			PluginMain.Log.LogInfo((object)string.Format("[NativeLobbySettingsMenu] {0} native lobby settings entries from {1}. Tab='{2}', added={3}.", (num > 0) ? "Injected" : "Reused", source, val.tabName, num));
			return true;
		}

		public static void RefreshFromLobbyButton(SettingsLayout layout)
		{
			EnsureInjected(layout, "LobbyModeDropdownButton.OnClick");
			SyncFromCurrentState();
		}

		public static void HandleSettingChanged(SettingItemBase entry)
		{
			if (!_isSynchronizing && !((Object)(object)entry == (Object)null) && IsTimeConfigKey(entry.key) && !((Object)(object)_lobbySettingsLayout == (Object)null) && TryGetCurrentProfileForEditing(out TimingProfile profile) && profile != null && TryGetEditableEntries(_lobbySettingsLayout, out SliderSettingItem dayDurationEntry, out SliderSettingItem startingQuotaEntry, out SliderSettingItem catchUpFactorEntry, out DropdownSettingItem quotaScalingModeEntry, out SliderSettingItem quotaPatternLengthEntry, out SliderSettingItem[] quotaMultiplierEntries))
			{
				QuotaScalingMode quotaScalingMode = ResolveQuotaScalingMode(quotaScalingModeEntry);
				float[] array = QuotaPatternEditor.BuildPattern(quotaMultiplierEntries.Select((SliderSettingItem multiplierEntry) => multiplierEntry.value).ToArray(), Mathf.RoundToInt(quotaPatternLengthEntry.value));
				float dayDurationSeconds = dayDurationEntry.value * 60f;
				int daysBeforeQuota = profile.DaysBeforeQuota;
				long startingQuota = (long)Mathf.Round(startingQuotaEntry.value);
				float value = catchUpFactorEntry.value;
				IReadOnlyList<float> quotaMultipliers;
				if (quotaScalingMode != QuotaScalingMode.CustomPattern)
				{
					quotaMultipliers = profile.QuotaMultipliers;
				}
				else
				{
					IReadOnlyList<float> readOnlyList = array;
					quotaMultipliers = readOnlyList;
				}
				TimingProfile updatedProfile = new TimingProfile("ActiveConfig", isVanillaProfile: false, dayDurationSeconds, daysBeforeQuota, startingQuota, value, quotaScalingMode, quotaMultipliers);
				if (!TimingCoordinator.TryApplyManualOverrides(profile, updatedProfile, "NativeLobbySettingsMenu.NotifyChanged", out IReadOnlyList<ValidationOutcome> _))
				{
					SyncFromCurrentState();
					return;
				}
				LobbyVisibility.BroadcastCurrentState();
				SyncFromCurrentState();
			}
		}

		private static void SyncFromCurrentState()
		{
			if ((Object)(object)_lobbySettingsLayout == (Object)null || !TryGetCurrentProfileForEditing(out TimingProfile profile) || profile == null || !TryGetEditableEntries(_lobbySettingsLayout, out SliderSettingItem dayDurationEntry, out SliderSettingItem startingQuotaEntry, out SliderSettingItem catchUpFactorEntry, out DropdownSettingItem quotaScalingModeEntry, out SliderSettingItem quotaPatternLengthEntry, out SliderSettingItem[] quotaMultiplierEntries))
			{
				return;
			}
			float[] array = QuotaPatternEditor.BuildEditableValues(profile.QuotaMultipliers, 6);
			_isSynchronizing = true;
			try
			{
				dayDurationEntry.value = Mathf.Clamp(profile.DayDurationSeconds / 60f, dayDurationEntry.min, dayDurationEntry.max);
				dayDurationEntry.defaultValue = dayDurationEntry.value;
				startingQuotaEntry.max = Mathf.Max(100000f, Mathf.Ceil((float)profile.StartingQuota / 5000f) * 5000f);
				startingQuotaEntry.value = Mathf.Clamp((float)profile.StartingQuota, startingQuotaEntry.min, startingQuotaEntry.max);
				startingQuotaEntry.defaultValue = startingQuotaEntry.value;
				catchUpFactorEntry.value = Mathf.Clamp(profile.CatchUpFactor, catchUpFactorEntry.min, catchUpFactorEntry.max);
				catchUpFactorEntry.defaultValue = catchUpFactorEntry.value;
				quotaScalingModeEntry.index = Mathf.Clamp((int)profile.QuotaScalingMode, 0, QuotaScalingModeOptions.Count - 1);
				quotaPatternLengthEntry.value = Mathf.Clamp((float)Mathf.Max(1, Mathf.Min(profile.QuotaMultipliers.Count, 6)), quotaPatternLengthEntry.min, quotaPatternLengthEntry.max);
				quotaPatternLengthEntry.defaultValue = quotaPatternLengthEntry.value;
				for (int i = 0; i < quotaMultiplierEntries.Length; i++)
				{
					SliderSettingItem val = quotaMultiplierEntries[i];
					val.max = Mathf.Max(100f, Mathf.Ceil(array[i]));
					val.value = Mathf.Clamp(array[i], val.min, val.max);
					val.defaultValue = val.value;
				}
			}
			finally
			{
				_isSynchronizing = false;
			}
		}

		private static bool TryGetCurrentProfileForEditing(out TimingProfile? profile)
		{
			if (!TimingCoordinator.TryGetResolvedProfile("NativeLobbySettingsMenu.Sync", out profile) || profile == null)
			{
				return false;
			}
			return true;
		}

		private static bool IsTimeConfigKey(string? key)
		{
			switch (key)
			{
			default:
				return IsQuotaMultiplierKey(key);
			case "timeconfig.day-duration-minutes":
			case "timeconfig.starting-quota":
			case "timeconfig.catch-up-factor":
			case "timeconfig.quota-scaling-mode":
			case "timeconfig.quota-pattern-length":
				return true;
			}
		}

		private static bool IsLobbySettingsLayout(SettingsLayout layout)
		{
			return FindTargetTab(layout) != null;
		}

		private static Tab? FindTargetTab(SettingsLayout layout)
		{
			foreach (Tab tab in layout.tabs)
			{
				if (tab.entries != null && tab.entries.Count != 0)
				{
					if (tab.entries.Any(IsLobbyModeEntry))
					{
						return tab;
					}
					if (!string.IsNullOrWhiteSpace(tab.tabName) && string.Equals(tab.tabName.Trim(), "Settings", StringComparison.OrdinalIgnoreCase))
					{
						return tab;
					}
				}
			}
			return null;
		}

		private static bool IsLobbyModeEntry(SettingItemBase entry)
		{
			if (!(entry is DropdownSettingItem))
			{
				return false;
			}
			if (string.Equals(entry.key, "timeconfig.section", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			return string.Equals(entry.label?.Trim(), "Lobby Mode", StringComparison.OrdinalIgnoreCase);
		}

		private static int EnsureTitleEntry(ICollection<SettingItemBase> entries, string key, string label)
		{
			string key2 = key;
			if (entries.Any((SettingItemBase entry) => entry.key == key2))
			{
				return 0;
			}
			TitleSettingItem val = ScriptableObject.CreateInstance<TitleSettingItem>();
			((Object)val).hideFlags = (HideFlags)61;
			((SettingItemBase)val).key = key2;
			((SettingItemBase)val).label = label;
			entries.Add((SettingItemBase)(object)val);
			return 1;
		}

		private static int EnsureDropdownEntry(ICollection<SettingItemBase> entries, string key, string label, IReadOnlyCollection<string> options, int defaultIndex)
		{
			string key2 = key;
			if (entries.Any((SettingItemBase entry) => entry.key == key2))
			{
				return 0;
			}
			DropdownSettingItem val = ScriptableObject.CreateInstance<DropdownSettingItem>();
			((Object)val).hideFlags = (HideFlags)61;
			((SettingItemBase)val).key = key2;
			((SettingItemBase)val).label = label;
			val.options = options.ToList();
			val.index = Mathf.Clamp(defaultIndex, 0, val.options.Count - 1);
			val.loadOnSceneStart = false;
			entries.Add((SettingItemBase)(object)val);
			return 1;
		}

		private static int EnsureSliderEntry(ICollection<SettingItemBase> entries, string key, string label, float min, float max, bool wholeNumbers)
		{
			string key2 = key;
			if (entries.Any((SettingItemBase entry) => entry.key == key2))
			{
				return 0;
			}
			SliderSettingItem val = ScriptableObject.CreateInstance<SliderSettingItem>();
			((Object)val).hideFlags = (HideFlags)61;
			((SettingItemBase)val).key = key2;
			((SettingItemBase)val).label = label;
			val.min = min;
			val.max = max;
			val.wholeNumbers = wholeNumbers;
			val.value = min;
			val.defaultValue = min;
			val.loadOnSceneStart = false;
			entries.Add((SettingItemBase)(object)val);
			return 1;
		}

		private static T? FindEntry<T>(SettingsLayout layout, string key) where T : SettingItemBase
		{
			string key2 = key;
			foreach (Tab tab in layout.tabs)
			{
				if (tab.entries != null)
				{
					T val = tab.entries.OfType<T>().FirstOrDefault((T entry) => ((SettingItemBase)entry).key == key2);
					if ((Object)(object)val != (Object)null)
					{
						return val;
					}
				}
			}
			return default(T);
		}

		private static bool TryGetEditableEntries(SettingsLayout layout, out SliderSettingItem? dayDurationEntry, out SliderSettingItem? startingQuotaEntry, out SliderSettingItem? catchUpFactorEntry, out DropdownSettingItem? quotaScalingModeEntry, out SliderSettingItem? quotaPatternLengthEntry, out SliderSettingItem[]? quotaMultiplierEntries)
		{
			dayDurationEntry = FindEntry<SliderSettingItem>(layout, "timeconfig.day-duration-minutes");
			startingQuotaEntry = FindEntry<SliderSettingItem>(layout, "timeconfig.starting-quota");
			catchUpFactorEntry = FindEntry<SliderSettingItem>(layout, "timeconfig.catch-up-factor");
			quotaScalingModeEntry = FindEntry<DropdownSettingItem>(layout, "timeconfig.quota-scaling-mode");
			quotaPatternLengthEntry = FindEntry<SliderSettingItem>(layout, "timeconfig.quota-pattern-length");
			quotaMultiplierEntries = GetQuotaMultiplierEntries(layout);
			if ((Object)(object)dayDurationEntry != (Object)null && (Object)(object)startingQuotaEntry != (Object)null && (Object)(object)catchUpFactorEntry != (Object)null && (Object)(object)quotaScalingModeEntry != (Object)null && (Object)(object)quotaPatternLengthEntry != (Object)null)
			{
				return quotaMultiplierEntries.Length == 6;
			}
			return false;
		}

		private static SliderSettingItem[] GetQuotaMultiplierEntries(SettingsLayout layout)
		{
			SliderSettingItem[] array = (SliderSettingItem[])(object)new SliderSettingItem[6];
			for (int i = 0; i < 6; i++)
			{
				SliderSettingItem val = FindEntry<SliderSettingItem>(layout, GetQuotaMultiplierKey(i));
				if ((Object)(object)val == (Object)null)
				{
					return Array.Empty<SliderSettingItem>();
				}
				array[i] = val;
			}
			return array;
		}

		private static QuotaScalingMode ResolveQuotaScalingMode(DropdownSettingItem entry)
		{
			if (entry.index != 1)
			{
				return QuotaScalingMode.Vanilla;
			}
			return QuotaScalingMode.CustomPattern;
		}

		private static string GetQuotaMultiplierKey(int index)
		{
			return $"timeconfig.quota-multiplier-{index + 1}";
		}

		private static bool IsQuotaMultiplierKey(string? key)
		{
			if (!string.IsNullOrWhiteSpace(key))
			{
				return key.StartsWith("timeconfig.quota-multiplier-", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}
	}
	public static class QuotaPatternEditor
	{
		public static float[] BuildEditableValues(IReadOnlyList<float> sourceValues, int slotCount)
		{
			if (slotCount <= 0)
			{
				throw new ArgumentOutOfRangeException("slotCount");
			}
			float[] array = new float[slotCount];
			float num = ((sourceValues != null && sourceValues.Count > 0) ? sourceValues[sourceValues.Count - 1] : 1f);
			for (int i = 0; i < slotCount; i++)
			{
				array[i] = ((i < sourceValues.Count) ? sourceValues[i] : num);
			}
			return array;
		}

		public static float[] BuildPattern(IReadOnlyList<float> editableValues, int desiredLength)
		{
			if (editableValues == null)
			{
				throw new ArgumentNullException("editableValues");
			}
			if (editableValues.Count == 0)
			{
				return Array.Empty<float>();
			}
			int num = Math.Max(1, Math.Min(desiredLength, editableValues.Count));
			float[] array = new float[num];
			for (int i = 0; i < num; i++)
			{
				array[i] = editableValues[i];
			}
			return array;
		}
	}
	public static class QuotaRuntimeStatePlanner
	{
		public static int ComputeRemainingDays(int daysBeforeQuota, int daysPassed)
		{
			return Math.Max(0, daysBeforeQuota - daysPassed);
		}

		public static bool ShouldResetInitialQuota(long previousStartingQuota, long currentQuota, long requiredQuota, int daysPassed, int successfulQuota)
		{
			if (successfulQuota == 0 && daysPassed == 0 && currentQuota == previousStartingQuota)
			{
				return requiredQuota == previousStartingQuota;
			}
			return false;
		}
	}
	public static class TimingCoordinator
	{
		private static readonly FieldRef<GameManager, GameSettings> GameSettingsRef = AccessTools.FieldRefAccess<GameManager, GameSettings>("_gs");

		private static readonly FieldRef<SaveManager, SaveData> CurrentSaveDataRef = AccessTools.FieldRefAccess<SaveManager, SaveData>("currentSaveData");

		private static readonly PropertyInfo? HasDayStartedProperty = AccessTools.Property(typeof(GameManager), "HasDayStarted");

		private static readonly PropertyInfo? NetworkTimerProperty = AccessTools.Property(typeof(GameManager), "Network_timer");

		private static readonly PropertyInfo? NetworkCurrentQuotaProperty = AccessTools.Property(typeof(GameManager), "NetworkcurrentQuota");

		private static readonly PropertyInfo? NetworkRequiredQuotaProperty = AccessTools.Property(typeof(GameManager), "NetworkrequiredQuotaToNextFloor");

		private static readonly PropertyInfo? NetworkDaysLeftProperty = AccessTools.Property(typeof(GameManager), "NetworkdaysLeft");

		private static readonly PropertyInfo? NetworkDaysPassedProperty = AccessTools.Property(typeof(GameManager), "NetworkdaysPassed");

		private static readonly PropertyInfo? NetworkSuccessfulQuotaProperty = AccessTools.Property(typeof(GameManager), "NetworksuccessfulQuota");

		private static ActiveSettings? _settings;

		private static ManualLogSource? _log;

		private static TimingProfile? _vanillaProfile;

		public static SessionTimingState? CurrentState { get; private set; }

		public static void Initialize(ActiveSettings settings, ManualLogSource log)
		{
			_settings = settings;
			_log = log;
			_vanillaProfile = null;
			CurrentState = null;
		}

		public static bool TryApplyFromResources(string context)
		{
			EnsureInitialized();
			if (!TryGetActiveGameSettings(context, out GameSettings gameSettings, includeSceneInstance: false))
			{
				return false;
			}
			return TryApplyToGameSettings(gameSettings, context);
		}

		public static bool TryApplyToGameSettings(GameSettings gameSettings, string context)
		{
			EnsureInitialized();
			CaptureVanillaProfile(gameSettings);
			if (!_settings.IsEnabled)
			{
				TimingProfile baseProfile = GetBaseProfile(gameSettings);
				CurrentState = BuildState(baseProfile.Name, baseProfile.IsVanillaProfile, baseProfile.DayDurationSeconds, baseProfile.DaysBeforeQuota, baseProfile.StartingQuota, baseProfile.CatchUpFactor, baseProfile.QuotaScalingMode, baseProfile.QuotaMultipliers.Count, context);
				return false;
			}
			if (!TryResolveProfile(gameSettings, context, out TimingProfile profile))
			{
				return false;
			}
			TimingProfile profile2 = profile;
			ApplyResolvedProfileToGameSettings(gameSettings, profile2, context);
			return true;
		}

		public static bool TryApplyInitialQuotaToSaveData(SaveData saveData, string context)
		{
			EnsureInitialized();
			if (saveData == null || !_settings.IsEnabled)
			{
				return false;
			}
			if (!TryGetActiveGameSettings(context, out GameSettings gameSettings))
			{
				return false;
			}
			if (!TryResolveProfile(gameSettings, context, out TimingProfile profile))
			{
				return false;
			}
			ApplyResolvedProfileToSaveData(saveData, profile, context, resetQuotaState: true);
			return true;
		}

		public static bool TryGetResolvedProfile(string context, out TimingProfile? profile)
		{
			EnsureInitialized();
			if (!TryGetActiveGameSettings(context, out GameSettings gameSettings))
			{
				profile = null;
				return false;
			}
			if (!_settings.IsEnabled)
			{
				profile = GetBaseProfile(gameSettings);
				return true;
			}
			return TryResolveProfile(gameSettings, context, out profile);
		}

		public static bool TryApplyManualOverrides(TimingProfile previousProfile, TimingProfile updatedProfile, string context, out IReadOnlyList<ValidationOutcome> outcomes)
		{
			EnsureInitialized();
			if (!TryGetActiveGameSettings(context, out GameSettings gameSettings))
			{
				outcomes = new ValidationOutcome[1] { ValidationOutcome.Error("GameSettings", "No active GameSettings were available to apply manual overrides.") };
				return false;
			}
			ValidationOutcome[] array = (outcomes = TimingProfileValidator.Validate(updatedProfile)).Where((ValidationOutcome outcome) => outcome.Status == ValidationStatus.Error).ToArray();
			if (array.Length != 0)
			{
				ValidationOutcome[] array2 = array;
				foreach (ValidationOutcome validationOutcome in array2)
				{
					_log.LogError((object)("[" + context + "] " + validationOutcome.TargetField + ": " + validationOutcome.Message));
				}
				return false;
			}
			CaptureVanillaProfile(gameSettings);
			_settings.SetManualOverrides(updatedProfile);
			if (!_settings.TryCreateResolvedProfile(GetBaseProfile(gameSettings), out TimingProfile profile, out IReadOnlyList<ValidationOutcome> outcomes2))
			{
				outcomes = outcomes2;
				foreach (ValidationOutcome item in outcomes2.Where((ValidationOutcome outcome) => outcome.Status == ValidationStatus.Error))
				{
					_log.LogError((object)("[" + context + "] " + item.TargetField + ": " + item.Message));
				}
				return false;
			}
			outcomes = outcomes2;
			ApplyResolvedProfileToGameSettings(gameSettings, profile, context);
			GameManager val = Object.FindFirstObjectByType<GameManager>();
			if ((Object)(object)val != (Object)null)
			{
				ApplyManualProfileToGameManager(val, previousProfile, profile, context);
			}
			SaveManager val2 = Object.FindFirstObjectByType<SaveManager>();
			if ((Object)(object)val2 != (Object)null)
			{
				SaveData val3 = CurrentSaveDataRef.Invoke(val2);
				if (val3 != null)
				{
					ApplyManualProfileToSaveData(val3, previousProfile, profile, context);
				}
			}
			return true;
		}

		public static bool TryApplyToGameManagerRuntime(GameManager gameManager, string context)
		{
			EnsureInitialized();
			if ((Object)(object)gameManager == (Object)null || !_settings.IsEnabled)
			{
				return false;
			}
			GameSettings val = GameSettingsRef.Invoke(gameManager);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if (!TryResolveProfile(val, context, out TimingProfile profile))
			{
				return false;
			}
			ApplyResolvedProfileToGameManager(gameManager, profile, context);
			return true;
		}

		private static bool TryResolveProfile(GameSettings gameSettings, string context, out TimingProfile? profile)
		{
			CaptureVanillaProfile(gameSettings);
			if (!_settings.TryCreateResolvedProfile(GetBaseProfile(gameSettings), out TimingProfile profile2, out IReadOnlyList<ValidationOutcome> outcomes))
			{
				foreach (ValidationOutcome item in outcomes.Where((ValidationOutcome o) => o.Status == ValidationStatus.Error))
				{
					_log.LogError((object)("[" + context + "] " + item.TargetField + ": " + item.Message));
				}
				profile = null;
				return false;
			}
			profile = profile2;
			return true;
		}

		private static void CaptureVanillaProfile(GameSettings gameSettings)
		{
			if (_vanillaProfile == null && !((Object)(object)gameSettings == (Object)null))
			{
				_vanillaProfile = new TimingProfile("Vanilla", isVanillaProfile: true, gameSettings.dayDuration, gameSettings.daysBeforeQuota, gameSettings.startingQuota, gameSettings.catchUpFactor, QuotaScalingMode.Vanilla, (gameSettings.quotas ?? Array.Empty<float>()).ToArray());
			}
		}

		private static TimingProfile GetBaseProfile(GameSettings gameSettings)
		{
			CaptureVanillaProfile(gameSettings);
			return _vanillaProfile ?? new TimingProfile("Vanilla", isVanillaProfile: true, gameSettings.dayDuration, gameSettings.daysBeforeQuota, gameSettings.startingQuota, gameSettings.catchUpFactor, QuotaScalingMode.Vanilla, (gameSettings.quotas ?? Array.Empty<float>()).ToArray());
		}

		private static bool TryGetActiveGameSettings(string context, out GameSettings? gameSettings, bool includeSceneInstance = true)
		{
			if (includeSceneInstance)
			{
				GameManager val = Object.FindFirstObjectByType<GameManager>();
				if ((Object)(object)val != (Object)null)
				{
					GameSettings val2 = GameSettingsRef.Invoke(val);
					if ((Object)(object)val2 != (Object)null)
					{
						gameSettings = val2;
						return true;
					}
				}
			}
			gameSettings = Resources.Load<GameSettings>("GameSettings");
			if ((Object)(object)gameSettings == (Object)null)
			{
				_log.LogDebug((object)("No GameSettings resource was available during " + context + "."));
				return false;
			}
			return true;
		}

		private static void ApplyResolvedProfileToGameSettings(GameSettings gameSettings, TimingProfile profile, string context)
		{
			gameSettings.dayDuration = profile.DayDurationSeconds;
			gameSettings.startingQuota = profile.StartingQuota;
			gameSettings.catchUpFactor = profile.CatchUpFactor;
			gameSettings.quotas = profile.QuotaMultipliers.ToArray();
			CurrentState = BuildState(profile.Name, profile.IsVanillaProfile, profile.DayDurationSeconds, profile.DaysBeforeQuota, profile.StartingQuota, profile.CatchUpFactor, profile.QuotaScalingMode, profile.QuotaMultipliers.Count, context);
			_log.LogInfo((object)("[" + context + "] Applied timing profile '" + profile.Name + "' " + $"(dayDuration={profile.DayDurationSeconds}, daysBeforeQuota={profile.DaysBeforeQuota}, " + $"startingQuota={profile.StartingQuota}, catchUpFactor={profile.CatchUpFactor}, " + $"quotaScalingMode={profile.QuotaScalingMode}, " + $"quotaMultipliers={profile.QuotaMultipliers.Count})."));
		}

		private static void ApplyResolvedProfileToGameManager(GameManager gameManager, TimingProfile profile, string context, bool resetQuotaState = true)
		{
			if (CanUpdatePreDayRuntimeState(gameManager))
			{
				NetworkTimerProperty?.SetValue(gameManager, profile.DayDurationSeconds);
				if (resetQuotaState)
				{
					NetworkCurrentQuotaProperty?.SetValue(gameManager, profile.StartingQuota);
					NetworkRequiredQuotaProperty?.SetValue(gameManager, profile.StartingQuota);
				}
				_log.LogInfo((object)("[" + context + "] Applied pre-day runtime state to GameManager " + $"(timer={profile.DayDurationSeconds}, quotaReset={resetQuotaState}, " + $"currentQuota={profile.StartingQuota}, requiredQuota={profile.StartingQuota})."));
			}
		}

		private static void ApplyManualProfileToGameManager(GameManager gameManager, TimingProfile previousProfile, TimingProfile updatedProfile, string context)
		{
			if (CanUpdatePreDayRuntimeState(gameManager))
			{
				NetworkTimerProperty?.SetValue(gameManager, updatedProfile.DayDurationSeconds);
				int daysPassed = ReadIntProperty(NetworkDaysPassedProperty, gameManager);
				int successfulQuota = ReadIntProperty(NetworkSuccessfulQuotaProperty, gameManager);
				bool flag = QuotaRuntimeStatePlanner.ShouldResetInitialQuota(previousProfile.StartingQuota, ReadLongProperty(NetworkCurrentQuotaProperty, gameManager), ReadLongProperty(NetworkRequiredQuotaProperty, gameManager), daysPassed, successfulQuota);
				if (flag)
				{
					NetworkCurrentQuotaProperty?.SetValue(gameManager, updatedProfile.StartingQuota);
					NetworkRequiredQuotaProperty?.SetValue(gameManager, updatedProfile.StartingQuota);
				}
				_log.LogInfo((object)("[" + context + "] Applied manual pre-day runtime state to GameManager " + $"(timer={updatedProfile.DayDurationSeconds}, preservedQuota={!flag})."));
			}
		}

		private static void ApplyResolvedProfileToSaveData(SaveData saveData, TimingProfile profile, string context, bool resetQuotaState)
		{
			if (resetQuotaState)
			{
				saveData.currentQuota = profile.StartingQuota;
				saveData.requiredQuotaToNextFloor = profile.StartingQuota;
			}
			_log.LogInfo((object)("[" + context + "] Applied save timing state " + $"(quotaReset={resetQuotaState}, currentQuota={saveData.currentQuota}, " + $"requiredQuota={saveData.requiredQuotaToNextFloor})."));
		}

		private static void ApplyManualProfileToSaveData(SaveData saveData, TimingProfile previousProfile, TimingProfile updatedProfile, string context)
		{
			bool flag = QuotaRuntimeStatePlanner.ShouldResetInitialQuota(previousProfile.StartingQuota, saveData.currentQuota, saveData.requiredQuotaToNextFloor, saveData.daysPassed, saveData.successfulQuota);
			if (flag)
			{
				saveData.currentQuota = updatedProfile.StartingQuota;
				saveData.requiredQuotaToNextFloor = updatedProfile.StartingQuota;
			}
			_log.LogInfo((object)("[" + context + "] Updated active save state " + $"(preservedQuota={!flag})."));
		}

		private static bool CanUpdatePreDayRuntimeState(GameManager gameManager)
		{
			return !(HasDayStartedProperty?.GetValue(gameManager) as bool?).GetValueOrDefault();
		}

		private static int ReadIntProperty(PropertyInfo? property, object instance)
		{
			object obj = property?.GetValue(instance);
			if (!(obj is int result))
			{
				if (obj is long num)
				{
					return checked((int)num);
				}
				return 0;
			}
			return result;
		}

		private static long ReadLongProperty(PropertyInfo? property, object instance)
		{
			object obj = property?.GetValue(instance);
			if (!(obj is long result))
			{
				if (obj is int num)
				{
					return num;
				}
				return 0L;
			}
			return result;
		}

		private static SessionTimingState BuildState(string profileName, bool isVanilla, float dayDurationSeconds, int daysBeforeQuota, long startingQuota, float catchUpFactor, QuotaScalingMode quotaScalingMode, int quotaMultiplierCount, string context)
		{
			return new SessionTimingState(context, profileName, isVanilla, dayDurationSeconds, daysBeforeQuota, startingQuota, catchUpFactor, quotaScalingMode, quotaMultiplierCount, DateTimeOffset.UtcNow);
		}

		private static void EnsureInitialized()
		{
			if (_settings == null || _log == null)
			{
				throw new InvalidOperationException("TimingCoordinator.Initialize must run before timing overrides are applied.");
			}
		}
	}
}
namespace TimeConfig.Patches
{
	[HarmonyPatch(typeof(GameManager), "OnAwake")]
	internal static class DayTimerPatches
	{
		private static readonly FieldRef<GameManager, GameSettings> GameSettingsRef = AccessTools.FieldRefAccess<GameManager, GameSettings>("_gs");

		private static void Postfix(GameManager __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			GameSettings val = GameSettingsRef.Invoke(__instance);
			if (!((Object)(object)val == (Object)null))
			{
				if (!NetworkServer.active)
				{
					LobbyVisibility.ClearClientState();
					return;
				}
				TimingCoordinator.TryApplyToGameSettings(val, "GameManager.OnAwake");
				TimingCoordinator.TryApplyToGameManagerRuntime(__instance, "GameManager.OnAwake");
				LobbyVisibility.BroadcastCurrentState();
			}
		}
	}
	[HarmonyPatch(typeof(GameManager))]
	internal static class LobbyPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("OnStartServer")]
		private static void OnStartServer_Postfix()
		{
			LobbyVisibility.BroadcastCurrentState();
		}

		[HarmonyPostfix]
		[HarmonyPatch("ServerOnClientScenePlayReady")]
		private static void ServerOnClientScenePlayReady_Postfix(NetworkConnectionToClient conn)
		{
			LobbyVisibility.SendToClient(conn);
		}

		[HarmonyPostfix]
		[HarmonyPatch("RpcSetInLobbyPresence")]
		private static void RpcSetInLobbyPresence_Postfix(GameManager __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && NetworkServer.active)
			{
				TimingCoordinator.TryApplyToGameManagerRuntime(__instance, "GameManager.RpcSetInLobbyPresence");
				LobbyVisibility.BroadcastCurrentState();
			}
		}
	}
	[HarmonyPatch]
	internal static class NativeLobbySettingsRuntimeUIPatches
	{
		private static readonly FieldRef<SettingsLayoutRuntimeUI, SettingsLayout> LayoutRef = AccessTools.FieldRefAccess<SettingsLayoutRuntimeUI, SettingsLayout>("layout");

		[HarmonyPrefix]
		[HarmonyPatch(typeof(SettingsLayoutRuntimeUI), "Awake")]
		private static void SettingsLayoutRuntimeUI_Awake_Prefix(SettingsLayoutRuntimeUI __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				SettingsLayout val = LayoutRef.Invoke(__instance);
				if (!((Object)(object)val == (Object)null))
				{
					NativeLobbySettingsMenu.EnsureInjected(val, "SettingsLayoutRuntimeUI.Awake");
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class NativeLobbySettingsButtonPatches
	{
		private static readonly FieldRef<LobbyModeDropdownButton, SettingsLayout> LayoutRef = AccessTools.FieldRefAccess<LobbyModeDropdownButton, SettingsLayout>("settingsLayout");

		[HarmonyPostfix]
		[HarmonyPatch(typeof(LobbyModeDropdownButton), "Awake")]
		private static void LobbyModeDropdownButton_Awake_Postfix(LobbyModeDropdownButton __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				SettingsLayout val = LayoutRef.Invoke(__instance);
				if (!((Object)(object)val == (Object)null))
				{
					NativeLobbySettingsMenu.RegisterLobbyLayout(val, "LobbyModeDropdownButton.Awake");
					NativeLobbySettingsMenu.EnsureInjected(val, "LobbyModeDropdownButton.Awake");
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(LobbyModeDropdownButton), "OnClick")]
		private static void LobbyModeDropdownButton_OnClick_Prefix(LobbyModeDropdownButton __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				SettingsLayout val = LayoutRef.Invoke(__instance);
				if (!((Object)(object)val == (Object)null))
				{
					NativeLobbySettingsMenu.RefreshFromLobbyButton(val);
				}
			}
		}
	}
	[HarmonyPatch(typeof(SettingItemBase), "NotifyChanged")]
	internal static class NativeLobbySettingChangePatches
	{
		private static void Postfix(SettingItemBase __instance)
		{
			NativeLobbySettingsMenu.HandleSettingChanged(__instance);
		}
	}
	[HarmonyPatch(typeof(LocalSaveManager), "CreateNewSave")]
	internal static class QuotaSaveInitializationPatches
	{
		private static readonly FieldRef<SaveManager, SaveData> CurrentSaveDataRef = AccessTools.FieldRefAccess<SaveManager, SaveData>("currentSaveData");

		private static void Prefix()
		{
			if (NetworkServer.active)
			{
				TimingCoordinator.TryApplyFromResources("LocalSaveManager.CreateNewSave");
			}
		}

		private static void Postfix()
		{
			if (!NetworkServer.active)
			{
				return;
			}
			SaveManager val = Object.FindFirstObjectByType<SaveManager>();
			if (!((Object)(object)val == (Object)null))
			{
				SaveData val2 = CurrentSaveDataRef.Invoke(val);
				if (val2 != null)
				{
					TimingCoordinator.TryApplyInitialQuotaToSaveData(val2, "LocalSaveManager.CreateNewSave");
				}
			}
		}
	}
	[HarmonyPatch(typeof(SaveManager), "ResetCurrentSaveToDefaults")]
	internal static class QuotaSaveResetPatches
	{
		private static readonly FieldRef<SaveManager, SaveData> CurrentSaveDataRef = AccessTools.FieldRefAccess<SaveManager, SaveData>("currentSaveData");

		private static void Prefix()
		{
			if (NetworkServer.active)
			{
				TimingCoordinator.TryApplyFromResources("SaveManager.ResetCurrentSaveToDefaults");
			}
		}

		private static void Postfix(SaveManager __instance)
		{
			if (NetworkServer.active && !((Object)(object)__instance == (Object)null))
			{
				SaveData val = CurrentSaveDataRef.Invoke(__instance);
				if (val != null)
				{
					TimingCoordinator.TryApplyInitialQuotaToSaveData(val, "SaveManager.ResetCurrentSaveToDefaults");
				}
			}
		}
	}
}
namespace TimeConfig.Network
{
	public static class LobbyVisibility
	{
		private static ManualLogSource? _log;

		private static SessionTimingState? _clientState;

		public static void Initialize(ManualLogSource log)
		{
			_log = log;
		}

		public static SessionTimingState? GetVisibleState()
		{
			if (NetworkServer.active)
			{
				return TimingCoordinator.CurrentState;
			}
			if (NetworkClient.active)
			{
				return _clientState;
			}
			return TimingCoordinator.CurrentState;
		}

		public static void ClearClientState()
		{
			_clientState = null;
		}

		public static void RegisterMessageDelegates()
		{
			Writer<TimingConfigMessage>.write = delegate(NetworkWriter writer, TimingConfigMessage msg)
			{
				NetworkWriterExtensions.WriteBool(writer, msg.IsVanilla);
				NetworkWriterExtensions.WriteString(writer, msg.ProfileName ?? string.Empty);
				NetworkWriterExtensions.WriteFloat(writer, msg.DayDurationSeconds);
				NetworkWriterExtensions.WriteInt(writer, msg.DaysBeforeQuota);
				NetworkWriterExtensions.WriteLong(writer, msg.StartingQuota);
				NetworkWriterExtensions.WriteFloat(writer, msg.CatchUpFactor);
				NetworkWriterExtensions.WriteInt(writer, msg.QuotaScalingModeValue);
				NetworkWriterExtensions.WriteInt(writer, msg.QuotaMultiplierCount);
			};
			Reader<TimingConfigMessage>.read = delegate(NetworkReader reader)
			{
				TimingConfigMessage result = default(TimingConfigMessage);
				result.IsVanilla = NetworkReaderExtensions.ReadBool(reader);
				result.ProfileName = NetworkReaderExtensions.ReadString(reader);
				result.DayDurationSeconds = NetworkReaderExtensions.ReadFloat(reader);
				result.DaysBeforeQuota = NetworkReaderExtensions.ReadInt(reader);
				result.StartingQuota = NetworkReaderExtensions.ReadLong(reader);
				result.CatchUpFactor = NetworkReaderExtensions.ReadFloat(reader);
				result.QuotaScalingModeValue = NetworkReaderExtensions.ReadInt(reader);
				result.QuotaMultiplierCount = NetworkReaderExtensions.ReadInt(reader);
				return result;
			};
			NetworkClient.RegisterHandler<TimingConfigMessage>((Action<TimingConfigMessage>)OnTimingConfigReceived, true);
		}

		public static void BroadcastCurrentState()
		{
			if (!NetworkServer.active)
			{
				return;
			}
			SessionTimingState currentState = TimingCoordinator.CurrentState;
			if (currentState != null)
			{
				NetworkServer.SendToAll<TimingConfigMessage>(BuildMessage(currentState), 0, false);
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogDebug((object)("[LobbyVisibility] Broadcast timing profile '" + currentState.ProfileName + "' to all clients."));
				}
			}
		}

		public static void SendToClient(NetworkConnectionToClient conn)
		{
			if (NetworkServer.active)
			{
				SessionTimingState currentState = TimingCoordinator.CurrentState;
				if (currentState != null)
				{
					((NetworkConnection)conn).Send<TimingConfigMessage>(BuildMessage(currentState), 0);
				}
			}
		}

		private static TimingConfigMessage BuildMessage(SessionTimingState state)
		{
			TimingConfigMessage result = default(TimingConfigMessage);
			result.IsVanilla = state.IsVanilla;
			result.ProfileName = state.ProfileName;
			result.DayDurationSeconds = state.DayDurationSeconds;
			result.DaysBeforeQuota = state.DaysBeforeQuota;
			result.StartingQuota = state.StartingQuota;
			result.CatchUpFactor = state.CatchUpFactor;
			result.QuotaScalingModeValue = (int)state.QuotaScalingMode;
			result.QuotaMultiplierCount = state.QuotaMultiplierCount;
			return result;
		}

		private static void OnTimingConfigReceived(TimingConfigMessage msg)
		{
			if (NetworkServer.activeHost)
			{
				return;
			}
			_clientState = new SessionTimingState("TimingConfigMessage", string.IsNullOrWhiteSpace(msg.ProfileName) ? "Vanilla" : msg.ProfileName, msg.IsVanilla, msg.DayDurationSeconds, msg.DaysBeforeQuota, msg.StartingQuota, msg.CatchUpFactor, ResolveQuotaScalingMode(msg.QuotaScalingModeValue), msg.QuotaMultiplierCount, DateTimeOffset.UtcNow);
			if (msg.IsVanilla)
			{
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[LobbyVisibility] Host is using vanilla timing — no overrides active.");
				}
				return;
			}
			ManualLogSource? log2 = _log;
			if (log2 != null)
			{
				log2.LogInfo((object)("[LobbyVisibility] Host timing profile '" + msg.ProfileName + "': " + $"dayDuration={msg.DayDurationSeconds}s, daysBeforeQuota={msg.DaysBeforeQuota}, " + $"startingQuota={msg.StartingQuota}, catchUpFactor={msg.CatchUpFactor}, " + $"quotaScalingMode={ResolveQuotaScalingMode(msg.QuotaScalingModeValue)}, " + $"quotaMultiplierCount={msg.QuotaMultiplierCount}"));
			}
		}

		private static QuotaScalingMode ResolveQuotaScalingMode(int rawValue)
		{
			if (!Enum.IsDefined(typeof(QuotaScalingMode), rawValue))
			{
				return QuotaScalingMode.Vanilla;
			}
			return (QuotaScalingMode)rawValue;
		}
	}
	public struct TimingConfigMessage : NetworkMessage
	{
		public bool IsVanilla;

		public string ProfileName;

		public float DayDurationSeconds;

		public int DaysBeforeQuota;

		public long StartingQuota;

		public float CatchUpFactor;

		public int QuotaScalingModeValue;

		public int QuotaMultiplierCount;
	}
}
namespace TimeConfig.Models
{
	public enum QuotaScalingMode
	{
		Vanilla,
		CustomPattern
	}
	public static class QuotaScalingModeParser
	{
		public static bool TryParse(string? rawValue, out QuotaScalingMode mode)
		{
			return Enum.TryParse<QuotaScalingMode>(rawValue?.Trim(), ignoreCase: true, out mode);
		}

		public static QuotaScalingMode ParseOrDefault(string? rawValue, QuotaScalingMode fallback = QuotaScalingMode.Vanilla)
		{
			if (!TryParse(rawValue, out var mode))
			{
				return fallback;
			}
			return mode;
		}
	}
	public sealed class SessionTimingState
	{
		public string Source { get; }

		public string ProfileName { get; }

		public bool IsVanilla { get; }

		public float DayDurationSeconds { get; }

		public int DaysBeforeQuota { get; }

		public long StartingQuota { get; }

		public float CatchUpFactor { get; }

		public QuotaScalingMode QuotaScalingMode { get; }

		public int QuotaMultiplierCount { get; }

		public DateTimeOffset AppliedAtUtc { get; }

		public SessionTimingState(string source, string profileName, bool isVanilla, float dayDurationSeconds, int daysBeforeQuota, long startingQuota, float catchUpFactor, QuotaScalingMode quotaScalingMode, int quotaMultiplierCount, DateTimeOffset appliedAtUtc)
		{
			Source = source;
			ProfileName = profileName;
			IsVanilla = isVanilla;
			DayDurationSeconds = dayDurationSeconds;
			DaysBeforeQuota = daysBeforeQuota;
			StartingQuota = startingQuota;
			CatchUpFactor = catchUpFactor;
			QuotaScalingMode = quotaScalingMode;
			QuotaMultiplierCount = quotaMultiplierCount;
			AppliedAtUtc = appliedAtUtc;
		}
	}
	public sealed class TimingProfile
	{
		public string Name { get; }

		public bool IsVanillaProfile { get; }

		public float DayDurationSeconds { get; }

		public int DaysBeforeQuota { get; }

		public long StartingQuota { get; }

		public float CatchUpFactor { get; }

		public QuotaScalingMode QuotaScalingMode { get; }

		public IReadOnlyList<float> QuotaMultipliers { get; }

		public TimingProfile(string name, bool isVanillaProfile, float dayDurationSeconds, int daysBeforeQuota, long startingQuota, float catchUpFactor, QuotaScalingMode quotaScalingMode, IReadOnlyList<float> quotaMultipliers)
		{
			Name = name ?? throw new ArgumentNullException("name");
			IsVanillaProfile = isVanillaProfile;
			DayDurationSeconds = dayDurationSeconds;
			DaysBeforeQuota = daysBeforeQuota;
			StartingQuota = startingQuota;
			CatchUpFactor = catchUpFactor;
			QuotaScalingMode = quotaScalingMode;
			QuotaMultipliers = quotaMultipliers ?? throw new ArgumentNullException("quotaMultipliers");
		}
	}
	public enum ValidationStatus
	{
		Valid,
		Warning,
		Error
	}
	public sealed class ValidationOutcome
	{
		public string TargetField { get; }

		public ValidationStatus Status { get; }

		public string Message { get; }

		public ValidationOutcome(string targetField, ValidationStatus status, string message)
		{
			TargetField = targetField;
			Status = status;
			Message = message;
		}

		public static ValidationOutcome Valid(string targetField, string message)
		{
			return new ValidationOutcome(targetField, ValidationStatus.Valid, message);
		}

		public static ValidationOutcome Warning(string targetField, string message)
		{
			return new ValidationOutcome(targetField, ValidationStatus.Warning, message);
		}

		public static ValidationOutcome Error(string targetField, string message)
		{
			return new ValidationOutcome(targetField, ValidationStatus.Error, message);
		}
	}
}
namespace TimeConfig.Configuration
{
	public sealed class ActiveSettings
	{
		private readonly ConfigFile _config;

		public const float PreserveFloat = -1f;

		public const int PreserveInt = -1;

		public const long PreserveLong = -1L;

		private readonly ConfigEntry<bool> _enableCustomTiming;

		private readonly ConfigEntry<float> _dayDurationSeconds;

		private readonly ConfigEntry<int> _daysBeforeQuota;

		private readonly ConfigEntry<long> _startingQuota;

		private readonly ConfigEntry<float> _catchUpFactor;

		private readonly ConfigEntry<string> _quotaScalingMode;

		private readonly ConfigEntry<string> _quotaMultipliersCsv;

		private readonly ConfigEntry<string> _activeProfileName;

		private readonly ProfileStore _profileStore;

		public bool IsEnabled => _enableCustomTiming.Value;

		public ProfileStore Profiles => _profileStore;

		public string ActiveProfileName => _activeProfileName.Value?.Trim() ?? string.Empty;

		private ActiveSettings(ConfigFile config, ConfigEntry<bool> enableCustomTiming, ConfigEntry<float> dayDurationSeconds, ConfigEntry<int> daysBeforeQuota, ConfigEntry<long> startingQuota, ConfigEntry<float> catchUpFactor, ConfigEntry<string> quotaScalingMode, ConfigEntry<string> quotaMultipliersCsv, ConfigEntry<string> activeProfileName, ProfileStore profileStore)
		{
			_config = config;
			_enableCustomTiming = enableCustomTiming;
			_dayDurationSeconds = dayDurationSeconds;
			_daysBeforeQuota = daysBeforeQuota;
			_startingQuota = startingQuota;
			_catchUpFactor = catchUpFactor;
			_quotaScalingMode = quotaScalingMode;
			_quotaMultipliersCsv = quotaMultipliersCsv;
			_activeProfileName = activeProfileName;
			_profileStore = profileStore;
		}

		public static ActiveSettings Bind(ConfigFile config)
		{
			ConfigEntry<bool> enableCustomTiming = config.Bind<bool>("General", "EnableCustomTiming", false, "Enable host-authoritative timing overrides for new sessions.");
			ConfigEntry<float> dayDurationSeconds = config.Bind<float>("Timing", "DayDurationSeconds", -1f, "Override the vanilla day duration in seconds. Use -1 to preserve the loaded value.");
			ConfigEntry<int> daysBeforeQuota = config.Bind<int>("Timing", "DaysBeforeQuota", -1, "Deprecated. Multi-day quota overrides are ignored; use -1 to preserve the loaded value.");
			ConfigEntry<long> startingQuota = config.Bind<long>("Quota", "StartingQuota", -1L, "Override the starting quota for new sessions. Use -1 to preserve the loaded value.");
			ConfigEntry<float> catchUpFactor = config.Bind<float>("Quota", "CatchUpFactor", -1f, "Override the quota catch-up factor. Use -1 to preserve the loaded value.");
			ConfigEntry<string> quotaScalingMode = config.Bind<string>("Quota", "QuotaScalingMode", string.Empty, "Set the quota scaling mode to Vanilla or CustomPattern. Leave blank to keep legacy behavior based on whether a custom multiplier pattern is present.");
			ConfigEntry<string> quotaMultipliersCsv = config.Bind<string>("Quota", "QuotaMultipliersCsv", string.Empty, "Comma-separated quota multipliers. Leave blank to preserve the loaded values.");
			ConfigEntry<string> activeProfileName = config.Bind<string>("Profiles", "ActiveProfileName", string.Empty, "Name of a saved profile to load on startup. Overrides all individual Timing/Quota entries when non-empty. Use the ProfileStore API or edit profile files under BepInEx/config/TimeConfig/profiles/.");
			ProfileStore profileStore = new ProfileStore(Path.GetDirectoryName(config.ConfigFilePath) ?? AppDomain.CurrentDomain.BaseDirectory);
			return new ActiveSettings(config, enableCustomTiming, dayDurationSeconds, daysBeforeQuota, startingQuota, catchUpFactor, quotaScalingMode, quotaMultipliersCsv, activeProfileName, profileStore);
		}

		public void SetManualOverrides(TimingProfile profile)
		{
			_enableCustomTiming.Value = true;
			_activeProfileName.Value = string.Empty;
			_dayDurationSeconds.Value = profile.DayDurationSeconds;
			_daysBeforeQuota.Value = -1;
			_startingQuota.Value = profile.StartingQuota;
			_catchUpFactor.Value = profile.CatchUpFactor;
			_quotaScalingMode.Value = profile.QuotaScalingMode.ToString();
			_quotaMultipliersCsv.Value = ((profile.QuotaScalingMode == QuotaScalingMode.CustomPattern) ? string.Join(",", profile.QuotaMultipliers.Select((float multiplier) => multiplier.ToString(CultureInfo.InvariantCulture))) : string.Empty);
			_config.Save();
		}

		public bool TryCreateResolvedProfile(TimingProfile baseProfile, out TimingProfile profile, out IReadOnlyList<ValidationOutcome> outcomes)
		{
			List<ValidationOutcome> list = new List<ValidationOutcome>();
			StoredProfile storedProfile = null;
			string text = _activeProfileName.Value?.Trim() ?? string.Empty;
			if (!string.IsNullOrEmpty(text) && _profileStore.TryLoad(text, out StoredProfile profile2))
			{
				storedProfile = profile2;
			}
			float dayDurationSeconds = ResolveFloat(storedProfile?.DayDurationSeconds ?? _dayDurationSeconds.Value, baseProfile.DayDurationSeconds);
			int daysBeforeQuota = baseProfile.DaysBeforeQuota;
			long startingQuota = ResolveLong(storedProfile?.StartingQuota ?? _startingQuota.Value, baseProfile.StartingQuota);
			float catchUpFactor = ResolveFloat(storedProfile?.CatchUpFactor ?? _catchUpFactor.Value, baseProfile.CatchUpFactor);
			string text2 = _quotaMultipliersCsv.Value?.Trim() ?? string.Empty;
			QuotaScalingMode quotaScalingMode = ResolveQuotaScalingMode(storedProfile?.QuotaScalingMode, _quotaScalingMode.Value, storedProfile?.QuotaMultipliers, text2);
			float[] source = baseProfile.QuotaMultipliers.ToArray();
			if (quotaScalingMode == QuotaScalingMode.CustomPattern)
			{
				float[] array = storedProfile?.QuotaMultipliers;
				if (array != null && array.Length > 0)
				{
					source = array;
					goto IL_0173;
				}
			}
			if (quotaScalingMode == QuotaScalingMode.CustomPattern)
			{
				if (!string.IsNullOrWhiteSpace(text2))
				{
					if (TimingProfileValidator.TryParseQuotaMultipliers(text2, out float[] parsedValues, out ValidationOutcome error))
					{
						source = parsedValues;
					}
					else if (error != null)
					{
						list.Add(error);
					}
				}
				else
				{
					source = new float[0];
				}
			}
			goto IL_0173;
			IL_0173:
			string text3 = ((!string.IsNullOrEmpty(text)) ? text : "ActiveConfig");
			profile = new TimingProfile(IsEnabled ? text3 : "Vanilla", !IsEnabled, dayDurationSeconds, daysBeforeQuota, startingQuota, catchUpFactor, quotaScalingMode, source.ToArray());
			list.AddRange(TimingProfileValidator.Validate(profile));
			outcomes = list;
			return list.All((ValidationOutcome outcome) => outcome.Status != ValidationStatus.Error);
		}

		private static float ResolveFloat(float value, float vanilla)
		{
			if (!(value > -1f))
			{
				return vanilla;
			}
			return value;
		}

		private static int ResolveInt(int value, int vanilla)
		{
			if (value <= -1)
			{
				return vanilla;
			}
			return value;
		}

		private static long ResolveLong(long value, long vanilla)
		{
			if (value <= -1)
			{
				return vanilla;
			}
			return value;
		}

		private static QuotaScalingMode ResolveQuotaScalingMode(QuotaScalingMode? storedMode, string configuredMode, float[]? storedMultipliers, string rawMultipliers)
		{
			if (storedMode.HasValue)
			{
				return storedMode.Value;
			}
			if (QuotaScalingModeParser.TryParse(configuredMode, out var mode))
			{
				return mode;
			}
			if (((storedMultipliers != null && storedMultipliers.Length != 0) ? 1 : 0) <= (false ? 1 : 0) && string.IsNullOrWhiteSpace(rawMultipliers))
			{
				return QuotaScalingMode.Vanilla;
			}
			return QuotaScalingMode.CustomPattern;
		}
	}
	public sealed class ProfileStore
	{
		private readonly string _profilesDir;

		public ProfileStore(string configDir)
		{
			_profilesDir = Path.Combine(configDir, "TimeConfig", "profiles");
		}

		public IReadOnlyList<string> ListProfiles()
		{
			if (!Directory.Exists(_profilesDir))
			{
				return Array.Empty<string>();
			}
			return (from f in Directory.GetFiles(_profilesDir, "*.profile")
				select Path.GetFileNameWithoutExtension(f) ?? Path.GetFileName(f)).OrderBy<string, string>((string n) => n, StringComparer.OrdinalIgnoreCase).ToList();
		}

		public bool TryLoad(string name, out StoredProfile? profile)
		{
			profile = null;
			string path = ProfilePath(name);
			if (!File.Exists(path))
			{
				return false;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			string[] array = File.ReadAllLines(path);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0 && text[0] != '#')
				{
					int num = text.IndexOf('=');
					if (num >= 1)
					{
						string key = text.Substring(0, num).Trim();
						string text2 = text;
						int num2 = num + 1;
						dictionary[key] = text2.Substring(num2, text2.Length - num2).Trim();
					}
				}
			}
			profile = new StoredProfile(ParseFloat(dictionary, "DayDurationSeconds", -1f), ParseInt(dictionary, "DaysBeforeQuota", -1), ParseLong(dictionary, "StartingQuota", -1L), ParseFloat(dictionary, "CatchUpFactor", -1f), ParseQuotaScalingMode(dictionary), ParseMultipliers(dictionary));
			return true;
		}

		public void Save(string name, TimingProfile profile)
		{
			Directory.CreateDirectory(_profilesDir);
			List<string> list = new List<string>
			{
				"# TimeConfig profile: " + name,
				"DayDurationSeconds=" + profile.DayDurationSeconds.ToString(CultureInfo.InvariantCulture),
				$"DaysBeforeQuota={profile.DaysBeforeQuota}",
				$"StartingQuota={profile.StartingQuota}",
				"CatchUpFactor=" + profile.CatchUpFactor.ToString(CultureInfo.InvariantCulture),
				$"QuotaScalingMode={profile.QuotaScalingMode}"
			};
			if (profile.QuotaScalingMode == QuotaScalingMode.CustomPattern && profile.QuotaMultipliers.Count > 0)
			{
				list.Add("QuotaMultipliers=" + string.Join(",", profile.QuotaMultipliers.Select((float m) => m.ToString(CultureInfo.InvariantCulture))));
			}
			File.WriteAllLines(ProfilePath(name), list);
		}

		public bool Delete(string name)
		{
			string path = ProfilePath(name);
			if (!File.Exists(path))
			{
				return false;
			}
			File.Delete(path);
			return true;
		}

		private string ProfilePath(string name)
		{
			return Path.Combine(_profilesDir, name + ".profile");
		}

		private static float ParseFloat(Dictionary<string, string> props, string key, float fallback)
		{
			if (!props.TryGetValue(key, out string value) || !float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return fallback;
			}
			return result;
		}

		private static int ParseInt(Dictionary<string, string> props, string key, int fallback)
		{
			if (!props.TryGetValue(key, out string value) || !int.TryParse(value, out var result))
			{
				return fallback;
			}
			return result;
		}

		private static long ParseLong(Dictionary<string, string> props, string key, long fallback)
		{
			if (!props.TryGetValue(key, out string value) || !long.TryParse(value, out var result))
			{
				return fallback;
			}
			return result;
		}

		private static QuotaScalingMode? ParseQuotaScalingMode(Dictionary<string, string> props)
		{
			if (!props.TryGetValue("QuotaScalingMode", out string value) || string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			if (!QuotaScalingModeParser.TryParse(value, out var mode))
			{
				return null;
			}
			return mode;
		}

		private static float[] ParseMultipliers(Dictionary<string, string> props)
		{
			if (!props.TryGetValue("QuotaMultipliers", out string value))
			{
				return Array.Empty<float>();
			}
			string[] array = value.Split(',');
			float[] array2 = new float[array.Length];
			for (int i = 0; i < array.Length; i++)
			{
				if (!float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i]))
				{
					return Array.Empty<float>();
				}
			}
			return array2;
		}
	}
	public sealed class StoredProfile
	{
		public float DayDurationSeconds { get; }

		public int DaysBeforeQuota { get; }

		public long StartingQuota { get; }

		public float CatchUpFactor { get; }

		public QuotaScalingMode? QuotaScalingMode { get; }

		public float[] QuotaMultipliers { get; }

		public StoredProfile(float dayDurationSeconds, int daysBeforeQuota, long startingQuota, float catchUpFactor, QuotaScalingMode? quotaScalingMode, float[] quotaMultipliers)
		{
			DayDurationSeconds = dayDurationSeconds;
			DaysBeforeQuota = daysBeforeQuota;
			StartingQuota = startingQuota;
			CatchUpFactor = catchUpFactor;
			QuotaScalingMode = quotaScalingMode;
			QuotaMultipliers = quotaMultipliers;
		}
	}
	public static class TimingProfileValidator
	{
		public const float MinDayDurationSeconds = 10f;

		public const float MaxDayDurationSeconds = 86400f;

		public const int MinDaysBeforeQuota = 1;

		public const int MaxDaysBeforeQuota = 30;

		public const long MaxQuotaValue = 1000000000000000000L;

		public const float MinCatchUpFactor = 0f;

		public const float MaxCatchUpFactor = 5f;

		public const float MinQuotaMultiplier = 0.01f;

		public const float MaxQuotaMultiplier = 100f;

		public static IReadOnlyList<ValidationOutcome> Validate(TimingProfile profile)
		{
			List<ValidationOutcome> list = new List<ValidationOutcome>();
			if (profile.DayDurationSeconds < 10f || profile.DayDurationSeconds > 86400f)
			{
				list.Add(ValidationOutcome.Error("DayDurationSeconds", $"Day duration must be between {10f} and {86400f} seconds."));
			}
			if (profile.DaysBeforeQuota < 1 || profile.DaysBeforeQuota > 30)
			{
				list.Add(ValidationOutcome.Error("DaysBeforeQuota", $"Days before quota must be between {1} and {30}."));
			}
			if (profile.StartingQuota < 0 || profile.StartingQuota > 1000000000000000000L)
			{
				list.Add(ValidationOutcome.Error("StartingQuota", $"Starting quota must be between 0 and {1000000000000000000L}."));
			}
			if (profile.CatchUpFactor < 0f || profile.CatchUpFactor > 5f)
			{
				list.Add(ValidationOutcome.Error("CatchUpFactor", $"Catch-up factor must be between {0f} and {5f}."));
			}
			if (profile.QuotaScalingMode == QuotaScalingMode.CustomPattern)
			{
				if (profile.QuotaMultipliers.Count == 0)
				{
					list.Add(ValidationOutcome.Error("QuotaMultipliers", "At least one quota multiplier is required when custom quota scaling is enabled."));
				}
				else if (profile.QuotaMultipliers.Any((float multiplier) => multiplier < 0.01f || multiplier > 100f))
				{
					list.Add(ValidationOutcome.Error("QuotaMultipliers", $"Each quota multiplier must be between {0.01f} and {100f}."));
				}
			}
			if (list.Count == 0)
			{
				list.Add(ValidationOutcome.Valid("Profile", "Timing profile values are valid."));
			}
			return list;
		}

		public static bool TryParseQuotaMultipliers(string rawValue, out float[] parsedValues, out ValidationOutcome? error)
		{
			string[] array = (from token in rawValue.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)
				select token.Trim()).ToArray();
			if (array.Length == 0)
			{
				parsedValues = new float[0];
				error = ValidationOutcome.Error("rawValue", "Quota multipliers cannot be empty when an override string is provided.");
				return false;
			}
			List<float> list = new List<float>(array.Length);
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					parsedValues = new float[0];
					error = ValidationOutcome.Error("rawValue", "'" + text + "' is not a valid floating-point quota multiplier.");
					return false;
				}
				list.Add(result);
			}
			parsedValues = list.ToArray();
			error = null;
			return true;
		}
	}
}