Decompiled source of ArtifactOfSacrifice v0.2.0

plugins/MuckArtifactSacrifice.dll

Decompiled a day ago
using System;
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.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
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("MuckArtifactSacrifice")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Risk of Rain 2 Artifact of Sacrifice for Muck: mobs can drop powerups, and powerup chests are removed or thinned.")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+b3815082b9868be7615596cfa6cd60a86ecd2179")]
[assembly: AssemblyProduct("Artifact of Sacrifice")]
[assembly: AssemblyTitle("MuckArtifactSacrifice")]
[assembly: InternalsVisibleTo("MuckArtifactSacrifice.Tests")]
[assembly: AssemblyVersion("0.2.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
}
namespace MuckArtifactSacrifice
{
	internal static class ArtifactLobbyLink
	{
		public static bool Register(string id, string name, string description, ConfigEntry<bool> enabled)
		{
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("evan.muck.artifactlobby", out var value) || (Object)(object)value.Instance == (Object)null)
				{
					return false;
				}
				object obj = (((object)value.Instance).GetType().Assembly.GetType("MuckArtifactLobby.ArtifactRegistry")?.GetMethod("Register", new Type[4]
				{
					typeof(string),
					typeof(string),
					typeof(string),
					typeof(ConfigEntry<bool>)
				}))?.Invoke(null, new object[4] { id, name, description, enabled });
				return obj is bool && (bool)obj;
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	internal static class ChestRules
	{
		public static bool Remove(bool enabled, bool testPowerup, float keepFraction, uint positionHash)
		{
			if (!enabled || testPowerup)
			{
				return false;
			}
			return !Keep(keepFraction, positionHash);
		}

		public static bool Keep(float keepFraction, uint positionHash)
		{
			if (float.IsNaN(keepFraction) || keepFraction >= 1f)
			{
				return true;
			}
			if (keepFraction <= 0f)
			{
				return false;
			}
			uint num = positionHash % 10000;
			uint num2 = (uint)Math.Round((double)keepFraction * 10000.0, MidpointRounding.AwayFromZero);
			if (num2 > 10000)
			{
				num2 = 10000u;
			}
			return num < num2;
		}

		public static uint HashPosition(float x, float y, float z)
		{
			int value = Quantize(x);
			int value2 = Quantize(y);
			int value3 = Quantize(z);
			return Mix(Mix(Mix(2166136261u, value), value2), value3);
		}

		private static int Quantize(float value)
		{
			return (int)Math.Round((double)value * 100.0, MidpointRounding.AwayFromZero);
		}

		private static uint Mix(uint hash, int value)
		{
			hash ^= (uint)value;
			hash *= 16777619;
			return hash;
		}
	}
	internal static class KillRules
	{
		public static bool Eligible(bool serverOwner, bool enabled, bool hasMob, bool hasType, bool neutral, bool woodman)
		{
			if (serverOwner && enabled && hasMob && hasType && !neutral)
			{
				return !woodman;
			}
			return false;
		}

		public static float Chance(float dropChance, float bossDropChance, float buffMultiplier, bool boss, bool buff)
		{
			float num = (boss ? bossDropChance : dropChance);
			if (buff)
			{
				num *= buffMultiplier;
			}
			if (float.IsNaN(num) || num < 0f)
			{
				return 0f;
			}
			if (num > 1f)
			{
				return 1f;
			}
			return num;
		}

		public static bool Hits(float roll, float chance)
		{
			return roll < chance;
		}
	}
	internal static class RetiredSwitch
	{
		public static readonly ConfigDefinition Old = new ConfigDefinition("Drops", "Enabled");

		public static readonly ConfigDefinition OldChestKeep = new ConfigDefinition("Chests", "ChestKeepFraction");

		public static bool Drop(ConfigFile cfg)
		{
			string value;
			return Take(cfg, Old, out value);
		}

		public static float? TakeChestKeep(ConfigFile cfg, out bool dropped)
		{
			dropped = Take(cfg, OldChestKeep, out var value);
			if (!dropped)
			{
				return null;
			}
			return CarriedChestKeep(value);
		}

		public static float? CarriedChestKeep(string raw)
		{
			if (!float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || result <= 0f)
			{
				return null;
			}
			return (result > 1f) ? 1f : result;
		}

		private static bool Take(ConfigFile cfg, ConfigDefinition key, out string value)
		{
			value = null;
			try
			{
				if (!(AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(cfg, null) is Dictionary<ConfigDefinition, string> dictionary) || !dictionary.TryGetValue(key, out value))
				{
					return false;
				}
				dictionary.Remove(key);
				cfg.Save();
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	internal sealed class SacrificeConfig
	{
		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<float> DropChance;

		public readonly ConfigEntry<float> BossDropChance;

		public readonly ConfigEntry<float> BuffChanceMultiplier;

		public readonly ConfigEntry<float> ChestKeepFraction;

		public readonly ConfigEntry<bool> SuppressBossPowerups;

		public readonly ConfigEntry<bool> SuppressCombatShrines;

		public readonly ConfigEntry<float> WhiteWeight;

		public readonly ConfigEntry<float> BlueWeight;

		public readonly ConfigEntry<float> OrangeWeight;

		public readonly ConfigEntry<bool> AnnounceInChat;

		public readonly ConfigEntry<bool> VerboseLogging;

		public readonly bool DroppedOldSwitch;

		public readonly bool DroppedOldChestKeep;

		public SacrificeConfig(ConfigFile cfg)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected O, but got Unknown
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected O, but got Unknown
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Expected O, but got Unknown
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Expected O, but got Unknown
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("Artifact", "Enabled", SacrificeTuning.Defaults.Enabled, "Turn the artifact on. Artifacts are opt-in, as in Risk of Rain 2. Off leaves mob loot, boss powerups, shrines, and chests exactly as vanilla.");
			DroppedOldSwitch = RetiredSwitch.Drop(cfg);
			DropChance = cfg.Bind<float>("Drops", "DropChance", SacrificeTuning.Defaults.DropChance, new ConfigDescription("Chance (0-1) a normal mob drops one powerup. 0.05 is the Risk of Rain 2 floor (a Beetle). Only the host's value is rolled.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			BossDropChance = cfg.Bind<float>("Drops", "BossDropChance", SacrificeTuning.Defaults.BossDropChance, new ConfigDescription("Chance (0-1) a boss drops one powerup. 0.35 is about a Risk of Rain 2 Stone Titan. A boss is bossType other than None, or mobType.boss. Only the host rolls.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			BuffChanceMultiplier = cfg.Bind<float>("Drops", "BuffChanceMultiplier", SacrificeTuning.Defaults.BuffChanceMultiplier, new ConfigDescription("Multiplies the chance when the mob's multiplier is above 1 (a buff). The product is clamped to 1. 1.5 is this mod, not the game.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 4f), Array.Empty<object>()));
			WhiteWeight = cfg.Bind<float>("Drops", "WhiteWeight", SacrificeTuning.Defaults.WhiteWeight, new ConfigDescription("Relative weight of a White powerup. Not a Muck chest table. 0.80 is this mod's common tier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			BlueWeight = cfg.Bind<float>("Drops", "BlueWeight", SacrificeTuning.Defaults.BlueWeight, new ConfigDescription("Relative weight of a Blue powerup. 0.19 is this mod's uncommon tier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			OrangeWeight = cfg.Bind<float>("Drops", "OrangeWeight", SacrificeTuning.Defaults.OrangeWeight, new ConfigDescription("Relative weight of an Orange powerup. 0.01 is this mod's legendary tier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			ChestKeepFraction = cfg.Bind<float>("Chests", "KeepFraction", SacrificeTuning.Defaults.ChestKeepFraction, new ConfigDescription("Fraction of powerup chests (LootContainerInteract) to keep. 0.05 keeps about 1 in 20. 0 removes them all, which is Risk of Rain 2. 1 leaves them. Storage chests, chief chests, and player chests are not touched. Every client must use the same value. Vanilla spawn still runs, so other world-gen rolls stay put.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			float? num = RetiredSwitch.TakeChestKeep(cfg, out DroppedOldChestKeep);
			if (num.HasValue)
			{
				ChestKeepFraction.Value = num.Value;
			}
			SuppressBossPowerups = cfg.Bind<bool>("Chests", "SuppressBossPowerups", false, "When true, the host skips the guaranteed powerup from LootExtra.BossLoot. The boss can still roll BossDropChance. Off leaves that drop vanilla.");
			SuppressCombatShrines = cfg.Bind<bool>("Chests", "SuppressCombatShrines", false, "When true, the host skips the powerup a cleared battle totem would drop. Off leaves the totem vanilla.");
			AnnounceInChat = cfg.Bind<bool>("Announce", "Chat", true, "While the artifact is on: the host sends its chest code to everyone through the vanilla chat packet, and a client prints its own code in local chat. Off prints nothing. The code covers Enabled and KeepFraction, which must match the host. The log always has the code.");
			VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Log each kill roll and each chest removed.");
		}

		public SacrificeTuning Tuning()
		{
			return new SacrificeTuning(Enabled.Value, DropChance.Value, BossDropChance.Value, BuffChanceMultiplier.Value, ChestKeepFraction.Value, SuppressBossPowerups.Value, SuppressCombatShrines.Value, WhiteWeight.Value, BlueWeight.Value, OrangeWeight.Value);
		}

		public string StampText()
		{
			return SacrificeStamp.Describe(Tuning());
		}

		public string StampCode()
		{
			return SacrificeStamp.Code(StampText());
		}
	}
	internal static class SacrificeDrop
	{
		private static readonly Random Rng = new Random();

		private static bool logged;

		public static void TryDrop(Mob mob)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SacrificeTuning sacrificeTuning = SacrificePlugin.Cfg.Tuning();
				if (!sacrificeTuning.Enabled || (Object)(object)mob == (Object)null)
				{
					return;
				}
				bool flag = (Object)(object)mob.mobType != (Object)null;
				bool neutral = flag && (int)mob.mobType.behaviour == 0;
				bool woodman = flag && mob.mobType.name == "Woodman";
				if (!KillRules.Eligible(LocalClient.serverOwner, sacrificeTuning.Enabled, (Object)(object)mob != (Object)null, flag, neutral, woodman))
				{
					return;
				}
				bool boss = (int)mob.bossType != 0 || mob.mobType.boss;
				bool buff = mob.IsBuff();
				float num = KillRules.Chance(sacrificeTuning.DropChance, sacrificeTuning.BossDropChance, sacrificeTuning.BuffChanceMultiplier, boss, buff);
				float num2 = (float)Rng.NextDouble();
				if (!KillRules.Hits(num2, num))
				{
					if (SacrificePlugin.Cfg.VerboseLogging.Value)
					{
						SacrificePlugin.Log.LogInfo((object)$"Sacrifice miss {num2:0.###} against {num:0.###} ({mob.mobType.name}).");
					}
					return;
				}
				int tier = TierWeights.Pick((float)Rng.NextDouble(), sacrificeTuning.WhiteWeight, sacrificeTuning.BlueWeight, sacrificeTuning.OrangeWeight);
				Powerup[] array = Pool(ItemManager.Instance, tier);
				if (array == null || array.Length == 0)
				{
					SacrificePlugin.Log.LogWarning((object)"Sacrifice rolled a drop but that tier's pool is empty.");
					return;
				}
				int num3 = TierWeights.Index(array.Length, Rng.Next(array.Length));
				Powerup val = ((num3 < 0) ? null : array[num3]);
				if (!((Object)(object)val == (Object)null))
				{
					Vector3 val2 = ((Component)mob).transform.position + Vector3.up;
					int nextId = ItemManager.Instance.GetNextId();
					ItemManager.Instance.DropPowerupAtPosition(val.id, val2, nextId);
					ServerSend.DropPowerupAtPosition(val.id, nextId, val2);
					if (SacrificePlugin.Cfg.VerboseLogging.Value)
					{
						SacrificePlugin.Log.LogInfo((object)$"Sacrifice drop {val.name} id {val.id} from {mob.mobType.name} (roll {num2:0.###}, chance {num:0.###}).");
					}
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					SacrificePlugin.Log.LogError((object)$"Sacrifice drop failed (further failures are not logged): {arg}");
				}
			}
		}

		internal static Powerup[] Pool(ItemManager items, int tier)
		{
			if (items == null)
			{
				return null;
			}
			return tier switch
			{
				0 => items.powerupsWhite, 
				1 => items.powerupsBlue, 
				2 => items.powerupsOrange, 
				_ => null, 
			};
		}
	}
	[HarmonyPatch(typeof(MobManager), "RemoveMob", new Type[] { typeof(int) })]
	internal static class RemoveMobPatch
	{
		private static bool logged;

		private static void Prefix(MobManager __instance, int __0)
		{
			try
			{
				if (!((Object)(object)__instance == (Object)null) && __instance.mobs != null && __instance.mobs.TryGetValue(__0, out var value))
				{
					SacrificeDrop.TryDrop(value);
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					SacrificePlugin.Log.LogError((object)$"Sacrifice death hook failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(LootContainerInteract), "Start", new Type[] { })]
	internal static class LootContainerStartPatch
	{
		private static bool logged;

		private static void Postfix(LootContainerInteract __instance)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SacrificeTuning sacrificeTuning = SacrificePlugin.Cfg.Tuning();
				if (!sacrificeTuning.Enabled || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				Vector3 position = ((Component)__instance).transform.position;
				uint positionHash = ChestRules.HashPosition(position.x, position.y, position.z);
				if (ChestRules.Remove(sacrificeTuning.Enabled, __instance.testPowerup, sacrificeTuning.ChestKeepFraction, positionHash))
				{
					int id = __instance.GetId();
					ResourceManager instance = ResourceManager.Instance;
					GameObject value = null;
					if ((Object)(object)instance != (Object)null && instance.list != null)
					{
						instance.list.TryGetValue(id, out value);
					}
					if ((Object)(object)value != (Object)null && (Object)(object)value.GetComponentInChildren<LootContainerInteract>() == (Object)(object)__instance)
					{
						instance.RemoveItem(id);
					}
					else
					{
						Object.Destroy((Object)(object)((Component)__instance).gameObject);
					}
					if (SacrificePlugin.Cfg.VerboseLogging.Value)
					{
						SacrificePlugin.Log.LogInfo((object)$"Sacrifice removed powerup chest {id} at {position}.");
					}
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					SacrificePlugin.Log.LogError((object)$"Sacrifice chest cull failed (that chest stays): {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(LootExtra), "BossLoot", new Type[]
	{
		typeof(Transform),
		typeof(BossType)
	})]
	internal static class BossLootPatch
	{
		private static bool logged;

		private static bool Prefix()
		{
			try
			{
				SacrificeTuning sacrificeTuning = SacrificePlugin.Cfg.Tuning();
				if (!sacrificeTuning.Enabled || !sacrificeTuning.SuppressBossPowerups)
				{
					return true;
				}
				if (!LocalClient.serverOwner)
				{
					return true;
				}
				return false;
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					SacrificePlugin.Log.LogError((object)$"Sacrifice boss-loot gate failed; vanilla boss loot still runs. {arg}");
				}
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(ShrineInteractable), "DropPowerup", new Type[] { })]
	internal static class ShrineDropPatch
	{
		private static bool logged;

		private static bool Prefix()
		{
			try
			{
				SacrificeTuning sacrificeTuning = SacrificePlugin.Cfg.Tuning();
				if (!sacrificeTuning.Enabled || !sacrificeTuning.SuppressCombatShrines)
				{
					return true;
				}
				if (!LocalClient.serverOwner)
				{
					return true;
				}
				return false;
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					SacrificePlugin.Log.LogError((object)$"Sacrifice shrine gate failed; the totem still drops. {arg}");
				}
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerStatus), "Awake", new Type[] { })]
	internal static class AnnouncePatch
	{
		private static PlayerStatus announced;

		private static void Postfix(PlayerStatus __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null || announced == __instance)
				{
					return;
				}
				announced = __instance;
				SacrificeTuning sacrificeTuning = SacrificePlugin.Cfg.Tuning();
				string text = SacrificePlugin.Cfg.StampCode();
				SacrificePlugin.Log.LogInfo((object)("Artifact of Sacrifice chest code " + text + " (" + sacrificeTuning.Summary() + ")."));
				if (!SacrificePlugin.Cfg.AnnounceInChat.Value)
				{
					return;
				}
				bool serverOwner = LocalClient.serverOwner;
				string[] array = SacrificeStamp.Lines(sacrificeTuning.Enabled, serverOwner, text);
				foreach (string text2 in array)
				{
					if (serverOwner)
					{
						ServerSend.SendChatMessage(-1, "", text2);
					}
					else if ((Object)(object)ChatBox.Instance != (Object)null)
					{
						ChatBox.Instance.AppendMessage(-1, text2, "");
					}
				}
			}
			catch (Exception arg)
			{
				SacrificePlugin.Log.LogError((object)$"Sacrifice announce failed: {arg}");
			}
		}
	}
	[BepInPlugin("evan.muck.artifactofsacrifice", "Artifact of Sacrifice", "0.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class SacrificePlugin : BaseUnityPlugin
	{
		public const string Guid = "evan.muck.artifactofsacrifice";

		public const string Name = "Artifact of Sacrifice";

		public const string Version = "0.2.0";

		internal static ManualLogSource Log;

		internal static SacrificeConfig Cfg;

		private void Awake()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new SacrificeConfig(((BaseUnityPlugin)this).Config);
			Harmony val = new Harmony("evan.muck.artifactofsacrifice");
			bool flag = TryPatch(val, typeof(RemoveMobPatch));
			bool flag2 = TryPatch(val, typeof(LootContainerStartPatch));
			if (!flag || !flag2)
			{
				val.UnpatchSelf();
				Log.LogError((object)"Artifact of Sacrifice disabled: a required patch failed. Drops and chests stay vanilla.");
				return;
			}
			if (!TryPatch(val, typeof(BossLootPatch)))
			{
				Log.LogWarning((object)"Boss loot gate failed. SuppressBossPowerups will not apply.");
			}
			if (!TryPatch(val, typeof(ShrineDropPatch)))
			{
				Log.LogWarning((object)"Battle totem gate failed. SuppressCombatShrines will not apply.");
			}
			if (!TryPatch(val, typeof(AnnouncePatch)))
			{
				Log.LogWarning((object)"Chest-code chat failed. The log line at startup still has the code.");
			}
			if (ArtifactLobbyLink.Register("evan.muck.artifactofsacrifice", "Sacrifice", "Mobs drop powerups; most powerup chests are gone.", Cfg.Enabled))
			{
				Log.LogInfo((object)"Artifact of Sacrifice: registered with Artifact Lobby; the lobby host's pick sets [Artifact] Enabled.");
			}
			Log.LogInfo((object)("Artifact of Sacrifice 0.2.0 loaded. " + Cfg.Tuning().Summary() + ". Chest code " + Cfg.StampCode() + ". The host rolls drops. Enabled and KeepFraction must match on every client."));
			if (Cfg.DroppedOldChestKeep)
			{
				Log.LogInfo((object)("Moved [Chests] ChestKeepFraction to [Chests] KeepFraction = " + Cfg.ChestKeepFraction.Value.ToString(CultureInfo.InvariantCulture) + ". An old 0 was the old default, so it became the new default."));
			}
			if (Cfg.DroppedOldSwitch)
			{
				Log.LogInfo((object)"Artifact of Sacrifice is now off unless you opt in. Removed the old [Drops] Enabled line. Set [Artifact] Enabled = true to turn it on.");
			}
			else if (!Cfg.Enabled.Value)
			{
				Log.LogInfo((object)"Artifact of Sacrifice is off. Set [Artifact] Enabled = true to turn it on.");
			}
		}

		private static bool TryPatch(Harmony harmony, Type patchClass)
		{
			try
			{
				harmony.CreateClassProcessor(patchClass).Patch();
				return true;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Artifact of Sacrifice failed to patch {patchClass.Name}: {arg}");
				return false;
			}
		}
	}
	internal readonly struct SacrificeTuning
	{
		public readonly bool Enabled;

		public readonly float DropChance;

		public readonly float BossDropChance;

		public readonly float BuffChanceMultiplier;

		public readonly float ChestKeepFraction;

		public readonly bool SuppressBossPowerups;

		public readonly bool SuppressCombatShrines;

		public readonly float WhiteWeight;

		public readonly float BlueWeight;

		public readonly float OrangeWeight;

		public static SacrificeTuning Defaults => new SacrificeTuning(enabled: false, 0.05f, 0.35f, 1.5f, 0.05f, suppressBossPowerups: false, suppressCombatShrines: false, 0.8f, 0.19f, 0.01f);

		public SacrificeTuning(bool enabled, float dropChance, float bossDropChance, float buffChanceMultiplier, float chestKeepFraction, bool suppressBossPowerups, bool suppressCombatShrines, float whiteWeight, float blueWeight, float orangeWeight)
		{
			Enabled = enabled;
			DropChance = Clamp01(dropChance);
			BossDropChance = Clamp01(bossDropChance);
			BuffChanceMultiplier = SanitizeBuff(buffChanceMultiplier);
			ChestKeepFraction = Thousandths(Clamp01(chestKeepFraction));
			SuppressBossPowerups = suppressBossPowerups;
			SuppressCombatShrines = suppressCombatShrines;
			WhiteWeight = NonNegative(whiteWeight);
			BlueWeight = NonNegative(blueWeight);
			OrangeWeight = NonNegative(orangeWeight);
		}

		public string Summary()
		{
			if (!Enabled)
			{
				return "off";
			}
			return string.Format(CultureInfo.InvariantCulture, "drops {0:0.##} (boss {1:0.##}, buff x{2:0.##}), chests kept {3:0.##}", DropChance, BossDropChance, BuffChanceMultiplier, ChestKeepFraction);
		}

		private static float Clamp01(float value)
		{
			if (float.IsNaN(value) || value < 0f)
			{
				return 0f;
			}
			if (value > 1f)
			{
				return 1f;
			}
			return value;
		}

		private static float Thousandths(float value)
		{
			return (float)(Math.Round((double)value * 1000.0, MidpointRounding.AwayFromZero) / 1000.0);
		}

		private static float SanitizeBuff(float value)
		{
			if (float.IsNaN(value) || value < 1f)
			{
				return 1f;
			}
			if (value > 4f)
			{
				return 4f;
			}
			return value;
		}

		private static float NonNegative(float value)
		{
			if (float.IsNaN(value) || value < 0f)
			{
				return 0f;
			}
			return value;
		}
	}
	internal static class SacrificeStamp
	{
		public const int ChatMaxLength = 120;

		public static string Describe(SacrificeTuning tuning)
		{
			return string.Join("|", tuning.Enabled ? "1" : "0", Milli(tuning.ChestKeepFraction));
		}

		public static string Code(string text)
		{
			uint num = 2166136261u;
			for (int i = 0; i < text.Length; i++)
			{
				num ^= text[i];
				num *= 16777619;
			}
			return num.ToString("X8");
		}

		public static string[] Lines(bool enabled, bool isHost, string code)
		{
			if (!enabled)
			{
				return new string[0];
			}
			return new string[1] { isHost ? HostLine(code) : LocalLine(code) };
		}

		public static string LocalLine(string code)
		{
			return "<color=orange>Artifact of Sacrifice.<color=white> On. Chest code " + code + ", must match the host's.";
		}

		public static string HostLine(string code)
		{
			return "<color=orange>Artifact of Sacrifice.<color=white> Host chest code is " + code + ".";
		}

		private static string Milli(float value)
		{
			return ((int)Math.Round((double)value * 1000.0, MidpointRounding.AwayFromZero)).ToString();
		}
	}
	internal static class TierWeights
	{
		public const int White = 0;

		public const int Blue = 1;

		public const int Orange = 2;

		public static int Pick(float roll, float white, float blue, float orange)
		{
			if (float.IsNaN(roll))
			{
				return -1;
			}
			double num = NonNegative(white);
			double num2 = NonNegative(blue);
			double num3 = NonNegative(orange);
			double num4 = num + num2 + num3;
			if (num4 <= 0.0)
			{
				return -1;
			}
			double num5 = ((roll < 0f) ? 0.0 : ((double)roll));
			if (num5 < num / num4)
			{
				return 0;
			}
			if (num5 < (num + num2) / num4)
			{
				return 1;
			}
			return 2;
		}

		public static int Index(int length, int rollIndex)
		{
			if (length <= 0)
			{
				return -1;
			}
			int num = rollIndex % length;
			if (num < 0)
			{
				num += length;
			}
			return num;
		}

		private static double NonNegative(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			return value;
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "MuckArtifactSacrifice";

		public const string PLUGIN_NAME = "Artifact of Sacrifice";

		public const string PLUGIN_VERSION = "0.2.0";
	}
}