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