Decompiled source of FixDryCactus v0.1.1

plugins/com.github.LengSword.FixDryCactus.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("com.github.LengSword.FixDryCactus")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+915c1b926707c61c53632fbef3c33f425434c7b8")]
[assembly: AssemblyProduct("com.github.LengSword.FixDryCactus")]
[assembly: AssemblyTitle("FixDryCactus")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://codeberg.org/yls-peak-mods/FixDryCactus")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace FixDryCactus
{
	[BepInPlugin("com.github.LengSword.FixDryCactus", "FixDryCactus", "0.1.1")]
	public class Plugin : BaseUnityPlugin
	{
		private const string MapRootName = "Map";

		private const string DesertSegmentName = "Desert_Segment";

		private const int FixedLodIndex = 1;

		private const float BiomeCheckIntervalSeconds = 1f;

		private const float ScanRetryIntervalSeconds = 1f;

		private const float ScanBackoffIntervalSeconds = 5f;

		private const int FastScanRetryLimit = 10;

		private static readonly string[] DryCactusRelativePaths = new string[3] { "Wall/Props/CacusHell/Cactus_Big_Dry", "Wall/Props/Cactus_Big_Dry", "Platteau/Props/Cactus_Big_Dry" };

		private readonly Dictionary<int, LODGroup> forcedLodGroups = new Dictionary<int, LODGroup>();

		private readonly HashSet<int> appliedInCurrentMesa = new HashSet<int>();

		private readonly HashSet<int> previousScanInstanceIds = new HashSet<int>();

		private bool wasInMesa;

		private bool scanStable;

		private bool hasLoggedDiscoveryFailure;

		private int failedScanAttempts;

		private float nextBiomeCheckTime;

		private float nextScanTime;

		public const string Id = "com.github.LengSword.FixDryCactus";

		internal static ManualLogSource Logger { get; private set; } = null;

		public static string Name => "FixDryCactus";

		public static string Version => "0.1.1";

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Logger.LogInfo((object)("Plugin " + Name + " is loaded!"));
			PluginConfig.Initialize(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger);
		}

		private void Update()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			if (Time.unscaledTime < nextBiomeCheckTime)
			{
				return;
			}
			nextBiomeCheckTime = Time.unscaledTime + 1f;
			bool flag = PruneDestroyedForcedLodGroups();
			if (!PluginConfig.EnableAutomaticFix.Value)
			{
				RestoreAutomaticLod();
				wasInMesa = false;
				ResetMesaScanState();
				return;
			}
			if ((int)GetCurrentBiome().GetValueOrDefault() != 6)
			{
				if (wasInMesa)
				{
					ResetMesaScanState();
				}
				wasInMesa = false;
				return;
			}
			if (!wasInMesa)
			{
				wasInMesa = true;
				ResetMesaScanState();
			}
			if (flag && scanStable)
			{
				scanStable = false;
				previousScanInstanceIds.Clear();
				nextScanTime = Time.unscaledTime;
			}
			if (!scanStable && !(Time.unscaledTime < nextScanTime))
			{
				ScanAndApplyDryCactusFix();
			}
		}

		private void ScanAndApplyDryCactusFix()
		{
			Dictionary<int, LODGroup> dictionary = FindDryCactusLodGroups();
			if (dictionary.Count == 0)
			{
				failedScanAttempts++;
				previousScanInstanceIds.Clear();
				float num = ((failedScanAttempts <= 10) ? 1f : 5f);
				nextScanTime = Time.unscaledTime + num;
				if (!hasLoggedDiscoveryFailure)
				{
					Logger.LogWarning((object)"Dry cactus LOD groups are not ready in MESA; discovery will be retried.");
					hasLoggedDiscoveryFailure = true;
				}
				else
				{
					Logger.LogDebug((object)$"Dry cactus discovery attempt {failedScanAttempts} found no LOD groups; retrying in {num:0.#} second(s).");
				}
				return;
			}
			failedScanAttempts = 0;
			int num2 = 0;
			foreach (var (num4, val2) in dictionary)
			{
				forcedLodGroups[num4] = val2;
				if (appliedInCurrentMesa.Add(num4))
				{
					val2.ForceLOD(1);
					num2++;
				}
			}
			if (num2 > 0)
			{
				Logger.LogInfo((object)$"Applied the dry cactus fix to {num2} new LOD group(s); tracking {forcedLodGroups.Count} total group(s).");
			}
			HashSet<int> hashSet = new HashSet<int>();
			foreach (int key in dictionary.Keys)
			{
				hashSet.Add(key);
			}
			HashSet<int> hashSet2 = hashSet;
			if (previousScanInstanceIds.Count > 0 && previousScanInstanceIds.SetEquals(hashSet2))
			{
				scanStable = true;
				Logger.LogDebug((object)$"Dry cactus discovery stabilized at {hashSet2.Count} LOD group(s).");
			}
			else
			{
				previousScanInstanceIds.Clear();
				previousScanInstanceIds.UnionWith(hashSet2);
				nextScanTime = Time.unscaledTime + 1f;
			}
		}

		private static Dictionary<int, LODGroup> FindDryCactusLodGroups()
		{
			Dictionary<int, LODGroup> dictionary = new Dictionary<int, LODGroup>();
			GameObject val = GameObject.Find("Map");
			if ((Object)(object)val == (Object)null)
			{
				return dictionary;
			}
			Transform[] componentsInChildren = val.GetComponentsInChildren<Transform>(true);
			foreach (Transform val2 in componentsInChildren)
			{
				if (!string.Equals(((Object)val2).name, "Desert_Segment", StringComparison.Ordinal))
				{
					continue;
				}
				string[] dryCactusRelativePaths = DryCactusRelativePaths;
				foreach (string text in dryCactusRelativePaths)
				{
					Transform val3 = val2.Find(text);
					if (!((Object)(object)val3 == (Object)null))
					{
						LODGroup[] componentsInChildren2 = ((Component)val3).GetComponentsInChildren<LODGroup>(true);
						foreach (LODGroup val4 in componentsInChildren2)
						{
							dictionary[((Object)val4).GetInstanceID()] = val4;
						}
					}
				}
			}
			return dictionary;
		}

		private bool PruneDestroyedForcedLodGroups()
		{
			List<int> list = new List<int>();
			foreach (var (item, val2) in forcedLodGroups)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					list.Add(item);
				}
			}
			foreach (int item2 in list)
			{
				forcedLodGroups.Remove(item2);
				appliedInCurrentMesa.Remove(item2);
			}
			return list.Count > 0;
		}

		private void RestoreAutomaticLod()
		{
			int num = 0;
			foreach (LODGroup value in forcedLodGroups.Values)
			{
				if (!((Object)(object)value == (Object)null))
				{
					value.ForceLOD(-1);
					num++;
				}
			}
			forcedLodGroups.Clear();
			appliedInCurrentMesa.Clear();
			if (num > 0)
			{
				Logger.LogInfo((object)$"Restored automatic LOD selection on {num} dry cactus LOD group(s).");
			}
		}

		private void ResetMesaScanState()
		{
			appliedInCurrentMesa.Clear();
			previousScanInstanceIds.Clear();
			scanStable = false;
			hasLoggedDiscoveryFailure = false;
			failedScanAttempts = 0;
			nextScanTime = Time.unscaledTime;
		}

		private static BiomeType? GetCurrentBiome()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				MountainProgressHandler instance = Singleton<MountainProgressHandler>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				ProgressPoint[] progressPoints = instance.progressPoints;
				if (progressPoints == null || progressPoints.Length == 0)
				{
					return null;
				}
				int num = Mathf.Clamp(instance.maxProgressPointReached, 0, progressPoints.Length - 1);
				return progressPoints[num].biome;
			}
			catch (Exception ex)
			{
				Logger.LogDebug((object)("Failed to resolve current biome: " + ex.Message));
				return null;
			}
		}
	}
	internal sealed class PluginConfig
	{
		internal static ConfigEntry<bool> EnableAutomaticFix { get; private set; }

		public static void Initialize(ConfigFile config, ManualLogSource logger)
		{
			EnableAutomaticFix = config.Bind<bool>("General", "EnableAutomaticFix", true, "Automatically apply the dry cactus LOD fix when entering MESA.");
			logger.LogInfo((object)"Plugin Config Loaded.");
		}
	}
}
namespace System.Diagnostics.CodeAnalysis
{
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ConstantExpectedAttribute : Attribute
	{
		public object? Min { get; set; }

		public object? Max { get; set; }
	}
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ExperimentalAttribute : Attribute
	{
		public string DiagnosticId { get; }

		public string? UrlFormat { get; set; }

		public ExperimentalAttribute(string diagnosticId)
		{
			DiagnosticId = diagnosticId;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullAttribute : Attribute
	{
		public string[] Members { get; }

		public MemberNotNullAttribute(string member)
		{
			Members = new string[1] { member };
		}

		public MemberNotNullAttribute(params string[] members)
		{
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullWhenAttribute : Attribute
	{
		public bool ReturnValue { get; }

		public string[] Members { get; }

		public MemberNotNullWhenAttribute(bool returnValue, string member)
		{
			ReturnValue = returnValue;
			Members = new string[1] { member };
		}

		public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
		{
			ReturnValue = returnValue;
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SetsRequiredMembersAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class StringSyntaxAttribute : Attribute
	{
		public const string CompositeFormat = "CompositeFormat";

		public const string DateOnlyFormat = "DateOnlyFormat";

		public const string DateTimeFormat = "DateTimeFormat";

		public const string EnumFormat = "EnumFormat";

		public const string GuidFormat = "GuidFormat";

		public const string Json = "Json";

		public const string NumericFormat = "NumericFormat";

		public const string Regex = "Regex";

		public const string TimeOnlyFormat = "TimeOnlyFormat";

		public const string TimeSpanFormat = "TimeSpanFormat";

		public const string Uri = "Uri";

		public const string Xml = "Xml";

		public string Syntax { get; }

		public object?[] Arguments { get; }

		public StringSyntaxAttribute(string syntax)
		{
			Syntax = syntax;
			Arguments = new object[0];
		}

		public StringSyntaxAttribute(string syntax, params object?[] arguments)
		{
			Syntax = syntax;
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class UnscopedRefAttribute : Attribute
	{
	}
}
namespace System.Runtime.Versioning
{
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresPreviewFeaturesAttribute : Attribute
	{
		public string? Message { get; }

		public string? Url { get; set; }

		public RequiresPreviewFeaturesAttribute()
		{
		}

		public RequiresPreviewFeaturesAttribute(string? message)
		{
			Message = message;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CallerArgumentExpressionAttribute : Attribute
	{
		public string ParameterName { get; }

		public CallerArgumentExpressionAttribute(string parameterName)
		{
			ParameterName = parameterName;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CollectionBuilderAttribute : Attribute
	{
		public Type BuilderType { get; }

		public string MethodName { get; }

		public CollectionBuilderAttribute(Type builderType, string methodName)
		{
			BuilderType = builderType;
			MethodName = methodName;
		}
	}
	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CompilerFeatureRequiredAttribute : Attribute
	{
		public const string RefStructs = "RefStructs";

		public const string RequiredMembers = "RequiredMembers";

		public string FeatureName { get; }

		public bool IsOptional { get; set; }

		public CompilerFeatureRequiredAttribute(string featureName)
		{
			FeatureName = featureName;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
	{
		public string[] Arguments { get; }

		public InterpolatedStringHandlerArgumentAttribute(string argument)
		{
			Arguments = new string[1] { argument };
		}

		public InterpolatedStringHandlerArgumentAttribute(params string[] arguments)
		{
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerAttribute : Attribute
	{
	}
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal static class IsExternalInit
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ModuleInitializerAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class OverloadResolutionPriorityAttribute : Attribute
	{
		public int Priority { get; }

		public OverloadResolutionPriorityAttribute(int priority)
		{
			Priority = priority;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ParamCollectionAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiredMemberAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresLocationAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SkipLocalsInitAttribute : Attribute
	{
	}
}