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.");
}
}
}