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