using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using GlobalSettings;
using HarmonyLib;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("try")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("try")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e83d6efb-7c27-4742-a9b2-b3dd8d0888d5")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[BepInPlugin("com.yourname.crestkeydirs5", "Crest Key + Directions V5", "1.6.7")]
public class CrestKeyDirectionsV5 : BaseUnityPlugin
{
private static class ReflectionCache
{
public static FieldInfo fi_cState;
public static FieldInfo fi_attacking;
public static FieldInfo fi_upAttacking;
public static FieldInfo fi_downAttacking;
public static FieldInfo fi_nailCharging;
public static FieldInfo fi_dashing;
public static FieldInfo fi_backDashing;
public static FieldInfo fi_isDashStabBouncing;
public static FieldInfo fi_cState_canControl;
public static FieldInfo fi_hero_acceptingInput;
public static FieldInfo fi_hero_controlRelinquished;
public static MethodInfo mi_AddInvincibleTime;
public static FieldInfo fi_invincibleTimer;
public static MethodInfo mi_ResetAttacksDash;
public static MethodInfo mi_ResetAttacks;
public static void Initialize(HeroController heroInstance)
{
if (!((Object)(object)heroInstance == (Object)null))
{
Type typeFromHandle = typeof(HeroController);
fi_cState = AccessTools.Field(typeFromHandle, "cState");
Type type = fi_cState?.FieldType;
if (type != null)
{
fi_attacking = AccessTools.Field(type, "attacking");
fi_upAttacking = AccessTools.Field(type, "upAttacking");
fi_downAttacking = AccessTools.Field(type, "downAttacking");
fi_nailCharging = AccessTools.Field(type, "nailCharging");
fi_dashing = AccessTools.Field(type, "dashing");
fi_backDashing = AccessTools.Field(type, "backDashing");
fi_cState_canControl = AccessTools.Field(type, "canControl");
}
fi_isDashStabBouncing = AccessTools.Field(typeFromHandle, "isDashStabBouncing");
fi_hero_acceptingInput = AccessTools.Field(typeFromHandle, "acceptingInput");
fi_hero_controlRelinquished = AccessTools.Field(typeFromHandle, "controlRelinquished");
mi_AddInvincibleTime = typeFromHandle.GetMethod("AddInvincibleTime", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
fi_invincibleTimer = typeFromHandle.GetField("invincibleTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
mi_ResetAttacksDash = AccessTools.Method(typeFromHandle, "ResetAttacksDash", new Type[0], (Type[])null);
mi_ResetAttacks = AccessTools.Method(typeFromHandle, "ResetAttacks", new Type[1] { typeof(bool) }, (Type[])null);
}
}
}
private static class KeyboardBindingCache
{
public static KeyCode Taunt { get; private set; } = (KeyCode)118;
public static KeyCode Up { get; private set; } = (KeyCode)273;
public static KeyCode Down { get; private set; } = (KeyCode)274;
public static KeyCode Left { get; private set; } = (KeyCode)276;
public static KeyCode Right { get; private set; } = (KeyCode)275;
public static void Initialize()
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
Taunt = ParseKeyCode(ReadKeyString("KeyTaunt", "V"), (KeyCode)118);
Up = ParseKeyCode(ReadKeyString("KeyUp", "UpArrow"), (KeyCode)273);
Down = ParseKeyCode(ReadKeyString("KeyDown", "DownArrow"), (KeyCode)274);
Left = ParseKeyCode(ReadKeyString("KeyLeft", "LeftArrow"), (KeyCode)276);
Right = ParseKeyCode(ReadKeyString("KeyRight", "RightArrow"), (KeyCode)275);
}
private static string ReadKeyString(string prefKey, string fallback)
{
try
{
Type type = AccessTools.TypeByName("Platform");
if (type != null)
{
object obj = AccessTools.Property(type, "Current")?.GetValue(null) ?? AccessTools.Field(type, "Current")?.GetValue(null) ?? AccessTools.Field(type, "current")?.GetValue(null);
if (obj != null)
{
object obj2 = AccessTools.Property(obj.GetType(), "LocalSharedData")?.GetValue(obj, null) ?? AccessTools.Field(obj.GetType(), "LocalSharedData")?.GetValue(obj) ?? AccessTools.Field(obj.GetType(), "localSharedData")?.GetValue(obj);
if (obj2 != null)
{
Type type2 = obj2.GetType();
MethodInfo method = type2.GetMethod("GetString", new Type[2]
{
typeof(string),
typeof(string)
});
if (method != null)
{
return (string)method.Invoke(obj2, new object[2] { prefKey, fallback });
}
}
}
}
}
catch
{
}
return PlayerPrefs.GetString(prefKey, fallback);
}
private static KeyCode ParseKeyCode(string s, KeyCode fallback)
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0170: Unknown result type (might be due to invalid IL or missing references)
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
//IL_0119: Unknown result type (might be due to invalid IL or missing references)
//IL_0156: Unknown result type (might be due to invalid IL or missing references)
//IL_016c: Unknown result type (might be due to invalid IL or missing references)
//IL_016d: Unknown result type (might be due to invalid IL or missing references)
//IL_0168: Unknown result type (might be due to invalid IL or missing references)
//IL_0169: Unknown result type (might be due to invalid IL or missing references)
if (string.IsNullOrEmpty(s))
{
return fallback;
}
s = s.Trim();
switch (s.ToLowerInvariant())
{
case "up":
return (KeyCode)273;
case "down":
return (KeyCode)274;
case "left":
return (KeyCode)276;
case "right":
return (KeyCode)275;
case "esc":
return (KeyCode)27;
case "enter":
return (KeyCode)13;
default:
{
if (s.Length == 1 && char.IsLetter(s[0]) && Enum.TryParse<KeyCode>(s.ToUpperInvariant(), ignoreCase: true, out KeyCode result))
{
return result;
}
if (s.Length == 1 && char.IsDigit(s[0]))
{
return (KeyCode)(48 + (s[0] - 48));
}
if (s.Length == 2 && (s[0] | 0x20) == 100 && char.IsDigit(s[1]))
{
return (KeyCode)(48 + (s[1] - 48));
}
if (Enum.TryParse<KeyCode>(s, ignoreCase: true, out KeyCode result2))
{
return result2;
}
return fallback;
}
}
}
}
public static ManualLogSource Log;
private const float LONG_PRESS_THRESHOLD = 0.2f;
private const float SPECIAL_PRESS_THRESHOLD = 3f;
private const float COOLDOWN_SECONDS = 0.3f;
private const float IFRAME_SECONDS = 0.1f;
public const string QUEEN_CREST_ID = "Yen";
private static float nextSwapTime = 0f;
private static float mainKeyDownTime = -1f;
private static int lastChosenDirMask = 0;
private static bool swappedThisPress = false;
private static bool specialTriggeredThisPress = false;
private static int lastTriggeredDirMask = 0;
private static bool lastTriggeredLong = false;
private static HeroController hero;
private static bool hasQueuedSwap = false;
private static string queuedTargetName = null;
private static string queuedReason = null;
private static int queuedEarliestFrame = -1;
private static bool queuedFromCombat = false;
private static bool isApplyingSwap = false;
private void Awake()
{
Log = ((BaseUnityPlugin)this).Logger;
Harmony.CreateAndPatchAll(typeof(CrestKeyDirectionsV5), (string)null);
Harmony.CreateAndPatchAll(typeof(BenchReplenishPatch), (string)null);
KeyboardBindingCache.Initialize();
Log.LogInfo((object)"CrestKeyDirectionsV5 loaded (Optimized). Target Queen ID: Yen");
}
private void Update()
{
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)hero == (Object)null)
{
hero = HeroController.instance;
}
if ((Object)(object)hero == (Object)null || hero.playerData == null)
{
return;
}
TryConsumeQueuedSwap();
KeyCode taunt = KeyboardBindingCache.Taunt;
if (Input.GetKeyDown(taunt))
{
mainKeyDownTime = Time.time;
ResetPressState();
}
if (Input.GetKeyUp(taunt))
{
mainKeyDownTime = -1f;
ResetPressState();
}
else
{
if (!Input.GetKey(taunt))
{
return;
}
if (mainKeyDownTime < 0f)
{
mainKeyDownTime = Time.time;
}
int dirHeldMask = GetDirHeldMask();
int dirDownMask = GetDirDownMask();
int num = ChooseDirection(dirHeldMask, dirDownMask);
float num2 = Time.time - mainKeyDownTime;
if (num == 0 && !specialTriggeredThisPress && num2 >= 3f)
{
HandleSpecial3SecSwap();
specialTriggeredThisPress = true;
nextSwapTime = Time.time + 0.3f;
}
else if (num != 0 && !(Time.time < nextSwapTime))
{
bool flag = num2 >= 0.2f;
bool flag2 = num != lastTriggeredDirMask;
bool flag3 = !flag2 && flag != lastTriggeredLong;
if (flag2 || (!swappedThisPress && flag3))
{
PerformDirectionalSwap(num, flag);
}
}
}
}
private void ResetPressState()
{
lastChosenDirMask = 0;
lastTriggeredDirMask = 0;
lastTriggeredLong = false;
swappedThisPress = false;
specialTriggeredThisPress = false;
}
private void HandleSpecial3SecSwap()
{
string currentCrestID = hero.playerData.CurrentCrestID;
string name = Gameplay.CursedCrest.name;
string text = "Cloakless Crest";
if ((Object)(object)Gameplay.CloaklessCrest != (Object)null)
{
text = Gameplay.CloaklessCrest.name;
}
string targetName = ((currentCrestID == name) ? text : name);
RequestSwap(targetName, "SPECIAL_3S");
}
private void PerformDirectionalSwap(int dirMask, bool isLong)
{
string currentCrestID = hero.playerData.CurrentCrestID;
string text = ResolveDirectionTarget(currentCrestID, dirMask, isLong);
if (!string.IsNullOrEmpty(text) && !(text == currentCrestID))
{
RequestSwap(text, isLong ? "LONG" : "SHORT");
lastTriggeredDirMask = dirMask;
lastTriggeredLong = isLong;
swappedThisPress = true;
nextSwapTime = Time.time + 0.3f;
}
}
private static void RequestSwap(string targetName, string reason)
{
if (!string.IsNullOrEmpty(targetName) && !((Object)(object)hero == (Object)null) && hero.playerData != null)
{
if (IsSafeToSwapNow())
{
ApplySwapNow(targetName, reason);
return;
}
hasQueuedSwap = true;
queuedTargetName = targetName;
queuedReason = reason;
queuedEarliestFrame = Time.frameCount + 1;
queuedFromCombat = queuedFromCombat || IsCombatLikeActive();
}
}
private static void TryConsumeQueuedSwap()
{
if (!hasQueuedSwap || (Object)(object)hero == (Object)null || hero.playerData == null || Time.frameCount < queuedEarliestFrame || !IsSafeToSwapNow())
{
return;
}
string currentCrestID = hero.playerData.CurrentCrestID;
if (string.IsNullOrEmpty(queuedTargetName) || queuedTargetName == currentCrestID)
{
hasQueuedSwap = false;
queuedFromCombat = false;
return;
}
if (queuedFromCombat)
{
ForceClearCombatStateLight();
}
ApplySwapNow(queuedTargetName, (queuedReason ?? "QUEUED") + (queuedFromCombat ? "_LIGHTCLR" : "_FAST"));
hasQueuedSwap = false;
queuedFromCombat = false;
}
private static bool IsSafeToSwapNow()
{
try
{
if (ReflectionCache.fi_cState == null && (Object)(object)hero != (Object)null)
{
ReflectionCache.Initialize(hero);
}
object obj = ReflectionCache.fi_cState?.GetValue(hero);
if (obj != null)
{
bool flag = GetCachedBool(obj, ReflectionCache.fi_attacking) || GetCachedBool(obj, ReflectionCache.fi_upAttacking) || GetCachedBool(obj, ReflectionCache.fi_downAttacking);
bool cachedBool = GetCachedBool(obj, ReflectionCache.fi_nailCharging);
bool flag2 = GetCachedBool(obj, ReflectionCache.fi_dashing) || GetCachedBool(obj, ReflectionCache.fi_backDashing);
if (flag || cachedBool || flag2)
{
return false;
}
}
if (GetCachedBool(hero, ReflectionCache.fi_isDashStabBouncing))
{
return false;
}
bool flag3 = true;
if (obj != null && ReflectionCache.fi_cState_canControl != null)
{
flag3 &= GetCachedBool(obj, ReflectionCache.fi_cState_canControl);
}
if (ReflectionCache.fi_hero_acceptingInput != null)
{
flag3 &= (bool)ReflectionCache.fi_hero_acceptingInput.GetValue(hero);
}
if (ReflectionCache.fi_hero_controlRelinquished != null)
{
flag3 &= !(bool)ReflectionCache.fi_hero_controlRelinquished.GetValue(hero);
}
return flag3;
}
catch
{
return false;
}
}
private static bool IsCombatLikeActive()
{
try
{
if (ReflectionCache.fi_cState == null && (Object)(object)hero != (Object)null)
{
ReflectionCache.Initialize(hero);
}
object obj = ReflectionCache.fi_cState?.GetValue(hero);
if (obj != null)
{
bool flag = GetCachedBool(obj, ReflectionCache.fi_attacking) || GetCachedBool(obj, ReflectionCache.fi_upAttacking) || GetCachedBool(obj, ReflectionCache.fi_downAttacking);
bool cachedBool = GetCachedBool(obj, ReflectionCache.fi_nailCharging);
bool flag2 = GetCachedBool(obj, ReflectionCache.fi_dashing) || GetCachedBool(obj, ReflectionCache.fi_backDashing);
if (flag || cachedBool || flag2)
{
return true;
}
}
if (GetCachedBool(hero, ReflectionCache.fi_isDashStabBouncing))
{
return true;
}
}
catch
{
}
return false;
}
private static bool GetCachedBool(object obj, FieldInfo fi)
{
if (obj == null || fi == null)
{
return false;
}
return (bool)fi.GetValue(obj);
}
private static void ForceClearCombatStateLight()
{
try
{
ReflectionCache.mi_ResetAttacksDash?.Invoke(hero, null);
}
catch
{
}
try
{
if (ReflectionCache.mi_ResetAttacks != null)
{
ReflectionCache.mi_ResetAttacks.Invoke(hero, new object[1] { true });
}
else
{
ReflectionCache.mi_ResetAttacksDash?.Invoke(hero, null);
}
}
catch
{
}
queuedEarliestFrame = Mathf.Max(queuedEarliestFrame, Time.frameCount + 1);
}
private static void ApplySwapNow(string targetName, string reason)
{
if (isApplyingSwap)
{
return;
}
isApplyingSwap = true;
try
{
ToolCrest crestByName = ToolItemManager.GetCrestByName(targetName);
if (!((Object)(object)crestByName == (Object)null))
{
int silk = hero.playerData.silk;
ApplyInvincibilitySafe(0.1f);
hero.ResetAllCrestState();
ToolItemManager.SetEquippedCrest(crestByName.name);
ToolItemManager.SendEquippedChangedEvent(true);
hero.playerData.silk = silk;
ManualLogSource log = Log;
if (log != null)
{
log.LogInfo((object)("Swapped -> " + crestByName.name + " [" + reason + "]"));
}
}
}
finally
{
isApplyingSwap = false;
}
}
private static string ResolveDirectionTarget(string current, int dir, bool isLong)
{
string name = Gameplay.ToolmasterCrest.name;
string secondary = "Yen";
string name2 = Gameplay.SpellCrest.name;
string bestHunterName = GetBestHunterName();
string name3 = Gameplay.ReaperCrest.name;
string name4 = Gameplay.WarriorCrest.name;
string name5 = Gameplay.WandererCrest.name;
string name6 = Gameplay.WitchCrest.name;
return dir switch
{
1 => ToggleLogic(current, isLong, name, secondary),
2 => ToggleLogic(current, isLong, name2, bestHunterName),
4 => ToggleLogic(current, isLong, name3, name4),
8 => ToggleLogic(current, isLong, name5, name6),
_ => null,
};
}
private static string ToggleLogic(string current, bool isLong, string primary, string secondary)
{
if (isLong)
{
return (current == secondary) ? primary : secondary;
}
return (current == primary) ? secondary : primary;
}
private static int GetDirHeldMask()
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
int num = 0;
if (Input.GetKey(KeyboardBindingCache.Up))
{
num |= 1;
}
if (Input.GetKey(KeyboardBindingCache.Down))
{
num |= 2;
}
if (Input.GetKey(KeyboardBindingCache.Left))
{
num |= 4;
}
if (Input.GetKey(KeyboardBindingCache.Right))
{
num |= 8;
}
return num;
}
private static int GetDirDownMask()
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
int num = 0;
if (Input.GetKeyDown(KeyboardBindingCache.Up))
{
num |= 1;
}
if (Input.GetKeyDown(KeyboardBindingCache.Down))
{
num |= 2;
}
if (Input.GetKeyDown(KeyboardBindingCache.Left))
{
num |= 4;
}
if (Input.GetKeyDown(KeyboardBindingCache.Right))
{
num |= 8;
}
return num;
}
private static int ChooseDirection(int heldMask, int downMask)
{
if (BitCount(downMask) == 1)
{
lastChosenDirMask = downMask;
return downMask;
}
if (BitCount(heldMask) == 1)
{
lastChosenDirMask = heldMask;
return heldMask;
}
if (heldMask != 0 && lastChosenDirMask != 0 && (heldMask & lastChosenDirMask) != 0)
{
return lastChosenDirMask;
}
return 0;
}
private static int BitCount(int x)
{
int num = 0;
while (x != 0)
{
x &= x - 1;
num++;
}
return num;
}
private static string GetBestHunterName()
{
try
{
if ((Object)(object)Gameplay.HunterCrest3 != (Object)null && Gameplay.HunterCrest3.IsUnlocked)
{
return Gameplay.HunterCrest3.name;
}
}
catch
{
}
try
{
if ((Object)(object)Gameplay.HunterCrest2 != (Object)null && Gameplay.HunterCrest2.IsUnlocked)
{
return Gameplay.HunterCrest2.name;
}
}
catch
{
}
return Gameplay.HunterCrest.name;
}
private static void ApplyInvincibilitySafe(float seconds)
{
if (seconds <= 0f || (Object)(object)hero == (Object)null)
{
return;
}
try
{
if (ReflectionCache.mi_AddInvincibleTime == null)
{
ReflectionCache.Initialize(hero);
}
if (ReflectionCache.mi_AddInvincibleTime != null)
{
ReflectionCache.mi_AddInvincibleTime.Invoke(hero, new object[1] { seconds });
}
else if (ReflectionCache.fi_invincibleTimer != null)
{
float num = (float)ReflectionCache.fi_invincibleTimer.GetValue(hero);
ReflectionCache.fi_invincibleTimer.SetValue(hero, Mathf.Max(num, seconds));
}
}
catch
{
}
}
[HarmonyPatch(typeof(HeroController), "Awake")]
[HarmonyPostfix]
private static void HeroAwakePostfix(HeroController __instance)
{
hero = __instance;
ReflectionCache.Initialize(hero);
}
}
[HarmonyPatch(typeof(ToolItemManager), "TryReplenishTools")]
public static class BenchReplenishPatch
{
private static bool isLoopingRefills;
public static bool Prefix(ref bool doReplenish, ReplenishMethod method)
{
//IL_017c: Unknown result type (might be due to invalid IL or missing references)
if (isLoopingRefills)
{
return true;
}
if (((object)(ReplenishMethod)(ref method)).ToString().IndexOf("Bench", StringComparison.OrdinalIgnoreCase) < 0)
{
return true;
}
doReplenish = true;
try
{
isLoopingRefills = true;
HeroController instance = HeroController.instance;
if ((Object)(object)instance == (Object)null || instance.playerData == null)
{
return true;
}
string currentCrestID = instance.playerData.CurrentCrestID;
List<string> list = new List<string>
{
Gameplay.ToolmasterCrest.name,
"Yen",
Gameplay.SpellCrest.name,
Gameplay.HunterCrest.name,
Gameplay.ReaperCrest.name,
Gameplay.WarriorCrest.name,
Gameplay.WandererCrest.name,
Gameplay.WitchCrest.name
};
foreach (string item in list)
{
ToolCrest crestByName = ToolItemManager.GetCrestByName(item);
if (!((Object)(object)crestByName == (Object)null) && crestByName.IsUnlocked && !crestByName.IsHidden && !(crestByName.name == currentCrestID))
{
instance.ResetAllCrestState();
ToolItemManager.SetEquippedCrest(crestByName.name);
ToolItemManager.TryReplenishTools(true, method);
}
}
instance.ResetAllCrestState();
ToolItemManager.SetEquippedCrest(currentCrestID);
ToolItemManager.SendEquippedChangedEvent(true);
return true;
}
finally
{
isLoopingRefills = false;
}
}
}