Decompiled source of PlayerCountConfig v1.2.1

PlayerCountConfig.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using MonoMod.RuntimeDetour;
using On.RoR2;
using On.RoR2.Artifacts;
using R2API.Utils;
using RoR2;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("PlayerCountConfig")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("PlayerCountConfig")]
[assembly: AssemblyTitle("PlayerCountConfig")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace ShrineClamper.Utils
{
	public static class ListHandlers
	{
		public static List<ItemDef> ItemDefList { get; }
	}
}
namespace PlayerCountConfig
{
	[BepInDependency("com.bepis.r2api", "3.0.7")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("com.thebugreport.playerCountConfig", "Player Count Config", "1.0.0")]
	public class Main : BaseUnityPlugin
	{
		public const string ModGUID = "com.thebugreport.playerCountConfig";

		public const string ModName = "Player Count Config";

		public const string ModVersion = "1.0.0";

		private Hook participatingPlayerCountHook;

		private Hook livingPlayerCountHook;

		private Hook getDifficultyScaledCostHook;

		private Hook getDifficultyScaledCostWithCoeffHook;

		private int shrineStacks;

		private int bossItems = 1;

		private BossGroup teleporterGroup;

		private int sacrificeOffset = 1;

		private static readonly List<string> interactableFreeze = new List<string> { "MAP_BAZAAR_TITLE", "MAP_ARENA_TITLE", "MAP_LIMBO_TITLE", "MAP_MYSTERYSPACE_TITLE" };

		public void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			PlayerConfigs.Init(((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 1");
			SacrificeArtifactManager.OnPrePopulateSceneServer += (hook_OnPrePopulateSceneServer)delegate(orig_OnPrePopulateSceneServer orig, SceneDirector self)
			{
				sacrificeOffset = 2;
				orig.Invoke(self);
			};
			SceneDirector.PlaceTeleporter += (hook_PlaceTeleporter)delegate(orig_PlaceTeleporter orig, SceneDirector self)
			{
				//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
				orig.Invoke(self);
				int num5 = 200;
				ClassicStageInfo component = ((Component)SceneInfo.instance).GetComponent<ClassicStageInfo>();
				if (Object.op_Implicit((Object)(object)component))
				{
					num5 = component.sceneDirectorInteractibleCredits;
					int participatingPlayerCount = Run.instance.participatingPlayerCount;
					float num6 = (float)(0.95 + (double)participatingPlayerCount * 0.05);
					num6 *= (float)Math.Max(1.0 + 0.1 * (double)Math.Min(participatingPlayerCount * 2 - Run.instance.stageClearCount - 2, 3), 1.0);
					num5 = (int)((float)num5 / num6);
					if (component.bonusInteractibleCreditObjects != null)
					{
						BonusInteractibleCreditObject[] bonusInteractibleCreditObjects = component.bonusInteractibleCreditObjects;
						foreach (BonusInteractibleCreditObject val in bonusInteractibleCreditObjects)
						{
							if (val.objectThatGrantsPointsIfEnabled.activeSelf)
							{
								num5 += val.points / participatingPlayerCount;
							}
						}
					}
				}
				if (PlayerConfigs.OverrideInteractableCount.Value && (!Object.op_Implicit((Object)(object)SceneInfo.instance) || !interactableFreeze.Contains(SceneInfo.instance.sceneDef.nameToken)))
				{
					int num7 = (PlayerConfigs.UseInteractableScaling.Value ? (Run.instance.participatingPlayerCount * PlayerConfigs.FixedOrMultiplierInteractableBase.Value) : PlayerConfigs.FixedOrMultiplierInteractableBase.Value) + PlayerConfigs.InteractableCountOffset.Value;
					int num8 = (int)Math.Floor((float)num5 * ((float)(1 + num7) / 2f)) + PlayerConfigs.InteractableCreditsOffset.Value;
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"Setting interactableCredit from {self.interactableCredit} / {num5} to {num8}");
					self.interactableCredit = num8;
				}
				sacrificeOffset = 1;
				if (PlayerConfigs.OverrideMonsterCount.Value)
				{
					int monsterCredit = self.monsterCredit;
					int num9 = (PlayerConfigs.UseMonsterScaling.Value ? (Run.instance.participatingPlayerCount * PlayerConfigs.FixedOrMultiplierMonsterBase.Value) : PlayerConfigs.FixedOrMultiplierMonsterBase.Value) + PlayerConfigs.MonsterCountOffset.Value;
					int num10 = (int)Math.Floor((float)monsterCredit * ((float)(1 + num9) / 2f)) + PlayerConfigs.MonsterCreditsOffset.Value;
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"Setting monsterCredit from {self.monsterCredit} to {num10}");
					self.monsterCredit = num10;
				}
			};
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 2");
			TeleporterInteraction.AddShrineStack += (hook_AddShrineStack)delegate(orig_AddShrineStack orig, TeleporterInteraction self)
			{
				shrineStacks++;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"[ShrineClamper] Shrine activated. Total shrine stacks: {shrineStacks}");
				orig.Invoke(self);
			};
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 3");
			TeleporterInteraction.OnInteractionBegin += (hook_OnInteractionBegin)delegate(orig_OnInteractionBegin orig, TeleporterInteraction self, Interactor user)
			{
				bossItems = (PlayerConfigs.OverrideBaseCount.Value ? (PlayerConfigs.FixedOrMultiplierBaseTeleporter.Value + PlayerConfigs.PlayerCountOffsetTeleporter.Value) : Run.instance.participatingPlayerCount);
				orig.Invoke(self, user);
			};
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 4");
			TeleporterInteraction.OnBossDirectorSpawnedMonsterServer += (hook_OnBossDirectorSpawnedMonsterServer)delegate(orig_OnBossDirectorSpawnedMonsterServer orig, TeleporterInteraction self, GameObject monster)
			{
				if ((Object)(object)teleporterGroup == (Object)null)
				{
					teleporterGroup = self.bossGroup;
				}
			};
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 5");
			BossGroup.DropRewards += (hook_DropRewards)delegate(orig_DropRewards orig, BossGroup self)
			{
				Run instance = Run.instance;
				int num = ((instance == null) ? 1 : instance.participatingPlayerCount);
				int num2 = ((!PlayerConfigs.OverrideBaseCount.Value) ? num : ((!PlayerConfigs.UsePlayerCountScaling.Value) ? (PlayerConfigs.FixedOrMultiplierBaseTeleporter.Value + PlayerConfigs.PlayerCountOffsetTeleporter.Value) : (num * PlayerConfigs.FixedOrMultiplierBaseTeleporter.Value + PlayerConfigs.PlayerCountOffsetTeleporter.Value)));
				int num3 = 0;
				if ((Object)(object)teleporterGroup == (Object)(object)self)
				{
					num3 = ((!PlayerConfigs.OverrideShrines.Value) ? (shrineStacks * num) : ((!PlayerConfigs.UsePlayerCountScalingShrine.Value) ? (shrineStacks * (PlayerConfigs.FixedOrMultiplierShrine.Value + PlayerConfigs.ShrineCountOffset.Value)) : (shrineStacks * (PlayerConfigs.FixedOrMultiplierShrine.Value * num + PlayerConfigs.ShrineCountOffset.Value))));
				}
				int num4 = num2 + num3 - 1;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Total drops calculated: Base Drops: {num2}, Shrine Bonus: {num3}, Total Drops: {num4 + 1}");
				self.scaleRewardsByPlayerCount = false;
				self.bonusRewardCount = num4;
				orig.Invoke(self);
				if ((Object)(object)teleporterGroup == (Object)(object)self)
				{
					shrineStacks = 0;
				}
			};
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 6");
			Stage.BeginServer += (hook_BeginServer)delegate(orig_BeginServer orig, Stage self)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Stage started — resetting shrineStacks to 0.");
				shrineStacks = 0;
				orig.Invoke(self);
			};
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Point 7");
		}

		private static int GetEffectivePricePlayerCount()
		{
			if (!PlayerConfigs.UseInteractablePriceScaling.Value)
			{
				return PlayerConfigs.FixedOrMultiplierInteractablePriceBase.Value;
			}
			return Run.instance.participatingPlayerCount * PlayerConfigs.FixedOrMultiplierInteractablePriceBase.Value;
		}

		private int CustomGetDifficultyScaledCost(Run self, int baseCost)
		{
			if (PlayerConfigs.OverrideInteractablePrice.Value)
			{
				return CalculateScaledPrice(baseCost, GetEffectivePricePlayerCount());
			}
			return getDifficultyScaledCostHook.GenerateTrampoline<Func<Run, int, int>>()(self, baseCost);
		}

		private int CustomGetDifficultyScaledCostWithCoeff(Run self, int baseCost, float difficultyCoefficient)
		{
			if (PlayerConfigs.OverrideInteractablePrice.Value)
			{
				return CalculateScaledPrice(baseCost, GetEffectivePricePlayerCount());
			}
			return getDifficultyScaledCostWithCoeffHook.GenerateTrampoline<Func<Run, int, float, int>>()(self, baseCost, difficultyCoefficient);
		}

		private static int CalculateScaledPrice(int originalCost, int targetPlayerCount)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			float runStopwatch = Run.instance.GetRunStopwatch();
			DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(Run.instance.selectedDifficulty);
			float num = Mathf.Floor(runStopwatch * (1f / 60f));
			float num2 = (float)(targetPlayerCount + PlayerConfigs.InteractablePriceOffset.Value) * 0.3f;
			float num3 = 0.7f + num2;
			float num4 = Mathf.Pow((float)targetPlayerCount, 0.2f);
			float num5 = 0.0506f * difficultyDef.scalingValue * num4;
			float num6 = Mathf.Pow(1.15f, (float)Run.instance.stageClearCount);
			float num7 = (num3 + num5 * num) * num6;
			int num8 = Mathf.FloorToInt((float)((double)originalCost * Math.Pow(num7, 1.25)));
			int num9 = Mathf.Max(0, num8);
			num9 += PlayerConfigs.InteractableCreditsPriceOffset.Value;
			Debug.Log((object)$"[PlayerCountConfig] Changing interactable price... Base cost: {originalCost}, Target player count: {targetPlayerCount}, Final price: {num9}");
			return num9;
		}

		private static int OverrideParticipatingPlayerCount(Func<Run, int> orig, Run self)
		{
			if (!PlayerConfigs.OverridePlayerCount.Value)
			{
				return orig(self);
			}
			int num = (PlayerConfigs.UsePlayerScaling.Value ? (orig(self) * PlayerConfigs.FixedOrMultiplierBase.Value) : PlayerConfigs.FixedOrMultiplierBase.Value) + PlayerConfigs.PlayerCountOffset.Value;
			return Mathf.Max(PlayerConfigs.MinimumPlayerCount.Value, num);
		}

		public void OnEnable()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Applying player count hooks...");
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			MethodInfo methodInfo = typeof(Run).GetProperty("participatingPlayerCount", bindingAttr)?.GetGetMethod(nonPublic: true);
			typeof(Run).GetProperty("livingPlayerCount", bindingAttr)?.GetGetMethod(nonPublic: true);
			if (methodInfo != null)
			{
				participatingPlayerCountHook = new Hook((MethodBase)methodInfo, (Delegate)new Func<Func<Run, int>, Run, int>(OverrideParticipatingPlayerCount));
				participatingPlayerCountHook.Apply();
			}
			MethodInfo method = typeof(Run).GetMethod("GetDifficultyScaledCost", bindingAttr, null, new Type[1] { typeof(int) }, null);
			if (method != null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[HOOK] Found GetDifficultyScaledCost method.");
				getDifficultyScaledCostHook = new Hook((MethodBase)method, (Delegate)new Func<Run, int, int>(CustomGetDifficultyScaledCost));
				getDifficultyScaledCostHook.Apply();
			}
			MethodInfo method2 = typeof(Run).GetMethod("GetDifficultyScaledCost", bindingAttr, null, new Type[2]
			{
				typeof(int),
				typeof(float)
			}, null);
			if (method2 != null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[HOOK] Found GetDifficultyScaledCost with coeff method.");
				getDifficultyScaledCostWithCoeffHook = new Hook((MethodBase)method2, (Delegate)new Func<Run, int, float, int>(CustomGetDifficultyScaledCostWithCoeff));
				getDifficultyScaledCostWithCoeffHook.Apply();
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Applied hooks!");
		}

		public void OnDisable()
		{
			Hook obj = participatingPlayerCountHook;
			if (obj != null)
			{
				obj.Undo();
			}
			Hook obj2 = livingPlayerCountHook;
			if (obj2 != null)
			{
				obj2.Undo();
			}
			Hook obj3 = getDifficultyScaledCostHook;
			if (obj3 != null)
			{
				obj3.Undo();
			}
			Hook obj4 = getDifficultyScaledCostWithCoeffHook;
			if (obj4 != null)
			{
				obj4.Undo();
			}
		}
	}
	public static class PlayerConfigs
	{
		public static ConfigEntry<bool> OverridePlayerCount { get; set; }

		public static ConfigEntry<bool> UsePlayerScaling { get; set; }

		public static ConfigEntry<int> FixedOrMultiplierBase { get; set; }

		public static ConfigEntry<int> PlayerCountOffset { get; set; }

		public static ConfigEntry<int> MinimumPlayerCount { get; set; }

		public static ConfigEntry<bool> OverrideInteractableCount { get; set; }

		public static ConfigEntry<bool> UseInteractableScaling { get; set; }

		public static ConfigEntry<int> FixedOrMultiplierInteractableBase { get; set; }

		public static ConfigEntry<int> InteractableCountOffset { get; set; }

		public static ConfigEntry<int> InteractableCreditsOffset { get; set; }

		public static ConfigEntry<bool> OverrideMonsterCount { get; set; }

		public static ConfigEntry<bool> UseMonsterScaling { get; set; }

		public static ConfigEntry<int> FixedOrMultiplierMonsterBase { get; set; }

		public static ConfigEntry<int> MonsterCountOffset { get; set; }

		public static ConfigEntry<int> MonsterCreditsOffset { get; set; }

		public static ConfigEntry<bool> OverrideInteractablePrice { get; set; }

		public static ConfigEntry<bool> UseInteractablePriceScaling { get; set; }

		public static ConfigEntry<int> FixedOrMultiplierInteractablePriceBase { get; set; }

		public static ConfigEntry<int> InteractablePriceOffset { get; set; }

		public static ConfigEntry<int> InteractableCreditsPriceOffset { get; set; }

		public static ConfigEntry<bool> OverrideBaseCount { get; set; }

		public static ConfigEntry<bool> UsePlayerCountScaling { get; set; }

		public static ConfigEntry<int> FixedOrMultiplierBaseTeleporter { get; set; }

		public static ConfigEntry<int> PlayerCountOffsetTeleporter { get; set; }

		public static ConfigEntry<bool> OverrideShrines { get; set; }

		public static ConfigEntry<bool> UsePlayerCountScalingShrine { get; set; }

		public static ConfigEntry<int> FixedOrMultiplierShrine { get; set; }

		public static ConfigEntry<int> ShrineCountOffset { get; set; }

		public static void Init(ConfigFile config)
		{
			OverridePlayerCount = config.Bind<bool>("Player Count", "Override Player Count", false, "If enabled, replaces the game's player count logic with the custom settings below.");
			UsePlayerScaling = config.Bind<bool>("Player Count", "Scale By Actual Player Count", true, "If enabled, multiplies the actual player count by the multiplier below. If disabled, uses the multiplier as a fixed player count.");
			FixedOrMultiplierBase = config.Bind<int>("Player Count", "Player Count Multiplier", 1, "If scaling is enabled, multiplies the player count by this value. If disabled, sets the player count to this value.");
			PlayerCountOffset = config.Bind<int>("Player Count", "Player Count Offset", 0, "Adds or subtracts from the final player count after all other calculations.");
			MinimumPlayerCount = config.Bind<int>("Player Count", "Minimum Player Count", 1, "Ensures the player count never goes below this value.");
			OverrideInteractableCount = config.Bind<bool>("Interactable Credits", "Override Interactable Credits", false, "If enabled, replaces the game's player count for interactable spawning specifically with the settings below.");
			UseInteractableScaling = config.Bind<bool>("Interactable Credits", "Scale Interactables By Player Count", true, "If enabled, multiplies the player count by the multiplier below for interactable credits. If disabled, uses the multiplier as a fixed player count. Each player adds 50% more credits after the first. (0 players halfs the credits. -1 sets the credits to 0).");
			FixedOrMultiplierInteractableBase = config.Bind<int>("Interactable Credits", "Player Count Multiplier", 1, "If scaling is enabled, multiplies the player count by this value for interactable credits. If disabled, sets the player count to this value.");
			InteractableCountOffset = config.Bind<int>("Interactable Credits", "Player Count Offset", 0, "Adds or subtracts from the final player count after all other calculations.");
			InteractableCreditsOffset = config.Bind<int>("Interactable Credits", "Interactable Credits Offset", 0, "Adds or subtracts a flat number of interactable credits after all other calculations.");
			OverrideInteractablePrice = config.Bind<bool>("Interactable Price", "Override Interactable Price", false, "If enabled, replaces the game's interactable price logic with the custom settings below.");
			UseInteractablePriceScaling = config.Bind<bool>("Interactable Price", "Scale Prices By Player Count", true, "If enabled, multiplies the player count by the multiplier below for price scaling. If disabled, uses the multiplier as a fixed player count for prices.");
			FixedOrMultiplierInteractablePriceBase = config.Bind<int>("Interactable Price", "Player Count Multiplier", 1, "If scaling is enabled, multiplies the player count by this value for prices. If disabled, sets the player count to this value.");
			InteractablePriceOffset = config.Bind<int>("Interactable Price", "Player Count Offset", 0, "Adds or subtracts from the player count after all other calculations.");
			InteractableCreditsPriceOffset = config.Bind<int>("Interactable Price", "Flat Price Offset", 0, "Adds or subtracts a flat amount to the final interactable price after all other calculations.");
			OverrideMonsterCount = config.Bind<bool>("Monster Credits", "Override Monster Spawn Credits", false, "If enabled, replaces the game's monster spawn logic (specifically when starting a stage) with the custom settings below.");
			UseMonsterScaling = config.Bind<bool>("Monster Credits", "Scale Monster Credits By Player Count", false, "If enabled, multiplies the player count by the multiplier below for monster credits. If disabled, uses the multiplier as a fixed player count. Each player adds 50% more credits after the first. (0 players halfs the credits. -1 sets the credits to 0).");
			FixedOrMultiplierMonsterBase = config.Bind<int>("Monster Credits", "Player Count Multiplier", 1, "If scaling is enabled, multiplies the player count by this value for monster credits. If disabled, sets the player count to this value.");
			MonsterCountOffset = config.Bind<int>("Monster Credits", "Player Count Offset", 0, "Adds or subtracts from the final player count after all other calculations.");
			MonsterCreditsOffset = config.Bind<int>("Monster Credits", "Monster Credits Offset", 0, "Adds or subtracts a flat number of monster credits after all other calculations.");
			OverrideBaseCount = config.Bind<bool>("Boss Drops", "Override Base Rewards", false, "If true, overrides the base number of items dropped by the teleporter before Shrine of the Mountain bonuses.");
			UsePlayerCountScaling = config.Bind<bool>("Boss Drops", "Use Player Scaling", true, "If true, base item drops scale with player count. If false, uses the fixed value below.");
			FixedOrMultiplierBaseTeleporter = config.Bind<int>("Boss Drops", "Player Count Multiplier", 1, "If scaling is enabled, multiplies the player count by this value for base drops. If disabled, sets the base drop count.");
			PlayerCountOffsetTeleporter = config.Bind<int>("Boss Drops", "Item Reward Offset", 0, "Adds or subtracts from the final base item count after all other calculations.");
			OverrideShrines = config.Bind<bool>("Mountain Shrines", "Override Shrine Rewards", false, "If true, overrides how Shrine of the Mountain affects teleporter item drops.");
			UsePlayerCountScalingShrine = config.Bind<bool>("Mountain Shrines", "Use Player Scaling", true, "If true, shrine bonuses scale with player count. If false, uses the fixed value below.");
			FixedOrMultiplierShrine = config.Bind<int>("Mountain Shrines", "Multiplier", 1, "If scaling is enabled, multiplies shrine rewards by this value per player. If disabled, sets the shrine reward count per shrine.");
			ShrineCountOffset = config.Bind<int>("Mountain Shrines", "Shrine Reward Offset", 0, "Adds or subtracts from the shrine reward count after all other calculations per shrine.");
		}
	}
}