Decompiled source of CustomTimer v0.1.1

plugins/CustomTimer.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CustomTimer.Config;
using CustomTimer.Core;
using CustomTimer.Detect;
using CustomTimer.Net;
using CustomTimer.Patch;
using CustomTimer.UI;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("Kai")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CustomTimer")]
[assembly: AssemblyTitle("CustomTimer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CustomTimer
{
	[BepInPlugin("Kai.CustomTimer", "CustomTimer", "0.2.0")]
	public class CustomTimer : BaseUnityPlugin
	{
		private float _syncElapsed;

		private const float SYNC_INTERVAL = 10f;

		internal static CustomTimer Instance { get; private set; }

		internal static ManualLogSource Logger => Instance._logger;

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		private void Awake()
		{
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			Patch();
			new TimerManager();
			TimerConfig.Bind(((BaseUnityPlugin)this).Config);
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded!");
		}

		private void Start()
		{
			TimerUIFactory.CreateOrGet();
		}

		internal void Patch()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
		}

		internal void Unpatch()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			TimerManager instance = TimerManager.Instance;
			if (instance == null || !instance.IsActive || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			instance.Tick(Time.deltaTime);
			RoundDirector instance2 = RoundDirector.instance;
			if (Object.op_Implicit((Object)(object)instance2) && instance2.allExtractionPointsCompleted && !TimerState.BlackoutHandled)
			{
				if (TimerConfigValues.EnableBlackoutOverride)
				{
					instance.ApplyForcedTime(TimerConfigValues.BlackoutOverrideSeconds);
					if (SemiFunc.IsMultiplayer())
					{
						CustomTimerNet.Instance.SyncTimer(instance.CurrentTime);
					}
				}
				TimerState.BlackoutHandled = true;
			}
			_syncElapsed += Time.deltaTime;
			if (_syncElapsed >= 10f)
			{
				_syncElapsed = 0f;
				CustomTimerNet.Instance.SyncTimer(instance.CurrentTime);
			}
		}
	}
	internal static class TimerLog
	{
		private static ManualLogSource _log => CustomTimer.Logger;

		public static void Info(string message)
		{
			_log.LogInfo((object)message);
		}

		public static void Warn(string message)
		{
			_log.LogWarning((object)message);
		}

		public static void Error(string message)
		{
			_log.LogError((object)message);
		}

		public static void Debug(string message)
		{
			if (TimerConfigValues.DebugLog)
			{
				_log.LogInfo((object)("[Debug] " + message));
			}
		}
	}
}
namespace CustomTimer.UI
{
	internal class CustomTimerUI : SemiUI
	{
		private TextMeshProUGUI text;

		private float lastTime = -1f;

		public override void Start()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			((SemiUI)this).Start();
			text = ((Component)this).GetComponent<TextMeshProUGUI>();
			((TMP_Text)text).richText = true;
			TextMeshProUGUI obj = text;
			((TMP_Text)obj).margin = ((TMP_Text)obj).margin + new Vector4(0f, 0f, -500f, 0f);
			TextMeshProUGUI obj2 = text;
			((TMP_Text)obj2).fontSize = ((TMP_Text)obj2).fontSize * 0.833f;
			if ((Object)(object)BigMessageUI.instance != (Object)null)
			{
				TextMeshProUGUI value = Traverse.Create((object)BigMessageUI.instance).Field("emojiText").GetValue<TextMeshProUGUI>();
				((TMP_Text)text).spriteAsset = ((TMP_Text)value).spriteAsset;
				TimerLog.Debug("[CustomTimerUI] SpriteAsset linked (BigMessageUI)");
			}
			else
			{
				CustomTimer.Logger.LogWarning((object)"[CustomTimerUI] BigMessageUI.instance is null");
			}
			((SemiUI)this).Hide();
		}

		public override void Update()
		{
			((SemiUI)this).Update();
			TimerManager instance = TimerManager.Instance;
			if (instance == null || !instance.IsActive || !TimerConfigValues.EnableHUD)
			{
				((TMP_Text)text).text = "";
				((SemiUI)this).Hide();
				return;
			}
			((SemiUI)this).Show();
			float num = Mathf.Max(0f, instance.CurrentTime);
			if (!(Mathf.Abs(num - lastTime) < 0.01f))
			{
				lastTime = num;
				int num2 = Mathf.FloorToInt(num / 60f);
				int num3 = Mathf.FloorToInt(num % 60f);
				int num4 = Mathf.FloorToInt(num * 1000f % 1000f);
				string timeColorTag = GetTimeColorTag(num);
				((TMP_Text)text).text = "<size=120%><sprite name=clock></size> " + timeColorTag + $"<b>{num2:00}:{num3:00}.{num4:000}</b>" + (string.IsNullOrEmpty(timeColorTag) ? "" : "</color>");
			}
		}

		private string GetTimeColorTag(float time)
		{
			TimerManager instance = TimerManager.Instance;
			if (instance != null && instance.IsPaused)
			{
				return "<color=#888888>";
			}
			if (time <= 30f)
			{
				return "<color=#ff3b3b>";
			}
			if (time <= 60f)
			{
				return "<color=#ffd93b>";
			}
			return "";
		}

		private void OnEnable()
		{
			if (TimerManager.Instance != null)
			{
				TimerManager.Instance.TimeUpEvent += OnTimeUp;
			}
		}

		private void OnDisable()
		{
			if (TimerManager.Instance != null)
			{
				TimerManager.Instance.TimeUpEvent -= OnTimeUp;
			}
		}

		private void OnTimeUp()
		{
			ForceZero();
		}

		private void ForceZero()
		{
			((TMP_Text)text).text = "<size=120%><sprite name=clock></size> <color=#ff3b3b><b>00:00.000</b></color>";
		}
	}
	internal static class TimerUIFactory
	{
		private const string UI_NAME = "CustomTimerTimerUI";

		public static GameObject CreateOrGet()
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = GameObject.Find("CustomTimerTimerUI");
			if ((Object)(object)val != (Object)null)
			{
				TimerLog.Debug("[CustomTimer] Timer UI already exists");
				return val;
			}
			GameObject val2 = GameObject.Find("Energy");
			if ((Object)(object)val2 == (Object)null)
			{
				CustomTimer.Logger.LogWarning((object)"[CustomTimer] Energy UI not found");
				return null;
			}
			GameObject val3 = Object.Instantiate<GameObject>(val2, val2.transform.parent);
			((Object)val3).name = "CustomTimerTimerUI";
			Transform transform = val3.transform;
			transform.localPosition -= new Vector3(25f, 80f, 0f);
			EnergyUI val4 = default(EnergyUI);
			if (val3.TryGetComponent<EnergyUI>(ref val4))
			{
				((Behaviour)val4).enabled = false;
			}
			Transform val5 = val3.transform.Find("EnergyMax");
			if ((Object)(object)val5 != (Object)null)
			{
				TextMeshProUGUI component = ((Component)val5).GetComponent<TextMeshProUGUI>();
				if ((Object)(object)component != (Object)null)
				{
					((Behaviour)component).enabled = false;
				}
			}
			Image componentInChildren = val3.GetComponentInChildren<Image>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				((Behaviour)componentInChildren).enabled = false;
			}
			TextMeshProUGUI[] componentsInChildren = val3.GetComponentsInChildren<TextMeshProUGUI>(true);
			foreach (TextMeshProUGUI val6 in componentsInChildren)
			{
				((Graphic)val6).color = Color.white;
			}
			val3.AddComponent<CustomTimerUI>();
			TimerLog.Debug("[CustomTimer] Timer UI created");
			return val3;
		}
	}
}
namespace CustomTimer.Patch
{
	[HarmonyPatch(typeof(ChatManager), "MessageSend")]
	internal static class ChatManager_MessageSend_Patch
	{
		[HarmonyPrefix]
		private static bool Prefix(ChatManager __instance, bool _possessed)
		{
			string chatMessage = __instance.chatMessage;
			if (string.IsNullOrWhiteSpace(chatMessage) || !chatMessage.StartsWith("/"))
			{
				return true;
			}
			if (CustomTimerCommand.TryHandle(chatMessage))
			{
				__instance.chatMessage = "";
				return false;
			}
			return true;
		}
	}
	internal static class CustomTimerCommand
	{
		public static bool TryHandle(string message)
		{
			string[] array = message.Split(' ');
			switch (array[0].ToLowerInvariant())
			{
			case "/time":
				HandleTime();
				return true;
			case "/timeset":
				HandleTimeSet(array);
				return true;
			case "/timemapset":
				HandleTimeMapSet(array);
				return true;
			default:
				return false;
			}
		}

		private static void HandleTime()
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				CustomTimer.Logger.LogInfo((object)"[Timer] Host only command");
				return;
			}
			TimerManager.Instance.TogglePause();
			CustomTimerNet.Instance.SyncTimer(TimerManager.Instance.CurrentTime);
			CustomTimerNet.Instance.BroadcastPause(TimerManager.Instance.IsPaused);
		}

		private static void HandleTimeSet(string[] args)
		{
			int result;
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				CustomTimer.Logger.LogInfo((object)"[Timer] Host only command");
			}
			else if (args.Length >= 2 && int.TryParse(args[1], out result))
			{
				TimerManager.Instance.ApplyForcedTime(result);
				CustomTimerNet.Instance.SyncTimer(TimerManager.Instance.CurrentTime);
			}
		}

		private static void HandleTimeMapSet(string[] args)
		{
			int result;
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				CustomTimer.Logger.LogInfo((object)"[Timer] Host only command");
			}
			else if (args.Length >= 2 && int.TryParse(args[1], out result))
			{
				string currentLevelName = LevelContext.CurrentLevelName;
				if (StageCategoryResolver.Resolve(currentLevelName) == StageCategory.Other)
				{
					TimerConfig.AddOrUpdateStageOverride(currentLevelName, result);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ExtractionPoint), "StateSet")]
	internal static class ExtractionPointStateSetPatch
	{
		private static void Prefix(ExtractionPoint __instance, State newState)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Invalid comparison between Unknown and I4
			if ((int)newState == 2)
			{
				if (!TimerState.Started && SemiFunc.IsMasterClientOrSingleplayer() && StageFilter.IsPlayableStage())
				{
					TimerState.Started = true;
					TimerState.Finished = false;
					ExtractionBonusTracker.Reset();
					TimerLog.Debug("[Timer] Started on level: " + LevelContext.CurrentLevelName);
					int initialSeconds = InitialTimeCalculator.Calculate();
					TimerManager.Instance.Initialize(initialSeconds);
					CustomTimerNet.Instance.SyncTimer(TimerManager.Instance.CurrentTime);
				}
			}
			else if ((int)newState == 7 && TimerState.Started && SemiFunc.IsMasterClientOrSingleplayer() && ExtractionBonusTracker.TryMark(__instance))
			{
				float num = TimerConfigValues.ExtractionBonusTime;
				TimerManager.Instance.AddTime(num);
				CustomTimerNet.Instance.SyncTimer(TimerManager.Instance.CurrentTime);
				TimerLog.Debug($"[Timer] Extraction COMPLETE → +{num}s");
			}
		}
	}
	internal static class ExtractionBonusTracker
	{
		private static HashSet<int> _processed = new HashSet<int>();

		public static bool TryMark(ExtractionPoint ep)
		{
			int instanceID = ((Object)ep).GetInstanceID();
			if (_processed.Contains(instanceID))
			{
				return false;
			}
			_processed.Add(instanceID);
			return true;
		}

		public static void Reset()
		{
			_processed.Clear();
		}
	}
	[HarmonyPatch(typeof(GoalUI), "Start")]
	internal static class GoalUI_Start_Patch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			if (SemiFunc.RunIsLevel() && TimerConfigValues.Enabled)
			{
				TimerLog.Debug("[CustomTimer] GoalUI.Start → create Timer UI");
				TimerReset.ResetAll();
				TimerUIFactory.CreateOrGet();
			}
		}
	}
	[HarmonyPatch(typeof(PunManager), "Awake")]
	internal class PunManager_Awake_Patch
	{
		private static void Postfix(PunManager __instance)
		{
			if (!((Object)(object)((Component)__instance).GetComponent<CustomTimerNet>() != (Object)null))
			{
				((Component)__instance).gameObject.AddComponent<CustomTimerNet>();
				TimerLog.Debug("[CustomTimer] CustomTimerNet attached to PunManager");
			}
		}
	}
}
namespace CustomTimer.Net
{
	public class CustomTimerNet : MonoBehaviourPun
	{
		public static CustomTimerNet Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
		}

		[PunRPC]
		public void RPC_CustomTimer_KillSelf()
		{
			PlayerAvatar instance = PlayerAvatar.instance;
			if (!((Object)(object)instance == (Object)null) && !instance.deadSet)
			{
				instance.PlayerDeath(-1);
				TimerLog.Debug("[CustomTimerNet] RPC_CustomTimer_KillSelf executed");
			}
		}

		[PunRPC]
		public void RPC_CustomTimer_KillIfNotInTruck()
		{
			PlayerAvatar instance = PlayerAvatar.instance;
			if (!((Object)(object)instance == (Object)null) && !instance.deadSet)
			{
				bool flag = (Object)(object)instance.RoomVolumeCheck != (Object)null && instance.RoomVolumeCheck.inTruck;
				bool finalHeal = instance.finalHeal;
				if (!flag || !finalHeal)
				{
					instance.PlayerDeath(-1);
				}
			}
		}

		public void SyncTimer(float currentTime)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				((MonoBehaviourPun)this).photonView.RPC("RPC_CustomTimer_SyncTime", (RpcTarget)1, new object[2]
				{
					currentTime,
					PhotonNetwork.Time
				});
			}
		}

		[PunRPC]
		private void RPC_CustomTimer_SyncTime(float serverTime, double sentAt)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				TimerManager.Instance.ApplyRemoteSync(serverTime, sentAt);
			}
		}

		public void BroadcastSetTime(float newTime)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				((MonoBehaviourPun)this).photonView.RPC("RPC_CustomTimer_SetTime", (RpcTarget)0, new object[1] { newTime });
			}
		}

		[PunRPC]
		private void RPC_CustomTimer_SetTime(float newTime)
		{
			TimerManager.Instance.ApplyForcedTime(newTime);
		}

		public void BroadcastPause(bool paused)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				((MonoBehaviourPun)this).photonView.RPC("RPC_CustomTimer_SetPause", (RpcTarget)1, new object[1] { paused });
			}
		}

		[PunRPC]
		private void RPC_CustomTimer_SetPause(bool paused)
		{
			TimerManager.Instance.SetPaused(paused);
		}
	}
}
namespace CustomTimer.Detect
{
	internal static class LevelDetector
	{
		private static string _lastLevel = "";

		public static bool DetectLevelChange()
		{
			LevelContext.UpdateCurrentLevel();
			if (LevelContext.CurrentLevelName != _lastLevel)
			{
				_lastLevel = LevelContext.CurrentLevelName;
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(SemiFunc), "OnLevelGenDone")]
	internal static class LevelGenDonePatch
	{
		private static void Postfix()
		{
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				LevelContext.UpdateCurrentLevel();
				TimerLog.Debug("[Timer] LevelGenDone → level='" + LevelContext.CurrentLevelName + "'");
				TimerReset.ResetAll();
			}
		}
	}
	internal static class StageFilter
	{
		public static bool IsPlayableStage()
		{
			if (SemiFunc.IsMainMenu())
			{
				return false;
			}
			if (!SemiFunc.RunIsLevel())
			{
				return false;
			}
			return true;
		}
	}
}
namespace CustomTimer.Config
{
	internal enum TimeUpAction
	{
		KillAll,
		ReloadImmediate,
		KillNonTruckPlayers
	}
	internal static class TimerConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<int> BaseInitialTime;

		public static ConfigEntry<int> ExtractionBonusTime;

		public static ConfigEntry<TimeUpAction> TimeUpActionMode;

		public static ConfigEntry<bool> EnableHUD;

		public static ConfigEntry<bool> DebugLog;

		public static ConfigEntry<int> StageBonus_Museum;

		public static ConfigEntry<int> StageBonus_Manor;

		public static ConfigEntry<int> StageBonus_Arctic;

		public static ConfigEntry<int> StageBonus_Wizard;

		public static ConfigEntry<int> StageBonus_Other;

		public static ConfigEntry<string> StageTimeOverrides;

		public static ConfigEntry<bool> EnablePlayerScaling;

		public static ConfigEntry<int> PlayerScalingThreshold;

		public static ConfigEntry<int> PlayerScalingSecondsPerPlayer;

		public static ConfigEntry<bool> EnableBlackoutOverride;

		public static ConfigEntry<int> BlackoutOverrideSeconds;

		public static ConfigEntry<bool> EnableTimeCap;

		public static ConfigEntry<int> MaxTimeSeconds;

		internal static void Bind(ConfigFile config)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Expected O, but got Unknown
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Expected O, but got Unknown
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Expected O, but got Unknown
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Expected O, but got Unknown
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Expected O, but got Unknown
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Expected O, but got Unknown
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Expected O, but got Unknown
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable or disable CustomTimer");
			BaseInitialTime = config.Bind<int>("Timer", "BaseInitialTime", 600, new ConfigDescription("Initial timer value (seconds). Must be >= 0", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>()));
			ExtractionBonusTime = config.Bind<int>("Timer", "ExtractionBonusTime", 300, new ConfigDescription("Bonus time added when extraction is completed (seconds). Must be >= 0", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>()));
			TimeUpActionMode = config.Bind<TimeUpAction>("TimeUp", "Action", TimeUpAction.KillNonTruckPlayers, "Behavior when timer reaches zero");
			EnableBlackoutOverride = config.Bind<bool>("Blackout", "EnableBlackoutOverride", true, "When stage becomes blackout, force timer to a specific value (host authoritative).");
			BlackoutOverrideSeconds = config.Bind<int>("Blackout", "BlackoutOverrideSeconds", 60, new ConfigDescription("Forced timer value (seconds) applied when blackout starts.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>()));
			EnableTimeCap = config.Bind<bool>("Timer", "EnableTimeCap", false, "Clamp timer so it never exceeds MaxTimeSeconds (affects AddTime / forced time).");
			MaxTimeSeconds = config.Bind<int>("Timer", "MaxTimeSeconds", 600, new ConfigDescription("Maximum timer value (seconds) when EnableTimeCap is true.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>()));
			EnablePlayerScaling = config.Bind<bool>("Player Scaling", "Enable", true, "Enable time bonus based on player count");
			PlayerScalingThreshold = config.Bind<int>("Player Scaling", "Threshold", 6, new ConfigDescription("Player count threshold. No bonus when current players >= this value.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
			PlayerScalingSecondsPerPlayer = config.Bind<int>("Player Scaling", "SecondsPerMissingPlayer", 30, new ConfigDescription("Bonus seconds added per missing player below threshold.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 600), Array.Empty<object>()));
			StageBonus_Museum = config.Bind<int>("Stage Bonus", "Museum", 0, new ConfigDescription("Additional time (seconds) for Museum stages", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-3600, 3600), Array.Empty<object>()));
			StageBonus_Manor = config.Bind<int>("Stage Bonus", "Manor", 0, new ConfigDescription("Additional time (seconds) for Manor stages", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-3600, 3600), Array.Empty<object>()));
			StageBonus_Arctic = config.Bind<int>("Stage Bonus", "Arctic", 0, new ConfigDescription("Additional time (seconds) for Arctic stages", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-3600, 3600), Array.Empty<object>()));
			StageBonus_Wizard = config.Bind<int>("Stage Bonus", "Wizard", 0, new ConfigDescription("Additional time (seconds) for Wizard stages", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-3600, 3600), Array.Empty<object>()));
			StageBonus_Other = config.Bind<int>("Stage Bonus", "Other", 0, new ConfigDescription("Additional time (seconds) for other or modded stages", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-3600, 3600), Array.Empty<object>()));
			StageTimeOverrides = config.Bind<string>("Stage Override", "StageTimeOverrides", "", "Custom stage time overrides added to BaseInitialTime. Format: stageName:seconds,stageName:seconds");
			EnableHUD = config.Bind<bool>("HUD", "EnableHUD", true, "Show timer HUD");
			DebugLog = config.Bind<bool>("Debug", "DebugLog", false, "Enable debug logging");
		}

		public static void AddOrUpdateStageOverride(string stageName, int seconds)
		{
			if (!string.IsNullOrEmpty(stageName))
			{
				string text = stageName.ToLowerInvariant();
				Dictionary<string, int> allOverridesMutable = StageOverrideResolver.GetAllOverridesMutable();
				allOverridesMutable[text] = seconds;
				StageOverrideResolver.WriteBack(allOverridesMutable);
				CustomTimer.Logger.LogInfo((object)$"[Timer] Stage override updated: {text} → {seconds}s");
			}
		}
	}
	internal static class TimerConfigValues
	{
		public static bool Enabled => TimerConfig.Enabled.Value;

		public static int BaseInitialTime => TimerConfig.BaseInitialTime.Value;

		public static int ExtractionBonusTime => TimerConfig.ExtractionBonusTime.Value;

		public static TimeUpAction TimeUpAction => TimerConfig.TimeUpActionMode.Value;

		public static bool EnableHUD => TimerConfig.EnableHUD.Value;

		public static bool DebugLog => TimerConfig.DebugLog.Value;

		public static bool EnablePlayerScaling => TimerConfig.EnablePlayerScaling.Value;

		public static int PlayerScalingThreshold => TimerConfig.PlayerScalingThreshold.Value;

		public static int PlayerScalingSecondsPerPlayer => TimerConfig.PlayerScalingSecondsPerPlayer.Value;

		public static bool EnableBlackoutOverride => TimerConfig.EnableBlackoutOverride.Value;

		public static int BlackoutOverrideSeconds => TimerConfig.BlackoutOverrideSeconds.Value;

		public static bool EnableTimeCap => TimerConfig.EnableTimeCap.Value;

		public static int MaxTimeSeconds => TimerConfig.MaxTimeSeconds.Value;

		public static int StageBonus_Museum => TimerConfig.StageBonus_Museum.Value;

		public static int StageBonus_Manor => TimerConfig.StageBonus_Manor.Value;

		public static int StageBonus_Arctic => TimerConfig.StageBonus_Arctic.Value;

		public static int StageBonus_Wizard => TimerConfig.StageBonus_Wizard.Value;

		public static int StageBonus_Other => TimerConfig.StageBonus_Other.Value;

		public static string StageTimeOverrides => TimerConfig.StageTimeOverrides.Value;
	}
}
namespace CustomTimer.Bootstrap
{
	internal class TimerBootstrap
	{
	}
}
namespace CustomTimer.Core
{
	internal static class LevelContext
	{
		public static string CurrentLevelName { get; private set; } = "";


		public static void UpdateCurrentLevel()
		{
			RunManager instance = RunManager.instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				Level levelCurrent = instance.levelCurrent;
				obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null);
			}
			if (obj == null)
			{
				obj = "";
			}
			CurrentLevelName = (string)obj;
		}
	}
	internal enum StageCategory
	{
		Museum,
		Manor,
		Arctic,
		Wizard,
		Other
	}
	internal static class InitialTimeCalculator
	{
		public static int Calculate()
		{
			string currentLevelName = LevelContext.CurrentLevelName;
			int baseInitialTime = TimerConfigValues.BaseInitialTime;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			if (StageOverrideResolver.TryGetOverride(currentLevelName, out var seconds))
			{
				num3 = seconds;
				TimerLog.Debug($"[Timer] Stage override bonus matched ({currentLevelName}) → +{num3}s");
			}
			num2 = StageCategoryResolver.Resolve(currentLevelName) switch
			{
				StageCategory.Museum => TimerConfigValues.StageBonus_Museum, 
				StageCategory.Manor => TimerConfigValues.StageBonus_Manor, 
				StageCategory.Arctic => TimerConfigValues.StageBonus_Arctic, 
				StageCategory.Wizard => TimerConfigValues.StageBonus_Wizard, 
				_ => TimerConfigValues.StageBonus_Other, 
			};
			if (TimerConfigValues.EnablePlayerScaling)
			{
				int playerScalingThreshold = TimerConfigValues.PlayerScalingThreshold;
				int playerScalingSecondsPerPlayer = TimerConfigValues.PlayerScalingSecondsPerPlayer;
				int count = SemiFunc.PlayerGetAll().Count;
				int num4 = Mathf.Max(0, playerScalingThreshold - count);
				num = num4 * playerScalingSecondsPerPlayer;
			}
			baseInitialTime += num2 + num3 + num;
			TimerLog.Debug($"[Timer] InitialTime = Base({TimerConfigValues.BaseInitialTime})" + $" + Stage({num2})" + $" + Override({num3})" + $" + Player({num})" + $" = {baseInitialTime}s");
			int num5 = baseInitialTime;
			if (TimerConfigValues.EnableTimeCap)
			{
				num5 = Mathf.Min(baseInitialTime, TimerConfigValues.MaxTimeSeconds);
				if (num5 != baseInitialTime)
				{
					TimerLog.Debug($"[Timer] InitialTime capped: {baseInitialTime}s → {num5}s" + $" (−{baseInitialTime - num5}s)");
				}
			}
			return Mathf.Max(0, num5);
		}
	}
	internal static class StageCategoryResolver
	{
		public static StageCategory Resolve(string levelName)
		{
			if (string.IsNullOrEmpty(levelName))
			{
				return StageCategory.Other;
			}
			string text = levelName.ToLowerInvariant();
			if (text.Contains("museum"))
			{
				return StageCategory.Museum;
			}
			if (text.Contains("manor"))
			{
				return StageCategory.Manor;
			}
			if (text.Contains("arctic"))
			{
				return StageCategory.Arctic;
			}
			if (text.Contains("wizard"))
			{
				return StageCategory.Wizard;
			}
			return StageCategory.Other;
		}
	}
	internal static class StageOverrideResolver
	{
		private static Dictionary<string, int>? _cache;

		public static bool TryGetOverride(string levelName, out int seconds)
		{
			seconds = 0;
			if (string.IsNullOrEmpty(levelName))
			{
				return false;
			}
			EnsureParsed();
			string text = levelName.ToLowerInvariant();
			foreach (KeyValuePair<string, int> item in _cache)
			{
				if (text.Contains(item.Key))
				{
					seconds = item.Value;
					return true;
				}
			}
			return false;
		}

		private static void EnsureParsed()
		{
			if (_cache != null)
			{
				return;
			}
			_cache = new Dictionary<string, int>();
			string stageTimeOverrides = TimerConfigValues.StageTimeOverrides;
			if (string.IsNullOrWhiteSpace(stageTimeOverrides))
			{
				return;
			}
			string[] array = stageTimeOverrides.Split(',');
			string[] array2 = array;
			foreach (string text in array2)
			{
				string[] array3 = text.Split(':');
				if (array3.Length == 2)
				{
					string text2 = array3[0].Trim().ToLowerInvariant();
					if (!string.IsNullOrEmpty(text2) && int.TryParse(array3[1], out var result))
					{
						_cache[text2] = result;
					}
				}
			}
		}

		public static Dictionary<string, int> GetAllOverridesMutable()
		{
			EnsureParsed();
			return new Dictionary<string, int>(_cache);
		}

		public static void WriteBack(Dictionary<string, int> dict)
		{
			_cache = dict ?? new Dictionary<string, int>();
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, int> item in _cache)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(",");
				}
				string value = (item.Key ?? "").Trim().ToLowerInvariant();
				if (!string.IsNullOrEmpty(value))
				{
					stringBuilder.Append(value);
					stringBuilder.Append(":");
					stringBuilder.Append(item.Value);
				}
			}
			TimerConfig.StageTimeOverrides.Value = stringBuilder.ToString();
			((ConfigEntryBase)TimerConfig.StageTimeOverrides).ConfigFile.Save();
			TimerLog.Debug("[Timer] StageTimeOverrides saved: " + TimerConfig.StageTimeOverrides.Value);
		}
	}
	internal class TimerManager
	{
		private float _authoritativeTime;

		private float _remoteBaseTime;

		private double _remoteSyncTime;

		private bool _isRemote;

		public static TimerManager Instance { get; private set; }

		public float CurrentTime
		{
			get
			{
				if (_isRemote)
				{
					if (IsPaused)
					{
						return _remoteBaseTime;
					}
					double num = PhotonNetwork.Time - _remoteSyncTime;
					return Mathf.Max(0f, _remoteBaseTime - (float)num);
				}
				return _authoritativeTime;
			}
		}

		public bool IsActive { get; private set; }

		public bool IsPaused { get; private set; }

		public event Action TimeUpEvent;

		public TimerManager()
		{
			Instance = this;
		}

		public void Initialize(int initialSeconds)
		{
			if (!TimerConfigValues.Enabled)
			{
				IsActive = false;
				return;
			}
			_authoritativeTime = Mathf.Max(0, initialSeconds);
			IsActive = true;
			IsPaused = false;
			TimerLog.Debug($"[Timer] Initialized: {_authoritativeTime}s");
			if (_authoritativeTime <= 0f)
			{
				OnTimeUp();
			}
		}

		public void Tick(float deltaTime)
		{
			if (IsActive && !IsPaused)
			{
				_authoritativeTime -= deltaTime;
				if (_authoritativeTime <= 0f)
				{
					_authoritativeTime = 0f;
					OnTimeUp();
				}
			}
		}

		public void Stop()
		{
			IsActive = false;
			IsPaused = false;
		}

		private void OnTimeUp()
		{
			IsActive = false;
			IsPaused = false;
			TimerLog.Debug("[Timer] Time up!");
			this.TimeUpEvent?.Invoke();
			TimeUpExecutor.Execute(TimerConfigValues.TimeUpAction);
		}

		public void AddTime(float seconds)
		{
			if (IsActive && !IsPaused && !TimerState.BlackoutHandled)
			{
				_authoritativeTime += seconds;
				if (TimerConfigValues.EnableTimeCap)
				{
					_authoritativeTime = Mathf.Min(_authoritativeTime, (float)TimerConfigValues.MaxTimeSeconds);
				}
				TimerLog.Debug($"[Timer] +{seconds}s (now {_authoritativeTime:F1}s)");
			}
		}

		public void ApplyRemoteSync(float serverTime, double sentAt)
		{
			_remoteBaseTime = serverTime;
			_remoteSyncTime = sentAt;
			_isRemote = true;
			IsActive = true;
		}

		public void ApplyForcedTime(float time)
		{
			time = Mathf.Max(0f, time);
			if (TimerConfigValues.EnableTimeCap)
			{
				time = Mathf.Min(time, (float)TimerConfigValues.MaxTimeSeconds);
			}
			_authoritativeTime = time;
			_remoteBaseTime = time;
			_remoteSyncTime = PhotonNetwork.Time;
			_isRemote = !SemiFunc.IsMasterClientOrSingleplayer();
			IsActive = true;
			TimerLog.Debug($"[Timer] Forced time applied: {time}s");
		}

		public void TogglePause()
		{
			if (IsActive)
			{
				IsPaused = !IsPaused;
				TimerLog.Debug("[Timer] Pause toggled → " + (IsPaused ? "PAUSED" : "RUNNING"));
			}
		}

		public void SetPaused(bool paused)
		{
			IsPaused = paused;
		}
	}
	internal static class TimerReset
	{
		public static void ResetAll()
		{
			TimerState.Reset();
			ExtractionBonusTracker.Reset();
			TimerManager.Instance.Stop();
			TimeUpExecutor.Reset();
			TimerLog.Debug("[Timer] Reset all");
		}
	}
	internal static class TimerState
	{
		public static bool Started;

		public static bool Finished;

		public static bool BlackoutHandled;

		public static void Reset()
		{
			Started = false;
			Finished = false;
			BlackoutHandled = false;
		}
	}
	internal static class TimeUpExecutor
	{
		[CompilerGenerated]
		private sealed class <ReloadCoroutine>d__7 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public float delay;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ReloadCoroutine>d__7(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(delay);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					Reload();
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static bool _executed;

		public static void Execute(TimeUpAction action)
		{
			if (!_executed)
			{
				_executed = true;
				TimerLog.Debug($"[TimeUpExecutor] Execute action = {action}");
				switch (action)
				{
				case TimeUpAction.KillAll:
					KillAllPlayers();
					break;
				case TimeUpAction.ReloadImmediate:
					Reload();
					break;
				case TimeUpAction.KillNonTruckPlayers:
					KillNonTruckPlayers();
					break;
				default:
					TimerLog.Debug($"[TimeUpExecutor] Action {action} not implemented yet");
					break;
				}
			}
		}

		public static void Reset()
		{
			_executed = false;
		}

		private static void KillAllPlayers()
		{
			if (!PhotonNetwork.InRoom)
			{
				TimerLog.Debug("[TimeUpExecutor] Singleplayer KillAll");
				PlayerAvatar instance = PlayerAvatar.instance;
				if ((Object)(object)instance != (Object)null && !instance.deadSet)
				{
					instance.PlayerDeath(-1);
				}
			}
			else if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				TimerLog.Debug("[TimeUpExecutor] Not host, skip KillAll");
			}
			else
			{
				TimerLog.Debug("[TimeUpExecutor] Multiplayer KillAll via PunManager RPC");
				PunManager.instance.photonView.RPC("RPC_CustomTimer_KillSelf", (RpcTarget)0, Array.Empty<object>());
			}
		}

		private static void KillNonTruckPlayers()
		{
			if (!PhotonNetwork.InRoom)
			{
				PlayerAvatar instance = PlayerAvatar.instance;
				if (!((Object)(object)instance == (Object)null) && !instance.deadSet)
				{
					bool flag = (Object)(object)instance.RoomVolumeCheck != (Object)null && instance.RoomVolumeCheck.inTruck;
					bool finalHeal = instance.finalHeal;
					if (!flag || !finalHeal)
					{
						instance.PlayerDeath(-1);
					}
				}
			}
			else if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				TimerLog.Debug("[TimeUpExecutor] KillNonTruckPlayers via PunManager RPC");
				PunManager.instance.photonView.RPC("RPC_CustomTimer_KillIfNotInTruck", (RpcTarget)0, Array.Empty<object>());
			}
		}

		private static void Reload()
		{
			TimerLog.Debug("[TimeUpExecutor] RestartScene");
			RunManager.instance.RestartScene();
		}

		private static void DelayedReload(float delay)
		{
			((MonoBehaviour)CustomTimer.Instance).StartCoroutine(ReloadCoroutine(delay));
		}

		[IteratorStateMachine(typeof(<ReloadCoroutine>d__7))]
		private static IEnumerator ReloadCoroutine(float delay)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ReloadCoroutine>d__7(0)
			{
				delay = delay
			};
		}
	}
}