Decompiled source of GameSpeedController v1.1.0

build/GameSpeedController.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("GameSpeedController")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+bf486d1cb86654782c27f8c4173bdfcd32d839e0")]
[assembly: AssemblyProduct("GameSpeedController")]
[assembly: AssemblyTitle("GameSpeedController")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GameSpeedController
{
	[BepInPlugin("com.duckieray.cardshop.gamespeed", "Game Speed Controller", "1.0.0")]
	public sealed class GameSpeedControllerPlugin : BaseUnityPlugin
	{
		private sealed class ControllerBehaviour : MonoBehaviour
		{
			private enum InteractionScalingMode
			{
				Manual,
				MatchTimeScale,
				InverseTimeScale
			}

			private sealed class PlayerSpeedRecord
			{
				private readonly MonoBehaviour _behaviour;

				private readonly FieldInfo _field;

				private readonly float _baseValue;

				public string DisplayName
				{
					get
					{
						if (!((Object)(object)_behaviour != (Object)null) || !(_field != null))
						{
							return "<unknown>";
						}
						return ((object)_behaviour).GetType().Name + "." + _field.Name;
					}
				}

				public PlayerSpeedRecord(MonoBehaviour behaviour, FieldInfo field, float baseValue)
				{
					_behaviour = behaviour;
					_field = field;
					_baseValue = baseValue;
				}

				public bool Apply(float multiplier)
				{
					if ((Object)(object)_behaviour == (Object)null || _field == null)
					{
						return false;
					}
					if (float.IsNaN(multiplier) || float.IsInfinity(multiplier) || multiplier <= 0f)
					{
						multiplier = 1f;
					}
					try
					{
						_field.SetValue(_behaviour, _baseValue * multiplier);
						return true;
					}
					catch
					{
						return false;
					}
				}

				public void Restore()
				{
					if ((Object)(object)_behaviour == (Object)null || _field == null)
					{
						return;
					}
					try
					{
						_field.SetValue(_behaviour, _baseValue);
					}
					catch
					{
					}
				}
			}

			private sealed class AgentRecord
			{
				private readonly Animator[] _animators;

				private readonly float[] _animatorBaseSpeeds;

				public NavMeshAgent Agent { get; private set; }

				public float BaseSpeed { get; }

				public float BaseAcceleration { get; }

				public float BaseAngularSpeed { get; }

				public string DisplayName { get; }

				public bool IsValid => (Object)(object)Agent != (Object)null;

				public AgentRecord(NavMeshAgent agent)
				{
					Agent = agent;
					BaseSpeed = agent.speed;
					BaseAcceleration = agent.acceleration;
					BaseAngularSpeed = agent.angularSpeed;
					DisplayName = BuildHierarchyPath(((Component)agent).transform);
					_animators = ((Component)agent).GetComponentsInChildren<Animator>(true);
					_animatorBaseSpeeds = new float[_animators.Length];
					for (int i = 0; i < _animators.Length; i++)
					{
						Animator val = _animators[i];
						_animatorBaseSpeeds[i] = (((Object)(object)val != (Object)null) ? val.speed : 1f);
					}
				}

				public void ApplyMovementMultiplier(float multiplier)
				{
					if (!IsValid)
					{
						return;
					}
					if (multiplier < 0f)
					{
						multiplier = 0f;
					}
					try
					{
						Agent.speed = BaseSpeed * multiplier;
						Agent.acceleration = BaseAcceleration * multiplier;
						Agent.angularSpeed = BaseAngularSpeed * Mathf.Clamp(multiplier, 0.1f, 10f);
					}
					catch
					{
						Agent = null;
					}
				}

				public void ApplyAnimatorMultiplier(float multiplier)
				{
					for (int i = 0; i < _animators.Length; i++)
					{
						Animator val = _animators[i];
						if (!((Object)(object)val == (Object)null))
						{
							try
							{
								val.speed = _animatorBaseSpeeds[i] * multiplier;
							}
							catch
							{
								_animators[i] = null;
							}
						}
					}
				}

				public void Restore()
				{
					if ((Object)(object)Agent != (Object)null)
					{
						try
						{
							Agent.speed = BaseSpeed;
							Agent.acceleration = BaseAcceleration;
							Agent.angularSpeed = BaseAngularSpeed;
						}
						catch
						{
							Agent = null;
						}
					}
					for (int i = 0; i < _animators.Length; i++)
					{
						Animator val = _animators[i];
						if (!((Object)(object)val == (Object)null))
						{
							try
							{
								val.speed = _animatorBaseSpeeds[i];
							}
							catch
							{
								_animators[i] = null;
							}
						}
					}
				}
			}

			private ManualLogSource _log;

			private ConfigFile _config;

			private ConfigEntry<float> _timeScaleMultiplier;

			private ConfigEntry<bool> _syncFixedDeltaTime;

			private ConfigEntry<bool> _syncMaximumDeltaTime;

			private ConfigEntry<float> _npcWalkMultiplier;

			private ConfigEntry<float> _interactionMultiplier;

			private ConfigEntry<InteractionScalingMode> _interactionScalingMode;

			private ConfigEntry<float> _agentScanInterval;

			private ConfigEntry<string> _excludedNameTokens;

			private ConfigEntry<string> _excludedComponentTokens;

			private ConfigEntry<bool> _logAgentDiscovery;

			private ConfigEntry<bool> _compensatePlayerMovement;

			private ConfigEntry<string> _playerNameTokens;

			private ConfigEntry<string> _playerComponentTokens;

			private ConfigEntry<string> _playerSpeedFieldNames;

			private ConfigEntry<bool> _logPlayerComponents;

			private ConfigEntry<float> _playerCompensationMultiplier;

			private NavMeshAgent[] _playerAgents = Array.Empty<NavMeshAgent>();

			private float[] _playerAgentBaseSpeed = Array.Empty<float>();

			private float[] _playerAgentBaseAcceleration = Array.Empty<float>();

			private float[] _playerAgentBaseAngular = Array.Empty<float>();

			private Animator[] _playerAnimators = Array.Empty<Animator>();

			private float[] _playerAnimatorBaseSpeed = Array.Empty<float>();

			private bool _playerCacheInitialised;

			private float _playerLastCompensation = 1f;

			private bool _pauseOverrideActive;

			private float _pauseResumeDeadline;

			private float _nextPlayerProbe;

			private string[] _playerNameKeywords = Array.Empty<string>();

			private string[] _playerComponentKeywords = Array.Empty<string>();

			private string[] _playerSpeedFieldKeywords = Array.Empty<string>();

			private readonly List<PlayerSpeedRecord> _playerSpeedRecords = new List<PlayerSpeedRecord>();

			private string[] _excludedNameKeywords = Array.Empty<string>();

			private string[] _excludedComponentKeywords = Array.Empty<string>();

			private float _baseTimeScale;

			private float _baseFixedDeltaTime;

			private float _baseMaximumDeltaTime;

			private float _lastAppliedTimeScale;

			private float _lastAppliedFixedDeltaTime;

			private readonly Dictionary<int, AgentRecord> _agentRecords = new Dictionary<int, AgentRecord>();

			private readonly List<int> _staleAgentIds = new List<int>();

			private float _nextAgentScan;

			private float _nextStatusLog;

			private bool _timeScaleDirty = true;

			private bool _agentScalingDirty = true;

			private bool _animatorScalingDirty = true;

			private bool _initialised;

			internal static ControllerBehaviour Instance { get; private set; }

			internal void Initialise(ManualLogSource logger, ConfigFile config)
			{
				_log = logger;
				_config = config;
				if (_initialised)
				{
					_log.LogWarning((object)"[GameSpeedController] Initialise called more than once.");
					return;
				}
				_baseTimeScale = Time.timeScale;
				_baseFixedDeltaTime = Time.fixedDeltaTime;
				_baseMaximumDeltaTime = Time.maximumDeltaTime;
				_lastAppliedTimeScale = _baseTimeScale;
				_lastAppliedFixedDeltaTime = _baseFixedDeltaTime;
				try
				{
					BindConfig();
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"[GameSpeedController] Failed to bind config: {arg}");
					((Behaviour)this).enabled = false;
					return;
				}
				SceneManager.sceneLoaded += OnSceneLoaded;
				_nextAgentScan = Time.unscaledTime + 0.5f;
				_nextStatusLog = Time.unscaledTime + 2f;
				_nextPlayerProbe = Time.unscaledTime + 0.5f;
				_pauseOverrideActive = false;
				_pauseResumeDeadline = 0f;
				_timeScaleDirty = true;
				_agentScalingDirty = true;
				_animatorScalingDirty = true;
				_initialised = true;
				_log.LogInfo((object)"[GameSpeedController] Behaviour initialised.");
				ApplyTimeScale(force: true);
				ScanAgents(force: true);
				ApplyAgentMultipliers();
				ApplyAnimatorMultipliers();
			}

			private void Awake()
			{
				if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this)
				{
					Object.Destroy((Object)(object)((Component)this).gameObject);
					return;
				}
				Instance = this;
				Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[GameSpeedController] Behaviour Awake.");
				}
			}

			private void OnEnable()
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[GameSpeedController] Behaviour enabled.");
				}
			}

			private void OnDisable()
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[GameSpeedController] Behaviour disabled.");
				}
			}

			private void OnDestroy()
			{
				if ((Object)(object)Instance == (Object)(object)this)
				{
					Instance = null;
				}
				SceneManager.sceneLoaded -= OnSceneLoaded;
				RestoreTimeSettings();
				RestoreAgents();
				RestorePlayerSpeedRecords();
				_agentRecords.Clear();
				_playerAgents = Array.Empty<NavMeshAgent>();
				_playerAgentBaseSpeed = Array.Empty<float>();
				_playerAgentBaseAcceleration = Array.Empty<float>();
				_playerAgentBaseAngular = Array.Empty<float>();
				_playerAnimators = Array.Empty<Animator>();
				_playerAnimatorBaseSpeed = Array.Empty<float>();
				_playerCacheInitialised = false;
				_playerLastCompensation = 1f;
				_pauseOverrideActive = false;
				_pauseResumeDeadline = 0f;
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[GameSpeedController] Behaviour destroyed.");
				}
			}

			private void Update()
			{
				if (!_initialised)
				{
					return;
				}
				float num = Mathf.Max(0f, _timeScaleMultiplier.Value);
				float num2 = Mathf.Max(num, 0.0001f);
				bool flag = num > 0.0001f && Time.timeScale <= 0.0001f;
				if (!_pauseOverrideActive && flag)
				{
					_pauseOverrideActive = true;
					_pauseResumeDeadline = Time.unscaledTime + 0.5f;
					_log.LogInfo((object)"[GameSpeedController] External pause detected; honoring game-controlled timescale.");
				}
				if (_pauseOverrideActive)
				{
					if (Time.timeScale <= 0.0001f)
					{
						_pauseResumeDeadline = Time.unscaledTime + 0.5f;
					}
					else if (Time.unscaledTime >= _pauseResumeDeadline)
					{
						_pauseOverrideActive = false;
						_timeScaleDirty = true;
						_log.LogInfo((object)"[GameSpeedController] Pause ended; restoring configured timescale.");
					}
				}
				if (!_pauseOverrideActive)
				{
					bool flag2 = false;
					if (!Mathf.Approximately(Time.timeScale, num))
					{
						_log.LogDebug((object)$"[GameSpeedController] Detected external timescale change. Actual={Time.timeScale:F3}, expected={num:F3}.");
						flag2 = true;
					}
					if (_syncFixedDeltaTime.Value && !Mathf.Approximately(Time.fixedDeltaTime, _baseFixedDeltaTime * num2))
					{
						_log.LogDebug((object)$"[GameSpeedController] Detected external fixedDeltaTime change. Actual={Time.fixedDeltaTime:E3}, expected={_baseFixedDeltaTime * num2:E3}.");
						flag2 = true;
					}
					if (_syncMaximumDeltaTime.Value && !Mathf.Approximately(Time.maximumDeltaTime, _baseMaximumDeltaTime * num2))
					{
						_log.LogDebug((object)$"[GameSpeedController] Detected external maximumDeltaTime change. Actual={Time.maximumDeltaTime:F3}, expected={_baseMaximumDeltaTime * num2:F3}.");
						flag2 = true;
					}
					if (flag2)
					{
						_timeScaleDirty = true;
					}
					if (_timeScaleDirty)
					{
						ApplyTimeScale(force: false);
					}
				}
				else
				{
					_timeScaleDirty = true;
				}
				float unscaledTime = Time.unscaledTime;
				if (unscaledTime >= _nextAgentScan)
				{
					ScanAgents(force: false);
				}
				if (_agentScalingDirty)
				{
					ApplyAgentMultipliers();
				}
				if (_animatorScalingDirty)
				{
					ApplyAnimatorMultipliers();
				}
				EnsurePlayerCache();
				float currentTimeScale = (_pauseOverrideActive ? 1f : Mathf.Max(num, 0.0001f));
				ApplyPlayerCompensation(currentTimeScale);
				if (unscaledTime >= _nextStatusLog)
				{
					_nextStatusLog = unscaledTime + 5f;
					_log.LogInfo((object)$"[GameSpeedController] Status: timeScale={Time.timeScale:F3}, fixedDelta={Time.fixedDeltaTime:E3}, agents={_agentRecords.Count}.");
				}
			}

			private void BindConfig()
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Expected O, but got Unknown
				//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fe: Expected O, but got Unknown
				//IL_0149: Unknown result type (might be due to invalid IL or missing references)
				//IL_0153: Expected O, but got Unknown
				//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e0: Expected O, but got Unknown
				//IL_03ba: Unknown result type (might be due to invalid IL or missing references)
				//IL_03c4: Expected O, but got Unknown
				_timeScaleMultiplier = _config.Bind<float>("General", "TimeScaleMultiplier", 2f, new ConfigDescription("Global timescale multiplier applied to the game.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), Array.Empty<object>()));
				_timeScaleMultiplier.SettingChanged += delegate
				{
					_timeScaleDirty = true;
					_animatorScalingDirty = true;
				};
				_syncFixedDeltaTime = _config.Bind<bool>("General", "SyncFixedDeltaTime", true, "Adjust Time.fixedDeltaTime proportionally to the timescale to keep physics stable.");
				_syncFixedDeltaTime.SettingChanged += delegate
				{
					_timeScaleDirty = true;
				};
				_syncMaximumDeltaTime = _config.Bind<bool>("General", "SyncMaximumDeltaTime", true, "Adjust Time.maximumDeltaTime proportionally to the timescale.");
				_syncMaximumDeltaTime.SettingChanged += delegate
				{
					_timeScaleDirty = true;
				};
				_npcWalkMultiplier = _config.Bind<float>("NPCs", "WalkSpeedMultiplier", 2f, new ConfigDescription("Multiplier applied to NavMeshAgent speed and acceleration for non-player agents.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
				_npcWalkMultiplier.SettingChanged += delegate
				{
					_agentScalingDirty = true;
				};
				_interactionMultiplier = _config.Bind<float>("NPCs", "ManualInteractionMultiplier", 0.5f, new ConfigDescription("Multiplier used when InteractionScalingMode = Manual.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
				_interactionMultiplier.SettingChanged += delegate
				{
					_animatorScalingDirty = true;
				};
				_interactionScalingMode = _config.Bind<InteractionScalingMode>("NPCs", "InteractionScalingMode", InteractionScalingMode.InverseTimeScale, "How to scale NPC interaction animations: Manual uses ManualInteractionMultiplier, MatchTimeScale uses the timescale, InverseTimeScale uses 1 / timescale.");
				_interactionScalingMode.SettingChanged += delegate
				{
					_animatorScalingDirty = true;
				};
				_agentScanInterval = _config.Bind<float>("Advanced", "AgentScanInterval", 1f, new ConfigDescription("Seconds between NavMeshAgent scans.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 5f), Array.Empty<object>()));
				_agentScanInterval.SettingChanged += delegate
				{
					_nextAgentScan = Time.unscaledTime;
				};
				_excludedNameTokens = _config.Bind<string>("Advanced", "ExcludedAgentNameTokens", "Player", "Comma separated name fragments; agents whose hierarchy names contain any entry are skipped.");
				_excludedNameTokens.SettingChanged += delegate
				{
					UpdateKeywordCaches();
				};
				_excludedComponentTokens = _config.Bind<string>("Advanced", "ExcludedComponentTokens", "Player", "Comma separated component type fragments; agents with matching components in their hierarchy are skipped.");
				_excludedComponentTokens.SettingChanged += delegate
				{
					UpdateKeywordCaches();
				};
				_logAgentDiscovery = _config.Bind<bool>("Advanced", "LogAgentDiscovery", false, "Enable to log when NPC agents are tracked or removed.");
				_compensatePlayerMovement = _config.Bind<bool>("Player", "CompensatePlayerMovement", true, "Keep player movement and animations at their baseline speed by applying an inverse timescale multiplier.");
				_playerNameTokens = _config.Bind<string>("Player", "NameHints", "Player", "Comma separated name fragments used to locate the player root when no tagged object is found.");
				_playerNameTokens.SettingChanged += delegate
				{
					UpdatePlayerKeywordCaches();
				};
				_playerComponentTokens = _config.Bind<string>("Player", "ComponentHints", string.Empty, "Comma separated component type fragments used to help locate the player root.");
				_playerComponentTokens.SettingChanged += delegate
				{
					UpdatePlayerKeywordCaches();
				};
				_playerSpeedFieldNames = _config.Bind<string>("Player", "SpeedFieldNames", "speed,moveSpeed,walkSpeed,runSpeed", "Comma separated list of float field names that should be compensated on player components.");
				_playerSpeedFieldNames.SettingChanged += delegate
				{
					UpdatePlayerKeywordCaches();
				};
				_logPlayerComponents = _config.Bind<bool>("Debug", "LogPlayerComponents", false, "Log discovered player component types and compensated fields when the cache initialises.");
				_playerCompensationMultiplier = _config.Bind<float>("Player", "CompensationMultiplier", 1f, new ConfigDescription("Additional multiplier applied to player movement compensation.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>()));
				_playerCompensationMultiplier.SettingChanged += delegate
				{
					_playerLastCompensation = float.NaN;
				};
				UpdateKeywordCaches();
			}

			private void EnsurePlayerCache()
			{
				if (!_compensatePlayerMovement.Value)
				{
					return;
				}
				if (_playerCacheInitialised && _playerAgents.Length == 0 && _playerAnimators.Length == 0 && _playerSpeedRecords.Count == 0)
				{
					_playerCacheInitialised = false;
				}
				if (_playerCacheInitialised || Time.unscaledTime < _nextPlayerProbe)
				{
					return;
				}
				_nextPlayerProbe = Time.unscaledTime + 2f;
				GameObject val = ResolvePlayerRoot();
				if ((Object)(object)val == (Object)null)
				{
					if (_logPlayerComponents.Value)
					{
						_log.LogDebug((object)"[GameSpeedController] Player search failed; update Player.NameHints/ComponentHints if the character uses different identifiers.");
					}
					_playerCacheInitialised = false;
				}
				else
				{
					CachePlayerComponents(val);
				}
			}

			private void CachePlayerComponents(GameObject root)
			{
				if ((Object)(object)root == (Object)null)
				{
					return;
				}
				RestorePlayerSpeedRecords();
				List<MonoBehaviour> behaviours = GatherPlayerBehaviours(root);
				_playerAgents = root.GetComponentsInChildren<NavMeshAgent>(true);
				_playerAgentBaseSpeed = new float[_playerAgents.Length];
				_playerAgentBaseAcceleration = new float[_playerAgents.Length];
				_playerAgentBaseAngular = new float[_playerAgents.Length];
				for (int i = 0; i < _playerAgents.Length; i++)
				{
					NavMeshAgent val = _playerAgents[i];
					if (!((Object)(object)val == (Object)null))
					{
						_playerAgentBaseSpeed[i] = val.speed;
						_playerAgentBaseAcceleration[i] = val.acceleration;
						_playerAgentBaseAngular[i] = val.angularSpeed;
					}
				}
				_playerAnimators = root.GetComponentsInChildren<Animator>(true);
				_playerAnimatorBaseSpeed = new float[_playerAnimators.Length];
				for (int j = 0; j < _playerAnimators.Length; j++)
				{
					Animator val2 = _playerAnimators[j];
					_playerAnimatorBaseSpeed[j] = (((Object)(object)val2 != (Object)null) ? val2.speed : 1f);
				}
				DiscoverPlayerSpeedFields(behaviours);
				_playerCacheInitialised = _playerAgents.Length != 0 || _playerAnimators.Length != 0 || _playerSpeedRecords.Count > 0;
				_playerLastCompensation = float.NaN;
				if (_playerCacheInitialised)
				{
					_log.LogInfo((object)$"[GameSpeedController] Player cache initialised on '{BuildHierarchyPath(root.transform)}' (agents={_playerAgents.Length}, animators={_playerAnimators.Length}, fields={_playerSpeedRecords.Count}).");
					if (_logPlayerComponents.Value)
					{
						LogPlayerComponentDetails(root, behaviours);
					}
				}
				else
				{
					_log.LogDebug((object)"[GameSpeedController] Player cache attempt found no compensatable components; will retry.");
					_nextPlayerProbe = Time.unscaledTime + 2f;
				}
			}

			private List<MonoBehaviour> GatherPlayerBehaviours(GameObject root)
			{
				List<MonoBehaviour> list = new List<MonoBehaviour>(16);
				if ((Object)(object)root == (Object)null)
				{
					return list;
				}
				HashSet<Component> visited = new HashSet<Component>();
				Queue<Component> queue = new Queue<Component>();
				MonoBehaviour[] components = root.GetComponents<MonoBehaviour>();
				for (int i = 0; i < components.Length; i++)
				{
					EnqueueComponent((Component)(object)components[i]);
				}
				components = root.GetComponentsInChildren<MonoBehaviour>(true);
				for (int i = 0; i < components.Length; i++)
				{
					EnqueueComponent((Component)(object)components[i]);
				}
				while (queue.Count > 0)
				{
					Component val = queue.Dequeue();
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					MonoBehaviour val2 = (MonoBehaviour)(object)((val is MonoBehaviour) ? val : null);
					if (val2 != null)
					{
						list.Add(val2);
					}
					FieldInfo[] fields = ((object)val).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (typeof(Object).IsAssignableFrom(fieldInfo.FieldType))
						{
							object value2;
							try
							{
								value2 = fieldInfo.GetValue(val);
							}
							catch
							{
								continue;
							}
							EnqueueObject(value2);
						}
					}
				}
				return list;
				void EnqueueComponent(Component component)
				{
					if (!((Object)(object)component == (Object)null) && visited.Add(component))
					{
						queue.Enqueue(component);
					}
				}
				void EnqueueObject(object value)
				{
					if (value != null)
					{
						Object val3 = (Object)((value is Object) ? value : null);
						if (val3 != null)
						{
							Component val4 = (Component)(object)((val3 is Component) ? val3 : null);
							if (val4 != null)
							{
								EnqueueComponent(val4);
								return;
							}
							GameObject val5 = (GameObject)(object)((val3 is GameObject) ? val3 : null);
							if (val5 != null)
							{
								MonoBehaviour[] components2 = val5.GetComponents<MonoBehaviour>();
								for (int k = 0; k < components2.Length; k++)
								{
									EnqueueComponent((Component)(object)components2[k]);
								}
								components2 = val5.GetComponentsInChildren<MonoBehaviour>(true);
								for (int k = 0; k < components2.Length; k++)
								{
									EnqueueComponent((Component)(object)components2[k]);
								}
								return;
							}
						}
						if (value is IEnumerable enumerable)
						{
							foreach (object item in enumerable)
							{
								EnqueueObject(item);
							}
						}
					}
				}
			}

			private GameObject ResolvePlayerRoot()
			{
				GameObject val = null;
				try
				{
					val = GameObject.FindWithTag("Player");
				}
				catch
				{
					val = null;
				}
				if (IsValidPlayerCandidate(val))
				{
					return val;
				}
				GameObject val2 = null;
				GameObject[] array = Resources.FindObjectsOfTypeAll<GameObject>();
				foreach (GameObject val3 in array)
				{
					if (IsValidPlayerCandidate(val3))
					{
						if (_playerNameKeywords.Length != 0 && MatchesAnyKeyword(((Object)val3).name, _playerNameKeywords))
						{
							return val3;
						}
						if (_playerComponentKeywords.Length != 0 && HasComponentKeyword(val3, _playerComponentKeywords))
						{
							return val3;
						}
						if ((Object)(object)val2 == (Object)null && _playerNameKeywords.Length == 0 && _playerComponentKeywords.Length == 0)
						{
							val2 = val3;
						}
					}
				}
				return val2;
			}

			private static bool IsValidPlayerCandidate(GameObject go)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)go == (Object)null)
				{
					return false;
				}
				if (!go.activeInHierarchy)
				{
					return false;
				}
				Scene scene = go.scene;
				if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded)
				{
					return false;
				}
				return true;
			}

			private static bool MatchesAnyKeyword(string value, string[] keywords)
			{
				if (string.IsNullOrEmpty(value) || keywords == null || keywords.Length == 0)
				{
					return false;
				}
				foreach (string text in keywords)
				{
					if (text.Length != 0 && value.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
				}
				return false;
			}

			private static bool HasComponentKeyword(GameObject go, string[] keywords)
			{
				if ((Object)(object)go == (Object)null || keywords == null || keywords.Length == 0)
				{
					return false;
				}
				Component[] componentsInChildren = go.GetComponentsInChildren<Component>(true);
				foreach (Component val in componentsInChildren)
				{
					if (!((Object)(object)val == (Object)null) && MatchesAnyKeyword(((object)val).GetType().Name, keywords))
					{
						return true;
					}
				}
				return false;
			}

			private void DiscoverPlayerSpeedFields(IReadOnlyList<MonoBehaviour> behaviours)
			{
				for (int i = 0; i < behaviours.Count; i++)
				{
					MonoBehaviour val = behaviours[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Type type = ((object)val).GetType();
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (!(fieldInfo.FieldType != typeof(float)) && MatchesSpeedField(fieldInfo.Name))
						{
							try
							{
								float baseValue = (float)fieldInfo.GetValue(val);
								_playerSpeedRecords.Add(new PlayerSpeedRecord(val, fieldInfo, baseValue));
							}
							catch (Exception arg)
							{
								_log.LogDebug((object)$"[GameSpeedController] Failed to cache player speed field {type.Name}.{fieldInfo.Name}: {arg}");
							}
						}
					}
				}
			}

			private bool MatchesSpeedField(string fieldName)
			{
				if (string.IsNullOrEmpty(fieldName))
				{
					return false;
				}
				for (int i = 0; i < _playerSpeedFieldKeywords.Length; i++)
				{
					string text = _playerSpeedFieldKeywords[i];
					if (text.Length != 0 && fieldName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
				}
				return false;
			}

			private void LogPlayerComponentDetails(GameObject root, IReadOnlyList<MonoBehaviour> behaviours)
			{
				try
				{
					HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
					if ((Object)(object)root != (Object)null)
					{
						hashSet.Add(typeof(Transform).FullName);
					}
					for (int i = 0; i < behaviours.Count; i++)
					{
						MonoBehaviour val = behaviours[i];
						if (!((Object)(object)val == (Object)null))
						{
							hashSet.Add(((object)val).GetType().FullName);
						}
					}
					if (hashSet.Count > 0)
					{
						string[] value = hashSet.Take(40).ToArray();
						_log.LogInfo((object)("[GameSpeedController] Player component types: " + string.Join(", ", value) + ((hashSet.Count > 40) ? ", ..." : string.Empty)));
					}
					if (_playerSpeedRecords.Count > 0)
					{
						string[] value2 = (from r in _playerSpeedRecords.Take(20)
							select r.DisplayName).ToArray();
						_log.LogInfo((object)("[GameSpeedController] Player compensated fields: " + string.Join(", ", value2) + ((_playerSpeedRecords.Count > 20) ? ", ..." : string.Empty)));
					}
				}
				catch (Exception arg)
				{
					_log.LogDebug((object)$"[GameSpeedController] Failed to log player components: {arg}");
				}
			}

			private void RestorePlayerSpeedRecords()
			{
				for (int i = 0; i < _playerSpeedRecords.Count; i++)
				{
					try
					{
						_playerSpeedRecords[i].Restore();
					}
					catch (Exception arg)
					{
						_log.LogDebug((object)$"[GameSpeedController] Failed to restore player speed field: {arg}");
					}
				}
				_playerSpeedRecords.Clear();
			}

			private void ApplyPlayerCompensation(float currentTimeScale)
			{
				if (!_compensatePlayerMovement.Value || !_playerCacheInitialised)
				{
					return;
				}
				float num = ((currentTimeScale > 0.0001f) ? (1f / currentTimeScale) : 1f);
				float num2 = ((_playerCompensationMultiplier != null) ? Mathf.Max(0f, _playerCompensationMultiplier.Value) : 1f);
				num *= num2;
				if (!float.IsNaN(_playerLastCompensation) && Mathf.Approximately(num, _playerLastCompensation))
				{
					return;
				}
				bool flag = false;
				for (int i = 0; i < _playerAgents.Length; i++)
				{
					NavMeshAgent val = _playerAgents[i];
					if (!((Object)(object)val == (Object)null))
					{
						try
						{
							val.speed = _playerAgentBaseSpeed[i] * num;
							val.acceleration = _playerAgentBaseAcceleration[i] * num;
							val.angularSpeed = _playerAgentBaseAngular[i] * Mathf.Clamp(num, 0.1f, 10f);
							flag = true;
						}
						catch (Exception arg)
						{
							_log.LogDebug((object)$"[GameSpeedController] Player NavMesh compensation failed: {arg}");
						}
					}
				}
				for (int j = 0; j < _playerAnimators.Length; j++)
				{
					Animator val2 = _playerAnimators[j];
					if (!((Object)(object)val2 == (Object)null))
					{
						try
						{
							val2.speed = _playerAnimatorBaseSpeed[j] * num;
							flag = true;
						}
						catch (Exception arg2)
						{
							_log.LogDebug((object)$"[GameSpeedController] Player Animator compensation failed: {arg2}");
						}
					}
				}
				for (int k = 0; k < _playerSpeedRecords.Count; k++)
				{
					if (_playerSpeedRecords[k].Apply(num))
					{
						flag = true;
					}
				}
				if (!flag)
				{
					_playerCacheInitialised = false;
					_nextPlayerProbe = Time.unscaledTime + 2f;
				}
				_playerLastCompensation = num;
			}

			private void UpdateKeywordCaches()
			{
				_excludedNameKeywords = SplitKeywords(_excludedNameTokens.Value);
				_excludedComponentKeywords = SplitKeywords(_excludedComponentTokens.Value);
				_agentScalingDirty = true;
				_animatorScalingDirty = true;
				UpdatePlayerKeywordCaches();
			}

			private void UpdatePlayerKeywordCaches()
			{
				_playerNameKeywords = SplitKeywords(_playerNameTokens?.Value);
				_playerComponentKeywords = SplitKeywords(_playerComponentTokens?.Value);
				_playerSpeedFieldKeywords = SplitKeywords(_playerSpeedFieldNames?.Value);
				if (_playerSpeedFieldKeywords.Length == 0)
				{
					_playerSpeedFieldKeywords = new string[4] { "speed", "movespeed", "walkspeed", "runspeed" };
				}
			}

			private static string[] SplitKeywords(string value)
			{
				if (string.IsNullOrWhiteSpace(value))
				{
					return Array.Empty<string>();
				}
				string[] array = value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = array[i].Trim();
				}
				return array;
			}

			private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
			{
				_log.LogInfo((object)("[GameSpeedController] Scene loaded: " + ((Scene)(ref scene)).name + "."));
				_nextAgentScan = Time.unscaledTime + 0.25f;
				_agentScalingDirty = true;
				_animatorScalingDirty = true;
			}

			private void ApplyTimeScale(bool force)
			{
				float num = Mathf.Max(0f, _timeScaleMultiplier.Value);
				float num2 = Mathf.Max(num, 0.0001f);
				if (force || !Mathf.Approximately(num, _lastAppliedTimeScale) || !Mathf.Approximately(Time.timeScale, num))
				{
					Time.timeScale = num;
					_lastAppliedTimeScale = num;
					_log.LogInfo((object)$"[GameSpeedController] Time scale set to {Time.timeScale:F3}.");
				}
				if (_syncFixedDeltaTime.Value)
				{
					float num3 = _baseFixedDeltaTime * num2;
					if (force || !Mathf.Approximately(Time.fixedDeltaTime, num3))
					{
						Time.fixedDeltaTime = num3;
						_lastAppliedFixedDeltaTime = num3;
						_log.LogDebug((object)$"[GameSpeedController] Updated fixedDeltaTime to {Time.fixedDeltaTime:E3}.");
					}
				}
				else if (force)
				{
					Time.fixedDeltaTime = _baseFixedDeltaTime;
					_lastAppliedFixedDeltaTime = _baseFixedDeltaTime;
				}
				if (_syncMaximumDeltaTime.Value)
				{
					float num4 = _baseMaximumDeltaTime * num2;
					if (force || !Mathf.Approximately(Time.maximumDeltaTime, num4))
					{
						Time.maximumDeltaTime = num4;
						_log.LogDebug((object)$"[GameSpeedController] Updated maximumDeltaTime to {Time.maximumDeltaTime:F3}.");
					}
				}
				else if (force)
				{
					Time.maximumDeltaTime = _baseMaximumDeltaTime;
				}
				_timeScaleDirty = false;
			}

			private void RestoreTimeSettings()
			{
				Time.timeScale = _baseTimeScale;
				Time.fixedDeltaTime = _baseFixedDeltaTime;
				Time.maximumDeltaTime = _baseMaximumDeltaTime;
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[GameSpeedController] Restored time settings.");
				}
			}

			private void ScanAgents(bool force)
			{
				_nextAgentScan = Time.unscaledTime + Mathf.Max(0.1f, _agentScanInterval.Value);
				NavMeshAgent[] array = Resources.FindObjectsOfTypeAll<NavMeshAgent>();
				foreach (NavMeshAgent val in array)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					GameObject gameObject = ((Component)val).gameObject;
					if ((Object)(object)gameObject == (Object)null || !gameObject.activeInHierarchy || !((Behaviour)val).isActiveAndEnabled || ShouldSkipAgent(val))
					{
						continue;
					}
					int instanceID = ((Object)val).GetInstanceID();
					if (!_agentRecords.ContainsKey(instanceID))
					{
						AgentRecord agentRecord = new AgentRecord(val);
						_agentRecords[instanceID] = agentRecord;
						if (_logAgentDiscovery.Value)
						{
							_log.LogInfo((object)$"[GameSpeedController] Tracking agent '{agentRecord.DisplayName}' (speed {agentRecord.BaseSpeed:F2}).");
						}
						_agentScalingDirty = true;
						_animatorScalingDirty = true;
					}
				}
				_staleAgentIds.Clear();
				foreach (KeyValuePair<int, AgentRecord> agentRecord2 in _agentRecords)
				{
					if (!agentRecord2.Value.IsValid)
					{
						_staleAgentIds.Add(agentRecord2.Key);
					}
				}
				foreach (int staleAgentId in _staleAgentIds)
				{
					if (_agentRecords.TryGetValue(staleAgentId, out var value) && _logAgentDiscovery.Value)
					{
						_log.LogInfo((object)("[GameSpeedController] Removing agent '" + value.DisplayName + "'."));
					}
					_agentRecords.Remove(staleAgentId);
				}
			}

			private bool ShouldSkipAgent(NavMeshAgent agent)
			{
				if ((Object)(object)agent == (Object)null)
				{
					return true;
				}
				Transform transform = ((Component)agent).transform;
				if ((Object)(object)transform == (Object)null)
				{
					return true;
				}
				if (((Component)agent).CompareTag("Player"))
				{
					return true;
				}
				Transform root = transform.root;
				if ((Object)(object)root != (Object)null && ((Component)root).CompareTag("Player"))
				{
					return true;
				}
				if (MatchesKeywords(transform, _excludedNameKeywords, targetIsName: true))
				{
					return true;
				}
				if (MatchesKeywords(transform, _excludedComponentKeywords, targetIsName: false))
				{
					return true;
				}
				return false;
			}

			private bool MatchesKeywords(Transform transform, string[] keywords, bool targetIsName)
			{
				if (keywords == null || keywords.Length == 0)
				{
					return false;
				}
				Transform val = transform;
				while ((Object)(object)val != (Object)null)
				{
					if (targetIsName)
					{
						string name = ((Object)val).name;
						if (!string.IsNullOrEmpty(name))
						{
							foreach (string text in keywords)
							{
								if (text.Length > 0 && name.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
								{
									return true;
								}
							}
						}
					}
					else
					{
						Component[] components = ((Component)val).GetComponents<Component>();
						foreach (Component val2 in components)
						{
							if ((Object)(object)val2 == (Object)null)
							{
								continue;
							}
							string name2 = ((object)val2).GetType().Name;
							foreach (string text2 in keywords)
							{
								if (text2.Length > 0 && name2.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
								{
									return true;
								}
							}
						}
					}
					val = val.parent;
				}
				return false;
			}

			private void ApplyAgentMultipliers()
			{
				foreach (AgentRecord value in _agentRecords.Values)
				{
					value.ApplyMovementMultiplier(_npcWalkMultiplier.Value);
				}
				_agentScalingDirty = false;
			}

			private void ApplyAnimatorMultipliers()
			{
				float multiplier = CalculateInteractionMultiplier();
				foreach (AgentRecord value in _agentRecords.Values)
				{
					value.ApplyAnimatorMultiplier(multiplier);
				}
				_animatorScalingDirty = false;
			}

			private float CalculateInteractionMultiplier()
			{
				switch (_interactionScalingMode.Value)
				{
				case InteractionScalingMode.MatchTimeScale:
					return Mathf.Max(0f, _timeScaleMultiplier.Value);
				case InteractionScalingMode.InverseTimeScale:
					if (!(_timeScaleMultiplier.Value > 0.0001f))
					{
						return 1f;
					}
					return 1f / _timeScaleMultiplier.Value;
				default:
					return Mathf.Max(0f, _interactionMultiplier.Value);
				}
			}

			private void RestoreAgents()
			{
				foreach (AgentRecord value in _agentRecords.Values)
				{
					value.Restore();
				}
			}

			private static string BuildHierarchyPath(Transform transform)
			{
				if ((Object)(object)transform == (Object)null)
				{
					return "<null>";
				}
				Stack<string> stack = new Stack<string>();
				Transform val = transform;
				while ((Object)(object)val != (Object)null)
				{
					stack.Push(((Object)val).name);
					val = val.parent;
				}
				return string.Join("/", stack.ToArray());
			}
		}

		private const string PluginGuid = "com.duckieray.cardshop.gamespeed";

		private const string PluginName = "Game Speed Controller";

		private const string PluginVersion = "1.0.0";

		private void Awake()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			if ((Object)(object)ControllerBehaviour.Instance != (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"[GameSpeedController] Behaviour already exists; destroying duplicate plugin component.");
				Object.Destroy((Object)(object)this);
				return;
			}
			GameObject val = new GameObject("GameSpeedControllerHost")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			val.AddComponent<ControllerBehaviour>().Initialise(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[GameSpeedController] Behaviour host initialised.");
		}
	}
}