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 ConcernedLadders v1.0.0
plugins/TheConcernedCat.ConcernedLadders.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using TheConcernedCat.ConcernedLadders.Domain; using TheConcernedCat.ConcernedLadders.Domain.Animation; using TheConcernedCat.ConcernedLadders.Pieces; using TheConcernedCat.ConcernedLadders.Runtime; using TheConcernedCat.ConcernedLadders.Runtime.Animation; using TheConcernedCat.Diagnostics; using TheConcernedCat.Ladders; using UnityEngine; [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("The Concerned Cat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2026 Eren Cansunar")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+4cb5e566ea8878970be7135d6319de5e7ee1fe14")] [assembly: AssemblyProduct("Concerned Ladders")] [assembly: AssemblyTitle("TheConcernedCat.ConcernedLadders")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Weakened/ConcernedCatMods")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace TheConcernedCat.Diagnostics { internal static class PathScrubber { public const int DefaultMaxLength = 2000; private const string WindowsSegmentChar = "[^\\\\/\\r\\n:*?\"<>|\\s]"; private const string UnixSegmentChar = "[^/\\s]"; private const string UnixRunChar = "[^/<>\\s]"; private const string WindowsFinalRunChar = "[^\\\\/\\r\\n:*?\"<>|\\s.,;]"; private const string UnixFinalRunChar = "[^/<>\\r\\n\\s.,;]"; private const string TokenCap = "{0,2}"; private const string WindowsChainRun = "(?: [^\\\\/\\r\\n:*?\"<>|\\s]+){0,2}"; private const string UnixChainRun = "(?: [^/<>\\s]+){0,2}"; private const string WindowsFinalRun = "(?: [^\\\\/\\r\\n:*?\"<>|\\s.,;]+){0,2}"; private const string UnixFinalRun = "(?: [^/<>\\r\\n\\s.,;]+){0,2}"; private const string NoFileExtension = "(?<!\\.[A-Za-z0-9]{1,8})"; private const string PathEnd = "(?=$|[\\r\\n'\"`,;])"; private static readonly Regex Urls = new Regex("\\b(?:https?|wss?|ftp)://\\S+", RegexOptions.Compiled); private static readonly Regex CoordinatePairs = new Regex("\\(\\s*-?\\d+(?:\\.\\d+)?\\s*,\\s*-?\\d+(?:\\.\\d+)?(?:\\s*,\\s*-?\\d+(?:\\.\\d+)?)?\\s*\\)", RegexOptions.Compiled); private static readonly Regex WindowsPath = new Regex("[A-Za-z]:[\\\\/](?:[^\\\\/\\r\\n:*?\"<>|\\s]+(?: [^\\\\/\\r\\n:*?\"<>|\\s]+){0,2}[\\\\/])*([^\\\\/\\r\\n:*?\"<>|\\s]*)(?:(?<!\\.[A-Za-z0-9]{1,8})(?: [^\\\\/\\r\\n:*?\"<>|\\s.,;]+){0,2}(?=$|[\\r\\n'\"`,;]))?", RegexOptions.Compiled); private static readonly Regex WindowsUncPath = new Regex("\\\\\\\\(?:[^\\\\/\\r\\n:*?\"<>|\\s]+(?: [^\\\\/\\r\\n:*?\"<>|\\s]+){0,2}\\\\){1,}([^\\\\/\\r\\n:*?\"<>|\\s]*)(?:(?<!\\.[A-Za-z0-9]{1,8})(?: [^\\\\/\\r\\n:*?\"<>|\\s.,;]+){0,2}(?=$|[\\r\\n'\"`,;]))?", RegexOptions.Compiled); private static readonly Regex WindowsRelativePath = new Regex("(?<![\\\\/\\w.<>])[^\\\\/\\r\\n:*?\"<>|\\s]+\\\\(?:[^\\\\/\\r\\n:*?\"<>|\\s]+(?: [^\\\\/\\r\\n:*?\"<>|\\s]+){0,2}\\\\){1,}([^\\\\/\\r\\n:*?\"<>|\\s]*)(?:(?<!\\.[A-Za-z0-9]{1,8})(?: [^\\\\/\\r\\n:*?\"<>|\\s.,;]+){0,2}(?=$|[\\r\\n'\"`,;]))?", RegexOptions.Compiled); private static readonly Regex FileThenProseBeforeRelative = new Regex("\\.[A-Za-z0-9]{1,8}(?: [^\\\\/\\r\\n:*?\"<>|\\s]+)+ $", RegexOptions.Compiled); private static readonly Regex UnixPath = new Regex("(?<![\\w.<>])/(?:[^/\\s]+(?: [^/<>\\s]+){0,2}/)+([^/\\s]*)(?:(?<!\\.[A-Za-z0-9]{1,8})(?: [^/<>\\r\\n\\s.,;]+){0,2}(?=$|[\\r\\n'\"`,;]))?", RegexOptions.Compiled); private static readonly Regex UsersFragment = new Regex("\\bUsers[\\\\/][^\\\\/\\s]+", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex SaveFileNames = new Regex("[^\\\\/\\s\"']+\\.(db|fwl|fch)(\\.old|\\.bak)?\\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex Ipv4 = new Regex("\\b\\d{1,3}(?:\\.\\d{1,3}){3}(?::\\d{1,5})?\\b", RegexOptions.Compiled); private static readonly Regex HexBlob = new Regex("\\b[A-Fa-f0-9]{32,}\\b", RegexOptions.Compiled); private static readonly Regex TokenBlob = new Regex("[A-Za-z0-9+/=_\\-]{40,}", RegexOptions.Compiled); private static readonly Regex LongDigits = new Regex("\\d{7,}", RegexOptions.Compiled); private static void CreateMarkerTokens(string text, out string cappedContinuation, out string rootedPathStart, out string rootedPathEnd) { string text2 = "<"; while (text.IndexOf(text2, StringComparison.Ordinal) >= 0) { text2 += "<"; } cappedContinuation = text2 + "|"; rootedPathStart = text2 + ":"; rootedPathEnd = text2 + "?"; } private static bool IsWindowsSegmentCharacter(char value) { if (!char.IsWhiteSpace(value)) { return "\\/\r\n:*?\"<>|".IndexOf(value) < 0; } return false; } private static bool HasCappedContinuation(string text, int index) { int num = 0; while (index < text.Length && text[index] == ' ') { index++; int num2 = index; while (index < text.Length && IsWindowsSegmentCharacter(text[index])) { index++; } if (index == num2) { return false; } num++; if (index < text.Length && text[index] == '\\') { return num >= 3; } } return false; } private static string MarkRootedWindowsPaths(Regex pattern, string text, string cappedContinuation, string rootedPathStart, string rootedPathEnd) { return pattern.Replace(text, delegate(Match match) { foreach (Match item in WindowsRelativePath.Matches(match.Value)) { if (item.Index > 0 && FileThenProseBeforeRelative.IsMatch(match.Value.Substring(0, item.Index))) { return rootedPathStart + match.Value.Substring(0, item.Index) + rootedPathEnd + match.Value.Substring(item.Index); } } return rootedPathStart + match.Value + rootedPathEnd + (HasCappedContinuation(text, match.Index + match.Length) ? cappedContinuation : ""); }); } private static bool IsCappedContinuationGap(string text, int marker, int markerLength, int matchIndex) { for (int i = marker + markerLength; i < matchIndex; i++) { char c = text[i]; if (c != ' ' && !IsWindowsSegmentCharacter(c)) { return false; } } return matchIndex > marker + markerLength; } private static string ReplaceRelativeWindowsPaths(string text, string replacement, string cappedContinuation, string rootedPathStart, string rootedPathEnd) { int usedMarker = -1; return WindowsRelativePath.Replace(text, delegate(Match match) { int num = match.Index; while (num > 0 && text[num - 1] != '\r' && text[num - 1] != '\n') { num--; } int num2 = ((match.Index > num) ? text.LastIndexOf(rootedPathStart, match.Index - 1, match.Index - num, StringComparison.Ordinal) : (-1)); int num3 = ((match.Index > num) ? text.LastIndexOf(rootedPathEnd, match.Index - 1, match.Index - num, StringComparison.Ordinal) : (-1)); if (num2 > num3) { return match.Value; } int num4 = ((match.Index > num) ? text.LastIndexOf(cappedContinuation, match.Index - 1, match.Index - num, StringComparison.Ordinal) : (-1)); if (num4 >= num && num4 != usedMarker && IsCappedContinuationGap(text, num4, cappedContinuation.Length, match.Index)) { usedMarker = num4; return match.Value; } return match.Result(replacement); }).Replace(cappedContinuation, "").Replace(rootedPathStart, "") .Replace(rootedPathEnd, ""); } public static string Scrub(string? text, bool keepFileName, int maxLength = 2000) { if (string.IsNullOrEmpty(text)) { return ""; } string text2 = (keepFileName ? "<path>/$1" : "<path>"); string replacement = text2; string input = text; input = Urls.Replace(input, "<url>"); input = CoordinatePairs.Replace(input, "(<pos>)"); CreateMarkerTokens(input, out string cappedContinuation, out string rootedPathStart, out string rootedPathEnd); input = MarkRootedWindowsPaths(WindowsPath, input, cappedContinuation, rootedPathStart, rootedPathEnd); input = MarkRootedWindowsPaths(WindowsUncPath, input, cappedContinuation, rootedPathStart, rootedPathEnd); input = ReplaceRelativeWindowsPaths(input, text2, cappedContinuation, rootedPathStart, rootedPathEnd); input = WindowsPath.Replace(input, text2); input = WindowsUncPath.Replace(input, text2); input = UnixPath.Replace(input, replacement); input = UsersFragment.Replace(input, "Users/<user>"); input = SaveFileNames.Replace(input, "<save>.$1"); input = Ipv4.Replace(input, "<ip>"); input = HexBlob.Replace(input, "<hex>"); input = TokenBlob.Replace(input, "<token>"); input = LongDigits.Replace(input, "<n>"); if (input.Length > maxLength) { input = input.Substring(0, maxLength) + "…[truncated]"; } return input; } } internal static class SafeFailure { public static string Describe(string command, string subcommand, Exception? exception) { string text = (string.IsNullOrWhiteSpace(subcommand) ? command : (command + " " + subcommand)); string text2 = Brief(exception); if (!string.IsNullOrEmpty(text2)) { return text + " could not finish: " + text2; } return text + " could not finish."; } public static string Brief(Exception? exception) { if (exception == null) { return ""; } string text = PathScrubber.Scrub(exception.Message, keepFileName: false); if (!string.IsNullOrEmpty(text)) { return exception.GetType().Name + ": " + text; } return exception.GetType().Name; } public static string Describe(Exception? exception) { if (exception != null) { return PathScrubber.Scrub(exception.ToString(), keepFileName: false, 8000); } return ""; } } } namespace TheConcernedCat.Ladders { internal enum ClimbExitKind { Unspecified, Bottom, Top, JumpedOff, LetGo } internal readonly struct ClimbExitPlan { public ClimbExitKind Kind { get; } public ClimbPoint Landing { get; } public ClimbHeading Facing { get; } public bool PlaceCharacter { get; } private ClimbExitPlan(ClimbExitKind kind, ClimbPoint landing, ClimbHeading facing, bool placeCharacter) { Kind = kind; Landing = landing; Facing = facing; PlaceCharacter = placeCharacter; } public static ClimbExitPlan Step(ClimbExitKind kind, ClimbPoint landing, ClimbHeading facing) { if (kind != ClimbExitKind.Bottom && kind != ClimbExitKind.Top) { throw new ArgumentOutOfRangeException("kind", "Only the two step-offs place a character."); } return new ClimbExitPlan(kind, landing, facing, placeCharacter: true); } public static ClimbExitPlan Release(ClimbExitKind kind) { return new ClimbExitPlan(kind, default(ClimbPoint), ClimbHeading.None, placeCharacter: false); } } internal readonly struct TopLanding { public bool HasStandingSpace { get; } public ClimbPoint Surface { get; } public static TopLanding None => new TopLanding(hasStandingSpace: false, default(ClimbPoint)); public TopLanding(bool hasStandingSpace, ClimbPoint surface) { HasStandingSpace = hasStandingSpace; Surface = surface; } } internal static class ClimbExit { public static ClimbExitPlan? OverTheTop(in LadderGeometry ladder, in TopLanding landing, ClimbLimits limits) { if (limits == null) { throw new ArgumentNullException("limits"); } if (!landing.HasStandingSpace || !landing.Surface.IsFinite) { return null; } ClimbHeading facingWhileClimbing = ladder.FacingWhileClimbing; ClimbPoint landing2 = landing.Surface.Offset(facingWhileClimbing, limits.TopStepInMetres).WithHeight(landing.Surface.Y + limits.TopStepUpMetres); return ClimbExitPlan.Step(ClimbExitKind.Top, landing2, facingWhileClimbing); } public static ClimbExitPlan OffTheBottom(in LadderGeometry ladder, ClimbLimits limits) { if (limits == null) { throw new ArgumentNullException("limits"); } ClimbPoint landing = ladder.Bottom.Offset(ladder.StandingSide, limits.BodyOffsetMetres); return ClimbExitPlan.Step(ClimbExitKind.Bottom, landing, ladder.StandingSide); } public static ClimbExitPlan LetGo(bool deliberate) { return ClimbExitPlan.Release(deliberate ? ClimbExitKind.JumpedOff : ClimbExitKind.LetGo); } } internal sealed class ClimbLimits { public float MountReachMetres { get; set; } = 1.1f; public float MountSideMarginMetres { get; set; } = 0.45f; public float MountHeightMarginMetres { get; set; } = 1.2f; public float MountFacingAgreement { get; set; } = 0.35f; public float ClimbSpeedMetresPerSecond { get; set; } = 1.8f; public float AlignSeconds { get; set; } = 0.25f; public float BodyOffsetMetres { get; set; } = 0.35f; public float TopStepUpMetres { get; set; } = 0.4f; public float TopStepInMetres { get; set; } = 0.6f; public float LostContactMetres { get; set; } = 1.6f; public float StaminaPerSecond { get; set; } public float SpeedMultiplier { get; set; } = 1f; public float EffectiveClimbSpeed => Math.Max(0.1f, ClimbSpeedMetresPerSecond * SpeedMultiplier); public static ClimbLimits Default => new ClimbLimits(); public ClimbLimits Validate() { Require(MountReachMetres > 0f && MountReachMetres <= 3f, "MountReachMetres"); Require(MountSideMarginMetres >= 0f && MountSideMarginMetres <= 2f, "MountSideMarginMetres"); Require(MountHeightMarginMetres >= 0f && MountHeightMarginMetres <= 3f, "MountHeightMarginMetres"); Require(MountFacingAgreement >= -1f && MountFacingAgreement <= 1f, "MountFacingAgreement"); Require(ClimbSpeedMetresPerSecond > 0f && ClimbSpeedMetresPerSecond <= 6f, "ClimbSpeedMetresPerSecond"); Require(AlignSeconds >= 0f && AlignSeconds <= 1f, "AlignSeconds"); Require(BodyOffsetMetres > 0f && BodyOffsetMetres <= 1f, "BodyOffsetMetres"); Require(TopStepUpMetres >= 0f && TopStepUpMetres <= 2f, "TopStepUpMetres"); Require(TopStepInMetres > 0f && TopStepInMetres <= 2f, "TopStepInMetres"); Require(LostContactMetres > BodyOffsetMetres, "LostContactMetres"); Require(StaminaPerSecond >= 0f && StaminaPerSecond <= 10f, "StaminaPerSecond"); Require(SpeedMultiplier >= 0.25f && SpeedMultiplier <= 3f, "SpeedMultiplier"); return this; } private static void Require(bool condition, string name) { if (!condition) { throw new ArgumentOutOfRangeException(name, "A climb limit is outside its designed range."); } } } internal sealed class ClimbOptions { public bool Enabled { get; set; } = true; public bool AutoMount { get; set; } = true; public float ClimbSpeedMultiplier { get; set; } = 1f; public float StaminaPerSecond { get; set; } public float ReMountCooldownSeconds { get; set; } = 0.35f; public float SurveyRadiusMetres { get; set; } = 4f; public float SurveyIntervalSeconds { get; set; } = 0.25f; public float TopProbeWithinMetres { get; set; } = 0.75f; public float TopFloorAboveMetres { get; set; } = 1f; public float TopFloorBelowMetres { get; set; } = 1.25f; public float MaxCorrectionSpeed { get; set; } = 4f; public ClimbLimits ToLimits() { return new ClimbLimits { SpeedMultiplier = Clamp(ClimbSpeedMultiplier, 0.25f, 3f, 1f), StaminaPerSecond = Clamp(StaminaPerSecond, 0f, 10f, 0f) }.Validate(); } private static float Clamp(float value, float low, float high, float fallback) { if (float.IsNaN(value) || float.IsInfinity(value)) { return fallback; } return Math.Min(Math.Max(value, low), high); } } internal readonly struct ClimbPoint : IEquatable<ClimbPoint> { public float X { get; } public float Y { get; } public float Z { get; } public bool IsFinite { get { if (IsFiniteValue(X) && IsFiniteValue(Y)) { return IsFiniteValue(Z); } return false; } } public ClimbPoint(float x, float y, float z) { X = x; Y = y; Z = z; } public float HorizontalDistanceTo(ClimbPoint other) { float num = X - other.X; float num2 = Z - other.Z; return (float)Math.Sqrt(num * num + num2 * num2); } public float VerticalDistanceTo(ClimbPoint other) { return Math.Abs(Y - other.Y); } public ClimbPoint WithHeight(float y) { return new ClimbPoint(X, y, Z); } public ClimbPoint Offset(ClimbHeading heading, float metres) { return new ClimbPoint(X + heading.X * metres, Y, Z + heading.Z * metres); } public ClimbPoint Raise(float metres) { return new ClimbPoint(X, Y + metres, Z); } public bool Equals(ClimbPoint other) { if (X.Equals(other.X) && Y.Equals(other.Y)) { return Z.Equals(other.Z); } return false; } public override bool Equals(object? obj) { if (obj is ClimbPoint other) { return Equals(other); } return false; } public override int GetHashCode() { return ((17 * 31 + X.GetHashCode()) * 31 + Y.GetHashCode()) * 31 + Z.GetHashCode(); } public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "({0:0.##}, {1:0.##}, {2:0.##})", X, Y, Z); } private static bool IsFiniteValue(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal readonly struct ClimbHeading : IEquatable<ClimbHeading> { public static ClimbHeading None => default(ClimbHeading); public float X { get; } public float Z { get; } public bool IsKnown => X * X + Z * Z > 0f; private ClimbHeading(float x, float z) { X = x; Z = z; } public static ClimbHeading FromXz(float x, float z) { float num = (float)Math.Sqrt(x * x + z * z); if (num <= 0.0001f || float.IsNaN(num) || float.IsInfinity(num)) { return None; } return new ClimbHeading(x / num, z / num); } public static ClimbHeading FromTo(ClimbPoint from, ClimbPoint to) { return FromXz(to.X - from.X, to.Z - from.Z); } public ClimbHeading Opposite() { if (!IsKnown) { return None; } return new ClimbHeading(0f - X, 0f - Z); } public float Agreement(ClimbHeading other) { if (!IsKnown || !other.IsKnown) { return 0f; } return X * other.X + Z * other.Z; } public bool Equals(ClimbHeading other) { if (X.Equals(other.X)) { return Z.Equals(other.Z); } return false; } public override bool Equals(object? obj) { if (obj is ClimbHeading other) { return Equals(other); } return false; } public override int GetHashCode() { return X.GetHashCode() * 31 + Z.GetHashCode(); } public override string ToString() { if (!IsKnown) { return "(none)"; } return string.Format(CultureInfo.InvariantCulture, "({0:0.##}, {1:0.##})", X, Z); } } internal static class ClimbPresentation { public const float MinimumContactIntervalSeconds = 0.22f; public static float PoseBlend(in ClimbTelemetry telemetry) { if (!telemetry.IsClimbing) { return 0f; } float alignFraction = telemetry.AlignFraction; if (float.IsNaN(alignFraction) || float.IsInfinity(alignFraction)) { return 0f; } return Math.Min(Math.Max(alignFraction, 0f), 1f); } public static float AnimatorForwardSpeed(in ClimbTelemetry telemetry) { if (!telemetry.IsClimbing) { return 0f; } float velocity = telemetry.Velocity; if (!float.IsNaN(velocity) && !float.IsInfinity(velocity)) { return velocity; } return 0f; } public static bool ShouldPlayRungContact(int previousRung, int currentRung, float velocity, float secondsSinceLast) { if (previousRung < 0 || currentRung < 0 || previousRung == currentRung) { return false; } if (float.IsNaN(velocity) || float.IsInfinity(velocity) || Math.Abs(velocity) < 0.01f) { return false; } if (!float.IsNaN(secondsSinceLast)) { return secondsSinceLast >= 0.22f; } return false; } } internal enum ClimbEndReason { Unspecified, LadderDestroyed, LadderUnloaded, LadderMoved, LostContact, CharacterDied, CharacterTaken, WorldUnloading, RuntimeStopped, ControllerFault } internal readonly struct ClimbConditions { public bool LadderAlive { get; } public bool LadderLoaded { get; } public bool LadderStill { get; } public bool CharacterAlive { get; } public bool CharacterFree { get; } public bool WorldUp { get; } public bool RuntimeRunning { get; } public float DistanceFromLadder { get; } public ClimbConditions(bool ladderAlive, bool ladderLoaded, bool ladderStill, bool characterAlive, bool characterFree, bool worldUp, bool runtimeRunning, float distanceFromLadder) { LadderAlive = ladderAlive; LadderLoaded = ladderLoaded; LadderStill = ladderStill; CharacterAlive = characterAlive; CharacterFree = characterFree; WorldUp = worldUp; RuntimeRunning = runtimeRunning; DistanceFromLadder = distanceFromLadder; } public static ClimbConditions Fine(float distanceFromLadder = 0f) { return new ClimbConditions(ladderAlive: true, ladderLoaded: true, ladderStill: true, characterAlive: true, characterFree: true, worldUp: true, runtimeRunning: true, distanceFromLadder); } } internal static class ClimbSafety { public static ClimbRestoration Restoration => new ClimbRestoration(restoreGravity: true, restoreMotor: true, restoreCollisions: true, clearClimbPose: true, stopClimbSound: true); public static ClimbEndReason MustEnd(in ClimbConditions conditions, ClimbLimits limits) { if (limits == null) { throw new ArgumentNullException("limits"); } if (!conditions.WorldUp) { return ClimbEndReason.WorldUnloading; } if (!conditions.RuntimeRunning) { return ClimbEndReason.RuntimeStopped; } if (!conditions.CharacterAlive) { return ClimbEndReason.CharacterDied; } if (!conditions.CharacterFree) { return ClimbEndReason.CharacterTaken; } if (!conditions.LadderAlive) { return ClimbEndReason.LadderDestroyed; } if (!conditions.LadderLoaded) { return ClimbEndReason.LadderUnloaded; } if (!conditions.LadderStill) { return ClimbEndReason.LadderMoved; } if (conditions.DistanceFromLadder > limits.LostContactMetres) { return ClimbEndReason.LostContact; } return ClimbEndReason.Unspecified; } public static ClimbExitPlan Release(ClimbEndReason reason) { if (reason == ClimbEndReason.Unspecified) { throw new ArgumentOutOfRangeException("reason", "A climb does not end without a reason."); } return ClimbExit.LetGo(deliberate: false); } public static string Explain(ClimbEndReason reason) { return reason switch { ClimbEndReason.LadderDestroyed => "The ladder broke.", ClimbEndReason.LadderUnloaded => "The ladder is too far away now.", ClimbEndReason.LadderMoved => "The ladder moved.", ClimbEndReason.LostContact => "You lost your grip.", ClimbEndReason.CharacterDied => "You died on the ladder.", ClimbEndReason.CharacterTaken => "Something else took hold of you.", ClimbEndReason.WorldUnloading => "The world is closing.", ClimbEndReason.RuntimeStopped => "Ladder climbing stopped.", ClimbEndReason.ControllerFault => "Climbing failed, and you were let go safely.", _ => "The climb ended.", }; } } internal readonly struct ClimbRestoration { public bool RestoreGravity { get; } public bool RestoreMotor { get; } public bool RestoreCollisions { get; } public bool ClearClimbPose { get; } public bool StopClimbSound { get; } public bool IsComplete { get { if (RestoreGravity && RestoreMotor && RestoreCollisions && ClearClimbPose) { return StopClimbSound; } return false; } } public ClimbRestoration(bool restoreGravity, bool restoreMotor, bool restoreCollisions, bool clearClimbPose, bool stopClimbSound) { RestoreGravity = restoreGravity; RestoreMotor = restoreMotor; RestoreCollisions = restoreCollisions; ClearClimbPose = clearClimbPose; StopClimbSound = stopClimbSound; } } internal enum ClimbPhase { None, Aligning, OnTheLadder, Ended } internal readonly struct ClimbTelemetry { public ClimbPhase Phase { get; } public ClimbMotion Motion { get; } public float Progress { get; } public float Height { get; } public float Velocity { get; } public float AnimationSpeed { get; } public int Rung { get; } public float AlignFraction { get; } public ClimbHeading Facing { get; } public static ClimbTelemetry Idle => default(ClimbTelemetry); public bool IsClimbing { get { if (Phase != ClimbPhase.Aligning) { return Phase == ClimbPhase.OnTheLadder; } return true; } } internal ClimbTelemetry(ClimbPhase phase, ClimbMotion motion, float progress, float height, float velocity, float animationSpeed, int rung, float alignFraction, ClimbHeading facing) { Phase = phase; Motion = motion; Progress = progress; Height = height; Velocity = velocity; AnimationSpeed = animationSpeed; Rung = rung; AlignFraction = alignFraction; Facing = facing; } } internal readonly struct ClimbFrame { public float DeltaSeconds { get; } public ClimbConditions Conditions { get; } public float ClimbInput { get; } public bool LetGoRequested { get; } public TopLanding TopLanding { get; } public ClimbFrame(float deltaSeconds, in ClimbConditions conditions, float climbInput, bool letGoRequested, in TopLanding topLanding) { DeltaSeconds = deltaSeconds; Conditions = conditions; ClimbInput = climbInput; LetGoRequested = letGoRequested; TopLanding = topLanding; } } internal readonly struct ClimbStep { public bool Continues { get; } public ClimbPoint BodyTarget { get; } public ClimbHeading BodyFacing { get; } public float StaminaUsed { get; } public bool HasExit { get; } public ClimbExitPlan Exit { get; } public ClimbEndReason EndReason { get; } public ClimbRestoration Restoration { get; } public ClimbTelemetry Telemetry { get; } private ClimbStep(bool continues, ClimbPoint bodyTarget, ClimbHeading bodyFacing, float staminaUsed, bool hasExit, ClimbExitPlan exit, ClimbEndReason endReason, ClimbRestoration restoration, ClimbTelemetry telemetry) { Continues = continues; BodyTarget = bodyTarget; BodyFacing = bodyFacing; StaminaUsed = staminaUsed; HasExit = hasExit; Exit = exit; EndReason = endReason; Restoration = restoration; Telemetry = telemetry; } internal static ClimbStep Continue(ClimbPoint bodyTarget, ClimbHeading facing, float staminaUsed, ClimbTelemetry telemetry) { return new ClimbStep(continues: true, bodyTarget, facing, staminaUsed, hasExit: false, default(ClimbExitPlan), ClimbEndReason.Unspecified, default(ClimbRestoration), telemetry); } internal static ClimbStep Ending(ClimbExitPlan exit, ClimbEndReason reason) { return new ClimbStep(continues: false, default(ClimbPoint), ClimbHeading.None, 0f, hasExit: true, exit, reason, ClimbSafety.Restoration, ClimbTelemetry.Idle); } internal static ClimbStep AlreadyOver(ClimbEndReason reason) { return new ClimbStep(continues: false, default(ClimbPoint), ClimbHeading.None, 0f, hasExit: false, default(ClimbExitPlan), reason, ClimbSafety.Restoration, ClimbTelemetry.Idle); } } internal sealed class MountGate { private float _cooldown; private bool _mustLeaveTheBand; public bool AllowsAutoMount { get { if (_cooldown <= 0f) { return !_mustLeaveTheBand; } return false; } } public bool AllowsDeliberateMount => _cooldown <= 0f; public void Tick(float deltaSeconds) { if (!float.IsNaN(deltaSeconds) && !(deltaSeconds <= 0f) && _cooldown > 0f) { _cooldown = Math.Max(0f, _cooldown - deltaSeconds); } } public void ClimbEnded(ClimbExitKind kind, float cooldownSeconds) { _cooldown = Math.Max(0f, cooldownSeconds); _mustLeaveTheBand = kind == ClimbExitKind.JumpedOff || kind == ClimbExitKind.LetGo; } public void OutOfTheBand() { _mustLeaveTheBand = false; } public static bool LeavesTheBand(MountRefusal refusal) { if ((uint)refusal <= 5u) { return true; } return false; } public void Reset() { _cooldown = 0f; _mustLeaveTheBand = false; } } internal sealed class ClimbSession { private readonly LadderRun _run; private readonly ClimbLimits _limits; private readonly ClimbTrack _track; private readonly ClimbPoint _mountedFrom; private readonly ClimbHeading _facedFrom; private float _aligned; public ClimbPhase Phase { get; private set; } public ClimbEndReason EndReason { get; private set; } public LadderRun Run => _run; public float Progress => _track.Progress; public ClimbPoint HeldAt => _run.AsOne.ClimbPositionAt(_track.Progress, _limits.BodyOffsetMetres); private ClimbSession(LadderRun run, ClimbLimits limits, float startProgress, ClimbPoint mountedFrom, ClimbHeading facedFrom) { _run = run; _limits = limits; _track = new ClimbTrack(run.Height, limits, startProgress); _mountedFrom = mountedFrom; _facedFrom = (facedFrom.IsKnown ? facedFrom : run.AsOne.FacingWhileClimbing); Phase = ((limits.AlignSeconds > 0f) ? ClimbPhase.Aligning : ClimbPhase.OnTheLadder); } public bool NearTheTop(float withinMetres) { return _track.Progress >= _run.Height - Math.Max(0f, withinMetres); } public static bool TryStart(LadderRun run, in ClimberState climber, ClimbLimits limits, bool laddersEnabled, bool byDeliberateUse, out ClimbSession session, out MountRefusal refusal) { if (run == null) { throw new ArgumentNullException("run"); } if (limits == null) { throw new ArgumentNullException("limits"); } session = null; MountDecision mountDecision = LadderMount.Decide(run.AsOne, in climber, limits, laddersEnabled, byDeliberateUse); if (!mountDecision.Allowed) { refusal = mountDecision.Refusal; return false; } refusal = MountRefusal.Unspecified; session = new ClimbSession(run, limits, mountDecision.Progress, climber.Feet, climber.Looking); return true; } public ClimbStep Step(in ClimbFrame frame) { if (Phase == ClimbPhase.Ended) { return ClimbStep.AlreadyOver(EndReason); } ClimbEndReason climbEndReason = ClimbSafety.MustEnd(frame.Conditions, _limits); if (climbEndReason != ClimbEndReason.Unspecified) { return End(climbEndReason, ClimbSafety.Release(climbEndReason)); } if (frame.LetGoRequested) { return End(ClimbEndReason.Unspecified, ClimbExit.LetGo(deliberate: true)); } float num = frame.DeltaSeconds; if (float.IsNaN(num) || num < 0f) { num = 0f; } if (Phase == ClimbPhase.Aligning) { return Align(num); } ClimbMotion climbMotion = _track.Step(frame.ClimbInput, num); switch (climbMotion) { case ClimbMotion.PressingAgainstTheTop: { ClimbExitPlan? climbExitPlan = ClimbExit.OverTheTop(_run.AsOne, frame.TopLanding, _limits); if (climbExitPlan.HasValue) { return End(ClimbEndReason.Unspecified, climbExitPlan.Value); } break; } case ClimbMotion.StandingOnTheGround: return End(ClimbEndReason.Unspecified, ClimbExit.OffTheBottom(_run.AsOne, _limits)); } return ClimbStep.Continue(HeldAt, _run.AsOne.FacingWhileClimbing, _track.StaminaFor(num), Telemetry(climbMotion, 1f)); } public ClimbStep Fault() { return Stop(ClimbEndReason.ControllerFault); } public ClimbStep Stop(ClimbEndReason reason) { if (reason == ClimbEndReason.Unspecified) { throw new ArgumentOutOfRangeException("reason", "A climb does not end without a reason."); } if (Phase == ClimbPhase.Ended) { return ClimbStep.AlreadyOver(EndReason); } return End(reason, ClimbSafety.Release(reason)); } private ClimbStep Align(float delta) { _aligned += delta; float num = ((_limits.AlignSeconds <= 0f) ? 1f : Math.Min(1f, Math.Max(0f, _aligned / _limits.AlignSeconds))); if (num >= 1f) { Phase = ClimbPhase.OnTheLadder; } float fraction = num * num * (3f - 2f * num); ClimbPoint bodyTarget = Between(_mountedFrom, HeldAt, fraction); ClimbHeading facing = Between(_facedFrom, _run.AsOne.FacingWhileClimbing, fraction); return ClimbStep.Continue(bodyTarget, facing, 0f, Telemetry(ClimbMotion.Resting, num)); } private ClimbStep End(ClimbEndReason reason, ClimbExitPlan exit) { Phase = ClimbPhase.Ended; EndReason = reason; return ClimbStep.Ending(exit, reason); } private ClimbTelemetry Telemetry(ClimbMotion motion, float alignFraction) { return new ClimbTelemetry(Phase, motion, _track.Progress, _run.Height, _track.Velocity, _track.AnimationSpeed(), _run.AsOne.RungAt(_track.Progress), alignFraction, _run.AsOne.FacingWhileClimbing); } private static ClimbPoint Between(ClimbPoint from, ClimbPoint to, float fraction) { return new ClimbPoint(from.X + (to.X - from.X) * fraction, from.Y + (to.Y - from.Y) * fraction, from.Z + (to.Z - from.Z) * fraction); } private static ClimbHeading Between(ClimbHeading from, ClimbHeading to, float fraction) { if (!from.IsKnown) { return to; } if (!to.IsKnown) { return from; } ClimbHeading result = ClimbHeading.FromXz(from.X + (to.X - from.X) * fraction, from.Z + (to.Z - from.Z) * fraction); if (!result.IsKnown) { return to; } return result; } } internal enum ClimbMotion { Unspecified, Resting, Climbing, Descending, PressingAgainstTheTop, StandingOnTheGround } internal sealed class ClimbTrack { private readonly float _span; private readonly ClimbLimits _limits; public float Progress { get; private set; } public ClimbMotion Motion { get; private set; } public float Velocity { get; private set; } public bool AtTop => Progress >= _span - 0.001f; public bool AtBottom => Progress <= 0.001f; public ClimbTrack(float span, ClimbLimits limits, float startProgress) { if (limits == null) { throw new ArgumentNullException("limits"); } if (float.IsNaN(span) || span <= 0f) { throw new ArgumentOutOfRangeException("span", "A run with no height is not climbable."); } _span = span; _limits = limits; Progress = Clamp(startProgress); Motion = ClimbMotion.Resting; } public ClimbMotion Step(float input, float deltaSeconds) { if (float.IsNaN(input) || float.IsNaN(deltaSeconds) || deltaSeconds <= 0f) { Velocity = 0f; Motion = ClimbMotion.Resting; return Motion; } float num = Math.Min(Math.Max(input, -1f), 1f); if (Math.Abs(num) < 0.1f) { Velocity = 0f; Motion = ClimbMotion.Resting; return Motion; } float progress = Progress; Velocity = num * _limits.EffectiveClimbSpeed; Progress = Clamp(Progress + Velocity * deltaSeconds); float num2 = Progress - progress; Velocity = ((deltaSeconds > 0f) ? (num2 / deltaSeconds) : 0f); if (num > 0f) { Motion = (AtTop ? ClimbMotion.PressingAgainstTheTop : ClimbMotion.Climbing); } else { Motion = (AtBottom ? ClimbMotion.StandingOnTheGround : ClimbMotion.Descending); } return Motion; } public void MoveTo(float progress) { Progress = Clamp(progress); Velocity = 0f; Motion = ClimbMotion.Resting; } public float AnimationSpeed() { float effectiveClimbSpeed = _limits.EffectiveClimbSpeed; if (!(effectiveClimbSpeed <= 0f)) { return Velocity / effectiveClimbSpeed; } return 0f; } public float StaminaFor(float deltaSeconds) { if (_limits.StaminaPerSecond <= 0f || deltaSeconds <= 0f || Math.Abs(Velocity) < 0.01f) { return 0f; } return _limits.StaminaPerSecond * deltaSeconds; } private float Clamp(float progress) { if (float.IsNaN(progress)) { return Progress; } return Math.Min(Math.Max(progress, 0f), _span); } } internal readonly struct LadderGeometry { public const float MinimumClimbableHeight = 0.9f; public const float ConventionalRungPitch = 0.35f; public ClimbPoint Bottom { get; } public ClimbPoint Top { get; } public ClimbHeading StandingSide { get; } public float Width { get; } public float RungPitch { get; } public float Height => Math.Max(0f, Top.Y - Bottom.Y); public ClimbHeading FacingWhileClimbing => StandingSide.Opposite(); public bool IsClimbable { get { if (Height >= 0.9f && StandingSide.IsKnown) { return Width > 0f; } return false; } } private LadderGeometry(ClimbPoint bottom, ClimbPoint top, ClimbHeading standingSide, float width, float rungPitch) { Bottom = bottom; Top = top; StandingSide = standingSide; Width = width; RungPitch = rungPitch; } public static bool TryMeasure(ClimbPoint bottom, ClimbPoint top, ClimbHeading standingSide, float width, float rungPitch, out LadderGeometry geometry) { geometry = default(LadderGeometry); if (!bottom.IsFinite || !top.IsFinite || !standingSide.IsKnown) { return false; } if (float.IsNaN(width) || width <= 0f || width > 4f) { return false; } if (float.IsNaN(rungPitch) || rungPitch <= 0f || rungPitch > 1f) { rungPitch = 0.35f; } if (top.Y - bottom.Y < 0.9f) { return false; } geometry = new LadderGeometry(bottom, top, standingSide, width, rungPitch); return true; } public ClimbPoint PointAt(float progress) { float height = Height; float num = ((height <= 0f) ? 0f : Math.Min(Math.Max(progress, 0f), height)); float num2 = ((height <= 0f) ? 0f : (num / height)); return new ClimbPoint(Bottom.X + (Top.X - Bottom.X) * num2, Bottom.Y + (Top.Y - Bottom.Y) * num2, Bottom.Z + (Top.Z - Bottom.Z) * num2); } public float ProgressOf(ClimbPoint point) { return Math.Min(Math.Max(point.Y - Bottom.Y, 0f), Height); } public float SidewaysOffsetOf(ClimbPoint point) { ClimbPoint climbPoint = PointAt(ProgressOf(point)); float num = point.X - climbPoint.X; float num2 = point.Z - climbPoint.Z; float num3 = num * StandingSide.X + num2 * StandingSide.Z; float num4 = num - StandingSide.X * num3; float num5 = num2 - StandingSide.Z * num3; return (float)Math.Sqrt(num4 * num4 + num5 * num5); } public float ReachFrom(ClimbPoint point) { ClimbPoint climbPoint = PointAt(ProgressOf(point)); float num = point.X - climbPoint.X; float num2 = point.Z - climbPoint.Z; return num * StandingSide.X + num2 * StandingSide.Z; } public ClimbPoint ClimbPositionAt(float progress, float bodyOffset) { return PointAt(progress).Offset(StandingSide, bodyOffset); } public int RungAt(float progress) { float num = ((RungPitch <= 0f) ? 0.35f : RungPitch); return (int)Math.Floor(Math.Min(Math.Max(progress, 0f), Height) / num); } public override string ToString() { return "ladder " + Bottom.ToString() + " -> " + Top.ToString() + " (" + Height.ToString("0.##") + " m, standing side " + StandingSide.ToString() + ")"; } } internal enum MountRefusal { Unspecified, NotClimbable, TooFar, OffToTheSide, WrongSide, OutOfSpan, LookingAway, CharacterBusy, Disabled, AlreadyClimbing } internal readonly struct ClimberState { public ClimbPoint Feet { get; } public ClimbHeading Looking { get; } public bool CanClimbNow { get; } public bool AlreadyClimbing { get; } public ClimberState(ClimbPoint feet, ClimbHeading looking, bool canClimbNow, bool alreadyClimbing) { Feet = feet; Looking = looking; CanClimbNow = canClimbNow; AlreadyClimbing = alreadyClimbing; } } internal readonly struct MountDecision { public bool Allowed { get; } public MountRefusal Refusal { get; } public float Progress { get; } public ClimbPoint Position { get; } public ClimbHeading Facing { get; } private MountDecision(bool allowed, MountRefusal refusal, float progress, ClimbPoint position, ClimbHeading facing) { Allowed = allowed; Refusal = refusal; Progress = progress; Position = position; Facing = facing; } public static MountDecision Allow(float progress, ClimbPoint position, ClimbHeading facing) { return new MountDecision(allowed: true, MountRefusal.Unspecified, progress, position, facing); } public static MountDecision Refuse(MountRefusal refusal) { if (refusal == MountRefusal.Unspecified) { throw new ArgumentOutOfRangeException("refusal", "A refusal needs a reason."); } return new MountDecision(allowed: false, refusal, 0f, default(ClimbPoint), ClimbHeading.None); } } internal static class LadderMount { public static MountDecision Decide(in LadderGeometry ladder, in ClimberState climber, ClimbLimits limits, bool laddersEnabled = true, bool byDeliberateUse = false) { if (limits == null) { throw new ArgumentNullException("limits"); } if (!laddersEnabled) { return MountDecision.Refuse(MountRefusal.Disabled); } if (climber.AlreadyClimbing) { return MountDecision.Refuse(MountRefusal.AlreadyClimbing); } if (!climber.CanClimbNow) { return MountDecision.Refuse(MountRefusal.CharacterBusy); } if (!ladder.IsClimbable) { return MountDecision.Refuse(MountRefusal.NotClimbable); } if (!climber.Feet.IsFinite) { return MountDecision.Refuse(MountRefusal.TooFar); } float num = climber.Feet.Y - ladder.Top.Y; float num2 = ladder.Bottom.Y - climber.Feet.Y; if (num > limits.MountHeightMarginMetres || num2 > limits.MountHeightMarginMetres) { return MountDecision.Refuse(MountRefusal.OutOfSpan); } float num3 = ladder.ReachFrom(climber.Feet); if (num3 < -0.05f) { return MountDecision.Refuse(MountRefusal.WrongSide); } if (num3 > limits.MountReachMetres) { return MountDecision.Refuse(MountRefusal.TooFar); } if (ladder.SidewaysOffsetOf(climber.Feet) > ladder.Width * 0.5f + limits.MountSideMarginMetres) { return MountDecision.Refuse(MountRefusal.OffToTheSide); } if (!byDeliberateUse) { ClimbHeading facingWhileClimbing = ladder.FacingWhileClimbing; if (climber.Looking.Agreement(facingWhileClimbing) < limits.MountFacingAgreement) { return MountDecision.Refuse(MountRefusal.LookingAway); } } float progress = ladder.ProgressOf(climber.Feet); return MountDecision.Allow(progress, ladder.ClimbPositionAt(progress, limits.BodyOffsetMetres), ladder.FacingWhileClimbing); } public static string Explain(MountRefusal refusal) { return refusal switch { MountRefusal.NotClimbable => "That is too short to climb; just step up.", MountRefusal.TooFar => "Stand closer to the ladder.", MountRefusal.OffToTheSide => "Stand in front of the ladder.", MountRefusal.WrongSide => "The rungs are on the other side.", MountRefusal.OutOfSpan => "The ladder does not reach here.", MountRefusal.LookingAway => "Face the ladder to climb it.", MountRefusal.CharacterBusy => "Not while you are doing that.", MountRefusal.Disabled => "Ladder climbing is switched off.", MountRefusal.AlreadyClimbing => "You are already on a ladder.", _ => "You cannot climb that.", }; } } internal sealed class LadderRun { private readonly List<LadderGeometry> _pieces; public IReadOnlyList<LadderGeometry> Pieces => _pieces; public ClimbPoint Bottom { get; } public ClimbPoint Top { get; } public float Height => Math.Max(0f, Top.Y - Bottom.Y); public LadderGeometry AsOne { get; private set; } internal LadderRun(List<LadderGeometry> pieces) { _pieces = pieces; Bottom = pieces[0].Bottom; Top = pieces[pieces.Count - 1].Top; } public LadderGeometry PieceAt(float progress) { float num = Bottom.Y + Math.Min(Math.Max(progress, 0f), Height); for (int i = 0; i < _pieces.Count; i++) { LadderGeometry result = _pieces[i]; if (num <= result.Top.Y + 0.01f) { return result; } } return _pieces[_pieces.Count - 1]; } public static bool TryJoin(IReadOnlyList<LadderGeometry> pieces, out LadderRun run) { run = null; if (pieces == null || pieces.Count == 0) { return false; } List<LadderGeometry> list = new List<LadderGeometry>(pieces); list.Sort((LadderGeometry left, LadderGeometry right) => left.Bottom.Y.CompareTo(right.Bottom.Y)); for (int num = 0; num < list.Count; num++) { if (!list[num].IsClimbable) { return false; } if (num != 0) { LadderGeometry ladderGeometry = list[num - 1]; LadderGeometry ladderGeometry2 = list[num]; if (ladderGeometry.StandingSide.Agreement(ladderGeometry2.StandingSide) < 0.98f) { return false; } float num2 = ladderGeometry2.Bottom.Y - ladderGeometry.Top.Y; if (num2 < -0.35f || num2 > 0.35f) { return false; } if (ladderGeometry.Top.HorizontalDistanceTo(ladderGeometry2.Bottom) > 0.35f) { return false; } } } if (!LadderGeometry.TryMeasure(list[0].Bottom, list[list.Count - 1].Top, list[0].StandingSide, list[0].Width, list[0].RungPitch, out var geometry)) { return false; } run = new LadderRun(list) { AsOne = geometry }; return true; } public static LadderRun Single(in LadderGeometry piece) { if (!piece.IsClimbable) { throw new ArgumentOutOfRangeException("piece", "That piece is not climbable."); } return new LadderRun(new List<LadderGeometry> { piece }) { AsOne = piece }; } } } namespace TheConcernedCat.ConcernedLadders { internal static class LaddersPaths { internal static string Root => Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedLadders"); } [BepInPlugin("com.theconcernedcat.valheim.concernedladders", "Concerned Ladders", "1.0.0")] [BepInDependency("com.jotunn.jotunn", "2.30.2")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.theconcernedcat.valheim.concernedladders"; public const string PluginName = "Concerned Ladders"; public const string PluginVersion = "1.0.0"; private ConfigEntry<bool>? _enabled; private LadderRuntime? _runtime; private LadderPieceRegistration? _pieces; private LadderSkill? _skill; private OriginalClimbPresenter? _presenter; private LadderSnapAlignment? _snap; private void Awake() { _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ClimbingEnabled", true, "Enable climbing on Concerned Ladders. Disabling restores player control; placed ladder prefabs remain registered. Restart after changing the installed plugin set."); _snap = new LadderSnapAlignment(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); if (!_snap.Install("com.theconcernedcat.valheim.concernedladders.snap")) { _snap = null; } _pieces = new LadderPieceRegistration(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }, _snap != null); _pieces.Install(); _skill = new LadderSkill(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); _skill.Register(); _runtime = new LadderRuntime(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }, _skill); _presenter = new OriginalClimbPresenter(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); bool flag; try { flag = _presenter.Install("com.theconcernedcat.valheim.concernedladders.presentation"); } catch (Exception exception) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Original climbing animation unavailable (" + SafeFailure.Brief(exception) + "); the walking pose is used.")); flag = false; } if (flag) { _runtime.SetPresenter(_presenter); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Climbing gait source LadderGait.blend " + "50bc77aeab206126c363b88bd8b3801e5bd1b471a018c447a310344df2b20a16".Substring(0, 12) + " (Blender 5.1.2).")); } else { try { _presenter.Dispose(); } catch { } _presenter = null; } _runtime.Enabled = _enabled.Value && _pieces.IsReady; _runtime.Install("com.theconcernedcat.valheim.concernedladders.motor"); string text = typeof(Plugin).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "1.0.0"; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Concerned Ladders " + text + " loaded.")); } private void Update() { if (_runtime != null && _enabled != null) { _runtime.Enabled = _enabled.Value && (_pieces?.IsReady ?? false); _runtime.Update(Time.deltaTime); } } private void OnDestroy() { try { _runtime?.Dispose(); _presenter?.Dispose(); } catch (Exception exception) { ((BaseUnityPlugin)this).Logger.LogError((object)("Ladder cleanup failed: " + SafeFailure.Brief(exception))); } _snap?.Dispose(); _pieces?.Dispose(); _snap = null; _runtime = null; _pieces = null; } } } namespace TheConcernedCat.ConcernedLadders.Runtime { internal interface IClimbPresenter : IDisposable { bool Install(string harmonyId); void Publish(Player player, in ClimbPresentationFrame frame); void End(Player? player, ClimbEnd reason); void Update(float deltaTime); } internal sealed class LadderClearance { private const float Skin = 0.02f; private readonly RaycastHit[] _hits = (RaycastHit[])(object)new RaycastHit[64]; private readonly Collider[] _overlaps = (Collider[])(object)new Collider[64]; private readonly Player _player; private readonly CapsuleCollider _capsule; private readonly int _mask; private readonly Vector3 _centreOffset; private readonly float _halfSegment; private readonly float _groundLift; private readonly HashSet<LadderSegment> _run = new HashSet<LadderSegment>(); private const float GroundClearance = 0.05f; private static readonly Vector3[] Footprint = (Vector3[])(object)new Vector3[8] { Vector3.forward, Vector3.back, Vector3.left, Vector3.right, new Vector3(0.7071f, 0f, 0.7071f), new Vector3(-0.7071f, 0f, 0.7071f), new Vector3(0.7071f, 0f, -0.7071f), new Vector3(-0.7071f, 0f, -0.7071f) }; private static readonly int TerrainLayer = LayerMask.NameToLayer("terrain"); internal float Radius { get; } internal float Height { get; } internal BlockerKind LastBlocker { get; private set; } internal string LastBlockerName { get; private set; } = string.Empty; internal float BottomGap { get; } internal LadderClearance(Player player, CapsuleCollider capsule, Vector3 feet) { //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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) _player = player; _capsule = capsule; Vector3 lossyScale = ((Component)capsule).transform.lossyScale; if (capsule.direction != 1 || !((Collider)capsule).enabled || !LadderSegment.Finite(lossyScale)) { throw new InvalidOperationException("The player's capsule is not a supported upright capsule."); } Radius = capsule.radius * Mathf.Max(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.z)); float num = (Height = Mathf.Max(2f * Radius, capsule.height * Mathf.Abs(lossyScale.y))); _halfSegment = Mathf.Max(0f, num * 0.5f - Radius); _centreOffset = ((Component)capsule).transform.TransformPoint(capsule.center) - ((Component)player).transform.position; if (!LadderRulesFinite(Radius) || !LadderRulesFinite(num) || !LadderSegment.Finite(_centreOffset) || Mathf.Abs(_centreOffset.x) > 0.05f || Mathf.Abs(_centreOffset.z) > 0.05f || Radius < 0.05f || Radius > 1f || num > 4f) { throw new InvalidOperationException("The player's capsule has unsupported dimensions."); } BottomGap = _centreOffset.y - num * 0.5f; _groundLift = Mathf.Max(0f, 0.05f - BottomGap); _mask = LayerMask.GetMask(new string[10] { "piece", "Default", "static_solid", "Default_small", "terrain", "blocker", "vehicle", "character", "character_net", "character_ghost" }); if (_mask == 0) { throw new InvalidOperationException("Collision layers were unavailable."); } } internal void SetRun(IEnumerable<LadderSegment> segments) { _run.Clear(); foreach (LadderSegment segment in segments) { if ((Object)(object)segment != (Object)null) { _run.Add(segment); } } } internal void ClearBlocker() { LastBlocker = BlockerKind.None; LastBlockerName = string.Empty; } internal bool IsUnchanged() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_capsule == (Object)null || !((Collider)_capsule).enabled || _capsule.direction != 1) { return false; } Vector3 lossyScale = ((Component)_capsule).transform.lossyScale; float num = _capsule.radius * Mathf.Max(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.z)); float num2 = Mathf.Max(2f * num, _capsule.height * Mathf.Abs(lossyScale.y)); Vector3 val = ((Component)_capsule).transform.TransformPoint(_capsule.center) - ((Component)_player).transform.position; if (LadderSegment.Finite(lossyScale) && LadderRulesFinite(num) && LadderRulesFinite(num2) && LadderSegment.Finite(val) && Vector3.Distance(val, _centreOffset) < 0.001f && Mathf.Abs(num - Radius) < 0.001f) { return Mathf.Abs(num2 * 0.5f - num - _halfSegment) < 0.001f; } return false; } internal bool HasRoom(Vector3 feet) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_0016: Unknown result type (might be due to invalid IL or missing references) if (!LadderSegment.Finite(feet)) { return false; } CapsuleAt(feet, out var low, out var high); int num = Physics.OverlapCapsuleNonAlloc(low, high, Mathf.Max(0.02f, Radius - 0.02f), _overlaps, _mask, (QueryTriggerInteraction)1); if (num >= _overlaps.Length) { Blocked(BlockerKind.Overflow, "query overflow"); return false; } for (int i = 0; i < num; i++) { if (Solid(_overlaps[i])) { Classify(_overlaps[i]); return false; } } return true; } internal bool CanMove(Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) if (!LadderSegment.Finite(from) || !LadderSegment.Finite(to) || !HasRoom(to)) { return false; } Vector3 val = to - from; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.0001f) { return true; } CapsuleAt(from, out var low, out var high); Vector3 val2 = val / magnitude; int num = Physics.CapsuleCastNonAlloc(low, high, Mathf.Max(0.02f, Radius - 0.02f), val2, _hits, magnitude, _mask, (QueryTriggerInteraction)1); if (num >= _hits.Length) { Blocked(BlockerKind.Overflow, "query overflow"); return false; } for (int i = 0; i < num; i++) { RaycastHit val3 = _hits[i]; if (Solid(((RaycastHit)(ref val3)).collider) && (!(((RaycastHit)(ref val3)).distance <= 0.02f) || !(Vector3.Dot(((RaycastHit)(ref val3)).normal, val2) >= -0.01f))) { Classify(((RaycastHit)(ref val3)).collider); return false; } } return true; } internal bool CanFollow(Vector3 from, Vector3[] points) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0010: 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_001b: Unknown result type (might be due to invalid IL or missing references) foreach (Vector3 val in points) { if (!CanMove(from, val)) { return false; } from = val; } return true; } internal bool HasLineOfSight(Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; float magnitude = ((Vector3)(ref val)).magnitude; if (!LadderSegment.Finite(from) || !LadderSegment.Finite(to)) { Blocked(BlockerKind.Overflow, "not finite"); return false; } if (magnitude < 0.001f) { return true; } int num = Physics.RaycastNonAlloc(from, val / magnitude, _hits, magnitude, _mask, (QueryTriggerInteraction)1); if (num >= _hits.Length) { Blocked(BlockerKind.Overflow, "query overflow"); return false; } for (int i = 0; i < num; i++) { Collider collider = ((RaycastHit)(ref _hits[i])).collider; if (Solid(collider) && !OwnRun(collider)) { Classify(collider); return false; } } return true; } internal bool Supported(Vector3 feet) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Vector3 floor; return TrySurface(feet, 0.15f, 0.2f, out floor); } internal bool SupportedFootprint(Vector3 feet) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) string why; return SupportedFootprint(feet, out why); } internal bool SupportedFootprint(Vector3 feet, out string why) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return SupportedFootprint(feet, 0.15f, out why); } internal bool SupportedFootprint(Vector3 feet, float step, out string why) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) why = ""; if (!TrySurface(feet, 0.15f, 0.2f, out var floor)) { why = "no surface under the centre"; return false; } float num = Radius * 0.9f; Vector3[] footprint = Footprint; foreach (Vector3 val in footprint) { if (!TrySurface(feet + val * num, 0.15f, 0.2f, out var floor2)) { why = "no surface at an edge"; return false; } if (Mathf.Abs(floor2.y - floor.y) > step) { why = "a step at an edge"; return false; } } return true; } internal bool TryTopSurface(Vector3 desired, out Vector3 floor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return TrySurface(desired, 0.75f, 0.5f, out floor); } internal bool TryNearbySurface(Vector3 desired, out Vector3 floor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return TrySurface(desired, 0.45f, 0.45f, out floor); } internal bool TryLandingSurface(Vector3 desired, float above, float below, out Vector3 floor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return TrySurface(desired, above, below, out floor); } internal bool TryFloorBelow(Vector3 feet, float maxDrop, out float floorY) { //IL_0008: 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) floorY = float.NaN; if (!TrySurface(feet, 0.15f, maxDrop, out var floor)) { return false; } floorY = floor.y; return true; } private bool TrySurface(Vector3 desired, float above, float below, out Vector3 floor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) floor = default(Vector3); if (!LadderSegment.Finite(desired)) { return false; } int num = Physics.RaycastNonAlloc(desired + Vector3.up * above, Vector3.down, _hits, above + below, _mask, (QueryTriggerInteraction)1); if (num >= _hits.Length) { return false; } float num2 = float.MaxValue; bool result = false; for (int i = 0; i < num; i++) { RaycastHit val = _hits[i]; if (Solid(((RaycastHit)(ref val)).collider) && !(((RaycastHit)(ref val)).normal.y < 0.65f) && !((Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<LadderSegment>() != (Object)null) && !((Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<Character>() != (Object)null)) { float num3 = Mathf.Abs(((RaycastHit)(ref val)).point.y - desired.y); if (!(num3 >= num2)) { num2 = num3; floor = new Vector3(desired.x, ((RaycastHit)(ref val)).point.y + 0.02f, desired.z); result = true; } } } return result; } private bool OwnRun(Collider collider) { LadderSegment componentInParent = ((Component)collider).GetComponentInParent<LadderSegment>(); if ((Object)(object)componentInParent != (Object)null) { return _run.Contains(componentInParent); } return false; } private void Classify(Collider collider) { if (OwnRun(collider)) { Blocked(BlockerKind.OwnLadder, ((Object)collider).name); return; } Character componentInParent = ((Component)collider).GetComponentInParent<Character>(); if ((Object)(object)componentInParent != (Object)null) { Blocked(BlockerKind.Actor, componentInParent.m_name); return; } Piece componentInParent2 = ((Component)collider).GetComponentInParent<Piece>(); if ((Object)(object)componentInParent2 != (Object)null) { Blocked(BlockerKind.Building, componentInParent2.m_name); } else if ((Object)(object)((Component)collider).GetComponent<Heightmap>() != (Object)null || ((Component)collider).gameObject.layer == TerrainLayer) { Blocked(BlockerKind.Terrain, "terrain"); } else { Blocked(BlockerKind.Other, ((Object)collider).name); } } private void Blocked(BlockerKind kind, string name) { LastBlocker = kind; string text = name ?? string.Empty; LastBlockerName = ((text.Length > 48) ? text.Substring(0, 48) : text); } private bool Solid(Collider collider) { if ((Object)(object)collider != (Object)null && !collider.isTrigger) { return !((Component)collider).transform.IsChildOf(((Component)_player).transform); } return false; } private static bool LadderRulesFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private void CapsuleAt(Vector3 feet, out Vector3 low, out Vector3 high) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0055: Unknown result type (might be due to invalid IL or missing references) Vector3 val = feet + _centreOffset + Vector3.up * _groundLift; low = val - Vector3.up * _halfSegment; high = val + Vector3.up * _halfSegment; } } internal sealed class LadderMotor : IDisposable { private const string ForemanMotor = "com.theconcernedcat.valheim.concernedforeman.ladders"; private FieldRef<Character, Vector3>? _move; private FieldRef<Character, float>? _fallHeight; private FieldRef<Character, float>? _groundTouch; private FieldRef<Character, Vector3>? _walkVelocity; private FieldRef<Character, bool>? _run; private Func<Player, bool>? _takeInput; private FieldRef<Player, int>? _interactMask; private Harmony? _harmony; private MethodInfo? _motion; internal bool Ready { get; private set; } internal bool Install(string id, Action<string> log) { //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Expected O, but got Unknown //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Expected O, but got Unknown //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Expected O, but got Unknown //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Expected O, but got Unknown //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Expected O, but got Unknown try { _move = AccessTools.FieldRefAccess<Character, Vector3>("m_moveDir"); _fallHeight = AccessTools.FieldRefAccess<Character, float>("m_maxAirAltitude"); _groundTouch = AccessTools.FieldRefAccess<Character, float>("m_lastGroundTouch"); _walkVelocity = AccessTools.FieldRefAccess<Character, Vector3>("m_currentVel"); _run = AccessTools.FieldRefAccess<Character, bool>("m_run"); _motion = AccessTools.Method(typeof(Character), "UpdateMotion", new Type[1] { typeof(float) }, (Type[])null); MethodInfo methodInfo = AccessTools.Method(typeof(Character), "Jump", new Type[1] { typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "TakeInput", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(Player), "SetControls", new Type[12] { typeof(Vector3), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool) }, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(Player), "Interact", new Type[3] { typeof(GameObject), typeof(bool), typeof(bool) }, (Type[])null); if (_motion == null || methodInfo == null || methodInfo2 == null || methodInfo2.ReturnType != typeof(bool) || methodInfo3 == null || methodInfo4 == null || methodInfo4.ReturnType != typeof(void) || !HasParameters(methodInfo3, "jump", "crouch") || Array.FindIndex(methodInfo3.GetParameters(), (ParameterInfo parameter) => parameter.Name == "movedir" && parameter.ParameterType == typeof(Vector3)) < 0) { log("Ladder climbing unavailable: the installed game's motor/input API did not match. Pieces remain registered."); return false; } _takeInput = AccessTools.MethodDelegate<Func<Player, bool>>(methodInfo2, (object)null, true); try { if (AccessTools.Field(typeof(Player), "m_interactMask")?.FieldType == typeof(int)) { _interactMask = AccessTools.FieldRefAccess<Player, int>("m_interactMask"); } } catch { _interactMask = null; } _harmony = new Harmony(id); _harmony.Patch((MethodBase)_motion, new HarmonyMethod(typeof(LadderMotor), "BeforeMotion", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(LadderMotor), "BeforeJump", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(LadderMotor), "BeforeControls", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(LadderMotor), "BeforeInteract", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; return true; } catch (Exception exception) { log("Ladder climbing unavailable: motor binding failed (" + SafeFailure.Brief(exception) + ")."); Dispose(); return false; } } internal bool HasForemanMotor() { if (_motion == null) { return false; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)_motion); if (patchInfo == null) { return false; } foreach (string owner in patchInfo.Owners) { if (string.Equals(owner, "com.theconcernedcat.valheim.concernedforeman.ladders", StringComparison.Ordinal)) { return true; } } return false; } internal bool TakesInput(Player player) { if (Ready && _takeInput != null) { return _takeInput(player); } return false; } internal Vector3 MoveDirection(Player player) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (_move == null) { return Vector3.zero; } return _move.Invoke((Character)(object)player); } internal float FallHeight(Player player) { if (_fallHeight == null) { return float.NaN; } return _fallHeight.Invoke((Character)(object)player); } internal bool RunIntent(Player player) { if (_run != null) { return _run.Invoke((Character)(object)player); } return false; } internal int InteractMask(Player player) { if (_interactMask == null) { return -1; } return _interactMask.Invoke(player); } internal void SetFallReference(Player player, float height) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (_fallHeight != null && LadderSegment.Finite(new Vector3(0f, height, 0f))) { _fallHeight.Invoke((Character)(object)player) = height; } } internal void BeginAirborne(Player player, Vector3 horizontal) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (_groundTouch != null) { _groundTouch.Invoke((Character)(object)player) = 1f; } SetWalkVelocity(player, horizontal); } internal void SetWalkVelocity(Player player, Vector3 horizontal) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) horizontal.y = 0f; if (_walkVelocity != null && LadderSegment.Finite(horizontal)) { _walkVelocity.Invoke((Character)(object)player) = horizontal; } } public void Dispose() { Ready = false; try { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } _harmony = null; } private static bool BeforeMotion(Character __instance, float __0) { return !(LadderRuntime.Current?.Drive(__instance, __0) ?? false); } private static bool BeforeJump(Character __instance) { return !(LadderRuntime.Current?.Jump(__instance) ?? false); } private static void BeforeControls(Player __instance, Vector3 movedir, ref bool jump, ref bool crouch) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) LadderRuntime.Current?.Controls(__instance, movedir, ref jump, ref crouch); } private static bool BeforeInteract(Player __instance) { if (__instance == Player.m_localPlayer) { return !(LadderRuntime.Current?.SuppressInteract() ?? false); } return true; } private static bool HasParameters(MethodInfo method, params string[] names) { ParameterInfo[] parameters = method.GetParameters(); foreach (string name in names) { if (Array.FindIndex(parameters, (ParameterInfo parameter) => parameter.Name == name && parameter.ParameterType == typeof(bool)) < 0) { return false; } } return true; } } internal sealed class LadderRuntime : IDisposable { private enum Mode { Off, Mounting, Climbing, Exiting } private struct Plan { internal LadderFace Face; internal float Lane; internal float Height; internal Vector3[] Route; internal bool Airborne; internal bool TopEntry; internal float Reach; } private enum Departure { Controlled, External, Jump, Push, Drop } private const float TopPlanBand = 1.3f; private const float TopEntryDrop = 1.3f; private const float TopEntrySightAbove = 0.2f; private const float IntermediateAbove = 1.15f; private const float IntermediateBelow = 0.45f; private const float TakeoverDistance = 0.5f; private const float TopBlockedBand = 2.2f; private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture; private readonly Action<string> _log; private readonly LadderMotor _motor = new LadderMotor(); private readonly LadderWalkPose _pose; private readonly LadderSkill _skill; private readonly ClimbLedger _ledger = new ClimbLedger(); private readonly List<LadderSegment> _segments = new List<LadderSegment>(); private readonly List<Vector3> _positions = new List<Vector3>(); private readonly List<Quaternion> _rotations = new List<Quaternion>(); private readonly List<float> _spanBottoms = new List<float>(); private readonly List<float> _spanTops = new List<float>(); private readonly List<LadderSegment> _hoverParts = new List<LadderSegment>(); private LadderSegment? _hoverSegment; private LadderGeometry _hoverRun; private bool _hoverValid; private float _hoverTime; private int _hoverRevision = -1; private float _plannedScale = 1f; private Player? _player; private Player? _sessionOwner; private Rigidbody? _body; private ZNetView? _view; private LadderClearance? _clearance; private LadderGeometry _run; private Mode _mode; private LadderFace _face; private ClimbGait _gait; private Vector3[] _route = Array.Empty<Vector3>(); private int _routeIndex; private float _laneDepth; private float _plane; private float _pitch; private float _gripHeight; private float _hangLowest; private float _transferStartHeight; private bool _gravityBefore; private bool _wrotePhysics; private float _cooldown; private float _poseSpeed; private float _topPlanTimer; private float _lastControlledHeight; private Vector3 _lastControlledPosition; private float _noticeCooldown; private float _compatibilityTimer; private int _mountFrame = -1; private bool _foremanConflict; private string _lastNotice = string.Empty; private bool _enabled = true; private readonly DropTrigger _drop = new DropTrigger(); private int _departFrame = -1; private int _runRevision = -1; private Vector3 _commanded; private IClimbPresenter? _presenter; private int _session; private int _discontinuity; private int _geometryRevision; private RungProfile[]? _profiles; private int _profilesRevision = -1; private int _profilesSession = -1; private long _sample; private float _travel; private float _lastSampleY; private Mode _lastSampleMode; private bool _mountAirborne; private bool _exitIsTop; private bool _exitIsBottom; private bool _mountFromTop; private Vector3 _routeStart; private float _routeRise; private float _routeLength; private ClimbEnd _endHint; private readonly RaycastHit[] _buildHover = (RaycastHit[])(object)new RaycastHit[64]; private const float TopExitSeconds = 0.45f; private const float SideExitSeconds = 0.45f; private const float BottomStepSeconds = 0.45f; private const float TopEntrySeconds = 0.45f; private const float QuickSeconds = 0.45f; private const float QuickRouteSpeed = 6f; private const float NoLandingStop = 1.3f; internal static LadderRuntime? Current { get; private set; } internal bool IsClimbing => _mode != Mode.Off; internal bool Ready => _motor.Ready; internal bool Enabled { get { return _enabled; } set { _enabled = value; if (!value && IsClimbing) { Release(Departure.Controlled); } } } private static bool LogLandings => ClimbTuning.LogLandingQueries; internal LadderRuntime(Action<string> log, LadderSkill skill) { _log = log ?? throw new ArgumentNullException("log"); _skill = skill ?? throw new ArgumentNullException("skill"); _pose = new LadderWalkPose(log); } internal void SetPresenter(IClimbPresenter? presenter) { _presenter = presenter; } internal bool Install(string harmonyId) { if (Current != null && Current != this) { _log("Ladder climbing unavailable: another Concerned Ladders runtime is already active."); return false; } Current = this; return _motor.Install(harmonyId, _log); } internal void Update(float deltaTime) { float num = LadderRules.FrameSeconds(deltaTime); _cooldown = Mathf.Max(0f, _cooldown - num); _noticeCooldown = Mathf.Max(0f, _noticeCooldown - num); _topPlanTimer = Mathf.Max(0f, _topPlanTimer - num); if (Player.m_localPlayer != _sessionOwner) { _ledger.Reset(); _skill.Forget(); _sessionOwner = Player.m_localPlayer; } try { _presenter?.Update(num); } catch (Exception exception) { PresenterFault(exception); } if (!IsClimbing) { TryBuildModeMount(); return; } try { if ((Object)(object)_player == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)_player || (Object)(object)ZNetScene.instance == (Object)null || !Enabled || (Object)(object)_view == (Object)null || !_view.IsValid() || !_view.IsOwner() || !Free(_player, attached: true)) { Release(Departure.External); return; } _compatibilityTimer -= num; if (_compatibilityTimer <= 0f) { _compatibilityTimer = 1f; _foremanConflict = _motor.HasForemanMotor(); if (_foremanConflict) { Notice("$cl_msg_foreman"); Release(Departure.Controlled); return; } } bool flag = _motor.TakesInput(_player); bool gameplayFocus = GameplayFocus(_player); _drop.Observe(CrouchHeld(), gameplayFocus); _drop.Press(CrouchPressed(), gameplayFocus); ClimbAction climbAction = ClimbControls.Resolve(gameplayFocus, JumpPressed(), CrouchPressed(), UsePressed(), ((Character)_player).InPlaceMode()); if (_mode == Mode.Climbing && climbAction == ClimbAction.Use && Time.frameCount != _mountFrame && !HoversInteractable(_player) && !TrySafeDismount()) { Notice("$cl_msg_nolanding"); } _pose.Apply((_mode == Mode.Exiting) ? _poseSpeed : ((_presenter != null || !flag) ? 0f : _poseSpeed)); } catch (Exception exception2) { Fault(exception2); } } private static bool UsePressed() { if (ZInput.GetButtonDown("Use") || ZInput.GetButtonDown("JoyUse")) { return !Hud.InRadial(); } return false; } private static bool JumpPressed() { if (!ZInput.GetButtonDown("Jump")) { return ZInput.GetButtonDown("JoyJump"); } return true; } private void TryBuildModeMount() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).InPlaceMode() || _cooldown > 0f || !_motor.Ready || !UsePressed() || JumpPressed() || !GameplayFocus(localPlayer) || (Object)(object)GameCamera.instance == (Object)null) { return; } int num = _motor.InteractMask(localPlayer); if (num == -1) { return; } Transform transform = ((Component)GameCamera.instance).transform; int num2 = Physics.RaycastNonAlloc(transform.position, transform.forward, _buildHover, 50f, num); if (num2 >= _buildHover.Length) { return; } float num3 = float.MaxValue; RaycastHit? val = null; for (int i = 0; i < num2 && i < _buildHover.Length; i++) { RaycastHit value = _buildHover[i]; if (!((Object)(object)((RaycastHit)(ref value)).collider == (Object)null)) { Rigidbody attachedRigidbody = ((RaycastHit)(ref value)).collider.attachedRigidbody; if ((!((Object)(object)attachedRigidbody != (Object)null) || !((Object)(object)((Component)attachedRigidbody).gameObject == (Object)(object)((Component)localPlayer).gameObject)) && ((RaycastHit)(ref value)).distance < num3) { num3 = ((RaycastHit)(ref value)).distance; val = value; } } } if (!val.HasValue) { return; } RaycastHit value2 = val.Value; LadderSegment componentInParent = ((Component)((RaycastHit)(ref value2)).collider).GetComponentInParent<LadderSegment>(); if (!((Object)(object)componentInParent == (Object)null)) { Vector3 eyePoint = ((Character)localPlayer).GetEyePoint(); value2 = val.Value; if (!(Vector3.Distance(eyePoint, ((RaycastHit)(ref value2)).point) >= localPlayer.m_maxInteractDistance)) { Use(componentInParent); } } } catch (Exception exception) { Fault(exception); } } private static bool CrouchPressed() { if (!ZInput.GetButtonDown("Crouch")) { return ZInput.GetButtonDown("JoyCrouch"); } return true; } private static bool CrouchHeld() { if (!ZInput.GetButton("Crouch")) { return ZInput.GetButton("JoyCrouch"); } return true; } internal void Controls(Player player, Vector3 movedir, ref bool jump, ref bool crouch) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (!IsClimbing || (Object)(object)_player == (Object)null || player != _player) { return; } try { bool num = GameplayFocus(player); if (!num) { _drop.Observe(CrouchHeld(), gameplayFocus: false); } ClimbAction climbAction = ClimbControls.Resolve(drop: num && _drop.Take(crouch), gameplayFocus: num, jump: jump, use: false, buildModeOwnsUse: false); jump = false; crouch = false; switch (climbAction) { case ClimbAction.Jump: JumpOff(player, CameraRelative(player, movedir)); break; case ClimbAction.Drop: DropOff(player); break; } } catch (Exception exception) { Fault(exception); } } private static Vector3 CameraRelative(Player player, Vector3 movedir) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) Vector3 lookDir = ((Character)player).GetLookDir(); lookDir.y = 0f; if (!LadderSegment.Finite(movedir) || !LadderSegment.Finite(lookDir) || ((Vector3)(ref lookDir)).sqrMagnitude < 1E-06f) { return Vector3.zero; } ((Vector3)(ref lookDir)).Normalize(); return movedir.z * lookDir + movedir.x * Vector3.Cross(Vector3.up, lookDir); } private bool GameplayFocus(Player player) { if (_motor.TakesInput(player) && !InventoryGui.IsVisible() && !Hud.InRadial()) { return !Hud.IsPieceSelectionVisible(); } return false; } internal bool SuppressInteract() { if (Time.frameCount == _departFrame) { return true; } if (!IsClimbing || (Object)(object)_player == (Object)null || !GameplayFocus(_player)) { return false; } if (!JumpPressed()) { if (CrouchPressed()) { if (!_drop.Armed) { return _drop.Pending; } return true; } return false; } return true; } private static bool HoversInteractable(Player player) { GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject != (Object)null) { return hoverObject.GetComponentInParent<Interactable>() != null; } return false; } internal bool InReach(LadderSegment segment) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)segment == (Object)null) { return false; } if (segment != _hoverSegment || _hoverRevision != LadderSegment.Revision || Time.time - _hoverTime > 0.5f) { _hoverSegment = segment; _hoverRevision = LadderSegment.Revision; _hoverTime = Time.time; _hoverValid = LadderSegment.TryRun(segment, _hoverParts, out _hoverRun); _hoverParts.Clear(); } CapsuleCollider collider = ((Character)localPlayer).GetCollider(); if (!_hoverValid || (Object)(object)collider == (Object)null) { return false; } float capsuleHeight = collider.height * Mathf.Abs(((Component)collider).transform.lossyScale.y); return ClimbPolicy.InReach(Reach(in _hoverRun, segment, ((Component)localPlayer).transform.position, capsuleHeight, ((Character)localPlayer).IsOnGround() && !StandsOnLadder(localPlayer))); } catch { return false; } } private static float Reach(in LadderGeometry geometry, LadderSegment segment, Vector3 feet, float capsuleHeight, bool supported) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_0033: Unknown result type (might be due to invalid IL or missing references) Vector3 point = feet + Vector3.up * (capsuleHeight * 0.72f); float planeDepth = (segment.BackFaceDepth + segment.FrontFaceDepth) * 0.5f; return ClimbPolicy.MountReach(in geometry, LadderSegment.Point(feet), LadderSegment.Point(point), planeDepth, segment.RungPitch * 0.5f, supported); } internal bool Use(LadderSegment segment) { //IL_017b: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } if (!_motor.Ready) { Notice("$cl_msg_startup"); return true; } if (!Enabled) { Notice("$cl_msg_disabled"); return true; } if (!_motor.TakesInput(localPlayer)) { return false; } if (IsClimbing) { if (Time.frameCount != _mountFrame && !JumpPressed() && !TrySafeDismount()) { Notice("$cl_msg_nolanding"); } return true; } if (JumpPressed()) { return true; } _foremanConflict = _motor.HasForemanMotor(); if (_foremanConflict) { Notice("$cl_msg_foreman"); return true; } Rigidbody component = ((Component)localPlayer).GetComponent<Rigidbody>(); ZNetView component2 = ((Component)localPlayer).GetComponent<ZNetView>(); CapsuleCollider collider = ((Character)localPlayer).GetCollider(); if ((Object)(object)component == (Object)null || component.isKinematic || (Object)(object)component2 == (Object)null || !component2.IsValid() || (Object)(object)collider == (Object)null || !ClimbPolicy.MayAdmit(component2.IsOwner(), Free(localPlayer), overlayOpen: false, _cooldown)) { Notice("$cl_msg_unavailable"); return true; } if (!LadderSegment.TryRun(segment, _segments, out var run)) { Notice("$cl_msg_invalidrun"); return true; } LadderClearance ladderClearance = new LadderClearance(localPlayer, collider, component.position); ladderClearance.SetRun(_segments); if (!TryPlanMount(localPlayer, component, ladderClearance, in run, segment, out Plan plan, out ClimbRefusal refusal, out string detail)) { Refuse(refusal, ladderClearance); _log("Ladder mount refused: " + refusal.ToString() + " (" + detail + ")."); _segments.Clear(); return true; } Commit(localPlayer, component, component2, ladderClearance, in run, segment, plan); return true; } catch (Exception exception) { Fault(exception); return true; } } private bool TryPlanMount(Player player, Rigidbody body, LadderClearance clearance, in LadderGeometry run, LadderSegment segment, out Plan plan, out ClimbRefusal refusal, out string detail) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: 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_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_03c3: Unknown result type (might be due to invalid IL or missing references) //IL_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_044a: Unknown