Decompiled source of ConfigurableStageCredit v0.3.5

plugins\ConfigurableStageCredit\ConfigurableStageCredit.dll

Decompiled 16 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ConfigurableStageCredit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Configure per-stage multipliers for Risk of Rain 2 enemy and interactable spawn credits.")]
[assembly: AssemblyFileVersion("0.3.5.0")]
[assembly: AssemblyInformationalVersion("0.3.5")]
[assembly: AssemblyProduct("ConfigurableStageCredit")]
[assembly: AssemblyTitle("ConfigurableStageCredit")]
[assembly: AssemblyVersion("0.3.5.0")]
[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;
		}
	}
}
namespace ConfigurableStageCredit
{
	[BepInPlugin("com.configurablestagecredit.plugin", "ConfigurableStageCredit", "0.3.5")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class CreditPrintSnapshot
		{
			internal readonly int StageNumber;

			internal bool HasEnemyCredit;

			internal bool HasLootCredit;

			internal double EnemyVanillaCredit;

			internal double EnemyNewCredit;

			internal float EnemyMultiplier;

			internal double LootVanillaCredit;

			internal double LootNewCredit;

			internal float LootMultiplier;

			internal CreditPrintSnapshot(int stageNumber)
			{
				StageNumber = stageNumber;
			}
		}

		public const string PluginGuid = "com.configurablestagecredit.plugin";

		public const string PluginName = "ConfigurableStageCredit";

		public const string PluginVersion = "0.3.5";

		internal static ManualLogSource ModLog;

		private static readonly FieldInfo MonsterCreditField = typeof(SceneDirector).GetField("monsterCredit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private CreditSettings _enemyCredits;

		private CreditSettings _lootCredits;

		private ConfigEntry<bool> _printCreditOnStageStart;

		private readonly Dictionary<int, CreditPrintSnapshot> _pendingCreditPrints = new Dictionary<int, CreditPrintSnapshot>();

		private readonly HashSet<int> _reportedCreditPrints = new HashSet<int>();

		private void Awake()
		{
			ModLog = ((BaseUnityPlugin)this).Logger;
			_enemyCredits = new CreditSettings(((BaseUnityPlugin)this).Config, "1-Enemy Credit");
			_lootCredits = new CreditSettings(((BaseUnityPlugin)this).Config, "2-Loot Credit");
			_printCreditOnStageStart = ((BaseUnityPlugin)this).Config.Bind<bool>("0-General", "PrintCreditOnStageStart", false, "When enabled, print the enemy and loot credit multipliers, original budgets, and modified budgets for every populated map.");
			SceneDirector.onPrePopulateMonstersSceneServer += OnPrePopulateMonsters;
			SceneDirector.onPrePopulateSceneServer += OnPrePopulateInteractables;
			if (MonsterCreditField == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Could not find SceneDirector.monsterCredit. Enemy credit scaling will be disabled; loot credit scaling can still work.");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded. Enemy and loot credit multipliers are configured independently in the BepInEx config file.");
		}

		private void OnDestroy()
		{
			SceneDirector.onPrePopulateMonstersSceneServer -= OnPrePopulateMonsters;
			SceneDirector.onPrePopulateSceneServer -= OnPrePopulateInteractables;
		}

		private static int GetCurrentStageNumber()
		{
			Run instance = Run.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				return Math.Max(1, instance.stageClearCount + 1);
			}
			return 1;
		}

		private void OnPrePopulateMonsters(SceneDirector director)
		{
			if (NetworkServer.active && !((Object)(object)director == (Object)null) && !(MonsterCreditField == null))
			{
				ScaleReflectedCredit(director, MonsterCreditField, _enemyCredits, "enemy");
			}
		}

		private void OnPrePopulateInteractables(SceneDirector director)
		{
			if (NetworkServer.active && !((Object)(object)director == (Object)null))
			{
				int currentStageNumber = GetCurrentStageNumber();
				float multiplier = _lootCredits.GetMultiplier(currentStageNumber);
				int interactableCredit = director.interactableCredit;
				int num = (director.interactableCredit = ScaleIntegerCredit(interactableCredit, multiplier));
				ModLog.LogDebug((object)string.Format(CultureInfo.InvariantCulture, "Stage {0}: loot credit {1} -> {2} (x{3:0.###}).", currentStageNumber, interactableCredit, num, multiplier));
				RecordCreditForChat(director, currentStageNumber, isEnemy: false, interactableCredit, num, multiplier);
			}
		}

		private void ScaleReflectedCredit(SceneDirector director, FieldInfo field, CreditSettings settings, string label)
		{
			try
			{
				object value = field.GetValue(director);
				if (value != null)
				{
					int currentStageNumber = GetCurrentStageNumber();
					double num = Convert.ToDouble(value, CultureInfo.InvariantCulture);
					float multiplier = settings.GetMultiplier(currentStageNumber);
					object value2 = ConvertCreditToFieldType(Math.Max(0.0, num * (double)multiplier), field.FieldType);
					field.SetValue(director, value2);
					double num2 = Convert.ToDouble(value2, CultureInfo.InvariantCulture);
					ModLog.LogDebug((object)string.Format(CultureInfo.InvariantCulture, "Stage {0}: {1} credit {2:0.##} -> {3:0.##} (x{4:0.###}).", currentStageNumber, label, num, num2, multiplier));
					RecordCreditForChat(director, currentStageNumber, isEnemy: true, num, num2, multiplier);
				}
			}
			catch (Exception ex)
			{
				ModLog.LogError((object)("Failed to scale enemy spawn credit: " + ex));
			}
		}

		private void RecordCreditForChat(SceneDirector director, int stage, bool isEnemy, double vanillaCredit, double newCredit, float multiplier)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (_printCreditOnStageStart == null || !_printCreditOnStageStart.Value || (Object)(object)director == (Object)null)
			{
				return;
			}
			Scene scene = ((Component)director).gameObject.scene;
			int handle = ((Scene)(ref scene)).handle;
			if (!_reportedCreditPrints.Contains(handle))
			{
				if (!_pendingCreditPrints.TryGetValue(handle, out var value))
				{
					value = new CreditPrintSnapshot(stage);
					_pendingCreditPrints.Add(handle, value);
				}
				if (isEnemy)
				{
					value.EnemyVanillaCredit = vanillaCredit;
					value.EnemyNewCredit = newCredit;
					value.EnemyMultiplier = multiplier;
					value.HasEnemyCredit = true;
				}
				else
				{
					value.LootVanillaCredit = vanillaCredit;
					value.LootNewCredit = newCredit;
					value.LootMultiplier = multiplier;
					value.HasLootCredit = true;
				}
				if (value.HasEnemyCredit && value.HasLootCredit)
				{
					_reportedCreditPrints.Add(handle);
					_pendingCreditPrints.Remove(handle);
					string message = string.Format(CultureInfo.InvariantCulture, "<style=cEvent>[ConfigurableStageCredit]</style> Stage {0}\n   Enemy x{1:0.###}: vanilla {2:0.##}, new {3:0.##}\n   Loot x{4:0.###}: vanilla {5:0.##}, new {6:0.##}", value.StageNumber, value.EnemyMultiplier, value.EnemyVanillaCredit, value.EnemyNewCredit, value.LootMultiplier, value.LootVanillaCredit, value.LootNewCredit);
					((MonoBehaviour)this).StartCoroutine(BroadcastCreditReportAfterSceneLoad(director, message));
				}
			}
		}

		private IEnumerator BroadcastCreditReportAfterSceneLoad(SceneDirector director, string message)
		{
			yield return (object)new WaitForSecondsRealtime(1f);
			if (!NetworkServer.active || (Object)(object)director == (Object)null)
			{
				yield break;
			}
			Scene val = ((Component)director).gameObject.scene;
			int handle = ((Scene)(ref val)).handle;
			val = SceneManager.GetActiveScene();
			if (handle != ((Scene)(ref val)).handle)
			{
				yield break;
			}
			try
			{
				Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
				{
					baseToken = message
				});
			}
			catch (Exception ex)
			{
				ModLog.LogWarning((object)("Could not print the scene credit report to chat: " + ex.Message));
			}
		}

		private static object ConvertCreditToFieldType(double value, Type fieldType)
		{
			value = Math.Max(0.0, value);
			if (fieldType == typeof(float))
			{
				return (float)Math.Min(value, 3.4028234663852886E+38);
			}
			if (fieldType == typeof(double))
			{
				return value;
			}
			if (fieldType == typeof(int))
			{
				return (int)Math.Min(2147483647.0, Math.Round(value, MidpointRounding.AwayFromZero));
			}
			if (fieldType == typeof(uint))
			{
				return (uint)Math.Min(4294967295.0, Math.Round(value, MidpointRounding.AwayFromZero));
			}
			if (fieldType == typeof(long))
			{
				if (value >= 9.223372036854776E+18)
				{
					return long.MaxValue;
				}
				return (long)Math.Round(value, MidpointRounding.AwayFromZero);
			}
			if (fieldType == typeof(ulong))
			{
				if (value >= 1.8446744073709552E+19)
				{
					return ulong.MaxValue;
				}
				return (ulong)Math.Round(value, MidpointRounding.AwayFromZero);
			}
			return Convert.ChangeType(value, fieldType, CultureInfo.InvariantCulture);
		}

		private static int ScaleIntegerCredit(int credit, float multiplier)
		{
			double value = Math.Max(0.0, (double)credit * (double)multiplier);
			return (int)Math.Min(2147483647.0, Math.Round(value, MidpointRounding.AwayFromZero));
		}
	}
	internal sealed class CreditSettings
	{
		private readonly ConfigFile _config;

		private readonly ConfigEntry<float> _startingMultiplier;

		private readonly ConfigEntry<float> _stageIncrement;

		private readonly ConfigEntry<float> _loopIncrement;

		private readonly ConfigEntry<float> _minimumMultiplier;

		private readonly ConfigEntry<float> _maximumMultiplier;

		private bool _normalizing;

		internal CreditSettings(ConfigFile config, string section)
		{
			_config = config;
			_startingMultiplier = config.Bind<float>(section, "StartingMultiplier", 1f, "Multiplier for stage 1. 1.15 means 15% more credits than the vanilla budget. If below MinimumMultiplier, it is set to MinimumMultiplier.");
			_stageIncrement = config.Bind<float>(section, "StageIncrement", 0f, "Additive change to the multiplier between stages, except when entering a new 5-stage loop.");
			_loopIncrement = config.Bind<float>(section, "LoopIncrement", 0f, "Additive change to the multiplier when entering a new loop (stages 6, 11, 16, etc.), replacing StageIncrement for that transition.");
			_minimumMultiplier = config.Bind<float>(section, "MinimumMultiplier", 0f, "Lower bound for the calculated multiplier. Negative values are reset to 0.");
			_maximumMultiplier = config.Bind<float>(section, "MaximumMultiplier", 10f, "Upper bound for the calculated multiplier. If below MinimumMultiplier, it is set to MinimumMultiplier.");
			_startingMultiplier.SettingChanged += OnSettingChanged;
			_stageIncrement.SettingChanged += OnSettingChanged;
			_loopIncrement.SettingChanged += OnSettingChanged;
			_minimumMultiplier.SettingChanged += OnSettingChanged;
			_maximumMultiplier.SettingChanged += OnSettingChanged;
			NormalizeSettings();
		}

		internal float GetMultiplier(int stageNumber)
		{
			NormalizeSettings();
			double minimum = _minimumMultiplier.Value;
			double maximum = _maximumMultiplier.Value;
			double num = ClampMultiplier(_startingMultiplier.Value, minimum, maximum);
			int num2 = Math.Max(1, stageNumber);
			for (int i = 2; i <= num2; i++)
			{
				bool flag = (i - 1) % 5 == 0;
				num += (double)(flag ? _loopIncrement.Value : _stageIncrement.Value);
				num = ClampMultiplier(num, minimum, maximum);
			}
			return (float)num;
		}

		private static double ClampMultiplier(double value, double minimum, double maximum)
		{
			if (double.IsNaN(value))
			{
				value = minimum;
			}
			return Math.Max(minimum, Math.Min(maximum, value));
		}

		private void OnSettingChanged(object sender, EventArgs eventArgs)
		{
			NormalizeSettings();
		}

		private void NormalizeSettings()
		{
			if (_normalizing)
			{
				return;
			}
			bool flag = false;
			_normalizing = true;
			try
			{
				if (IsInvalidNumber(_startingMultiplier.Value))
				{
					_startingMultiplier.Value = 1f;
					flag = true;
				}
				if (IsInvalidNumber(_stageIncrement.Value))
				{
					_stageIncrement.Value = 0f;
					flag = true;
				}
				if (IsInvalidNumber(_loopIncrement.Value))
				{
					_loopIncrement.Value = 0f;
					flag = true;
				}
				float num = _minimumMultiplier.Value;
				if (IsInvalidNumber(num) || num < 0f)
				{
					num = 0f;
					_minimumMultiplier.Value = num;
					flag = true;
				}
				float value = _maximumMultiplier.Value;
				if (IsInvalidNumber(value) || value < num)
				{
					value = num;
					_maximumMultiplier.Value = value;
					flag = true;
				}
			}
			finally
			{
				_normalizing = false;
			}
			if (!flag)
			{
				return;
			}
			try
			{
				_config.Save();
			}
			catch (Exception ex)
			{
				Plugin.ModLog.LogWarning((object)("Could not save normalized settings: " + ex.Message));
			}
		}

		private static bool IsInvalidNumber(float value)
		{
			if (!float.IsNaN(value))
			{
				return float.IsInfinity(value);
			}
			return true;
		}
	}
}