Decompiled source of GuaranteedInteractables v0.2.1

GuaranteedInteractables.dll

Decompiled 18 hours ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using RoR2;
using UnityEngine;

[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace GuaranteedInteractables;

[BepInPlugin("com.goose.guaranteedinteractables", "Guaranteed Interactables", "0.2.1")]
public sealed class Plugin : BaseUnityPlugin
{
	private enum InteractableKind
	{
		WhitePrinter,
		GreenPrinter,
		RedPrinter,
		YellowPrinter,
		Scrapper,
		VoidCradle,
		GunnerDrone,
		HealingDrone,
		SmallChest,
		LargeChest,
		EquipmentChest,
		CategoryChest,
		ChanceShrine
	}

	public const string PluginGuid = "com.goose.guaranteedinteractables";

	public const string PluginName = "Guaranteed Interactables";

	public const string PluginVersion = "0.2.1";

	private readonly Dictionary<InteractableKind, DirectorCard> cards = new Dictionary<InteractableKind, DirectorCard>();

	private readonly Dictionary<InteractableKind, int> spawned = new Dictionary<InteractableKind, int>();

	private readonly Dictionary<InteractableKind, ConfigEntry<int>> minimums = new Dictionary<InteractableKind, ConfigEntry<int>>();

	private ConfigEntry<int> totalRandomDrones;

	private ConfigEntry<int> totalRandomPrinters;

	private ConfigEntry<int> totalRandomChests;

	private readonly Dictionary<InteractableKind, ConfigEntry<bool>> poolEnabled = new Dictionary<InteractableKind, ConfigEntry<bool>>();

	private static readonly InteractableKind[] PrinterKinds = new InteractableKind[4]
	{
		InteractableKind.WhitePrinter,
		InteractableKind.GreenPrinter,
		InteractableKind.RedPrinter,
		InteractableKind.YellowPrinter
	};

	private static readonly InteractableKind[] RandomChestKinds = new InteractableKind[4]
	{
		InteractableKind.SmallChest,
		InteractableKind.LargeChest,
		InteractableKind.EquipmentChest,
		InteractableKind.CategoryChest
	};

	private static readonly InteractableKind[] DroneKinds = new InteractableKind[2]
	{
		InteractableKind.GunnerDrone,
		InteractableKind.HealingDrone
	};

	private void Awake()
	{
		minimums[InteractableKind.WhitePrinter] = BindMinimum("White item printers", 1);
		minimums[InteractableKind.GreenPrinter] = BindMinimum("Green item printers", 1);
		minimums[InteractableKind.RedPrinter] = BindMinimum("Red item printers", 1);
		minimums[InteractableKind.YellowPrinter] = BindMinimum("Yellow item printers", 1);
		minimums[InteractableKind.Scrapper] = BindMinimum("Scrappers", 1);
		minimums[InteractableKind.VoidCradle] = BindMinimum("Void Cradles", 1);
		minimums[InteractableKind.GunnerDrone] = BindMinimum("Gunner Drones", 0);
		minimums[InteractableKind.HealingDrone] = BindMinimum("Healing Drones", 0);
		minimums[InteractableKind.SmallChest] = BindMinimum("Small Chests", 0);
		minimums[InteractableKind.LargeChest] = BindMinimum("Large Chests", 0);
		minimums[InteractableKind.EquipmentChest] = BindMinimum("Equipment Chests", 0);
		minimums[InteractableKind.CategoryChest] = BindMinimum("Category Chests", 0);
		minimums[InteractableKind.ChanceShrine] = BindMinimum("Shrines of Chance", 0);
		totalRandomDrones = BindMinimum("Total random drones", 0);
		totalRandomPrinters = BindMinimum("Total random printers", 0);
		totalRandomChests = BindMinimum("Total random chests", 0);
		BindPool("Random printer pool", PrinterKinds, defaultValue: true);
		BindPool("Random chest pool", RandomChestKinds, defaultValue: true);
		BindPool("Random drone pool", DroneKinds, defaultValue: true);
		SceneDirector.onPrePopulateSceneServer += OnPrePopulateSceneServer;
		SceneDirector.onGenerateInteractableCardSelection += OnGenerateInteractableCardSelection;
		SceneDirector.onSpawnInteractableServer += OnSpawnInteractableServer;
		SceneDirector.onPostPopulateSceneServer += OnPostPopulateSceneServer;
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Guaranteed Interactables loaded.");
	}

	private ConfigEntry<int> BindMinimum(string name, int defaultValue)
	{
		return ((BaseUnityPlugin)this).Config.Bind<int>("Guaranteed interactables", name, defaultValue, "Minimum present after vanilla interactable generation. Set to 0 to disable.");
	}

	private void BindPool(string section, InteractableKind[] kinds, bool defaultValue)
	{
		foreach (InteractableKind interactableKind in kinds)
		{
			poolEnabled[interactableKind] = ((BaseUnityPlugin)this).Config.Bind<bool>(section, "Include " + GetDisplayName(interactableKind), defaultValue, "Allow this type to be selected by the matching Total random setting.");
		}
	}

	private static string GetDisplayName(InteractableKind kind)
	{
		return kind.ToString();
	}

	private void OnDestroy()
	{
		SceneDirector.onPrePopulateSceneServer -= OnPrePopulateSceneServer;
		SceneDirector.onGenerateInteractableCardSelection -= OnGenerateInteractableCardSelection;
		SceneDirector.onSpawnInteractableServer -= OnSpawnInteractableServer;
		SceneDirector.onPostPopulateSceneServer -= OnPostPopulateSceneServer;
	}

	private void OnPrePopulateSceneServer(SceneDirector sceneDirector)
	{
		cards.Clear();
		spawned.Clear();
		foreach (InteractableKind value in Enum.GetValues(typeof(InteractableKind)))
		{
			spawned[value] = 0;
		}
	}

	private void OnGenerateInteractableCardSelection(SceneDirector sceneDirector, DirectorCardCategorySelection selection)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		if (!IsNormalStage() || (Object)(object)selection == (Object)null || selection.categories == null)
		{
			return;
		}
		Category[] categories = selection.categories;
		for (int i = 0; i < categories.Length; i++)
		{
			Category val = categories[i];
			if (val.cards != null)
			{
				DirectorCard[] array = val.cards;
				foreach (DirectorCard directorCard in array)
				{
					CacheCard(directorCard);
				}
			}
		}
	}

	private void CacheCard(DirectorCard directorCard)
	{
		InteractableSpawnCard val = (InteractableSpawnCard)((directorCard != null) ? /*isinst with value type is only supported in some contexts*/: null);
		if (!((Object)(object)val == (Object)null) && !((Object)(object)((SpawnCard)val).prefab == (Object)null) && TryGetKind(val, out var kind))
		{
			cards[kind] = directorCard;
		}
	}

	private void OnSpawnInteractableServer(SceneDirector sceneDirector, GameObject spawnedObject, DirectorCard card)
	{
		InteractableSpawnCard val = (InteractableSpawnCard)((card != null) ? /*isinst with value type is only supported in some contexts*/: null);
		if (!((Object)(object)val == (Object)null) && !((Object)(object)((SpawnCard)val).prefab == (Object)null) && TryGetKind(val, out var kind))
		{
			spawned[kind] = ((!spawned.ContainsKey(kind)) ? 1 : (spawned[kind] + 1));
		}
	}

	private void OnPostPopulateSceneServer(SceneDirector sceneDirector)
	{
		if (!IsNormalStage())
		{
			return;
		}
		foreach (KeyValuePair<InteractableKind, ConfigEntry<int>> minimum in minimums)
		{
			InteractableKind key = minimum.Key;
			if (IsAvailableInThisRun(key))
			{
				int num = Math.Max(0, minimum.Value.Value);
				int num2 = (spawned.ContainsKey(key) ? spawned[key] : 0);
				for (int i = num2; i < num; i++)
				{
					TrySpawnGuaranteed(key);
				}
			}
		}
		EnsureRandomMinimum("drones", totalRandomDrones.Value, DroneKinds);
		EnsureRandomMinimum("printers", totalRandomPrinters.Value, PrinterKinds);
		EnsureRandomMinimum("chests", totalRandomChests.Value, RandomChestKinds);
	}

	private void EnsureRandomMinimum(string label, int desired, InteractableKind[] kinds)
	{
		desired = Math.Max(0, desired);
		int num = 0;
		List<InteractableKind> list = new List<InteractableKind>();
		foreach (InteractableKind interactableKind in kinds)
		{
			num += (spawned.ContainsKey(interactableKind) ? spawned[interactableKind] : 0);
			if (IsAvailableInThisRun(interactableKind) && cards.ContainsKey(interactableKind) && IsPoolEnabled(interactableKind))
			{
				list.Add(interactableKind);
			}
		}
		if (list.Count == 0)
		{
			if (num < desired)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("No eligible " + label + " spawn cards were available on this stage."));
			}
			return;
		}
		int num2 = num;
		int num3 = Math.Max(20, (desired - num) * 10);
		while (num2 < desired && num3-- > 0)
		{
			InteractableKind kind = list[Random.Range(0, list.Count)];
			if (TrySpawnGuaranteed(kind))
			{
				num2++;
			}
		}
		if (num2 < desired)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Could only place " + num2 + " of " + desired + " requested random " + label + "."));
		}
	}

	private bool IsPoolEnabled(InteractableKind kind)
	{
		if (poolEnabled.TryGetValue(kind, out var value))
		{
			return value.Value;
		}
		return true;
	}

	private bool TrySpawnGuaranteed(InteractableKind kind)
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Expected O, but got Unknown
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Expected O, but got Unknown
		if (!cards.TryGetValue(kind, out var value) || value == null)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)string.Concat("Could not find a spawn card for ", kind, " on this stage."));
			return false;
		}
		SpawnCard spawnCard = value.spawnCard;
		InteractableSpawnCard val = (InteractableSpawnCard)(object)((spawnCard is InteractableSpawnCard) ? spawnCard : null);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		DirectorPlacementRule val2 = new DirectorPlacementRule();
		val2.placementMode = (PlacementMode)4;
		val2.minDistance = 20f;
		val2.maxDistance = 120f;
		val2.preventOverhead = true;
		DirectorPlacementRule val3 = val2;
		DirectorSpawnRequest val4 = new DirectorSpawnRequest((SpawnCard)(object)val, val3, Run.instance.spawnRng);
		GameObject val5 = DirectorCore.instance.TrySpawnObject(val4);
		if ((Object)(object)val5 == (Object)null)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)string.Concat("No valid position was found for guaranteed ", kind, "."));
			return false;
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)string.Concat("Spawned guaranteed ", kind, "."));
		return true;
	}

	private static bool IsNormalStage()
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Invalid comparison between Unknown and I4
		SceneInfo instance = SceneInfo.instance;
		SceneDef val = (((Object)(object)instance != (Object)null) ? instance.sceneDef : null);
		if ((Object)(object)val != (Object)null && (int)val.sceneType == 1)
		{
			return val.validForRandomSelection;
		}
		return false;
	}

	private static bool IsAvailableInThisRun(InteractableKind kind)
	{
		if (kind == InteractableKind.VoidCradle)
		{
			if ((Object)(object)Run.instance != (Object)null)
			{
				return Run.instance.IsExpansionEnabled(DLC1Content.DLC1);
			}
			return false;
		}
		return true;
	}

	private static bool TryGetKind(InteractableSpawnCard card, out InteractableKind kind)
	{
		if (!TryGetKind(((Object)((SpawnCard)card).prefab).name, out kind))
		{
			return TryGetKind(((Object)card).name, out kind);
		}
		return true;
	}

	private static bool TryGetKind(string name, out InteractableKind kind)
	{
		switch (name)
		{
		case "Duplicator":
			kind = InteractableKind.WhitePrinter;
			return true;
		case "DuplicatorLarge":
			kind = InteractableKind.GreenPrinter;
			return true;
		case "DuplicatorMilitary":
			kind = InteractableKind.RedPrinter;
			return true;
		case "DuplicatorWild":
			kind = InteractableKind.YellowPrinter;
			return true;
		case "Scrapper":
			kind = InteractableKind.Scrapper;
			return true;
		case "VoidChest":
			kind = InteractableKind.VoidCradle;
			return true;
		case "Drone1Broken":
			kind = InteractableKind.GunnerDrone;
			return true;
		case "Drone2Broken":
			kind = InteractableKind.HealingDrone;
			return true;
		case "Chest1":
			kind = InteractableKind.SmallChest;
			return true;
		case "Chest2":
			kind = InteractableKind.LargeChest;
			return true;
		case "EquipmentBarrel":
			kind = InteractableKind.EquipmentChest;
			return true;
		default:
			if (name.StartsWith("CategoryChest") || name.StartsWith("iscCategoryChest"))
			{
				kind = InteractableKind.CategoryChest;
				return true;
			}
			if (name.StartsWith("ShrineChance") || name.StartsWith("iscShrineChance"))
			{
				kind = InteractableKind.ChanceShrine;
				return true;
			}
			kind = InteractableKind.WhitePrinter;
			return false;
		}
	}
}