using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; 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 HarmonyLib; using Microsoft.CodeAnalysis; using Pigeon.Math; using UnityEngine; [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("Sparroh")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.5.0")] [assembly: AssemblyInformationalVersion("1.0.5")] [assembly: AssemblyProduct("MissionSelectCleanup")] [assembly: AssemblyTitle("MissionSelectCleanup")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.5.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public static class ConfigManager { private const float DebounceSeconds = 0.25f; private const int SettingsSchemaVersion = 5; private static ConfigFile config; private static ManualLogSource logger; private static FileSystemWatcher configWatcher; private static volatile bool reloadPending; private static float lastReloadTime; public static ConfigEntry EnableFeature { get; private set; } public static ConfigEntry Tightness { get; private set; } public static ConfigEntry NeighborAngle { get; private set; } public static ConfigEntry CoreSearchRadius { get; private set; } public static ConfigEntry MinTrianglesPerRegion { get; private set; } public static ConfigEntry MaxTrianglesPerRegion { get; private set; } public static ConfigEntry FallbackMode { get; private set; } public static ConfigEntry HideSpecialMissionsFromMap { get; private set; } public static ConfigEntry AlwaysHideNameFilters { get; private set; } public static ConfigEntry HideWhenCompletedNameFilters { get; private set; } public static ConfigEntry SpecialMissionNameFilters { get; private set; } public static ConfigEntry DebugLog { get; private set; } private static ConfigEntry SchemaVersion { get; set; } public static bool Enabled { get { if (EnableFeature != null) { return EnableFeature.Value; } return true; } } public static void Initialize(ConfigFile configFile, ManualLogSource log) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Expected O, but got Unknown config = configFile; logger = log; SchemaVersion = config.Bind("Internal", "Settings Schema Version", 0, "Internal. Bumped when defaults change so old cfg values are migrated."); EnableFeature = config.Bind("General", "Enable Feature", true, "When enabled, mission icons cluster in the densest pocket of each region mask."); Tightness = config.Bind("General", "Tightness", 0.92f, new ConfigDescription("How small the dense core is. 0 = use most of the region mask, 1 = Max Triangles only (squared curve).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); NeighborAngle = config.Bind("General", "Neighbor Angle", 22f, new ConfigDescription("Degrees between triangle centers to count as connected (diagnostics / seed blob). Lower splits long chains.", (AcceptableValueBase)(object)new AcceptableValueRange(8f, 60f), Array.Empty())); CoreSearchRadius = config.Bind("General", "Core Search Radius", 28f, new ConfigDescription("Degrees used when scoring local density to find the best pocket center.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 90f), Array.Empty())); MinTrianglesPerRegion = config.Bind("General", "Min Triangles Per Region", 10, new ConfigDescription("Never keep fewer than this many core triangles (8 missions + specials).", (AcceptableValueBase)(object)new AcceptableValueRange(8, 64), Array.Empty())); MaxTrianglesPerRegion = config.Bind("General", "Max Triangles Per Region", 10, new ConfigDescription("At high tightness, core size caps here. Lower = denser piles (10 ≈ just enough for a full roll).", (AcceptableValueBase)(object)new AcceptableValueRange(8, 64), Array.Empty())); FallbackMode = config.Bind("General", "Fallback Mode", "Region", new ConfigDescription("If the dense core is full: Region = stay in core only (no spill). Planet = vanilla any free globe triangle. Expand is treated like Region (no multi-patch spill).", (AcceptableValueBase)(object)new AcceptableValueList(new string[3] { "Region", "Planet", "Expand" }), Array.Empty())); HideSpecialMissionsFromMap = config.Bind("General", "Hide Special Missions From Map", true, "When enabled, filtered specials are removed from the 3D globe (side panel unchanged). Always-hide + hide-when-completed lists apply."); AlwaysHideNameFilters = config.Bind("General", "Always Hide Name Filters", "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation", "Comma-separated substrings (mission ID/name/type). Always removed from the globe when Hide Special Missions From Map is on."); HideWhenCompletedNameFilters = config.Bind("General", "Hide When Completed Name Filters", "containment,ground zero,oxythane breach escalation,oxythane breach", "Comma-separated substrings. Removed from the globe only after PlayerData.HasCompletedMission is true (e.g. Ground Zero, Containment)."); SpecialMissionNameFilters = config.Bind("General", "Special Mission Name Filters", "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation", "Legacy always-hide list. Prefer Always Hide Name Filters; still used as fallback if that is empty."); DebugLog = config.Bind("General", "Debug Log", false, "Extra cluster-build and hide diagnostics."); MigrateDefaultsIfNeeded(); try { SetupFileWatcher(); } catch (Exception ex) { logger.LogError((object)("Error setting up config file watcher: " + ex.Message)); } } private static void MigrateDefaultsIfNeeded() { if (SchemaVersion.Value < 5) { logger.LogInfo((object)$"Migrating MissionSelectCleanup settings schema {SchemaVersion.Value} → {5}."); if (SchemaVersion.Value < 3) { Tightness.Value = 0.92f; NeighborAngle.Value = 22f; CoreSearchRadius.Value = 28f; MinTrianglesPerRegion.Value = 10; MaxTrianglesPerRegion.Value = 10; FallbackMode.Value = "Region"; } if (SchemaVersion.Value < 4) { AlwaysHideNameFilters.Value = "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation"; HideWhenCompletedNameFilters.Value = "containment,ground zero,oxythane breach escalation,oxythane breach"; SpecialMissionNameFilters.Value = "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation"; HideSpecialMissionsFromMap.Value = true; } SchemaVersion.Value = 5; try { config.Save(); } catch (Exception ex) { logger.LogWarning((object)("Could not save migrated config: " + ex.Message)); } RegionClusterCache.Invalidate(); SpecialMissionFilter.InvalidateCache(); } } public static void Tick() { if (!reloadPending || Time.unscaledTime - lastReloadTime < 0.25f) { return; } reloadPending = false; lastReloadTime = Time.unscaledTime; try { config.Reload(); RegionClusterCache.Invalidate(); SpecialMissionFilter.InvalidateCache(); logger.LogInfo((object)"Config reloaded from disk."); } catch (Exception ex) { logger.LogError((object)("Error reloading config: " + ex.Message)); } } public static void Dispose() { if (configWatcher != null) { configWatcher.EnableRaisingEvents = false; configWatcher.Changed -= OnConfigFileChanged; configWatcher.Created -= OnConfigFileChanged; configWatcher.Renamed -= OnConfigFileChanged; configWatcher.Dispose(); configWatcher = null; } } private static void SetupFileWatcher() { configWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.missionselectcleanup.cfg"); configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite; configWatcher.Changed += OnConfigFileChanged; configWatcher.Created += OnConfigFileChanged; configWatcher.Renamed += OnConfigFileChanged; configWatcher.EnableRaisingEvents = true; } private static void OnConfigFileChanged(object sender, FileSystemEventArgs e) { reloadPending = true; } } [HarmonyPatch(typeof(MissionSelectWindow))] internal static class FindUnusedMissionIndexPatch { [HarmonyPrefix] [HarmonyPatch("FindUnusedMissionIndex")] private static bool FindUnusedMissionIndex_Prefix(MissionSelectWindow __instance, PlanetData planet, ref Random rand, WorldRegion region, ref int __result) { if (!ConfigManager.Enabled) { return true; } if (planet == null || region == null || planet.activeMissions == null) { return true; } if (RegionClusterCache.TryPickTriangle(planet, region, ref rand, out var triangleIndex) && triangleIndex >= 0) { __result = triangleIndex; return false; } if (string.Equals(ConfigManager.FallbackMode?.Value ?? "Region", "Planet", StringComparison.OrdinalIgnoreCase)) { return true; } __result = FindAnyFreeTriangle(planet, ref rand); ConfigEntry debugLog = ConfigManager.DebugLog; if (debugLog != null && debugLog.Value) { ManualLogSource logger = MissionSelectCleanupPlugin.Logger; if (logger != null) { logger.LogWarning((object)$"Dense core full for region '{((region != null) ? region.NameID : null)}'; emergency free tri={__result} (set Fallback Mode=Planet to make this intentional)."); } } return false; } private static int FindAnyFreeTriangle(PlanetData planet, ref Random rand) { int triangleCount = planet.activeMissions.TriangleCount; Span span = ((triangleCount > 512) ? ((Span)new int[triangleCount]) : stackalloc int[triangleCount]); Span span2 = span; int num = span2.Length; for (int i = 0; i < span2.Length; i++) { span2[i] = i; } while (num > 0) { int index = ((Random)(ref rand)).Next(num); int num2 = span2[index]; ActiveMission data = planet.activeMissions.GetData(num2); if (data != null && (Object)(object)data.Button == (Object)null) { return num2; } span2[index] = span2[num - 1]; num--; } return -1; } } internal static class HideSpecialMissionsPatch { private static MethodInfo clearMissionMethod; private static FieldInfo planetsField; private static bool loggedMissingClear; public static void Apply(Harmony harmony) { //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected O, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Expected O, but got Unknown //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Expected O, but got Unknown clearMissionMethod = AccessTools.Method(typeof(MissionSelectWindow), "ClearMission", new Type[2] { typeof(PlanetData), typeof(int) }, (Type[])null); planetsField = AccessTools.Field(typeof(MissionSelectWindow), "planets"); MethodInfo methodInfo = AccessTools.Method(typeof(MissionSelectWindow), "AddMission", new Type[8] { typeof(Mission), typeof(int), typeof(WorldRegion), typeof(Transform), typeof(object), typeof(bool).MakeByRefType(), typeof(bool), typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(MissionSelectWindow), "AddMission", new Type[3] { typeof(MissionData), typeof(object), typeof(bool) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(MissionSelectWindow), "SetupMissions", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(MissionSelectWindow), "Setup", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(HideSpecialMissionsPatch), "AddMission_Mission_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ManualLogSource logger = MissionSelectCleanupPlugin.Logger; if (logger != null) { logger.LogInfo((object)"Patched AddMission(Mission, ...)"); } } else { ManualLogSource logger2 = MissionSelectCleanupPlugin.Logger; if (logger2 != null) { logger2.LogError((object)"Could not find AddMission(Mission, ...)."); } } if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(HideSpecialMissionsPatch), "AddMission_MissionData_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ManualLogSource logger3 = MissionSelectCleanupPlugin.Logger; if (logger3 != null) { logger3.LogInfo((object)"Patched AddMission(MissionData, ...)"); } } else { ManualLogSource logger4 = MissionSelectCleanupPlugin.Logger; if (logger4 != null) { logger4.LogError((object)"Could not find AddMission(MissionData, ...)."); } } if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(HideSpecialMissionsPatch), "Setup_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ManualLogSource logger5 = MissionSelectCleanupPlugin.Logger; if (logger5 != null) { logger5.LogInfo((object)"Patched Setup(bool) for map cleanup"); } } else if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(HideSpecialMissionsPatch), "SetupMissions_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ManualLogSource logger6 = MissionSelectCleanupPlugin.Logger; if (logger6 != null) { logger6.LogInfo((object)"Patched SetupMissions for map cleanup"); } } else { ManualLogSource logger7 = MissionSelectCleanupPlugin.Logger; if (logger7 != null) { logger7.LogError((object)"Could not find Setup/SetupMissions for map cleanup."); } } if (clearMissionMethod == null) { ManualLogSource logger8 = MissionSelectCleanupPlugin.Logger; if (logger8 != null) { logger8.LogError((object)"Could not find ClearMission(PlanetData, int)."); } } } private static bool AddMission_Mission_Prefix(Mission mission) { if (!ConfigManager.Enabled) { return true; } SpecialMissionFilter.HideReason hideReason = SpecialMissionFilter.GetHideReason(mission); if (hideReason == SpecialMissionFilter.HideReason.None) { return true; } ConfigEntry debugLog = ConfigManager.DebugLog; if (debugLog != null && debugLog.Value) { ManualLogSource logger = MissionSelectCleanupPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[MapHide] Blocked AddMission {SpecialMissionFilter.Describe(mission)} ({hideReason})"); } } return false; } private static bool AddMission_MissionData_Prefix(MissionData mission) { if (!ConfigManager.Enabled) { return true; } Mission mission2 = ((MissionData)(ref mission)).Mission; SpecialMissionFilter.HideReason hideReason = SpecialMissionFilter.GetHideReason(mission2); if (hideReason == SpecialMissionFilter.HideReason.None) { return true; } ConfigEntry debugLog = ConfigManager.DebugLog; if (debugLog != null && debugLog.Value) { ManualLogSource logger = MissionSelectCleanupPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[MapHide] Blocked AddMission(Data) {SpecialMissionFilter.Describe(mission2)} ({hideReason})"); } } return false; } private static void Setup_Postfix(MissionSelectWindow __instance) { CleanupFilteredPins(__instance); } private static void SetupMissions_Postfix(MissionSelectWindow __instance) { CleanupFilteredPins(__instance); } private static void CleanupFilteredPins(MissionSelectWindow window) { if (!ConfigManager.Enabled || (Object)(object)window == (Object)null) { return; } ConfigEntry hideSpecialMissionsFromMap = ConfigManager.HideSpecialMissionsFromMap; if ((hideSpecialMissionsFromMap != null && !hideSpecialMissionsFromMap.Value) || planetsField == null || !(planetsField.GetValue(window) is PlanetData[] array)) { return; } int num = 0; foreach (PlanetData val in array) { if (val == null || val.activeMissions == null) { continue; } int triangleCount = val.activeMissions.TriangleCount; for (int j = 0; j < triangleCount; j++) { ActiveMission data = val.activeMissions.GetData(j); if (data != null && !((Object)(object)data.Button == (Object)null)) { Mission val2 = null; try { val2 = ((MissionData)(ref data.Button.Mission)).Mission; } catch { continue; } if (val2 != null && SpecialMissionFilter.GetHideReason(val2) != SpecialMissionFilter.HideReason.None && TryClearMission(window, val, j)) { num++; } } } } if (num <= 0) { return; } ConfigEntry debugLog = ConfigManager.DebugLog; if (debugLog != null && debugLog.Value) { ManualLogSource logger = MissionSelectCleanupPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[MapHide] Cleared {num} filtered globe pin(s)."); } } } private static bool TryClearMission(MissionSelectWindow window, PlanetData planet, int index) { if (clearMissionMethod == null) { if (!loggedMissingClear) { loggedMissingClear = true; ManualLogSource logger = MissionSelectCleanupPlugin.Logger; if (logger != null) { logger.LogError((object)"ClearMission method missing; cannot free globe slots."); } } try { ActiveMission data = planet.activeMissions.GetData(index); if ((Object)(object)data?.Button != (Object)null) { ((Component)data.Button).gameObject.SetActive(false); } } catch { return false; } return true; } try { clearMissionMethod.Invoke(window, new object[2] { planet, index }); return true; } catch (Exception ex) { ManualLogSource logger2 = MissionSelectCleanupPlugin.Logger; if (logger2 != null) { logger2.LogWarning((object)("ClearMission failed: " + ex.Message)); } return false; } } } [BepInPlugin("sparroh.missionselectcleanup", "MissionSelectCleanup", "1.0.5")] [MycoMod(/*Could not decode attribute arguments.*/)] public class MissionSelectCleanupPlugin : BaseUnityPlugin { public const string PluginGUID = "sparroh.missionselectcleanup"; public const string PluginName = "MissionSelectCleanup"; public const string PluginVersion = "1.0.5"; internal static ManualLogSource Logger; private Harmony harmony; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; ConfigManager.Initialize(((BaseUnityPlugin)this).Config, Logger); try { harmony = new Harmony("sparroh.missionselectcleanup"); harmony.PatchAll(typeof(FindUnusedMissionIndexPatch)); HideSpecialMissionsPatch.Apply(harmony); Logger.LogInfo((object)(string.Format("{0} v{1} loaded. Enabled={2}, ", "MissionSelectCleanup", "1.0.5", ConfigManager.Enabled) + $"Tightness={ConfigManager.Tightness.Value:0.##}, MaxTris={ConfigManager.MaxTrianglesPerRegion.Value}, " + $"HideSpecials={ConfigManager.HideSpecialMissionsFromMap.Value}, " + "Fallback=" + ConfigManager.FallbackMode.Value)); } catch (Exception arg) { Logger.LogError((object)$"Error applying patches: {arg}"); } } private void Update() { ConfigManager.Tick(); } private void OnDestroy() { ConfigManager.Dispose(); RegionClusterCache.Invalidate(); SpecialMissionFilter.InvalidateCache(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } internal static class RegionClusterCache { private sealed class PlanetCache { public int TriangleCount; public int BiomeDataHash; public readonly Dictionary Regions = new Dictionary(8); } internal sealed class RegionSlots { public int[] SortedTriangles; public float[] SortedDots; public Vector3 Center; public int BiomeTriangleCount; public int ComponentCount; public int BlobTriangleCount; public float CoreRadiusDegrees; } private static readonly Dictionary CacheByPlanet = new Dictionary(4); private static bool loggedProof; private static ManualLogSource Log => MissionSelectCleanupPlugin.Logger; public static void Invalidate() { CacheByPlanet.Clear(); loggedProof = false; } public static bool TryPickTriangle(PlanetData planet, WorldRegion region, ref Random rand, out int triangleIndex) { triangleIndex = -1; if (planet == null || planet.activeMissions == null || region == null) { return false; } if (!TryGetRegionSlots(planet, region, out var slots) || slots.SortedTriangles == null || slots.SortedTriangles.Length == 0) { return false; } if (TryPickFree(planet, slots.SortedTriangles, slots.SortedTriangles.Length, ref rand, out triangleIndex)) { LogProofOnce(region, slots, slots.SortedTriangles.Length); return true; } string.Equals(ConfigManager.FallbackMode?.Value ?? "Expand", "Planet", StringComparison.OrdinalIgnoreCase); return false; } private static void LogProofOnce(WorldRegion region, RegionSlots slots, int keep) { if (!loggedProof) { loggedProof = true; string arg = ((region != null) ? region.NameID : "?"); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)($"Tight placement active: region '{arg}' core {keep}/{slots.BiomeTriangleCount} biome tris " + $"(components={slots.ComponentCount}, blob={slots.BlobTriangleCount}, " + $"coreRadius~{slots.CoreRadiusDegrees:0.#}°, tightness={ConfigManager.Tightness?.Value ?? 0f:0.##}).")); } } } private static bool TryPickFree(PlanetData planet, int[] sorted, int count, ref Random rand, out int triangleIndex) { triangleIndex = -1; count = Mathf.Clamp(count, 0, sorted.Length); if (count <= 0) { return false; } Span span = ((count > 256) ? ((Span)new int[count]) : stackalloc int[count]); Span span2 = span; int num = 0; for (int i = 0; i < count; i++) { int num2 = sorted[i]; if (IsTriangleFree(planet, num2)) { span2[num++] = num2; } } if (num <= 0) { return false; } triangleIndex = span2[((Random)(ref rand)).Next(num)]; return true; } private static bool IsTriangleFree(PlanetData planet, int tri) { if (tri < 0 || tri >= planet.activeMissions.TriangleCount) { return false; } ActiveMission data = planet.activeMissions.GetData(tri); if (data != null) { return (Object)(object)data.Button == (Object)null; } return false; } private static bool TryGetRegionSlots(PlanetData planet, WorldRegion region, out RegionSlots slots) { slots = null; Planet planetReference = planet.planetReference; if (planetReference == null) { return false; } if ((Object)(object)Global.Instance == (Object)null || Global.Instance.Regions == null) { return false; } int instanceID = ((Object)planetReference).GetInstanceID(); int triangleCount = planet.activeMissions.TriangleCount; int[] planetBiomeData = planetReference.PlanetBiomeData; int num = ((planetBiomeData != null) ? planetBiomeData.Length : 0); int num2 = HashSettings(); if (!CacheByPlanet.TryGetValue(instanceID, out var value) || value.TriangleCount != triangleCount || value.BiomeDataHash != (num ^ num2)) { value = BuildPlanetCache(planet, planetReference, triangleCount, planetBiomeData, num ^ num2); CacheByPlanet[instanceID] = value; } int num3 = Array.IndexOf(Global.Instance.Regions, region) + 1; if (num3 <= 0) { return false; } return value.Regions.TryGetValue(num3, out slots); } private static int HashSettings() { return ((((17 * 31 + (ConfigManager.Tightness?.Value ?? 0.9f).GetHashCode()) * 31 + (ConfigManager.MinTrianglesPerRegion?.Value ?? 10)) * 31 + (ConfigManager.MaxTrianglesPerRegion?.Value ?? 10)) * 31 + (ConfigManager.NeighborAngle?.Value ?? 22f).GetHashCode()) * 31 + (ConfigManager.CoreSearchRadius?.Value ?? 28f).GetHashCode(); } private static PlanetCache BuildPlanetCache(PlanetData planet, Planet planetAsset, int triCount, int[] biomeData, int biomeHash) { //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) PlanetCache planetCache = new PlanetCache { TriangleCount = triCount, BiomeDataHash = biomeHash }; Dictionary> dictionary = new Dictionary>(8); int num = ((biomeData != null) ? biomeData.Length : 0); if (num > 0) { int num2 = Math.Min(triCount, num); for (int i = 0; i < num2; i++) { int num3 = biomeData[i]; if (num3 > 0) { if (!dictionary.TryGetValue(num3, out var value)) { value = (dictionary[num3] = new List(32)); } value.Add(i); } } } if (dictionary.Count == 0 && Global.Instance?.Regions != null) { BuildSpatialPartitionBuckets(planet, planetAsset, triCount, dictionary); } else if (num > 0 && num < triCount) { ConfigEntry debugLog = ConfigManager.DebugLog; if (debugLog != null && debugLog.Value) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)$"PlanetBiomeData length ({num}) < triangle count ({triCount}); only tagged tris used."); } } } float neighborDot = Mathf.Cos((ConfigManager.NeighborAngle?.Value ?? 22f) * (MathF.PI / 180f)); float coreSearchDot = Mathf.Cos((ConfigManager.CoreSearchRadius?.Value ?? 28f) * (MathF.PI / 180f)); int minKeep = ConfigManager.MinTrianglesPerRegion?.Value ?? 10; int maxKeep = ConfigManager.MaxTrianglesPerRegion?.Value ?? 10; float num4 = Mathf.Clamp01(ConfigManager.Tightness?.Value ?? 0.9f); float tightnessSquared = num4 * num4; foreach (KeyValuePair> item2 in dictionary) { List value2 = item2.Value; if (value2.Count == 0) { continue; } List> list2 = BuildComponents(planet, value2, neighborDot); int count = list2.Count; List list3 = ((list2.Count > 0) ? list2[0] : value2); if (list2.Count > 1) { list2.Sort((List a, List b) => b.Count.CompareTo(a.Count)); list3 = list2[0]; } Vector3 center; float coreRadiusDeg; List list4 = SelectDenseCore(planet, value2, list3, coreSearchDot, minKeep, maxKeep, tightnessSquared, out center, out coreRadiusDeg); if (list4.Count == 0) { list4 = new List(list3); } List<(int, float)> list5 = new List<(int, float)>(list4.Count); for (int num5 = 0; num5 < list4.Count; num5++) { Vector3 center2 = planet.activeMissions.GetCenter(list4[num5]); float item = ((((Vector3)(ref center2)).sqrMagnitude > 1E-08f) ? Vector3.Dot(center, ((Vector3)(ref center2)).normalized) : (-1f)); list5.Add((list4[num5], item)); } list5.Sort(((int tri, float dot) a, (int tri, float dot) b) => b.dot.CompareTo(a.dot)); int[] array = new int[list5.Count]; float[] array2 = new float[list5.Count]; for (int num6 = 0; num6 < list5.Count; num6++) { array[num6] = list5[num6].Item1; array2[num6] = list5[num6].Item2; } RegionSlots value3 = new RegionSlots { SortedTriangles = array, SortedDots = array2, Center = center, BiomeTriangleCount = value2.Count, ComponentCount = count, BlobTriangleCount = list3.Count, CoreRadiusDegrees = coreRadiusDeg }; planetCache.Regions[item2.Key] = value3; ConfigEntry debugLog2 = ConfigManager.DebugLog; if (debugLog2 != null && debugLog2.Value) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)($"Region id {item2.Key}: biome={value2.Count}, components={count}, " + $"seedBlob={list3.Count}, core={array.Length}, coreRadius~{coreRadiusDeg:0.#}°, center={center}")); } } } if (planetCache.Regions.Count == 0) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)$"Built empty region cluster cache for planet '{((Object)planetAsset).name}' (triCount={triCount}, biomeLen={num})."); } } return planetCache; } private static List SelectDenseCore(PlanetData planet, List biomeTris, List seedPool, float coreSearchDot, int minKeep, int maxKeep, float tightnessSquared, out Vector3 center, out float coreRadiusDeg) { //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_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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) center = Vector3.forward; coreRadiusDeg = 0f; int count = biomeTris.Count; if (count == 0) { return new List(); } Vector3[] array = (Vector3[])(object)new Vector3[count]; Dictionary dictionary = new Dictionary(count); for (int i = 0; i < count; i++) { Vector3 center2 = planet.activeMissions.GetCenter(biomeTris[i]); array[i] = ((((Vector3)(ref center2)).sqrMagnitude > 1E-08f) ? ((Vector3)(ref center2)).normalized : Vector3.forward); dictionary[biomeTris[i]] = i; } int num = 0; int num2 = -1; float num3 = -1f; for (int j = 0; j < count; j++) { int num4 = 0; for (int k = 0; k < count; k++) { if (Vector3.Dot(array[j], array[k]) >= coreSearchDot) { num4++; } } bool flag = false; for (int l = 0; l < seedPool.Count; l++) { if (seedPool[l] == biomeTris[j]) { flag = true; break; } } int num5 = num4 * 2 + (flag ? 1 : 0); float num6 = 0f; for (int m = 0; m < count; m++) { num6 += Vector3.Dot(array[j], array[m]); } num6 /= (float)count; if (num5 > num2 || (num5 == num2 && num6 > num3)) { num2 = num5; num3 = num6; num = j; } } center = array[num]; maxKeep = Mathf.Clamp(maxKeep, 1, count); minKeep = Mathf.Clamp(minKeep, 1, count); int num7 = Mathf.RoundToInt(Mathf.Lerp((float)count, (float)maxKeep, tightnessSquared)); num7 = Mathf.Clamp(num7, minKeep, count); if (tightnessSquared >= 0.5f) { num7 = Mathf.Min(num7, maxKeep); } List<(int, float)> list = new List<(int, float)>(count); for (int n = 0; n < count; n++) { list.Add((n, Vector3.Dot(center, array[n]))); } list.Sort(((int local, float dot) a, (int local, float dot) b) => b.dot.CompareTo(a.dot)); List list2 = new List(num7); float num8 = 1f; for (int num9 = 0; num9 < num7; num9++) { list2.Add(biomeTris[list[num9].Item1]); num8 = list[num9].Item2; } Vector3 val = Vector3.zero; for (int num10 = 0; num10 < list2.Count; num10++) { int num11 = dictionary[list2[num10]]; val += array[num11]; } if (((Vector3)(ref val)).sqrMagnitude > 1E-08f) { center = ((Vector3)(ref val)).normalized; } num8 = Mathf.Clamp(num8, -1f, 1f); coreRadiusDeg = Mathf.Acos(num8) * 57.29578f; return list2; } private static List> BuildComponents(PlanetData planet, List biomeTris, float neighborDot) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0039: 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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) int count = biomeTris.Count; Vector3[] array = (Vector3[])(object)new Vector3[count]; for (int i = 0; i < count; i++) { Vector3 center = planet.activeMissions.GetCenter(biomeTris[i]); array[i] = ((((Vector3)(ref center)).sqrMagnitude > 1E-08f) ? ((Vector3)(ref center)).normalized : Vector3.forward); } List[] array2 = new List[count]; for (int j = 0; j < count; j++) { array2[j] = new List(6); } for (int k = 0; k < count; k++) { for (int l = k + 1; l < count; l++) { if (Vector3.Dot(array[k], array[l]) >= neighborDot) { array2[k].Add(l); array2[l].Add(k); } } } bool[] array3 = new bool[count]; List> list = new List>(8); Queue queue = new Queue(count); for (int m = 0; m < count; m++) { if (array3[m]) { continue; } List list2 = new List(8); array3[m] = true; queue.Enqueue(m); while (queue.Count > 0) { int num = queue.Dequeue(); list2.Add(biomeTris[num]); List list3 = array2[num]; for (int n = 0; n < list3.Count; n++) { int num2 = list3[n]; if (!array3[num2]) { array3[num2] = true; queue.Enqueue(num2); } } } list.Add(list2); } list.Sort((List a, List b) => b.Count.CompareTo(a.Count)); return list; } private static void BuildSpatialPartitionBuckets(PlanetData planet, Planet planetAsset, int triCount, Dictionary> buckets) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) WorldRegion[] regions = Global.Instance.Regions; for (int i = 0; i < regions.Length; i++) { if (regions[i] == null) { continue; } Planet planet2 = regions[i].Planet; if ((planet2 == null || planet2 == planetAsset) && (planet2 != null || planetAsset == GetDefaultPlanet())) { int key = i + 1; if (!buckets.ContainsKey(key)) { buckets[key] = new List(triCount / Math.Max(1, regions.Length) + 8); } } } if (buckets.Count == 0) { return; } int[] array = new int[buckets.Count]; buckets.Keys.CopyTo(array, 0); Array.Sort(array); Vector3[] array2 = (Vector3[])(object)new Vector3[array.Length]; for (int j = 0; j < array2.Length; j++) { float num = ((float)j + 0.5f) / (float)array2.Length * MathF.PI * 2f; float num2 = ((j % 2 == 0) ? 0.35f : (-0.35f)); int num3 = j; Vector3 val = new Vector3(Mathf.Cos(num2) * Mathf.Cos(num), Mathf.Sin(num2), Mathf.Cos(num2) * Mathf.Sin(num)); array2[num3] = ((Vector3)(ref val)).normalized; } for (int k = 0; k < triCount; k++) { Vector3 center = planet.activeMissions.GetCenter(k); if (((Vector3)(ref center)).sqrMagnitude < 1E-08f) { continue; } ((Vector3)(ref center)).Normalize(); int num4 = 0; float num5 = float.NegativeInfinity; for (int l = 0; l < array2.Length; l++) { float num6 = Vector3.Dot(center, array2[l]); if (num6 > num5) { num5 = num6; num4 = l; } } buckets[array[num4]].Add(k); } ManualLogSource log = Log; if (log != null) { log.LogWarning((object)$"Planet '{((Object)planetAsset).name}' has no usable PlanetBiomeData; using spatial partition across {array.Length} regions."); } } private static Planet GetDefaultPlanet() { Global instance = Global.Instance; if (instance?.Planets != null && instance.Planets.Length != 0) { return instance.Planets[0]; } return null; } } internal static class SpecialMissionFilter { public enum HideReason { None, Always, Completed } public const string DefaultAlwaysHideFilters = "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation"; public const string DefaultHideWhenCompletedFilters = "containment,ground zero,oxythane breach escalation,oxythane breach"; public const string DefaultFilters = "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation"; private static string[] alwaysFilters = Array.Empty(); private static string[] completedFilters = Array.Empty(); private static string alwaysRaw; private static string completedRaw; public static bool ShouldHideFromGlobe(Mission mission) { return GetHideReason(mission) != HideReason.None; } public static bool ShouldHideFromGlobe(ref MissionData data) { return ShouldHideFromGlobe(((MissionData)(ref data)).Mission); } public static HideReason GetHideReason(Mission mission) { if (mission == null) { return HideReason.None; } ConfigEntry hideSpecialMissionsFromMap = ConfigManager.HideSpecialMissionsFromMap; if (hideSpecialMissionsFromMap != null && !hideSpecialMissionsFromMap.Value) { return HideReason.None; } EnsureCaches(); string name = ((object)mission).GetType().Name; string id = null; string displayName = null; try { id = mission.ID; } catch { } try { displayName = mission.MissionName; } catch { } if (!string.IsNullOrEmpty(name) && name.IndexOf("Amalgamation", StringComparison.OrdinalIgnoreCase) >= 0) { return HideReason.Always; } if (MatchesAny(alwaysFilters, id, displayName, name)) { return HideReason.Always; } if (MatchesAny(completedFilters, id, displayName, name) && HasCompleted(mission)) { return HideReason.Completed; } return HideReason.None; } public static string Describe(Mission mission) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) if (mission == null) { return "(null)"; } string text = "?"; string text2 = "?"; try { text = mission.ID ?? "?"; } catch { } try { text2 = mission.MissionName ?? "?"; } catch { } return $"id='{text}' name='{text2}' type={((object)mission).GetType().Name} flags={mission.MissionFlags}"; } private static bool HasCompleted(Mission mission) { try { PlayerData instance = PlayerData.Instance; if (instance == null) { return false; } return instance.HasCompletedMission(mission); } catch { return false; } } private static bool MatchesAny(string[] filters, string id, string displayName, string typeName) { if (filters == null || filters.Length == 0) { return false; } foreach (string text in filters) { if (text.Length != 0) { if (!string.IsNullOrEmpty(id) && id.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (!string.IsNullOrEmpty(displayName) && displayName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (!string.IsNullOrEmpty(typeName) && typeName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } return false; } private static void EnsureCaches() { string text = ConfigManager.AlwaysHideNameFilters?.Value ?? ConfigManager.SpecialMissionNameFilters?.Value ?? "ouroboros,ouroboros blitz,incursion,weekly overtime,overtime,amalgamation hunt,amalgamation"; string text2 = ConfigManager.HideWhenCompletedNameFilters?.Value ?? "containment,ground zero,oxythane breach escalation,oxythane breach"; if (text != alwaysRaw) { alwaysRaw = text; alwaysFilters = ParseFilters(text); } if (text2 != completedRaw) { completedRaw = text2; completedFilters = ParseFilters(text2); } } private static string[] ParseFilters(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return Array.Empty(); } string[] array = raw.Split(new char[3] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries); List list = new List(array.Length); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } list.Sort((string x, string y) => y.Length.CompareTo(x.Length)); return list.ToArray(); } public static void InvalidateCache() { alwaysRaw = null; completedRaw = null; alwaysFilters = Array.Empty(); completedFilters = Array.Empty(); } } namespace MissionSelectCleanup { public static class MyPluginInfo { public const string PLUGIN_GUID = "MissionSelectCleanup"; public const string PLUGIN_NAME = "MissionSelectCleanup"; public const string PLUGIN_VERSION = "1.0.5"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }