Decompiled source of NkuhanaTrialSkipper v1.0.0

plugins/NkuhanaTrialSkipper/NkuhanaTrialSkipper.dll

Decompiled 19 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RiskOfOptions;
using RiskOfOptions.OptionConfigs;
using RiskOfOptions.Options;
using RoR2;
using RoR2.Quests;
using RoR2.Quests.TrialQuests;
using UnityEngine;
using UnityEngine.Networking;

[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("NkuhanaTrialSkipper")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("NkuhanaTrialSkipper")]
[assembly: AssemblyTitle("NkuhanaTrialSkipper")]
[assembly: AssemblyVersion("1.0.0.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 NkuhanaTrialSkipper
{
	[BepInPlugin("com.zcode.NkuhanaTrialSkipper", "NkuhanaTrialSkipper", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class NkuhanaTrialSkipperPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.zcode.NkuhanaTrialSkipper";

		public const string PluginName = "NkuhanaTrialSkipper";

		public const string PluginVersion = "1.0.0";

		internal static ConfigEntry<bool> Enabled { get; private set; }

		internal static ConfigEntry<float> TimeCostSeconds { get; private set; }

		internal static ConfigEntry<bool> MaximizeBronzeChoices { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Enabled", true, "总开关。开启后每次进入 N'kuhana 试炼(Hallowed Concepts DLC)时立即按成功结算。");
			TimeCostSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Settings", "TimeCostSeconds", 60f, "每次跳过结算后,Reckoning 剩余时间直接设置到的档位差(每局总预算 210 秒):210 → 150 → 90 → 30。剩余时间不足该值时不触发跳过,试炼正常进行。");
			MaximizeBronzeChoices = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "MaximizeBronzeChoices", true, "跳过结算时,把铜手(Greater Trial Blessing)的自选列表最大化:注入该试炼可能刷出的全部虚灵物品种类(原版规则是只列出本次试炼实际捡到的)。");
			Harmony.CreateAndPatchAll(typeof(TrialSkipperPatches), (string)null);
			RegisterRiskOfOptions();
			Log.LogInfo((object)"NkuhanaTrialSkipper v1.0.0 loaded.");
		}

		private void RegisterRiskOfOptions()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			if (Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions"))
			{
				ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(Enabled));
				ModSettingsManager.AddOption((BaseOption)new SliderOption(TimeCostSeconds, new SliderConfig
				{
					min = 0f,
					max = 210f
				}));
				ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(MaximizeBronzeChoices));
			}
		}
	}
	internal static class RewardPools
	{
		internal static IReadOnlyList<ItemDef> GetPool(BaseTrialQuest quest)
		{
			if (!(quest is TrialQuestSadismOpenChests))
			{
				if (!(quest is TrialQuestSadismBreakTargets))
				{
					if (!(quest is TrialQuestSadismMoney))
					{
						if (quest is TrialSpeedrunA || quest is TrialSpeedrunB || quest is TrialSpeedrunC)
						{
							return (IReadOnlyList<ItemDef>)(object)new ItemDef[5]
							{
								Items.Hoof,
								Items.SprintBonus,
								Items.Feather,
								Items.JumpBoost,
								Items.SprintOutOfCombat
							};
						}
						if (!(quest is TrialQuestSurvivorProcCity))
						{
							if (!(quest is TrialQuestSurvivorUmbra))
							{
								if (quest is TrialQuestSurvivorMonstersOffMap)
								{
									return (IReadOnlyList<ItemDef>)(object)new ItemDef[6]
									{
										Items.Hoof,
										Items.SpeedOnPickup,
										Items.SprintOutOfCombat,
										Items.SpeedBoostPickup,
										Items.JumpBoost,
										Items.MoveSpeedOnKill
									};
								}
								return null;
							}
							return (IReadOnlyList<ItemDef>)(object)new ItemDef[14]
							{
								Items.Bear,
								Items.BarrierOnKill,
								Items.IgniteOnKill,
								Items.Hoof,
								Items.AttackSpeedPerNearbyAllyOrEnemy,
								Items.Tooth,
								Items.SlowOnHit,
								Items.JumpBoost,
								Items.ChainLightning,
								Items.Missile,
								Items.PrimarySkillShuriken,
								Items.KillEliteFrenzy,
								Items.GhostOnKill,
								Items.SprintArmor
							};
						}
						return (IReadOnlyList<ItemDef>)(object)new ItemDef[10]
						{
							Items.IgniteOnKill,
							Items.StickyBomb,
							Items.Dagger,
							Items.SecondarySkillMagazine,
							Items.Missile,
							Items.Thorns,
							Items.ChainLightning,
							Items.FireRing,
							Items.IceRing,
							Items.GhostOnKill
						};
					}
					return (IReadOnlyList<ItemDef>)(object)new ItemDef[10]
					{
						Items.StickyBomb,
						Items.NearbyDamageBonus,
						Items.Syringe,
						Items.IgniteOnKill,
						Items.Firework,
						Items.Hoof,
						Items.GoldOnHit,
						Items.SpeedOnPickup,
						Items.ExecuteLowHealthElite,
						Items.ChainLightning
					};
				}
				return (IReadOnlyList<ItemDef>)(object)new ItemDef[2]
				{
					Items.Hoof,
					Items.SprintBonus
				};
			}
			return (IReadOnlyList<ItemDef>)(object)new ItemDef[3]
			{
				Items.Hoof,
				Items.StunChanceOnHit,
				Items.Firework
			};
		}
	}
	[HarmonyPatch]
	internal static class TrialSkipperPatches
	{
		private static readonly HashSet<BaseTrialQuest> ArmedQuests = new HashSet<BaseTrialQuest>();

		private static readonly HashSet<BaseTrialQuest> SkippedQuests = new HashSet<BaseTrialQuest>();

		private static readonly MethodInfo EndQuestMethod = AccessTools.Method(typeof(BaseQuest), "EndQuest", (Type[])null, (Type[])null);

		private static readonly FieldInfo PickedUpItemsField = AccessTools.Field(typeof(CreateItemPotentialFromPickedUpItems), "_pickedUpItems");

		private static DLC4TrialSelector _trackedSelector;

		private static int _skipsThisRun;

		[HarmonyPatch(typeof(BaseTrialQuest), "OnEnter")]
		[HarmonyPostfix]
		private static void ArmQuestOnEnter(BaseTrialQuest __instance)
		{
			if (NetworkServer.active)
			{
				ArmedQuests.Add(__instance);
			}
		}

		[HarmonyPatch(typeof(QuestRunner), "Update")]
		[HarmonyPostfix]
		private static void TriggerSkipOnQuestActive(QuestRunner __instance)
		{
			if (NetworkServer.active && ArmedQuests.Count != 0)
			{
				BaseQuest quest = __instance.quest;
				BaseTrialQuest val = (BaseTrialQuest)(object)((quest is BaseTrialQuest) ? quest : null);
				if (val != null && ArmedQuests.Remove(val))
				{
					TrySkipQuest(val);
				}
			}
		}

		[HarmonyPatch(typeof(BaseTrialQuest), "OnExit")]
		[HarmonyPostfix]
		private static void ApplyTimeCostOnExit(BaseTrialQuest __instance)
		{
			if (!NetworkServer.active)
			{
				return;
			}
			ArmedQuests.Remove(__instance);
			if (SkippedQuests.Remove(__instance))
			{
				DLC4TrialSelector trialSelector = GetTrialSelector();
				if (!Object.op_Implicit((Object)(object)trialSelector))
				{
					NkuhanaTrialSkipperPlugin.Log.LogWarning((object)"Time cost not applied: DLC4TrialSelector not found.");
					return;
				}
				EnsureRunTracking(trialSelector);
				_skipsThisRun++;
				float value = NkuhanaTrialSkipperPlugin.TimeCostSeconds.Value;
				float num = Mathf.Max(DLC4TrialSelector.totalAllotedSecondsForTrials - value * (float)_skipsThisRun, 0f);
				trialSelector.SetRemainingTrialTimeServer(num);
				NkuhanaTrialSkipperPlugin.Log.LogInfo((object)$"Trial skip settled (#{_skipsThisRun} this run), Reckoning set to {num:F0}s.");
			}
		}

		private static void TrySkipQuest(BaseTrialQuest quest)
		{
			if (!NkuhanaTrialSkipperPlugin.Enabled.Value)
			{
				return;
			}
			DLC4TrialSelector trialSelector = GetTrialSelector();
			if (!Object.op_Implicit((Object)(object)trialSelector))
			{
				NkuhanaTrialSkipperPlugin.Log.LogWarning((object)"Skip not triggered: DLC4TrialSelector not found.");
				return;
			}
			float remainingTrialTimeServer = trialSelector.GetRemainingTrialTimeServer();
			float value = NkuhanaTrialSkipperPlugin.TimeCostSeconds.Value;
			if (remainingTrialTimeServer < value)
			{
				NkuhanaTrialSkipperPlugin.Log.LogInfo((object)$"Skip not triggered: Reckoning remaining {remainingTrialTimeServer:F1}s is below the required {value:F0}s, playing the trial normally.");
				return;
			}
			EnsureRunTracking(trialSelector);
			float num = Mathf.Max(DLC4TrialSelector.totalAllotedSecondsForTrials - value * (float)(_skipsThisRun + 1), 0f);
			SkippedQuests.Add(quest);
			InjectRewardPool(quest);
			EndQuestMethod.Invoke(quest, new object[1] { true });
			NkuhanaTrialSkipperPlugin.Log.LogInfo((object)$"Trial skip triggered: instant success settlement, Reckoning will be set to {num:F0}s on settle (currently {remainingTrialTimeServer:F1}s).");
		}

		private static void InjectRewardPool(BaseTrialQuest quest)
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			if (!NkuhanaTrialSkipperPlugin.MaximizeBronzeChoices.Value)
			{
				return;
			}
			IReadOnlyList<ItemDef> pool = RewardPools.GetPool(quest);
			if (pool == null || pool.Count == 0)
			{
				NkuhanaTrialSkipperPlugin.Log.LogInfo((object)("No reward pool mapped for " + ((object)quest).GetType().Name + ", bronze hand stays vanilla."));
				return;
			}
			CreateItemPotentialFromPickedUpItems[] array = Object.FindObjectsOfType<CreateItemPotentialFromPickedUpItems>(true);
			if (array.Length == 0)
			{
				NkuhanaTrialSkipperPlugin.Log.LogWarning((object)"Bronze hand not maximized: no CreateItemPotentialFromPickedUpItems found in scene.");
				return;
			}
			CreateItemPotentialFromPickedUpItems[] array2 = array;
			foreach (CreateItemPotentialFromPickedUpItems obj in array2)
			{
				if (!(PickedUpItemsField.GetValue(obj) is HashSet<PickupIndex> hashSet))
				{
					continue;
				}
				int num = 0;
				foreach (ItemDef item2 in pool)
				{
					if (Object.op_Implicit((Object)(object)item2))
					{
						PickupIndex item = PickupCatalog.FindPickupIndex(item2.itemIndex);
						if (((PickupIndex)(ref item)).isValid && hashSet.Add(item))
						{
							num++;
						}
					}
				}
				NkuhanaTrialSkipperPlugin.Log.LogInfo((object)$"Bronze hand choice list maximized: injected {num}/{pool.Count} ethereal item types.");
			}
		}

		private static DLC4TrialSelector GetTrialSelector()
		{
			DLC4ProgressionController instance = DLC4ProgressionController.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return null;
			}
			return instance.trialSelector;
		}

		private static void EnsureRunTracking(DLC4TrialSelector trialSelector)
		{
			if (_trackedSelector != trialSelector)
			{
				_trackedSelector = trialSelector;
				_skipsThisRun = 0;
			}
		}
	}
}