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.");
}
}
}