Decompiled source of ArtifactOfGlass v0.2.0

plugins/MuckArtifactGlass.dll

Decompiled 8 hours 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("MuckArtifactGlass")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Risk of Rain 2 Artifact of Glass for Muck: much more damage, a fraction of max HP.")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+b3815082b9868be7615596cfa6cd60a86ecd2179")]
[assembly: AssemblyProduct("Artifact of Glass")]
[assembly: AssemblyTitle("MuckArtifactGlass")]
[assembly: InternalsVisibleTo("MuckArtifactGlass.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 MuckArtifactGlass
{
	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 sealed class GlassConfig
	{
		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<float> DamageMultiplier;

		public readonly ConfigEntry<float> MaxHpMultiplier;

		public readonly ConfigEntry<bool> AnnounceInChat;

		public readonly bool DroppedOldSwitch;

		public GlassConfig(ConfigFile cfg)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("Artifact", "Enabled", GlassTuning.Defaults.Enabled, "Turn the artifact on. Artifacts are opt-in, as in Risk of Rain 2. Off leaves damage, max HP, healing, and the host HP bar exactly as vanilla.");
			DroppedOldSwitch = RetiredSwitch.Drop(cfg);
			DamageMultiplier = cfg.Bind<float>("Glass", "DamageMultiplier", 5f, new ConfigDescription("Damage multiplier on your strength. 5 is Risk of Rain 2 (+400% damage). Melee, bows, Survivors primaries and abilities, and chopping and mining all deal this much more. Item procs follow the bigger hit. Pen Nib, Bandit backstab and Crowbar multiply on top.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
			MaxHpMultiplier = cfg.Bind<float>("Glass", "MaxHpMultiplier", 0.1f, new ConfigDescription("Multiplier on the max HP vanilla just computed (base 100, plus Red Pill and Dracula). 0.1 is Risk of Rain 2 (one tenth). Floored, minimum 1. Blue Pill shield is scaled by the same factor (floored, may reach 0).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f), Array.Empty<object>()));
			AnnounceInChat = cfg.Bind<bool>("Announce", "Chat", true, "While the artifact is on: the host sends its config code to everyone through the vanilla chat packet, and a client prints its own code in local chat. Off prints nothing. The log always has the code.");
		}

		public GlassTuning Tuning()
		{
			return new GlassTuning(Enabled.Value, DamageMultiplier.Value, MaxHpMultiplier.Value);
		}

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

		public string StampCode()
		{
			return GlassStamp.Code(StampText());
		}

		public void HookSettingChanged()
		{
			Enabled.SettingChanged += OnGameplayChanged;
			DamageMultiplier.SettingChanged += OnGameplayChanged;
			MaxHpMultiplier.SettingChanged += OnGameplayChanged;
		}

		private void OnGameplayChanged(object sender, EventArgs e)
		{
			GlassTuning glassTuning = Tuning();
			GlassPlugin.Log.LogInfo((object)("Artifact of Glass config " + StampCode() + " (" + glassTuning.Summary() + ")."));
			try
			{
				if ((Object)(object)PlayerStatus.Instance != (Object)null)
				{
					PlayerStatus.Instance.UpdateStats();
				}
			}
			catch (Exception arg)
			{
				GlassPlugin.Log.LogError((object)$"Artifact of Glass could not reapply max HP after a config change: {arg}");
			}
		}
	}
	[BepInPlugin("evan.muck.artifactofglass", "Artifact of Glass", "0.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class GlassPlugin : BaseUnityPlugin
	{
		public const string Guid = "evan.muck.artifactofglass";

		public const string Name = "Artifact of Glass";

		public const string Version = "0.2.0";

		internal static ManualLogSource Log;

		internal static GlassConfig 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 GlassConfig(((BaseUnityPlugin)this).Config);
			Harmony val = new Harmony("evan.muck.artifactofglass");
			bool flag = TryPatch(val, typeof(StrengthPatch));
			bool flag2 = TryPatch(val, typeof(UpdateStatsPatch));
			bool flag3 = TryPatch(val, typeof(AwakePatch));
			if (!flag || !flag2 || !flag3)
			{
				val.UnpatchSelf();
				Log.LogError((object)"Artifact of Glass disabled: a required patch failed. Damage and max HP stay vanilla.");
				return;
			}
			if (!TryPatch(val, typeof(GatherPatch)))
			{
				Log.LogWarning((object)"Gathering patch failed. Chopping and mining get the 5x too.");
			}
			if (!TryPatch(val, typeof(LifestealPatch)))
			{
				Log.LogWarning((object)"Lifesteal patch failed. Crimson Dagger heals from the 5x hit.");
			}
			if (!TryPatch(val, typeof(DraculaPatch)))
			{
				Log.LogWarning((object)"Dracula clamp failed. A Dracula kill can leave HP above the glass max until the next stat update.");
			}
			if (!TryPatch(val, typeof(HealingPatch)))
			{
				Log.LogWarning((object)"Healing clamp failed. Passive regen can sit a fraction above max HP.");
			}
			if (!TryPatch(val, typeof(HostHpEstimatePatch)))
			{
				Log.LogWarning((object)"Host HP-bar estimate failed. Teammate bars can lag until the next health packet.");
			}
			Cfg.HookSettingChanged();
			if (ArtifactLobbyLink.Register("evan.muck.artifactofglass", "Glass", "5x damage, one tenth max HP and shield.", Cfg.Enabled))
			{
				Log.LogInfo((object)"Artifact of Glass: registered with Artifact Lobby; the lobby host's pick sets [Artifact] Enabled.");
			}
			Log.LogInfo((object)("Artifact of Glass 0.2.0 loaded. " + Cfg.Tuning().Summary() + ". Config code " + Cfg.StampCode() + ". Every player should match this code."));
			if (Cfg.DroppedOldSwitch)
			{
				Log.LogInfo((object)"Artifact of Glass is now off unless you opt in. Removed the old [Glass] Enabled line. Set [Artifact] Enabled = true to turn it on.");
			}
			else if (!Cfg.Enabled.Value)
			{
				Log.LogInfo((object)"Artifact of Glass 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 Glass failed to patch {patchClass.Name}: {arg}");
				return false;
			}
		}
	}
	internal static class GlassStamp
	{
		public const int ChatMaxLength = 120;

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

		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 Glass.<color=white> On. Your config code is " + code + ".";
		}

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

		private static string Milli(float value)
		{
			return ((int)Math.Round((double)value * 1000.0, MidpointRounding.AwayFromZero)).ToString();
		}
	}
	[HarmonyPatch(typeof(PlayerStatus), "UpdateStats", new Type[] { })]
	internal static class UpdateStatsPatch
	{
		private static bool logged;

		[HarmonyPriority(200)]
		[HarmonyAfter(new string[] { "evan.muck.shapedglass" })]
		private static void Postfix(PlayerStatus __instance)
		{
			Apply(__instance, "UpdateStats");
		}

		internal static void Apply(PlayerStatus player, string where)
		{
			try
			{
				GlassTuning glassTuning = GlassPlugin.Cfg.Tuning();
				if (glassTuning.Enabled && !((Object)(object)player == (Object)null))
				{
					player.maxHp = glassTuning.ScaleMaxHp(player.maxHp);
					player.hp = GlassTuning.ClampHp(player.hp, player.maxHp);
					player.maxShield = glassTuning.ScaleShield(player.maxShield);
					player.shield = GlassTuning.ClampHp(player.shield, player.maxShield);
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass {where} failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(PowerupInventory), "GetStrengthMultiplier", new Type[] { typeof(int[]) })]
	internal static class StrengthPatch
	{
		private static bool logged;

		[HarmonyPriority(200)]
		[HarmonyAfter(new string[] { "evan.muck.shapedglass" })]
		private static void Postfix(ref float __result)
		{
			try
			{
				__result = GlassPlugin.Cfg.Tuning().ScaleStrength(__result);
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass strength failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(HitableResource), "Hit", new Type[]
	{
		typeof(int),
		typeof(float),
		typeof(int),
		typeof(Vector3),
		typeof(int)
	})]
	internal static class GatherPatch
	{
		private const string BuildTag = "Build";

		private static bool logged;

		private static void Prefix(HitableResource __instance, ref int __0, int __2, int __4)
		{
			try
			{
				GlassTuning glassTuning = GlassPlugin.Cfg.Tuning();
				if (glassTuning.Enabled && !((Object)(object)__instance == (Object)null))
				{
					__0 = glassTuning.GatherDamage(__0, ((Component)__instance).CompareTag("Build"), __2, __4);
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass gathering damage failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(PowerupInventory), "GetLifestealMultiplier", new Type[] { typeof(int[]) })]
	internal static class LifestealPatch
	{
		private static bool logged;

		private static void Postfix(ref float __result)
		{
			try
			{
				__result = GlassPlugin.Cfg.Tuning().ScaleLifesteal(__result);
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass lifesteal failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerStatus), "Awake", new Type[] { })]
	internal static class AwakePatch
	{
		private static PlayerStatus announced;

		[HarmonyPriority(200)]
		[HarmonyAfter(new string[] { "evan.muck.shapedglass" })]
		private static void Postfix(PlayerStatus __instance)
		{
			UpdateStatsPatch.Apply(__instance, "Awake");
			Announce(__instance);
		}

		private static void Announce(PlayerStatus player)
		{
			try
			{
				if ((Object)(object)player == (Object)null || announced == player)
				{
					return;
				}
				announced = player;
				GlassTuning glassTuning = GlassPlugin.Cfg.Tuning();
				string text = GlassPlugin.Cfg.StampCode();
				GlassPlugin.Log.LogInfo((object)("Artifact of Glass config " + text + " (" + glassTuning.Summary() + ")."));
				if (!GlassPlugin.Cfg.AnnounceInChat.Value)
				{
					return;
				}
				bool serverOwner = LocalClient.serverOwner;
				string[] array = GlassStamp.Lines(glassTuning.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)
			{
				GlassPlugin.Log.LogError((object)$"Artifact of Glass announce failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerStatus), "Dracula", new Type[] { })]
	internal static class DraculaPatch
	{
		private static bool logged;

		[HarmonyPriority(200)]
		[HarmonyAfter(new string[] { "evan.muck.shapedglass" })]
		private static void Postfix(PlayerStatus __instance)
		{
			try
			{
				if (GlassPlugin.Cfg.Tuning().Enabled && !((Object)(object)__instance == (Object)null))
				{
					__instance.hp = GlassTuning.ClampHp(__instance.hp, __instance.maxHp);
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass Dracula clamp failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerStatus), "Healing", new Type[] { })]
	internal static class HealingPatch
	{
		private static bool logged;

		[HarmonyPriority(200)]
		private static void Postfix(PlayerStatus __instance)
		{
			try
			{
				if (GlassPlugin.Cfg.Tuning().Enabled && !((Object)(object)__instance == (Object)null))
				{
					__instance.hp = GlassTuning.ClampHp(__instance.hp, __instance.maxHp);
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass healing clamp failed: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(PowerupInventory), "GetMaxHpAndShield", new Type[] { typeof(int[]) })]
	internal static class HostHpEstimatePatch
	{
		private static bool logged;

		[HarmonyPriority(200)]
		[HarmonyAfter(new string[] { "evan.muck.shapedglass" })]
		private static void Postfix(PowerupInventory __instance, int[] __0, ref int __result)
		{
			try
			{
				GlassTuning glassTuning = GlassPlugin.Cfg.Tuning();
				if (glassTuning.Enabled && !((Object)(object)__instance == (Object)null))
				{
					int vanillaHp = 100 + __instance.GetHpMultiplier(__0);
					int shield = __instance.GetShield(__0);
					__result = glassTuning.ScaleHostTotal(__result, vanillaHp, shield);
				}
			}
			catch (Exception arg)
			{
				if (!logged)
				{
					logged = true;
					GlassPlugin.Log.LogError((object)$"Artifact of Glass host HP estimate failed: {arg}");
				}
			}
		}
	}
	internal readonly struct GlassTuning
	{
		public const float DefaultDamage = 5f;

		public const float DefaultMaxHp = 0.1f;

		public const int UndefinedWeaponType = -1;

		public const int MeleeWeaponType = 0;

		public const int RockWeaponType = 2;

		public const int ElectroHitEffect = 3;

		public readonly bool Enabled;

		public readonly float DamageMultiplier;

		public readonly float MaxHpMultiplier;

		public static GlassTuning Defaults => new GlassTuning(enabled: false, 5f, 0.1f);

		public GlassTuning(bool enabled, float damageMultiplier, float maxHpMultiplier)
		{
			Enabled = enabled;
			DamageMultiplier = SanitizeDamage(damageMultiplier);
			MaxHpMultiplier = SanitizeHp(maxHpMultiplier);
		}

		public float ScaleStrength(float vanillaStrength)
		{
			if (!Enabled)
			{
				return vanillaStrength;
			}
			return vanillaStrength * DamageMultiplier;
		}

		public int GatherDamage(int damage, bool targetIsBuild, int hitEffect, int hitWeaponType)
		{
			if (!Enabled || DamageMultiplier == 1f || damage <= 0 || !IsGathering(targetIsBuild, hitEffect, hitWeaponType))
			{
				return damage;
			}
			return (int)Math.Floor((double)damage / (double)DamageMultiplier + 0.0001);
		}

		public static bool IsGathering(bool targetIsBuild, int hitEffect, int hitWeaponType)
		{
			if (targetIsBuild)
			{
				return false;
			}
			if (hitWeaponType >= 0 && hitWeaponType <= 2)
			{
				return true;
			}
			if (hitWeaponType == -1)
			{
				return hitEffect == 3;
			}
			return false;
		}

		public float ScaleLifesteal(float vanillaLifesteal)
		{
			if (!Enabled || DamageMultiplier == 1f)
			{
				return vanillaLifesteal;
			}
			return (float)((double)vanillaLifesteal / (double)DamageMultiplier * 0.999999);
		}

		public int ScaleMaxHp(int vanillaMaxHp)
		{
			if (!Enabled || MaxHpMultiplier == 1f)
			{
				return vanillaMaxHp;
			}
			double num = Scaled(vanillaMaxHp);
			if (num < 1.0)
			{
				return 1;
			}
			return (int)Math.Floor(num);
		}

		public int ScaleShield(int vanillaShield)
		{
			if (!Enabled || MaxHpMultiplier == 1f || vanillaShield <= 0)
			{
				return vanillaShield;
			}
			return (int)Math.Floor(Scaled(vanillaShield));
		}

		public int ScaleHostTotal(int total, int vanillaHp, int vanillaShield)
		{
			if (!Enabled || MaxHpMultiplier == 1f)
			{
				return total;
			}
			int num = vanillaHp + vanillaShield;
			int num2 = ((num > 0 && vanillaShield > 0) ? ((int)Math.Round((double)total * (double)vanillaShield / (double)num)) : 0);
			return ScaleMaxHp(total - num2) + ScaleShield(num2);
		}

		private double Scaled(int value)
		{
			return (double)value * (double)MaxHpMultiplier + 0.0001;
		}

		public static float ClampHp(float hp, int maxHp)
		{
			if (!(hp > (float)maxHp))
			{
				return hp;
			}
			return maxHp;
		}

		public string Summary()
		{
			if (!Enabled)
			{
				return "off";
			}
			return string.Format(CultureInfo.InvariantCulture, "damage x{0:0.##}, max HP x{1:0.##}", DamageMultiplier, MaxHpMultiplier);
		}

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

		private static float SanitizeHp(float value)
		{
			if (float.IsNaN(value) || value <= 0f || value > 1f)
			{
				return 1f;
			}
			return value;
		}
	}
	internal static class RetiredSwitch
	{
		public static readonly ConfigDefinition Old = new ConfigDefinition("Glass", "Enabled");

		public static bool Drop(ConfigFile cfg)
		{
			try
			{
				if (!(AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(cfg, null) is Dictionary<ConfigDefinition, string> dictionary) || !dictionary.Remove(Old))
				{
					return false;
				}
				cfg.Save();
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "MuckArtifactGlass";

		public const string PLUGIN_NAME = "Artifact of Glass";

		public const string PLUGIN_VERSION = "0.2.0";
	}
}