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<bool> EnableFeature { get; private set; }
public static ConfigEntry<float> Tightness { get; private set; }
public static ConfigEntry<float> NeighborAngle { get; private set; }
public static ConfigEntry<float> CoreSearchRadius { get; private set; }
public static ConfigEntry<int> MinTrianglesPerRegion { get; private set; }
public static ConfigEntry<int> MaxTrianglesPerRegion { get; private set; }
public static ConfigEntry<string> FallbackMode { get; private set; }
public static ConfigEntry<bool> HideSpecialMissionsFromMap { get; private set; }
public static ConfigEntry<string> AlwaysHideNameFilters { get; private set; }
public static ConfigEntry<string> HideWhenCompletedNameFilters { get; private set; }
public static ConfigEntry<string> SpecialMissionNameFilters { get; private set; }
public static ConfigEntry<bool> DebugLog { get; private set; }
private static ConfigEntry<int> 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<int>("Internal", "Settings Schema Version", 0, "Internal. Bumped when defaults change so old cfg values are migrated.");
EnableFeature = config.Bind<bool>("General", "Enable Feature", true, "When enabled, mission icons cluster in the densest pocket of each region mask.");
Tightness = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>()));
NeighborAngle = config.Bind<float>("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<float>(8f, 60f), Array.Empty<object>()));
CoreSearchRadius = config.Bind<float>("General", "Core Search Radius", 28f, new ConfigDescription("Degrees used when scoring local density to find the best pocket center.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 90f), Array.Empty<object>()));
MinTrianglesPerRegion = config.Bind<int>("General", "Min Triangles Per Region", 10, new ConfigDescription("Never keep fewer than this many core triangles (8 missions + specials).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 64), Array.Empty<object>()));
MaxTrianglesPerRegion = config.Bind<int>("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<int>(8, 64), Array.Empty<object>()));
FallbackMode = config.Bind<string>("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<string>(new string[3] { "Region", "Planet", "Expand" }), Array.Empty<object>()));
HideSpecialMissionsFromMap = config.Bind<bool>("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<string>("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<string>("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<string>("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<bool>("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<bool> 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<int> span = ((triangleCount > 512) ? ((Span<int>)new int[triangleCount]) : stackalloc int[triangleCount]);
Span<int> 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<bool> 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<bool> 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<bool> 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<bool> 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<int, RegionSlots> Regions = new Dictionary<int, RegionSlots>(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<int, PlanetCache> CacheByPlanet = new Dictionary<int, PlanetCache>(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<int> span = ((count > 256) ? ((Span<int>)new int[count]) : stackalloc int[count]);
Span<int> 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<int, List<int>> dictionary = new Dictionary<int, List<int>>(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<int>(32));
}
value.Add(i);
}
}
}
if (dictionary.Count == 0 && Global.Instance?.Regions != null)
{
BuildSpatialPartitionBuckets(planet, planetAsset, triCount, dictionary);
}
else if (num > 0 && num < triCount)
{
ConfigEntry<bool> 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<int, List<int>> item2 in dictionary)
{
List<int> value2 = item2.Value;
if (value2.Count == 0)
{
continue;
}
List<List<int>> list2 = BuildComponents(planet, value2, neighborDot);
int count = list2.Count;
List<int> list3 = ((list2.Count > 0) ? list2[0] : value2);
if (list2.Count > 1)
{
list2.Sort((List<int> a, List<int> b) => b.Count.CompareTo(a.Count));
list3 = list2[0];
}
Vector3 center;
float coreRadiusDeg;
List<int> list4 = SelectDenseCore(planet, value2, list3, coreSearchDot, minKeep, maxKeep, tightnessSquared, out center, out coreRadiusDeg);
if (list4.Count == 0)
{
list4 = new List<int>(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<bool> 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<int> SelectDenseCore(PlanetData planet, List<int> biomeTris, List<int> 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<int>();
}
Vector3[] array = (Vector3[])(object)new Vector3[count];
Dictionary<int, int> dictionary = new Dictionary<int, int>(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<int> list2 = new List<int>(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<List<int>> BuildComponents(PlanetData planet, List<int> 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<int>[] array2 = new List<int>[count];
for (int j = 0; j < count; j++)
{
array2[j] = new List<int>(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<int>> list = new List<List<int>>(8);
Queue<int> queue = new Queue<int>(count);
for (int m = 0; m < count; m++)
{
if (array3[m])
{
continue;
}
List<int> list2 = new List<int>(8);
array3[m] = true;
queue.Enqueue(m);
while (queue.Count > 0)
{
int num = queue.Dequeue();
list2.Add(biomeTris[num]);
List<int> 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<int> a, List<int> b) => b.Count.CompareTo(a.Count));
return list;
}
private static void BuildSpatialPartitionBuckets(PlanetData planet, Planet planetAsset, int triCount, Dictionary<int, List<int>> 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<int>(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<string>();
private static string[] completedFilters = Array.Empty<string>();
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<bool> 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>();
}
string[] array = raw.Split(new char[3] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries);
List<string> list = new List<string>(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<string>();
completedFilters = Array.Empty<string>();
}
}
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)
{
}
}
}