Decompiled source of SlopeWalker v1.0.0

SlopeWalker.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 SlopeWalker
{
	[BepInPlugin("LJS.SlopeWalker", "SlopeWalker", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "LJS.SlopeWalker";

		public const string NAME = "SlopeWalker";

		public const string VERSION = "1.0.0";

		public static ManualLogSource Log;

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> MaxWalkableSlope;

		public static ConfigEntry<float> SlowdownAtMax;

		public static ConfigEntry<float> StaminaDrainMult;

		public static ConfigEntry<bool> GrantRunXP;

		public static ConfigEntry<bool> TerrainOnly;

		public static ConfigEntry<bool> DebugLog;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: 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
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "모드 전체 켜기/끄기.");
			MaxWalkableSlope = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MaxWalkableSlope", 55f, new ConfigDescription("걸어 오를 수 있는 최대 경사각(도). 바닐라 걷기 미끄러짐 한계각(38°) 이하로\n설정하면 38°로 취급되어 효과가 없습니다.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(38f, 80f), Array.Empty<object>()));
			SlowdownAtMax = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SlowdownAtMax", 0.5f, new ConfigDescription("MaxWalkableSlope 각도 직전(가장 가파른 지점)에서의 이동속도 배율.\n1.0에 가까울수록 감속이 적고, 낮을수록 가파른 경사에서 많이 느려집니다.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>()));
			StaminaDrainMult = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StaminaDrainMult", 0.3f, new ConfigDescription("가파른 구간을 오르막으로 걸을 때의 스태미나 소모량.\n스킬·버프가 반영된 '달리기 스태미나 소모율'에 이 값을 곱해서 사용합니다.\n0으로 설정하면 스태미나를 소모하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			GrantRunXP = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "GrantRunXP", false, "true로 설정하면 가파른 구간을 오르막으로 걷는 동안에도\n바닐라 달리기와 같은 방식으로 달리기 스킬 경험치를 획득합니다.");
			TerrainOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "TerrainOnly", false, "true로 설정하면 지형(Heightmap) 경사에서만 효과가 적용되고,\n건축물(지붕 등)의 경사에서는 바닐라 그대로 동작합니다.");
			DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, "켜면 현재 경사각·적용 상태·스태미나 소모량을 로그로 출력합니다.");
			_harmony = new Harmony("LJS.SlopeWalker");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[SlopeWalker] 1.0.0 로드 완료.");
		}
	}
	internal sealed class SlopeWalkerState
	{
		public bool Active = true;

		public float RunXpTimer;
	}
	internal static class SlopeWalkerStates
	{
		private static readonly ConditionalWeakTable<Character, SlopeWalkerState> Table = new ConditionalWeakTable<Character, SlopeWalkerState>();

		public static SlopeWalkerState Get(Character character)
		{
			return Table.GetOrCreateValue(character);
		}
	}
	internal static class VanillaAccess
	{
		public static readonly FieldRef<Character, Vector3> LastGroundNormal = BuildFieldRef<Character, Vector3>("m_lastGroundNormal");

		public static readonly FieldRef<Character, float> Slippage = BuildFieldRef<Character, float>("m_slippage");

		public static readonly FieldRef<Character, bool> Sliding = BuildFieldRef<Character, bool>("m_sliding");

		public static readonly FieldRef<Character, Collider> LastGroundCollider = BuildFieldRef<Character, Collider>("m_lastGroundCollider");

		public static readonly FieldRef<Player, float> RunStaminaDrain = BuildFieldRef<Player, float>("m_runStaminaDrain");

		public static readonly Func<Character, float> GetSlideAngle = BuildMethodDelegate<Func<Character, float>>(typeof(Character), "GetSlideAngle", Type.EmptyTypes);

		private static readonly FieldInfo MoveStaminaRateField = BuildStaticField(typeof(Game), "m_moveStaminaRate");

		public static float GetMoveStaminaRate()
		{
			if (MoveStaminaRateField == null)
			{
				return 1f;
			}
			try
			{
				return (float)MoveStaminaRateField.GetValue(null);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SlopeWalker] Game.m_moveStaminaRate 읽기 실패, 기본값(1) 사용: " + ex.Message));
				return 1f;
			}
		}

		private static FieldRef<T, F> BuildFieldRef<T, F>(string fieldName)
		{
			try
			{
				return AccessTools.FieldRefAccess<T, F>(fieldName);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("[SlopeWalker] 필드 접근 델리게이트 생성 실패: " + typeof(T).Name + "." + fieldName + " — " + ex.Message));
				}
				return null;
			}
		}

		private static TDelegate BuildMethodDelegate<TDelegate>(Type type, string methodName, Type[] parameterTypes) where TDelegate : Delegate
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, methodName, parameterTypes, (Type[])null);
				if (methodInfo == null)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogError((object)("[SlopeWalker] 메서드를 찾지 못함: " + type.Name + "." + methodName + " — 모드 효과가 비활성화된 채로 동작합니다."));
					}
					return null;
				}
				return AccessTools.MethodDelegate<TDelegate>(methodInfo, (object)null, true);
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogError((object)("[SlopeWalker] 메서드 델리게이트 생성 실패: " + type.Name + "." + methodName + " — " + ex.Message + " — 모드 효과가 비활성화된 채로 동작합니다."));
				}
				return null;
			}
		}

		private static FieldInfo BuildStaticField(Type type, string fieldName)
		{
			try
			{
				return AccessTools.Field(type, fieldName);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("[SlopeWalker] 정적 필드 조회 실패: " + type.Name + "." + fieldName + " — " + ex.Message));
				}
				return null;
			}
		}
	}
}
namespace SlopeWalker.Patches
{
	[HarmonyPatch(typeof(Character), "ApplySlide")]
	internal static class ApplySlidePatch
	{
		private const float UphillDotThreshold = 0.3f;

		private const float StaminaRecoverThreshold = 10f;

		private static readonly HashSet<string> LoggedExceptionKeys = new HashSet<string>();

		private static bool Prefix(Character __instance, float dt, ref Vector3 currentVel, Vector3 bodyVel, bool running)
		{
			try
			{
				return Run(__instance, dt, ref currentVel, running);
			}
			catch (Exception ex)
			{
				string item = ex.GetType().FullName + ":" + ex.Message;
				if (LoggedExceptionKeys.Add(item))
				{
					Plugin.Log.LogError((object)$"[SlopeWalker] ApplySlide 처리 중 예외, 이번 프레임부터 바닐라 동작으로 폴백(동일 예외는 이후 로그 생략): {ex}");
				}
				else if (Plugin.DebugLog.Value)
				{
					Plugin.Log.LogWarning((object)("[SlopeWalker] (반복 억제됨) ApplySlide 예외: " + ex.Message));
				}
				return true;
			}
		}

		private static bool Run(Character character, float dt, ref Vector3 currentVel, bool running)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Enabled.Value)
			{
				return true;
			}
			if (running)
			{
				return true;
			}
			if (!character.IsPlayer())
			{
				return true;
			}
			Player val = (Player)(object)((character is Player) ? character : null);
			if (val == null)
			{
				return true;
			}
			if (!character.IsOnGround())
			{
				return true;
			}
			FieldRef<Character, Vector3> lastGroundNormal = VanillaAccess.LastGroundNormal;
			if (lastGroundNormal == null)
			{
				return true;
			}
			Vector3 val2 = lastGroundNormal.Invoke(character);
			Func<Character, float> getSlideAngle = VanillaAccess.GetSlideAngle;
			if (getSlideAngle == null)
			{
				return true;
			}
			float num = TiltAngleDegrees(val2);
			float num2 = getSlideAngle(character);
			float num3 = Mathf.Max(Plugin.MaxWalkableSlope.Value, num2);
			SlopeWalkerState slopeWalkerState = SlopeWalkerStates.Get(character);
			if (num <= num2)
			{
				slopeWalkerState.Active = true;
				return true;
			}
			if (num > num3)
			{
				return true;
			}
			if (Plugin.TerrainOnly.Value && !IsTerrainGround(character))
			{
				return true;
			}
			float stamina = val.GetStamina();
			if (slopeWalkerState.Active && stamina <= 0f)
			{
				slopeWalkerState.Active = false;
			}
			else if (!slopeWalkerState.Active && stamina >= 10f)
			{
				slopeWalkerState.Active = true;
			}
			if (!slopeWalkerState.Active)
			{
				return true;
			}
			Vector3 val3 = val2;
			val3.y = 0f;
			val3 = ((((Vector3)(ref val3)).sqrMagnitude > 0.0001f) ? ((Vector3)(ref val3)).normalized : Vector3.forward);
			Vector3 val4 = -val3;
			Vector3 val5 = default(Vector3);
			((Vector3)(ref val5))..ctor(currentVel.x, 0f, currentVel.z);
			if (((Vector3)(ref val5)).sqrMagnitude > 0.01f && Vector3.Dot(((Vector3)(ref val5)).normalized, val4) > 0.3f)
			{
				float num4 = Mathf.InverseLerp(num2, num3, num);
				float num5 = Mathf.Lerp(1f, Plugin.SlowdownAtMax.Value, num4);
				val5 *= num5;
				currentVel = new Vector3(val5.x, currentVel.y, val5.z);
				float num6 = ComputeFinalRunStaminaDrainRate(val, val4);
				float num7 = dt * num6 * Plugin.StaminaDrainMult.Value * VanillaAccess.GetMoveStaminaRate();
				if (num7 > 0f)
				{
					((Character)val).UseStamina(num7);
				}
				if (Plugin.GrantRunXP.Value)
				{
					slopeWalkerState.RunXpTimer += dt;
					if (slopeWalkerState.RunXpTimer > 1f)
					{
						slopeWalkerState.RunXpTimer = 0f;
						((Character)val).RaiseSkill((SkillType)102, 1f);
					}
				}
				if (Plugin.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)$"[SlopeWalker] 경사 {num:0.0}도, 속도배율 {num5:0.00}, 스태미나소모 {num7:0.00}, 스태미나 {stamina:0.0}");
				}
			}
			else if (Plugin.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)$"[SlopeWalker] 경사 {num:0.0}도, 미끄러짐만 방지(오르막 이동 아님), 스태미나 {stamina:0.0}");
			}
			FieldRef<Character, float> slippage = VanillaAccess.Slippage;
			FieldRef<Character, bool> sliding = VanillaAccess.Sliding;
			if (slippage != null)
			{
				slippage.Invoke(character) = 0f;
			}
			if (sliding != null)
			{
				sliding.Invoke(character) = false;
			}
			return false;
		}

		private static float TiltAngleDegrees(Vector3 groundNormal)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return Mathf.Clamp(Mathf.Acos(Mathf.Clamp01(groundNormal.y)) * 57.29578f, 0f, 90f);
		}

		private static bool IsTerrainGround(Character character)
		{
			FieldRef<Character, Collider> lastGroundCollider = VanillaAccess.LastGroundCollider;
			if (lastGroundCollider == null)
			{
				return true;
			}
			Collider val = lastGroundCollider.Invoke(character);
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			return (Object)(object)((Component)val).GetComponentInParent<Heightmap>() != (Object)null;
		}

		private static float ComputeFinalRunStaminaDrainRate(Player player, Vector3 moveDir)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			FieldRef<Player, float> runStaminaDrain = VanillaAccess.RunStaminaDrain;
			float num = ((runStaminaDrain != null) ? runStaminaDrain.Invoke(player) : 10f);
			float skillFactor = ((Character)player).GetSkillFactor((SkillType)102);
			float num2 = Mathf.Lerp(1f, 0.5f, skillFactor);
			float num3 = num * num2;
			num3 -= num3 * ((Character)player).GetEquipmentMovementModifier();
			num3 += num3 * ((Character)player).GetEquipmentRunStaminaModifier();
			((Character)player).GetSEMan().ModifyRunStaminaDrain(num3, ref num3, moveDir, true);
			return num3;
		}
	}
}