using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
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.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Hitcontinue")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Hitcontinue")]
[assembly: AssemblyTitle("Hitcontinue")]
[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 Hitcontinue
{
[BepInPlugin("com.clippton.hitcontinue", "Hitcontinue", "1.0.0")]
public sealed class HitcontinuePlugin : BaseUnityPlugin
{
private struct RigidbodyState
{
private readonly Rigidbody rb;
private readonly bool isKinematic;
private readonly Vector3 velocity;
private readonly Vector3 angularVelocity;
public RigidbodyState(Rigidbody rb)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
this.rb = rb;
isKinematic = rb.isKinematic;
velocity = rb.velocity;
angularVelocity = rb.angularVelocity;
}
public void Restore()
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)rb == (Object)null))
{
rb.isKinematic = isKinematic;
rb.velocity = velocity;
rb.angularVelocity = angularVelocity;
}
}
}
private struct AnimatorState
{
private readonly Animator animator;
private readonly float speed;
public AnimatorState(Animator animator)
{
this.animator = animator;
speed = animator.speed;
}
public void Restore()
{
if ((Object)(object)animator != (Object)null)
{
animator.speed = speed;
}
}
}
private struct AgentState
{
private readonly NavMeshAgent agent;
private readonly bool isStopped;
private readonly Vector3 velocity;
public AgentState(NavMeshAgent agent)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
this.agent = agent;
isStopped = agent.isStopped;
velocity = agent.velocity;
}
public void Restore()
{
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)agent == (Object)null) && agent.isOnNavMesh)
{
agent.isStopped = isStopped;
agent.velocity = velocity;
}
}
}
[CompilerGenerated]
private sealed class <CacheWhenPlayerExistsRoutine>d__28 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public HitcontinuePlugin <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <CacheWhenPlayerExistsRoutine>d__28(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Expected O, but got Unknown
int num = <>1__state;
HitcontinuePlugin hitcontinuePlugin = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
break;
case 1:
<>1__state = -1;
break;
}
if (!hitcontinuePlugin.cacheBuilt)
{
hitcontinuePlugin.FindPlayerRoots();
if (hitcontinuePlugin.playerRoots.Count > 0)
{
hitcontinuePlugin.BuildObjectCache();
hitcontinuePlugin.cacheBuilt = true;
hitcontinuePlugin.cacheRoutine = null;
return false;
}
<>2__current = (object)new WaitForSecondsRealtime(1f);
<>1__state = 1;
return true;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <StopRoutine>d__33 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public float length;
public object timeController;
public bool trueStop;
public HitcontinuePlugin <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <StopRoutine>d__33(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Expected O, but got Unknown
int num = <>1__state;
HitcontinuePlugin hitcontinuePlugin = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSecondsRealtime(length);
<>1__state = 1;
return true;
case 1:
{
<>1__state = -1;
float num2 = (float)currentStopField.GetValue(timeController);
if (length < num2)
{
return false;
}
hitstopActive = false;
Time.timeScale = GetNormalTimeScale(timeController);
if (trueStop)
{
CallSetAllPitch(timeController, 1f);
}
currentStopField.SetValue(timeController, 0f);
hitcontinuePlugin.RestoreObjects();
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 const string Guid = "com.clippton.hitcontinue";
private static HitcontinuePlugin instance;
private static readonly Harmony harmony = new Harmony("com.clippton.hitcontinue");
private static ConfigEntry<bool> timeControllerPatchEnabled;
private static ConfigEntry<bool> shotgunHammerPatchEnabled;
private static FieldInfo currentStopField;
private static bool hitstopActive;
private readonly List<Transform> playerRoots = new List<Transform>();
private readonly List<Rigidbody> cachedRigidbodies = new List<Rigidbody>();
private readonly List<Animator> cachedAnimators = new List<Animator>();
private readonly List<NavMeshAgent> cachedAgents = new List<NavMeshAgent>();
private readonly List<RigidbodyState> frozenRigidbodies = new List<RigidbodyState>();
private readonly List<AnimatorState> frozenAnimators = new List<AnimatorState>();
private readonly List<AgentState> frozenAgents = new List<AgentState>();
private Coroutine cacheRoutine;
private bool cacheBuilt;
private bool frozen;
private void Awake()
{
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
//IL_00c9: Expected O, but got Unknown
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00fa: Expected O, but got Unknown
instance = this;
timeControllerPatchEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Hitstop", "TimeControllerPatch", true, "Convert regular TimeController HitStop/TrueStop calls into selective hitstop.");
shotgunHammerPatchEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Hitstop", "ShotgunHammerPatch", true, "Convert ShotgunHammer TrueStop calls into selective hitstop, even if the general TimeController patch is disabled.");
Type type = AccessTools.TypeByName("TimeController");
if (type == null)
{
((BaseUnityPlugin)this).Logger.LogError((object)"Could not find TimeController.");
return;
}
currentStopField = AccessTools.Field(type, "currentStop");
if (currentStopField == null)
{
((BaseUnityPlugin)this).Logger.LogError((object)"Could not find TimeController.currentStop.");
return;
}
harmony.Patch((MethodBase)AccessTools.Method(type, "HitStop", (Type[])null, (Type[])null), new HarmonyMethod(typeof(HitcontinuePlugin), "HitStopPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(type, "TrueStop", (Type[])null, (Type[])null), new HarmonyMethod(typeof(HitcontinuePlugin), "TrueStopPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
PatchGameUpdateMethods();
SceneManager.sceneLoaded += OnSceneLoaded;
StartCacheScan();
((BaseUnityPlugin)this).Logger.LogInfo((object)"Hitcontinue loaded.");
}
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
harmony.UnpatchSelf();
RestoreObjects();
StopCacheScan();
hitstopActive = false;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
RestoreObjects();
ClearCache();
cacheBuilt = false;
hitstopActive = false;
StartCacheScan();
}
private void PatchGameUpdateMethods()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Expected O, but got Unknown
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Expected O, but got Unknown
HarmonyMethod prefix = new HarmonyMethod(typeof(HitcontinuePlugin), "UpdateFreezePrefix", (Type[])null);
HarmonyMethod prefix2 = new HarmonyMethod(typeof(HitcontinuePlugin), "CoroutineFreezePrefix", (Type[])null);
int num = 0;
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
if (assembly.GetName().Name != "Assembly-CSharp")
{
continue;
}
Type[] types;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types;
}
Type[] array = types;
foreach (Type type in array)
{
if (type == null)
{
continue;
}
if (typeof(MonoBehaviour).IsAssignableFrom(type))
{
if (typeof(BaseUnityPlugin).IsAssignableFrom(type))
{
continue;
}
num += PatchUpdateMethod(type, "Update", prefix);
num += PatchUpdateMethod(type, "FixedUpdate", prefix);
num += PatchUpdateMethod(type, "LateUpdate", prefix);
}
num += PatchCoroutineMethods(type, prefix2);
}
}
((BaseUnityPlugin)this).Logger.LogInfo((object)("Hitcontinue patched " + num + " update/coroutine methods."));
}
private int PatchUpdateMethod(Type type, string methodName, HarmonyMethod prefix)
{
MethodInfo methodInfo = AccessTools.DeclaredMethod(type, methodName, (Type[])null, (Type[])null);
if (methodInfo == null)
{
return 0;
}
if (methodInfo.IsStatic || methodInfo.GetParameters().Length != 0 || methodInfo.ReturnType != typeof(void))
{
return 0;
}
try
{
harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
return 1;
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not patch " + type.FullName + "." + methodName + ": " + ex.Message));
return 0;
}
}
private int PatchCoroutineMethods(Type type, HarmonyMethod prefix)
{
if (type == null || !typeof(IEnumerator).IsAssignableFrom(type))
{
return 0;
}
MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "MoveNext", (Type[])null, (Type[])null);
if (methodInfo == null || methodInfo.ReturnType != typeof(bool))
{
return 0;
}
try
{
harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
return 1;
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not patch coroutine " + type.FullName + ".MoveNext: " + ex.Message));
return 0;
}
}
private static bool UpdateFreezePrefix(MonoBehaviour __instance)
{
if (!hitstopActive || (Object)(object)instance == (Object)null || (Object)(object)__instance == (Object)null)
{
return true;
}
return !instance.ShouldFreeze(((Component)__instance).gameObject);
}
private static bool CoroutineFreezePrefix(object __instance)
{
if (!hitstopActive || (Object)(object)instance == (Object)null || __instance == null)
{
return true;
}
if (!TryGetCoroutineOwner(__instance, out var owner))
{
return true;
}
if ((Object)(object)owner == (Object)null)
{
return true;
}
return !instance.ShouldFreeze(((Component)owner).gameObject);
}
private static bool TryGetCoroutineOwner(object coroutine, out MonoBehaviour owner)
{
owner = null;
FieldInfo[] fields = coroutine.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo fieldInfo in fields)
{
if (typeof(MonoBehaviour).IsAssignableFrom(fieldInfo.FieldType))
{
object? value = fieldInfo.GetValue(coroutine);
owner = (MonoBehaviour)((value is MonoBehaviour) ? value : null);
if ((Object)(object)owner != (Object)null)
{
return true;
}
}
}
return false;
}
private void StartCacheScan()
{
StopCacheScan();
cacheRoutine = ((MonoBehaviour)this).StartCoroutine(CacheWhenPlayerExistsRoutine());
}
private void StopCacheScan()
{
if (cacheRoutine != null)
{
((MonoBehaviour)this).StopCoroutine(cacheRoutine);
cacheRoutine = null;
}
}
[IteratorStateMachine(typeof(<CacheWhenPlayerExistsRoutine>d__28))]
private IEnumerator CacheWhenPlayerExistsRoutine()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <CacheWhenPlayerExistsRoutine>d__28(0)
{
<>4__this = this
};
}
private static bool HitStopPrefix(object __instance, float length)
{
if ((Object)(object)instance == (Object)null || !timeControllerPatchEnabled.Value)
{
return true;
}
instance.DoStop(__instance, length, trueStop: false);
return false;
}
private static bool TrueStopPrefix(object __instance, float length)
{
if ((Object)(object)instance == (Object)null)
{
return true;
}
if (!timeControllerPatchEnabled.Value && (!shotgunHammerPatchEnabled.Value || !IsShotgunHammerTrueStopCall()))
{
return true;
}
instance.DoStop(__instance, length, trueStop: true);
return false;
}
private static bool IsShotgunHammerTrueStopCall()
{
StackTrace stackTrace = new StackTrace();
for (int i = 0; i < stackTrace.FrameCount; i++)
{
MethodBase method = stackTrace.GetFrame(i).GetMethod();
if (!(method == null) && !(method.DeclaringType == null))
{
Type declaringType = method.DeclaringType;
if (declaringType.Name == "ShotgunHammer")
{
return true;
}
if (declaringType.FullName != null && declaringType.FullName.Contains("ShotgunHammer"))
{
return true;
}
}
}
return false;
}
private void DoStop(object timeController, float length, bool trueStop)
{
float num = (float)currentStopField.GetValue(timeController);
if (length <= num)
{
return;
}
currentStopField.SetValue(timeController, length);
if (!cacheBuilt)
{
FindPlayerRoots();
if (playerRoots.Count <= 0)
{
Time.timeScale = GetNormalTimeScale(timeController);
return;
}
BuildObjectCache();
cacheBuilt = true;
}
if (!frozen)
{
FreezeObjects();
}
hitstopActive = true;
Time.timeScale = GetNormalTimeScale(timeController);
if (trueStop)
{
CallSetAllPitch(timeController, 0f);
}
((MonoBehaviour)this).StartCoroutine(StopRoutine(timeController, length, trueStop));
}
[IteratorStateMachine(typeof(<StopRoutine>d__33))]
private IEnumerator StopRoutine(object timeController, float length, bool trueStop)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <StopRoutine>d__33(0)
{
<>4__this = this,
timeController = timeController,
length = length,
trueStop = trueStop
};
}
private void FindPlayerRoots()
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
playerRoots.Clear();
GameObject[] array = GameObject.FindGameObjectsWithTag("Player");
foreach (GameObject val in array)
{
if (!((Object)(object)val != (Object)null))
{
continue;
}
Scene scene = val.scene;
if (((Scene)(ref scene)).IsValid())
{
scene = val.scene;
if (((Scene)(ref scene)).isLoaded)
{
playerRoots.Add(val.transform);
}
}
}
}
private void BuildObjectCache()
{
cachedRigidbodies.Clear();
cachedAnimators.Clear();
cachedAgents.Clear();
Rigidbody[] array = Object.FindObjectsOfType<Rigidbody>();
foreach (Rigidbody val in array)
{
if ((Object)(object)val != (Object)null && ShouldFreeze(((Component)val).gameObject))
{
cachedRigidbodies.Add(val);
}
}
Animator[] array2 = Object.FindObjectsOfType<Animator>();
foreach (Animator val2 in array2)
{
if ((Object)(object)val2 != (Object)null && ShouldFreeze(((Component)val2).gameObject))
{
cachedAnimators.Add(val2);
}
}
NavMeshAgent[] array3 = Object.FindObjectsOfType<NavMeshAgent>();
foreach (NavMeshAgent val3 in array3)
{
if ((Object)(object)val3 != (Object)null && ShouldFreeze(((Component)val3).gameObject))
{
cachedAgents.Add(val3);
}
}
((BaseUnityPlugin)this).Logger.LogInfo((object)("Hitcontinue cache built: " + playerRoots.Count + " player roots, " + cachedRigidbodies.Count + " rigidbodies, " + cachedAnimators.Count + " animators, " + cachedAgents.Count + " nav agents."));
}
private void ClearCache()
{
playerRoots.Clear();
cachedRigidbodies.Clear();
cachedAnimators.Clear();
cachedAgents.Clear();
}
private void FreezeObjects()
{
//IL_0038: 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)
//IL_0108: Unknown result type (might be due to invalid IL or missing references)
frozen = true;
foreach (Rigidbody cachedRigidbody in cachedRigidbodies)
{
if (!((Object)(object)cachedRigidbody == (Object)null))
{
frozenRigidbodies.Add(new RigidbodyState(cachedRigidbody));
cachedRigidbody.velocity = Vector3.zero;
cachedRigidbody.angularVelocity = Vector3.zero;
cachedRigidbody.isKinematic = true;
}
}
foreach (Animator cachedAnimator in cachedAnimators)
{
if (!((Object)(object)cachedAnimator == (Object)null))
{
frozenAnimators.Add(new AnimatorState(cachedAnimator));
cachedAnimator.speed = 0f;
}
}
foreach (NavMeshAgent cachedAgent in cachedAgents)
{
if (!((Object)(object)cachedAgent == (Object)null))
{
frozenAgents.Add(new AgentState(cachedAgent));
if (cachedAgent.isOnNavMesh)
{
cachedAgent.isStopped = true;
cachedAgent.velocity = Vector3.zero;
}
}
}
}
private void RestoreObjects()
{
foreach (RigidbodyState frozenRigidbody in frozenRigidbodies)
{
frozenRigidbody.Restore();
}
foreach (AnimatorState frozenAnimator in frozenAnimators)
{
frozenAnimator.Restore();
}
foreach (AgentState frozenAgent in frozenAgents)
{
frozenAgent.Restore();
}
frozenRigidbodies.Clear();
frozenAnimators.Clear();
frozenAgents.Clear();
frozen = false;
}
private bool ShouldFreeze(GameObject obj)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)obj != (Object)null)
{
Scene scene = obj.scene;
if (((Scene)(ref scene)).IsValid())
{
scene = obj.scene;
if (((Scene)(ref scene)).isLoaded)
{
return !IsPlayerOrChild(obj);
}
}
}
return false;
}
private bool IsPlayerOrChild(GameObject obj)
{
Transform val = obj.transform;
while ((Object)(object)val != (Object)null)
{
foreach (Transform playerRoot in playerRoots)
{
if ((Object)(object)playerRoot != (Object)null && (Object)(object)val == (Object)(object)playerRoot)
{
return true;
}
}
val = val.parent;
}
return false;
}
private static float GetNormalTimeScale(object timeController)
{
float num = (float)AccessTools.Field(timeController.GetType(), "timeScale").GetValue(timeController);
float num2 = (float)AccessTools.Field(timeController.GetType(), "timeScaleModifier").GetValue(timeController);
return num * num2;
}
private static void CallSetAllPitch(object timeController, float pitch)
{
MethodInfo methodInfo = AccessTools.Method(timeController.GetType(), "SetAllPitch", (Type[])null, (Type[])null);
if (methodInfo != null)
{
methodInfo.Invoke(timeController, new object[1] { pitch });
}
}
}
}