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