Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SlopeWalker v1.0.0
SlopeWalker.dll
Decompiled a week agousing 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; } } }