Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CustomTimer v1.0.1
plugins/CustomTimer.dll
Decompiled 3 weeks agousing 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+d9182f8f23da47ea970b38df06f64abb63ec16f9")] [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!"); } 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(); } 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 { [CompilerGenerated] private sealed class <StartRoutine>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public CustomTimerUI <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <StartRoutine>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; CustomTimerUI customTimerUI = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)(object)AssetManager.instance == (Object)null) { <>2__current = null; <>1__state = 1; return true; } customTimerUI.text = ((Component)customTimerUI).GetComponent<TextMeshProUGUI>(); if ((Object)(object)customTimerUI.text == (Object)null) { customTimerUI.text = ((Component)customTimerUI).GetComponentInChildren<TextMeshProUGUI>(true); } if ((Object)(object)customTimerUI.text == (Object)null) { CustomTimer.Logger.LogWarning((object)"[CustomTimerUI] TextMeshProUGUI not found"); ((Behaviour)customTimerUI).enabled = false; return false; } ((SemiUI)customTimerUI).uiText = customTimerUI.text; ((SemiUI)customTimerUI).textRectTransform = (Transform)(object)((Component)customTimerUI.text).GetComponent<RectTransform>(); if (((SemiUI)customTimerUI).doNotDisable == null) { ((SemiUI)customTimerUI).doNotDisable = new List<GameObject>(); } customTimerUI.<>n__0(); ((TMP_Text)customTimerUI.text).richText = true; TextMeshProUGUI text = customTimerUI.text; ((TMP_Text)text).margin = ((TMP_Text)text).margin + new Vector4(0f, 0f, -500f, 0f); TextMeshProUGUI text2 = customTimerUI.text; ((TMP_Text)text2).fontSize = ((TMP_Text)text2).fontSize * 0.833f; if ((Object)(object)BigMessageUI.instance != (Object)null) { TextMeshProUGUI value = Traverse.Create((object)BigMessageUI.instance).Field("emojiText").GetValue<TextMeshProUGUI>(); if ((Object)(object)value != (Object)null) { ((TMP_Text)customTimerUI.text).spriteAsset = ((TMP_Text)value).spriteAsset; TimerLog.Debug("[CustomTimerUI] SpriteAsset linked (BigMessageUI)"); } else { CustomTimer.Logger.LogWarning((object)"[CustomTimerUI] BigMessageUI emojiText is null"); } } ((SemiUI)customTimerUI).Hide(); customTimerUI.ready = true; 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 TextMeshProUGUI text; private float lastTime = -1f; private bool ready; public override void Start() { ((MonoBehaviour)this).StartCoroutine(StartRoutine()); } [IteratorStateMachine(typeof(<StartRoutine>d__4))] private IEnumerator StartRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <StartRoutine>d__4(0) { <>4__this = this }; } public override void Update() { if (!ready || (Object)(object)text == (Object)null) { return; } ((SemiUI)this).Update(); TimerManager instance = TimerManager.Instance; bool flag = instance?.IsActive ?? false; bool flag2 = (Object)(object)StatsUI.instance != (Object)null && ((SemiUI)StatsUI.instance).showTimer > 0f; if (!flag || !TimerConfigValues.EnableHUD || flag2) { ((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() { if (!((Object)(object)text == (Object)null)) { ((TMP_Text)text).text = "<size=120%><sprite name=clock></size> <color=#ff3b3b><b>00:00.000</b></color>"; } } [CompilerGenerated] [DebuggerHidden] private void <>n__0() { ((SemiUI)this).Start(); } } 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 ExtractionPoint_StateSet_Patch { [CompilerGenerated] private sealed class <TimeUpCompleteCheckFlow>d__1 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <TimeUpCompleteCheckFlow>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(5f); <>1__state = 1; return true; case 1: <>1__state = -1; if (TimerState.BlackoutHandled) { TimerLog.Debug("[Timer] TimeUpComplete ignored: blackout already handled"); TimerState.TimeUpOccurred = false; return false; } if (AnyPlayerAlive()) { TimerManager.Instance.ApplySurvivorBonusTime(TimerConfigValues.ExtractionBonusTime); CustomTimerNet.Instance.SyncTimer(TimerManager.Instance.CurrentTime); TimerLog.Debug("[Timer] TimeUpComplete: survivor found → resume with bonus"); } else { TimerLog.Debug("[Timer] TimeUpComplete: no survivor"); } TimerState.TimeUpOccurred = false; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Invalid comparison between Unknown and I4 //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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); TimerChatNotifier.Instance.NotifyTimerStart(TimerManager.Instance.CurrentTime); } return; } if ((int)newState == 3) { TimerState.ExtractionInProgress = true; } if ((int)newState == 5) { if (TimerState.Started) { TimerState.ExtractionInProgress = false; if (TimerState.TimeUpPenalty) { TimerState.TimeUpPenalty = false; TimerLog.Debug("[Timer] TimeUp triggered on Cancel"); TimerManager.Instance.ForceTimeUp(); } } } else if ((int)newState == 7) { if (TimerState.TimeUpOccurred && SemiFunc.IsMasterClientOrSingleplayer()) { ((MonoBehaviour)CustomTimer.Instance).StartCoroutine(TimeUpCompleteCheckFlow()); } if (TimerState.Started && ExtractionBonusTracker.TryMark(__instance)) { TimerManager.Instance.SetPaused(paused: false); TimerState.ExtractionInProgress = false; TimerState.TimeUpPenalty = false; float num = TimerConfigValues.ExtractionBonusTime; TimerManager.Instance.AddTime(num); CustomTimerNet.Instance.SyncTimer(TimerManager.Instance.CurrentTime); TimerLog.Debug($"[Timer] COMPLETE → +{num}s & RESUME"); } } } [IteratorStateMachine(typeof(<TimeUpCompleteCheckFlow>d__1))] private static IEnumerator TimeUpCompleteCheckFlow() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <TimeUpCompleteCheckFlow>d__1(0); } private static bool AnyPlayerAlive() { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null) && !player.deadSet) { return true; } } return false; } } 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; } public void SyncTimer(float currentTime) { if (SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.IsMultiplayer()) { ((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() && SemiFunc.IsMultiplayer()) { ((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() && SemiFunc.IsMultiplayer()) { ((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; public static ConfigEntry<bool> EnableChatNotify; public static ConfigEntry<bool> EnableTruckNotify; public static ConfigEntry<bool> EnableChatThreshold; public static ConfigEntry<string> ChatNotifyThresholdSeconds; public static ConfigEntry<bool> EnableChatCountdown; public static ConfigEntry<bool> EnableChatBonusNotify; public static ConfigEntry<float> ChatNotifyCooldownSeconds; 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 //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: 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"); EnableChatNotify = config.Bind<bool>("Chat Notify", "Enable", true, "Enable host-authoritative chat notifications for timer events."); EnableTruckNotify = config.Bind<bool>("Truck Notify", "Enable", true, "Enable timer notifications on the truck screen display."); EnableChatCountdown = config.Bind<bool>("Chat Notify", "EnableCountdown", true, "Enable chat countdown notifications from 10 to 1 seconds."); EnableChatBonusNotify = config.Bind<bool>("Chat Notify", "EnableBonusChat", true, "Enable chat notifications when bonus time is added or time is modified."); EnableChatThreshold = config.Bind<bool>("Chat Notify", "EnableThresholdChat", false, "Enable chat notifications when remaining time crosses threshold seconds."); ChatNotifyThresholdSeconds = config.Bind<string>("Chat Notify", "ThresholdSeconds", "60,30,20,10", "Seconds at which threshold notifications are triggered (comma-separated)."); ChatNotifyCooldownSeconds = config.Bind<float>("Chat Notify", "CooldownSeconds", 5f, new ConfigDescription("Minimum interval (seconds) between chat notifications.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>())); 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; public static bool EnableChatNotify => TimerConfig.EnableChatNotify.Value; public static bool EnableChatCountdown => TimerConfig.EnableChatCountdown.Value; public static bool EnableChatThreshold => TimerConfig.EnableChatThreshold.Value; public static bool EnableChatBonusNotify => TimerConfig.EnableChatBonusNotify.Value; public static string ChatNotifyThresholdSeconds => TimerConfig.ChatNotifyThresholdSeconds.Value; public static float ChatNotifyCooldownSeconds => TimerConfig.ChatNotifyCooldownSeconds.Value; public static bool EnableTruckNotify => TimerConfig.EnableTruckNotify.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 sealed class TimerChatNotifier { private int _lastCountdownSecond = -1; private float _lastBonusNotifyTime = -999f; private HashSet<int> _thresholds; private float _lastThresholdNotifyTime = -999f; public static TimerChatNotifier Instance { get; } = new TimerChatNotifier(); private TimerChatNotifier() { } public void Tick(float previousTime, float currentTime) { if (SemiFunc.IsMasterClient() && !(currentTime <= 0f)) { HandleThreshold(previousTime, currentTime); HandleCountdown(currentTime); } } private void SendChatToAllPlayers(string message) { if (!TimerConfigValues.EnableChatNotify) { return; } List<PlayerAvatar> playerList = GameDirector.instance.PlayerList; if (playerList == null) { return; } foreach (PlayerAvatar item in playerList) { if (!((Object)(object)item == (Object)null)) { item.ChatMessageSend(message); } } } private void SendChatToAlivePlayers(string message) { if (!TimerConfigValues.EnableChatNotify) { return; } List<PlayerAvatar> playerList = GameDirector.instance.PlayerList; if (playerList == null) { return; } foreach (PlayerAvatar item in playerList) { if (!((Object)(object)item == (Object)null) && !item.deadSet) { item.ChatMessageSend(message); } } } private void SendTruckMessage(string message) { if (TimerConfigValues.EnableTruckNotify && !((Object)(object)TruckScreenText.instance == (Object)null)) { TruckScreenText.instance.MessageSendCustom("", message, 0); } } public void NotifyTimerStart(float startTime) { if (SemiFunc.IsMasterClient()) { if (TimerConfigValues.EnableTruckNotify) { string message = "[TIMER] Start: " + TimerTextFormatter.FormatTime(startTime, withMilliseconds: false); SendTruckMessage(message); } if (TimerConfigValues.EnableChatNotify) { string message2 = "Timer started with " + TimerTextFormatter.FormatTimeForTTS(startTime); SendChatToAllPlayers(message2); } } } public void NotifyTimerStop(float time) { if (SemiFunc.IsMasterClient()) { if (TimerConfigValues.EnableTruckNotify) { SendTruckMessage("[TIMER] Timer paused"); } if (TimerConfigValues.EnableChatNotify) { SendChatToAllPlayers("Timer paused"); } } } public void NotifyTimerResume(float time) { if (SemiFunc.IsMasterClient()) { if (TimerConfigValues.EnableTruckNotify) { SendTruckMessage("[TIMER] Time: " + TimerTextFormatter.FormatTime(time, withMilliseconds: false)); } if (TimerConfigValues.EnableChatNotify) { SendChatToAllPlayers("Timer resumed with " + TimerTextFormatter.FormatTimeForTTS(time)); } } } public void NotifyTimeUp() { if (SemiFunc.IsMasterClient()) { if (TimerConfigValues.EnableTruckNotify) { SendTruckMessage("[TIMER] Time up"); } if (TimerConfigValues.EnableChatNotify) { SendChatToAllPlayers("Time up"); } } } private void HandleCountdown(float currentTime) { if (!TimerConfigValues.EnableChatCountdown) { return; } if (currentTime > 10f) { _lastCountdownSecond = -1; return; } int num = Mathf.CeilToInt(currentTime); if (num != _lastCountdownSecond && num >= 1 && num <= 10) { _lastCountdownSecond = num; string message = num.ToString(); string message2 = "[TIMER] Time: " + TimerTextFormatter.FormatTime(currentTime, withMilliseconds: false); SendChatToAllPlayers(message); SendTruckMessage(message2); } } private void EnsureThresholdsParsed() { if (_thresholds != null) { return; } _thresholds = new HashSet<int>(); string chatNotifyThresholdSeconds = TimerConfigValues.ChatNotifyThresholdSeconds; if (string.IsNullOrWhiteSpace(chatNotifyThresholdSeconds)) { return; } string[] array = chatNotifyThresholdSeconds.Split(','); foreach (string text in array) { if (int.TryParse(text.Trim(), out var result) && result > 0) { _thresholds.Add(result); } } } private void HandleThreshold(float previousTime, float currentTime) { EnsureThresholdsParsed(); if (_thresholds == null || _thresholds.Count == 0 || currentTime <= 10f || currentTime <= 3f || Time.time - _lastThresholdNotifyTime < TimerConfigValues.ChatNotifyCooldownSeconds) { return; } int num = -1; foreach (int threshold in _thresholds) { if (previousTime > (float)threshold && currentTime <= (float)threshold && num < threshold) { num = threshold; } } if (num >= 0) { _lastThresholdNotifyTime = Time.time; string message = "[TIMER] Time: " + TimerTextFormatter.FormatTime(num, withMilliseconds: false); string message2 = num.ToString(); if (TimerConfigValues.EnableChatNotify && TimerConfigValues.EnableChatThreshold) { SendChatToAllPlayers(message2); } SendTruckMessage(message); } } public void NotifyBonusTime(float newTime) { if (SemiFunc.IsMasterClient()) { if (TimerConfigValues.EnableTruckNotify) { string message = "[TIMER] Time: " + TimerTextFormatter.FormatTime(newTime, withMilliseconds: false); SendTruckMessage(message); } if (TimerConfigValues.EnableChatNotify && TimerConfigValues.EnableChatBonusNotify) { string message2 = "Timer " + TimerTextFormatter.FormatTimeForTTS(newTime); SendChatToAllPlayers(message2); } } } public void NotifySurvivor(float newTime) { if (SemiFunc.IsMasterClient() && TimerConfigValues.EnableChatNotify) { string message = "You survived. " + TimerTextFormatter.FormatTimeForTTS(newTime); SendChatToAlivePlayers(message); } } } internal class TimerManager { private float _authoritativeTime; private float _remoteBaseTime; private double _remoteSyncTime; private bool _isRemote; private float _prevTime; 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) { return; } _prevTime = CurrentTime; _authoritativeTime -= deltaTime; if (_authoritativeTime <= 0f) { _authoritativeTime = 0f; if (TimerState.ExtractionInProgress) { TimerState.TimeUpPenalty = true; TimerLog.Debug("[Timer] TimeUp deferred (Extraction)"); return; } OnTimeUp(); } TimerChatNotifier.Instance.Tick(_prevTime, CurrentTime); } public void Stop() { IsActive = false; IsPaused = false; } private void OnTimeUp() { IsActive = false; IsPaused = false; TimerState.TimeUpOccurred = true; TimerLog.Debug("[Timer] Time up!"); TimerChatNotifier.Instance.NotifyTimeUp(); 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); } TimerChatNotifier.Instance.NotifyBonusTime(_authoritativeTime); 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; TimerChatNotifier.Instance.NotifyBonusTime(_authoritativeTime); TimerLog.Debug($"[Timer] Forced time applied: {time}s"); } public void ApplySurvivorBonusTime(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; TimerChatNotifier.Instance.NotifySurvivor(_authoritativeTime); TimerLog.Debug($"[Timer] Forced time (silent): {time}s"); } public void ForceTimeUp() { if (IsActive) { OnTimeUp(); } } public void TogglePause() { if (IsActive) { IsPaused = !IsPaused; if (IsPaused) { TimerChatNotifier.Instance.NotifyTimerStop(CurrentTime); } else { TimerChatNotifier.Instance.NotifyTimerResume(CurrentTime); } 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(); TimerLog.Debug("[Timer] Reset all"); } } internal static class TimerState { public static bool Started; public static bool Finished; public static bool BlackoutHandled; public static bool ExtractionInProgress; public static bool TimeUpPenalty; public static bool TimeUpOccurred; public static void Reset() { Started = false; Finished = false; BlackoutHandled = false; ExtractionInProgress = false; TimeUpPenalty = false; TimeUpOccurred = false; } } internal static class TimerTextFormatter { public static string FormatTime(float time, bool withMilliseconds = true) { time = Mathf.Max(0f, time); int num = Mathf.FloorToInt(time / 60f); int num2 = Mathf.FloorToInt(time % 60f); if (!withMilliseconds) { return $"{num:00}:{num2:00}"; } int num3 = Mathf.FloorToInt(time * 1000f % 1000f); return $"{num:00}:{num2:00}.{num3:000}"; } public static string FormatTimeForTTS(float time) { time = Mathf.Max(0f, time); int num = Mathf.CeilToInt(time); int num2 = num / 60; int num3 = num % 60; if (num2 > 0 && num3 > 0) { return string.Format("{0} minute{1} {2} second{3}", num2, (num2 > 1) ? "s" : "", num3, (num3 > 1) ? "s" : ""); } if (num2 > 0) { return string.Format("{0} minute{1}", num2, (num2 > 1) ? "s" : ""); } return string.Format("{0} second{1}", num3, (num3 > 1) ? "s" : ""); } } internal static class TimeUpExecutor { [CompilerGenerated] private sealed class <ReloadCoroutine>d__5 : 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__5(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(); } } public static void Execute(TimeUpAction action) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { return; } if (SemiFunc.RunIsShop()) { CustomTimer.Logger.LogInfo((object)"Skip shop TimeUp"); return; } switch (action) { case TimeUpAction.KillAll: KillAll(); break; case TimeUpAction.KillNonTruckPlayers: KillNonTruckPlayers(); break; case TimeUpAction.ReloadImmediate: Reload(); break; } } private static void KillAll() { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null) && !player.deadSet) { player.PlayerDeath(-1); } } } private static void KillNonTruckPlayers() { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null) && !player.deadSet) { bool flag = (Object)(object)player.RoomVolumeCheck != (Object)null && player.RoomVolumeCheck.inTruck; if (!player.finalHeal) { player.PlayerDeath(-1); } } } } 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__5))] private static IEnumerator ReloadCoroutine(float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ReloadCoroutine>d__5(0) { delay = delay }; } } }