using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
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("MuckShrineOfBlood")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Adds the Risk of Rain 2 Shrine of Blood. Spend current HP for coins. Never drops you below 1 HP.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+e84dc69f2eb55ea4293da66d1fed421eede60311")]
[assembly: AssemblyProduct("Shrine of Blood")]
[assembly: AssemblyTitle("MuckShrineOfBlood")]
[assembly: InternalsVisibleTo("MuckShrineOfBlood.Tests")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace MuckShrineOfBlood;
internal sealed class BloodConfig
{
public readonly ConfigEntry<int> Count;
public readonly ConfigEntry<float> MinSpacing;
public readonly ConfigEntry<int> FirstCostPercent;
public readonly ConfigEntry<float> GoldMultiplier;
public readonly ConfigEntry<int> MaxUses;
public readonly ConfigEntry<bool> VerboseLogging;
public BloodConfig(ConfigFile cfg)
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Expected O, but got Unknown
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0068: Expected O, but got Unknown
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Expected O, but got Unknown
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00d0: Expected O, but got Unknown
//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
//IL_00fd: Expected O, but got Unknown
Count = cfg.Bind<int>("World", "Count", 2, new ConfigDescription("How many Shrines of Blood to place on grass on each map, including each Wave Run island. 0 places none. On by default. Every player should use the same value.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
MinSpacing = cfg.Bind<float>("World", "MinSpacing", 80f, new ConfigDescription("Minimum distance in meters between shrines. Every player should use the same value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 500f), Array.Empty<object>()));
FirstCostPercent = cfg.Bind<int>("Shrine", "FirstCostPercent", 50, new ConfigDescription("Percent of current HP taken on the first use. Each later use costs 1 - (1 - previous)^2, the Risk of Rain 2 curve (50, then 75, then 93.75). The host's value is the one that is paid.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 90), Array.Empty<object>()));
GoldMultiplier = cfg.Bind<float>("Shrine", "GoldMultiplier", 0.5f, new ConfigDescription("Coins per HP spent, floored. 0.5 is the Risk of Rain 2 rate: half the HP, so 50 HP pays 25 coins. A scrap that floors to 0 still pays 1 coin. The host's value is the one that is paid.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
MaxUses = cfg.Bind<int>("Shrine", "MaxUses", 3, new ConfigDescription("Uses on each shrine. Risk of Rain 2 allows 3. Every player should use the same value.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Log each refused ask.");
}
public string Code()
{
return ConfigStamp.Code(Count.Value, (int)Math.Round(MinSpacing.Value * 100f), FirstCostPercent.Value, (int)Math.Round(GoldMultiplier.Value * 1000f), MaxUses.Value);
}
}
internal static class BloodHost
{
public static bool TryAccept(int hostUseIndex, int askUseIndex, float hp, int maxUses, float firstPercent, float goldMultiplier, out BloodQuote quote)
{
quote = BloodMath.Quote(hp, askUseIndex, maxUses, firstPercent, goldMultiplier);
if (askUseIndex != hostUseIndex)
{
quote.Ok = false;
return false;
}
return quote.Ok;
}
}
internal struct BloodQuote
{
public bool Ok;
public int UseIndex;
public float Hp;
public float Cost;
public int Gold;
public float Left;
}
internal static class BloodMath
{
public const float WikiFirstPercent = 50f;
public const float WikiGoldPerHp = 0.5f;
public const int WikiUses = 3;
public const int DefaultCount = 2;
public const float DefaultSpacing = 80f;
public const float MaxTrackedHp = 100000f;
public const int MaxGold = 1000000;
public const float PercentCap = 0.99f;
public static float PercentAt(float firstPercent, int useIndex)
{
if (useIndex < 0)
{
useIndex = 0;
}
double num = (double)firstPercent / 100.0;
if (double.IsNaN(num) || num < 0.0)
{
num = 0.0;
}
if (num > 0.9900000095367432)
{
num = 0.9900000095367432;
}
for (int i = 0; i < useIndex; i++)
{
double num2 = 1.0 - num;
num = 1.0 - num2 * num2;
if (num > 0.9900000095367432)
{
num = 0.9900000095367432;
}
}
return (float)num;
}
public static BloodQuote Quote(float hp, int useIndex, int maxUses, float firstPercent, float goldMultiplier)
{
BloodQuote result = new BloodQuote
{
UseIndex = useIndex,
Hp = hp
};
if (useIndex < 0 || maxUses <= 0 || useIndex >= maxUses)
{
return result;
}
if (float.IsNaN(hp) || float.IsInfinity(hp) || !(hp > 1f) || hp > 100000f)
{
return result;
}
double num = (double)hp * (double)PercentAt(firstPercent, useIndex);
if ((double)hp - num < 1.0)
{
num = (double)hp - 1.0;
}
if (!(num > 0.0) || double.IsNaN(num) || double.IsInfinity(num))
{
return result;
}
result.Ok = true;
result.Cost = (float)num;
result.Left = (float)((double)hp - num);
if (result.Left < 1f)
{
result.Left = 1f;
}
result.Gold = Gold(result.Cost, goldMultiplier);
return result;
}
public static int Gold(float hpSpent, float multiplier)
{
if (!(hpSpent > 0f) || !(multiplier > 0f) || float.IsNaN(hpSpent) || float.IsNaN(multiplier))
{
return 0;
}
if (float.IsInfinity(hpSpent) || float.IsInfinity(multiplier))
{
return 1000000;
}
double num = (double)hpSpent * (double)multiplier;
if (double.IsNaN(num) || num <= 0.0)
{
return 0;
}
int num2 = (int)Math.Floor(num + 1E-06);
if (num2 < 1)
{
num2 = 1;
}
if (num2 > 1000000)
{
num2 = 1000000;
}
return num2;
}
public static float Apply(float currentHp, float cost)
{
if (float.IsNaN(currentHp) || float.IsInfinity(currentHp))
{
return currentHp;
}
if (!(currentHp > 1f) || float.IsNaN(cost) || !(cost > 0f))
{
return currentHp;
}
double num = (double)currentHp - (double)cost;
if (num < 1.0)
{
num = 1.0;
}
return (float)num;
}
}
internal static class BloodPayout
{
private static bool missingCoin;
public static bool CanGrant()
{
if (ItemManager.Instance == null)
{
return false;
}
if ((Object)(object)ItemManager.Instance.GetItemByName("Coin") != (Object)null)
{
return (Object)(object)InventoryUI.Instance != (Object)null;
}
return false;
}
public static void Grant(int gold)
{
if (gold <= 0)
{
return;
}
if (ItemManager.Instance == null || (Object)(object)InventoryUI.Instance == (Object)null)
{
BloodPlugin.Log.LogWarning((object)("Shrine of Blood could not pay " + gold + " coins: inventory is missing."));
return;
}
InventoryItem itemByName = ItemManager.Instance.GetItemByName("Coin");
if ((Object)(object)itemByName == (Object)null)
{
if (!missingCoin)
{
missingCoin = true;
BloodPlugin.Log.LogWarning((object)"Shrine of Blood: no Coin item, so the payout was not given.");
}
return;
}
int num = gold;
int num2 = 0;
while (num > 0 && num2++ < 6)
{
InventoryItem val = ScriptableObject.CreateInstance<InventoryItem>();
val.Copy(itemByName, num);
int num3 = InventoryUI.Instance.AddItemToInventory(val);
Object.Destroy((Object)(object)val);
if (num3 <= 0)
{
break;
}
if (num3 >= num)
{
BloodPlugin.Log.LogWarning((object)("Shrine of Blood paid " + (gold - num) + " of " + gold + " coins. The inventory is full."));
break;
}
num = num3;
}
}
}
[BepInPlugin("evan.muck.shrineofblood", "Shrine of Blood", "0.1.0")]
public class BloodPlugin : BaseUnityPlugin
{
public const string Guid = "evan.muck.shrineofblood";
public const string Name = "Shrine of Blood";
public const string Version = "0.1.0";
internal static ManualLogSource Log;
internal static BloodConfig Cfg;
internal static PayLedger Pays = new PayLedger();
internal static EchoGate Echo = new EchoGate();
private void Awake()
{
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
Cfg = new BloodConfig(((BaseUnityPlugin)this).Config);
Pays = new PayLedger();
Echo = new EchoGate();
Harmony harmony = new Harmony("evan.muck.shrineofblood");
if (!TryPatch(harmony, typeof(StructureSpawnerStartPatch)))
{
Log.LogError((object)"Shrine of Blood 0.1.0 could not hook world generation. No shrines will appear.");
return;
}
if (!TryPatch(harmony, typeof(ChatLinePatch)))
{
Log.LogWarning((object)"Shrine of Blood could not hook chat. Shrines will appear, but a use will not sync.");
}
Log.LogInfo((object)("Shrine of Blood 0.1.0 loaded. Config " + Cfg.Code() + "."));
}
private static bool TryPatch(Harmony harmony, Type patchClass)
{
try
{
harmony.CreateClassProcessor(patchClass).Patch();
return true;
}
catch (Exception ex)
{
Log.LogError((object)("Shrine of Blood failed to patch " + patchClass.Name + ": " + ex));
return false;
}
}
}
public class BloodShrine : MonoBehaviour, Interactable, SharedObject
{
private static readonly Color Spent = new Color(0.25f, 0.16f, 0.16f);
private int id;
private ShrineUses shown;
private ShrineUses host;
private float pendingUntil;
private Material[] materials;
public static void Build(int shrineId, Vector3 ground, int uses)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Expected O, but got Unknown
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: Unknown result type (might be due to invalid IL or missing references)
//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("ShrineOfBlood");
val.transform.position = ground;
val.layer = 15;
BoxCollider obj = val.AddComponent<BoxCollider>();
((Collider)obj).isTrigger = true;
obj.size = new Vector3(2.2f, 2.2f, 2.2f);
obj.center = new Vector3(0f, 1f, 0f);
GameObject val2 = new GameObject("ShrineOfBlood_solid")
{
layer = 10
};
val2.transform.SetParent(val.transform, false);
BoxCollider obj2 = val2.AddComponent<BoxCollider>();
((Collider)obj2).isTrigger = false;
obj2.size = new Vector3(1.7f, 1.7f, 1.7f);
obj2.center = new Vector3(0f, 0.85f, 0f);
BloodShrine bloodShrine = val.AddComponent<BloodShrine>();
bloodShrine.SetId(shrineId);
bloodShrine.shown = new ShrineUses(uses);
bloodShrine.host = new ShrineUses(uses);
bloodShrine.materials = ShrineMesh.Attach(val.transform);
BloodShrines.Add(shrineId, bloodShrine);
ResourceManager.Instance.AddObject(shrineId, val);
}
public void Interact()
{
if (shown == null || shown.Spent || Time.time < pendingUntil)
{
return;
}
PlayerStatus instance = PlayerStatus.Instance;
if ((Object)(object)instance == (Object)null || instance.IsPlayerDead())
{
return;
}
BloodConfig cfg = BloodPlugin.Cfg;
int maxUses = cfg?.MaxUses.Value ?? 3;
float firstPercent = ((cfg != null) ? ((float)cfg.FirstCostPercent.Value) : 50f);
float goldMultiplier = cfg?.GoldMultiplier.Value ?? 0.5f;
BloodQuote quote = BloodMath.Quote(instance.hp, shown.UseIndex, maxUses, firstPercent, goldMultiplier);
if (!quote.Ok)
{
Say(BloodText.TooHurt);
return;
}
if (quote.Gold > 0 && !BloodPayout.CanGrant())
{
Say(BloodText.NoCoins);
return;
}
pendingUntil = Time.time + 3f;
if (LocalClient.serverOwner)
{
Commit(MyId(), quote);
}
else
{
ClientSend.SendChatMessage(BloodWire.Ask(id, quote.UseIndex, BloodWire.Milli(quote.Hp)));
}
}
public void LocalExecute()
{
}
public void ServerExecute(int fromClient = -1)
{
}
public void AllExecute()
{
}
public void RemoveObject()
{
Object.Destroy((Object)(object)((Component)this).gameObject);
}
public string GetName()
{
if (shown == null)
{
return BloodText.Prompt(spent: false, BloodMath.PercentAt(50f, 0));
}
BloodConfig cfg = BloodPlugin.Cfg;
float firstPercent = ((cfg != null) ? ((float)cfg.FirstCostPercent.Value) : 50f);
return BloodText.Prompt(shown.Spent, BloodMath.PercentAt(firstPercent, shown.UseIndex));
}
public bool IsStarted()
{
if (shown != null)
{
return shown.Spent;
}
return false;
}
public void SetId(int newId)
{
id = newId;
}
public int GetId()
{
return id;
}
public void AcceptAsk(int payerId, int useIndex, int hpMilli)
{
if (host == null || payerId < 0)
{
return;
}
BloodConfig cfg = BloodPlugin.Cfg;
int maxUses = cfg?.MaxUses.Value ?? 3;
float firstPercent = ((cfg != null) ? ((float)cfg.FirstCostPercent.Value) : 50f);
float goldMultiplier = cfg?.GoldMultiplier.Value ?? 0.5f;
if (!BloodHost.TryAccept(host.UseIndex, useIndex, BloodWire.FromMilli(hpMilli), maxUses, firstPercent, goldMultiplier, out var quote))
{
if (cfg != null && cfg.VerboseLogging.Value)
{
BloodPlugin.Log.LogInfo((object)("Shrine " + id + " refused use " + useIndex + " from client " + payerId + "."));
}
}
else
{
Commit(payerId, quote);
}
}
internal void ApplyPay(PayMessage pay)
{
pendingUntil = 0f;
if (shown != null && pay.UseIndex >= shown.UseIndex)
{
while (shown.UseIndex <= pay.UseIndex && shown.TrySpend())
{
}
if (shown.Spent)
{
Grey();
}
}
if (pay.PayerId != MyId())
{
return;
}
PlayerStatus instance = PlayerStatus.Instance;
if (!((Object)(object)instance == (Object)null) && !instance.IsPlayerDead() && instance.hp > 0f)
{
float num = BloodMath.Apply(instance.hp, BloodWire.FromMilli(pay.CostMilli));
if (num < 1f)
{
num = 1f;
}
instance.hp = num;
BloodPayout.Grant(pay.Gold);
}
}
private void Commit(int payerId, BloodQuote quote)
{
if (host != null && host.UseIndex == quote.UseIndex && host.TrySpend())
{
string text = BloodText.Activation(PlayerNames.Of(payerId), quote.Cost, quote.Gold, host.Left);
string text2 = BloodWire.Pay(id, payerId, quote.UseIndex, BloodWire.Milli(quote.Cost), quote.Gold);
ServerSend.SendChatMessage(-1, "", text2);
ServerSend.SendChatMessage(-1, "", text);
Say(text2);
Say(text);
BloodPlugin.Log.LogInfo((object)text);
}
}
private void OnDestroy()
{
BloodShrines.Remove(id, this);
if (materials == null)
{
return;
}
for (int i = 0; i < materials.Length; i++)
{
if ((Object)(object)materials[i] != (Object)null)
{
Object.Destroy((Object)(object)materials[i]);
}
}
}
private void Grey()
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
if (materials == null)
{
return;
}
for (int i = 0; i < materials.Length; i++)
{
if ((Object)(object)materials[i] != (Object)null)
{
materials[i].color = Spent;
}
}
}
private static int MyId()
{
if (!((Object)(object)LocalClient.instance != (Object)null))
{
return -1;
}
return LocalClient.instance.myId;
}
private static void Say(string message)
{
ChatBox instance = ChatBox.Instance;
if ((Object)(object)instance != (Object)null)
{
instance.AppendMessage(-1, message, "");
}
}
}
internal static class BloodShrines
{
private static readonly Dictionary<int, BloodShrine> All = new Dictionary<int, BloodShrine>();
public static void Add(int id, BloodShrine shrine)
{
All[id] = shrine;
}
public static void Remove(int id, BloodShrine shrine)
{
if (All.TryGetValue(id, out var value) && value == shrine)
{
All.Remove(id);
}
}
public static bool TryGet(int id, out BloodShrine shrine)
{
return All.TryGetValue(id, out shrine);
}
public static void RetireAll()
{
List<BloodShrine> list = new List<BloodShrine>(All.Values);
All.Clear();
for (int i = 0; i < list.Count; i++)
{
if ((Object)(object)list[i] != (Object)null)
{
Object.Destroy((Object)(object)((Component)list[i]).gameObject);
}
}
}
}
internal static class BloodText
{
public const int ChatMax = 120;
public const string Head = "Shrine of Blood: ";
private static readonly string[] Reserved = new string[5] { "~sv", "~wr", "~bq", "~gs", "~cd" };
public static string TooHurt => "Not enough health.";
public static string NoCoins => "The shrine has no coins to give.";
public static string Prompt(bool spent, float fraction)
{
if (spent)
{
return "Shrine of Blood (spent)";
}
return "Shrine of Blood (" + PercentLabel(fraction) + "% HP)";
}
public static string Player(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return "Someone";
}
name = name.Replace('\r', ' ').Replace('\n', ' ').Trim();
if (name.Length > 40)
{
name = name.Substring(0, 40).Trim();
}
if (name.Length != 0)
{
return name;
}
return "Someone";
}
public static string Activation(string playerName, float hpSpent, int gold, int usesLeftHere)
{
string text = Player(playerName);
string text2 = ((gold == 1) ? "coin" : "coins");
string text3 = ((usesLeftHere <= 0) ? "It is spent." : ((usesLeftHere == 1) ? "1 use left here." : (usesLeftHere + " uses left here.")));
return Fit("Shrine of Blood: " + text + " paid " + Hp(hpSpent) + " HP for " + gold.ToString(CultureInfo.InvariantCulture) + " " + text2 + ". " + text3);
}
public static string Spot(float standX, float standY, float standZ, float aimX, float aimY, float aimZ)
{
return string.Format(CultureInfo.InvariantCulture, "Shrine of Blood spot {0:0.0} {1:0.0} {2:0.0} {3:0.0} {4:0.0} {5:0.0}", standX, standY, standZ, aimX, aimY, aimZ);
}
public static string Placed(int count, int seed)
{
return "Placed " + count.ToString(CultureInfo.InvariantCulture) + " Shrines of Blood (seed " + seed.ToString(CultureInfo.InvariantCulture) + ")";
}
public static bool IsActivation(string message)
{
if (!string.IsNullOrEmpty(message))
{
return message.StartsWith("Shrine of Blood: ");
}
return false;
}
public static bool UsesReservedPrefix(string message)
{
if (string.IsNullOrEmpty(message))
{
return false;
}
for (int i = 0; i < Reserved.Length; i++)
{
if (message.StartsWith(Reserved[i]))
{
return true;
}
}
return false;
}
public static string PercentLabel(float fraction)
{
if (float.IsNaN(fraction) || fraction < 0f)
{
return "0";
}
double num = Math.Round((double)fraction * 100.0, 1, MidpointRounding.AwayFromZero);
if (Math.Abs(num - Math.Round(num)) < 0.05)
{
return ((int)Math.Round(num)).ToString(CultureInfo.InvariantCulture);
}
return num.ToString("0.0", CultureInfo.InvariantCulture);
}
public static string Hp(float value)
{
if (float.IsNaN(value) || value < 0f)
{
return "0";
}
double num = Math.Round(value, 1, MidpointRounding.AwayFromZero);
if (Math.Abs(num - Math.Round(num)) < 0.05)
{
return ((int)Math.Round(num)).ToString(CultureInfo.InvariantCulture);
}
return num.ToString("0.0", CultureInfo.InvariantCulture);
}
public static string Fit(string line)
{
if (string.IsNullOrEmpty(line))
{
return "";
}
if (line.Length <= 120)
{
return line;
}
return line.Substring(0, 120);
}
}
internal struct PayMessage
{
public int ShrineId;
public int PayerId;
public int UseIndex;
public int CostMilli;
public int Gold;
}
internal static class BloodWire
{
public const string Token = "~sb1";
public const string Family = "~sb";
public static bool IsHidden(string message)
{
if (!string.IsNullOrEmpty(message))
{
return message.StartsWith("~sb");
}
return false;
}
public static int Milli(float hp)
{
if (float.IsNaN(hp) || float.IsInfinity(hp) || hp <= 0f)
{
return 0;
}
double num = Math.Round((double)hp * 1000.0, MidpointRounding.AwayFromZero);
if (num > 2147483647.0)
{
return int.MaxValue;
}
return (int)num;
}
public static float FromMilli(int milli)
{
if (milli <= 0)
{
return 0f;
}
return (float)milli / 1000f;
}
public static string Ask(int shrineId, int useIndex, int hpMilli)
{
return string.Format(CultureInfo.InvariantCulture, "{0} a {1} {2} {3}", "~sb1", shrineId, useIndex, hpMilli);
}
public static string Pay(int shrineId, int payerId, int useIndex, int costMilli, int gold)
{
return string.Format(CultureInfo.InvariantCulture, "{0} p {1} {2} {3} {4} {5}", "~sb1", shrineId, payerId, useIndex, costMilli, gold);
}
public static bool TryParseAsk(string message, out int shrineId, out int useIndex, out int hpMilli)
{
shrineId = 0;
useIndex = 0;
hpMilli = 0;
string[] array = Split(message);
if (array == null || array.Length != 5 || array[0] != "~sb1" || array[1] != "a")
{
return false;
}
if (!ParseId(array[2], out shrineId) || !ParseUse(array[3], out useIndex))
{
return false;
}
if (!int.TryParse(array[4], NumberStyles.None, CultureInfo.InvariantCulture, out hpMilli))
{
return false;
}
if (hpMilli <= 0 || hpMilli > 100000000)
{
return false;
}
return true;
}
public static bool TryParsePay(string message, out PayMessage pay)
{
pay = default(PayMessage);
string[] array = Split(message);
if (array == null || array.Length != 7 || array[0] != "~sb1" || array[1] != "p")
{
return false;
}
if (!ParseId(array[2], out pay.ShrineId) || !ParseId(array[3], out pay.PayerId) || !ParseUse(array[4], out pay.UseIndex))
{
return false;
}
if (!int.TryParse(array[5], NumberStyles.None, CultureInfo.InvariantCulture, out pay.CostMilli))
{
return false;
}
if (!int.TryParse(array[6], NumberStyles.None, CultureInfo.InvariantCulture, out pay.Gold))
{
return false;
}
if (pay.CostMilli <= 0 || pay.CostMilli > 100000000)
{
return false;
}
if (pay.Gold < 0 || pay.Gold > 1000000)
{
return false;
}
return true;
}
private static bool ParseId(string text, out int id)
{
if (int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out id))
{
return id >= 0;
}
return false;
}
private static bool ParseUse(string text, out int useIndex)
{
if (int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out useIndex) && useIndex >= 0)
{
return useIndex < 64;
}
return false;
}
private static string[] Split(string message)
{
if (string.IsNullOrEmpty(message))
{
return null;
}
return message.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
}
internal static class BloodWorld
{
private static ResourceManager placedFor;
private static int placedSeed;
private static bool warnedMissingManager;
private static readonly List<(double X, double Z)> placed = new List<(double, double)>();
public static void PlaceOnce(StructureSpawner spawner)
{
//IL_013e: Unknown result type (might be due to invalid IL or missing references)
//IL_0143: Unknown result type (might be due to invalid IL or missing references)
//IL_0150: Unknown result type (might be due to invalid IL or missing references)
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_0171: Unknown result type (might be due to invalid IL or missing references)
//IL_0177: Invalid comparison between Unknown and I4
//IL_0182: Unknown result type (might be due to invalid IL or missing references)
//IL_0188: Invalid comparison between Unknown and I4
//IL_0192: Unknown result type (might be due to invalid IL or missing references)
//IL_0198: Invalid comparison between Unknown and I4
//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
//IL_01de: Unknown result type (might be due to invalid IL or missing references)
//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
ResourceManager instance = ResourceManager.Instance;
int seed = GameManager.GetSeed();
bool num = instance != null;
switch (WorldGate.Decide(num, num && (Object)(object)placedFor == (Object)(object)instance, placedSeed == seed))
{
case PlaceKind.MissingManager:
if (!warnedMissingManager)
{
warnedMissingManager = true;
BloodPlugin.Log.LogWarning((object)"Shrine of Blood skipped placement: ResourceManager.Instance is null.");
}
break;
case PlaceKind.AlreadyPlaced:
break;
case PlaceKind.NewWorld:
case PlaceKind.NewMap:
{
BloodShrines.RetireAll();
if (BloodPlugin.Pays != null)
{
BloodPlugin.Pays.Reset();
}
if (BloodPlugin.Echo != null)
{
BloodPlugin.Echo.Reset();
}
placedFor = instance;
placedSeed = seed;
placed.Clear();
warnedMissingManager = false;
float halfExtent = Placement.HalfExtent(MapGenerator.mapChunkSize, spawner.worldScale);
BloodConfig cfg = BloodPlugin.Cfg;
int num2 = Placement.ClampCount(cfg.Count.Value);
float value = cfg.MinSpacing.Value;
int value2 = cfg.MaxUses.Value;
int num3 = num2 * 8;
int num4 = 0;
RaycastHit val = default(RaycastHit);
for (int i = 0; i < num3; i++)
{
if (num4 >= num2)
{
break;
}
(double, double) item = Placement.Candidate(seed, i, halfExtent);
if (Placement.FarEnough(placed, item.Item1, item.Item2, value) && Physics.Raycast(new Vector3((float)item.Item1, 200f, (float)item.Item2), Vector3.down, ref val, 500f, LayerMask.op_Implicit(spawner.whatIsTerrain)))
{
float y = ((RaycastHit)(ref val)).point.y;
bool hit = (int)WorldUtility.WorldHeightToBiome(y) == 2;
bool below = (int)WorldUtility.WorldHeightToBiome(y - 0.5f) == 2;
bool above = (int)WorldUtility.WorldHeightToBiome(y + 0.5f) == 2;
if (Placement.InsideGrass(hit, below, above))
{
BloodShrine.Build(ShrineIds.For(i), ((RaycastHit)(ref val)).point, value2);
placed.Add(item);
num4++;
Placement.StandAndAim(((RaycastHit)(ref val)).point.x, ((RaycastHit)(ref val)).point.y, ((RaycastHit)(ref val)).point.z, out var sx, out var sy, out var sz, out var ax, out var ay, out var az);
BloodPlugin.Log.LogInfo((object)BloodText.Spot(sx, sy, sz, ax, ay, az));
}
}
}
BloodPlugin.Log.LogInfo((object)BloodText.Placed(num4, seed));
BloodPlugin.Log.LogInfo((object)("Shrine of Blood config " + cfg.Code() + "."));
break;
}
}
}
}
[HarmonyPatch(typeof(ChatBox), "AppendMessage", new Type[]
{
typeof(int),
typeof(string),
typeof(string)
})]
internal static class ChatLinePatch
{
private static bool Prefix(int fromUser, string message)
{
bool flag = BloodWire.IsHidden(message);
try
{
if (flag)
{
PayMessage pay;
BloodShrine shrine2;
if (fromUser >= 0 && LocalClient.serverOwner && BloodWire.TryParseAsk(message, out var shrineId, out var useIndex, out var hpMilli))
{
if (BloodShrines.TryGet(shrineId, out var shrine))
{
shrine.AcceptAsk(fromUser, useIndex, hpMilli);
}
}
else if (fromUser < 0 && BloodWire.TryParsePay(message, out pay) && BloodPlugin.Pays != null && BloodPlugin.Pays.Claim(pay.ShrineId, pay.UseIndex) && BloodShrines.TryGet(pay.ShrineId, out shrine2))
{
shrine2.ApplyPay(pay);
}
return false;
}
if (BloodText.IsActivation(message) && BloodPlugin.Echo != null)
{
return BloodPlugin.Echo.Allow(message, Time.time);
}
}
catch (Exception ex)
{
if (BloodPlugin.Log != null)
{
BloodPlugin.Log.LogError((object)("Shrine of Blood chat failed: " + ex));
}
}
return !flag;
}
}
internal static class ConfigStamp
{
public static string Code(int count, int spacingCm, int firstPercent, int goldThousand, int maxUses)
{
uint h = 2166136261u;
h = Mix(h, count);
h = Mix(h, spacingCm);
h = Mix(h, firstPercent);
h = Mix(h, goldThousand);
return Mix(h, maxUses).ToString("X8");
}
private static uint Mix(uint h, int value)
{
h ^= (uint)value;
return h * 16777619;
}
}
internal sealed class EchoGate
{
private string line;
private float at = float.NaN;
public bool Allow(string message, float now, float window = 2f)
{
if (!float.IsNaN(at) && message == line && now >= at && now - at < window)
{
return false;
}
line = message;
at = now;
return true;
}
public void Reset()
{
line = null;
at = float.NaN;
}
}
internal static class Hash01
{
public static ulong SplitMix64(ulong x)
{
long num = (long)x + -7046029254386353131L;
long num2 = (num ^ (num >>> 30)) * -4658895280553007687L;
long num3 = (num2 ^ (num2 >>> 27)) * -7723592293110705685L;
return (ulong)(num3 ^ (num3 >>> 31));
}
public static double Of(int a, int b, int c)
{
return ToUnit(SplitMix64(SplitMix64(Key(a, b)) ^ (uint)c));
}
private static ulong Key(int a, int b)
{
return ((ulong)(uint)a << 32) | (uint)b;
}
private static double ToUnit(ulong h)
{
return (double)(h >> 11) * 1.1102230246251565E-16;
}
}
internal sealed class ObjPart
{
public readonly string Material;
public readonly List<float> Positions = new List<float>();
public int Triangles => Positions.Count / 9;
public ObjPart(string material)
{
Material = material;
}
public void Add(List<float> verts, int a, int b, int c)
{
Copy(verts, a);
Copy(verts, b);
Copy(verts, c);
}
private void Copy(List<float> verts, int index)
{
int num = index * 3;
Positions.Add(verts[num]);
Positions.Add(verts[num + 1]);
Positions.Add(verts[num + 2]);
}
}
internal sealed class ObjModel
{
public readonly List<ObjPart> Parts;
public int Triangles
{
get
{
int num = 0;
for (int i = 0; i < Parts.Count; i++)
{
num += Parts[i].Triangles;
}
return num;
}
}
public ObjModel(List<ObjPart> parts)
{
Parts = parts;
}
public void Bounds(out float minX, out float minY, out float minZ, out float maxX, out float maxY, out float maxZ)
{
minX = (minY = (minZ = float.PositiveInfinity));
maxX = (maxY = (maxZ = float.NegativeInfinity));
for (int i = 0; i < Parts.Count; i++)
{
List<float> positions = Parts[i].Positions;
for (int j = 0; j < positions.Count; j += 3)
{
float num = positions[j];
float num2 = positions[j + 1];
float num3 = positions[j + 2];
if (num < minX)
{
minX = num;
}
if (num2 < minY)
{
minY = num2;
}
if (num3 < minZ)
{
minZ = num3;
}
if (num > maxX)
{
maxX = num;
}
if (num2 > maxY)
{
maxY = num2;
}
if (num3 > maxZ)
{
maxZ = num3;
}
}
}
}
public static ObjModel Parse(string text)
{
List<float> list = new List<float>();
List<ObjPart> list2 = new List<ObjPart>();
ObjPart objPart = null;
string text2 = "stone";
if (string.IsNullOrEmpty(text))
{
return new ObjModel(list2);
}
string[] array = text.Split(new char[2] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < array.Length; i++)
{
string text3 = array[i].Trim();
if (text3.Length == 0 || text3[0] == '#')
{
continue;
}
if (text3.StartsWith("usemtl "))
{
text2 = text3.Substring(7).Trim();
objPart = null;
}
else if (text3.StartsWith("v "))
{
string[] array2 = text3.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
list.Add(float.Parse(array2[1], CultureInfo.InvariantCulture));
list.Add(float.Parse(array2[2], CultureInfo.InvariantCulture));
list.Add(float.Parse(array2[3], CultureInfo.InvariantCulture));
}
else
{
if (!text3.StartsWith("f "))
{
continue;
}
string[] array3 = text3.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
List<int> list3 = new List<int>(array3.Length - 1);
int count = list.Count / 3;
for (int j = 1; j < array3.Length; j++)
{
list3.Add(VertexIndex(array3[j], count));
}
if (list3.Count >= 3)
{
if (objPart == null || objPart.Material != text2)
{
objPart = new ObjPart(text2);
list2.Add(objPart);
}
for (int k = 1; k < list3.Count - 1; k++)
{
objPart.Add(list, list3[0], list3[k], list3[k + 1]);
}
}
}
}
return new ObjModel(list2);
}
private static int VertexIndex(string corner, int count)
{
int num = int.Parse(corner.Split(new char[1] { '/' })[0], CultureInfo.InvariantCulture);
if (num < 0)
{
return count + num;
}
return num - 1;
}
}
internal sealed class PayLedger
{
private readonly HashSet<long> seen = new HashSet<long>();
public bool Claim(int shrineId, int useIndex)
{
long item = ((long)shrineId << 8) ^ (uint)useIndex;
return seen.Add(item);
}
public void Reset()
{
seen.Clear();
}
}
internal static class Placement
{
public const int Salt = 1112294449;
public const float BiomeMargin = 0.5f;
public const int MaxCount = 16;
public const float StandDistance = 2.5f;
public const float Eye = 1f;
public const float AimHeight = 0.55f;
public static int ClampCount(int count)
{
if (count < 0)
{
return 0;
}
if (count > 16)
{
return 16;
}
return count;
}
public static float HalfExtent(int mapChunkSize, float worldScaleAfterBuffer)
{
return (float)mapChunkSize / 2f * worldScaleAfterBuffer;
}
public static (double X, double Z) Candidate(int seed, int attempt, float halfExtent)
{
double item = (Hash01.Of(seed, 1112294449, 2 * attempt) * 2.0 - 1.0) * (double)halfExtent;
double item2 = (Hash01.Of(seed, 1112294449, 2 * attempt + 1) * 2.0 - 1.0) * (double)halfExtent;
return (X: item, Z: item2);
}
public static bool FarEnough(IReadOnlyList<(double X, double Z)> placed, double x, double z, float minSpacing)
{
double num = (double)minSpacing * (double)minSpacing;
for (int i = 0; i < placed.Count; i++)
{
double num2 = placed[i].X - x;
double num3 = placed[i].Z - z;
if (num2 * num2 + num3 * num3 < num)
{
return false;
}
}
return true;
}
public static bool InsideGrass(bool hit, bool below, bool above)
{
return hit && below && above;
}
public static void StandAndAim(float x, float y, float z, out float sx, out float sy, out float sz, out float ax, out float ay, out float az)
{
sx = x + 2.5f;
sy = y + 1f;
sz = z;
ax = x;
ay = y + 0.55f;
az = z;
}
}
internal static class PlayerNames
{
public static string Of(int clientId)
{
Dictionary<int, PlayerManager> players = GameManager.players;
if (players != null && players.TryGetValue(clientId, out var value) && (Object)(object)value != (Object)null && !string.IsNullOrEmpty(value.username))
{
return value.username;
}
return "Someone";
}
}
internal static class ShrineIds
{
public const int Base = 1500000000;
public const int Span = 1000000;
public static int For(int attempt)
{
return 1500000000 + attempt;
}
public static bool IsOurs(int id)
{
if (id >= 1500000000)
{
return id < 1501000000;
}
return false;
}
}
internal static class ShrineMesh
{
public const string ResourceName = "MuckShrineOfBlood.shrine.obj";
private static Mesh[] meshes;
private static string[] materials;
private static bool failed;
private static Shader shader;
private static bool shaderLogged;
public static Material[] Attach(Transform parent)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_0085: Expected O, but got Unknown
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
Ensure();
if (failed || meshes == null)
{
return (Material[])(object)new Material[0];
}
Shader val = ShaderNow();
Material[] array = (Material[])(object)new Material[meshes.Length];
for (int i = 0; i < meshes.Length; i++)
{
GameObject val2 = new GameObject("ShrineOfBlood_" + materials[i])
{
layer = 10
};
val2.transform.SetParent(parent, false);
val2.AddComponent<MeshFilter>().sharedMesh = meshes[i];
MeshRenderer val3 = val2.AddComponent<MeshRenderer>();
if (!((Object)(object)val == (Object)null))
{
Material val4 = new Material(val);
val4.color = ShrinePalette.ColorOf(materials[i]);
array[i] = val4;
((Renderer)val3).sharedMaterial = val4;
}
}
return array;
}
public static string ReadResource()
{
using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MuckShrineOfBlood.shrine.obj");
if (stream == null)
{
return null;
}
using StreamReader streamReader = new StreamReader(stream);
return streamReader.ReadToEnd();
}
private static void Ensure()
{
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00a9: 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)
//IL_00c9: Expected O, but got Unknown
//IL_011e: Unknown result type (might be due to invalid IL or missing references)
if (meshes != null || failed)
{
return;
}
string text = ReadResource();
if (string.IsNullOrEmpty(text))
{
failed = true;
BloodPlugin.Log.LogError((object)"Shrine of Blood mesh is missing from the assembly.");
return;
}
ObjModel objModel = ObjModel.Parse(text);
if (objModel.Triangles <= 0)
{
failed = true;
BloodPlugin.Log.LogError((object)"Shrine of Blood mesh parsed no triangles.");
return;
}
meshes = (Mesh[])(object)new Mesh[objModel.Parts.Count];
materials = new string[objModel.Parts.Count];
for (int i = 0; i < objModel.Parts.Count; i++)
{
ObjPart objPart = objModel.Parts[i];
materials[i] = objPart.Material;
Mesh val = new Mesh
{
name = "ShrineOfBlood_" + objPart.Material,
hideFlags = (HideFlags)61
};
List<Vector3> list = new List<Vector3>(objPart.Positions.Count / 3);
List<int> list2 = new List<int>(objPart.Triangles * 3);
for (int j = 0; j < objPart.Positions.Count; j += 3)
{
list.Add(new Vector3(objPart.Positions[j], objPart.Positions[j + 1], objPart.Positions[j + 2]));
list2.Add(list.Count - 1);
}
val.SetVertices(list);
val.SetTriangles(list2, 0);
val.RecalculateNormals();
val.RecalculateBounds();
meshes[i] = val;
}
}
private static Shader ShaderNow()
{
if ((Object)(object)shader != (Object)null)
{
return shader;
}
shader = Shader.Find("Standard");
if ((Object)(object)shader == (Object)null)
{
shader = Shader.Find("Diffuse");
}
if ((Object)(object)shader == (Object)null)
{
shader = Shader.Find("Legacy Shaders/Diffuse");
}
if ((Object)(object)shader == (Object)null && !shaderLogged)
{
shaderLogged = true;
BloodPlugin.Log.LogWarning((object)"Shrine of Blood found no lit shader. The collider is still there.");
}
return shader;
}
}
internal static class ShrinePalette
{
private static readonly Dictionary<string, float[]> Colors = new Dictionary<string, float[]>
{
{
"stone",
new float[3] { 0.55f, 0.5f, 0.46f }
},
{
"stone_dark",
new float[3] { 0.28f, 0.24f, 0.22f }
},
{
"blood",
new float[3] { 0.62f, 0.07f, 0.08f }
},
{
"gold",
new float[3] { 0.95f, 0.72f, 0.22f }
}
};
public static IEnumerable<string> Names => Colors.Keys;
public static bool Knows(string material)
{
if (material != null)
{
return Colors.ContainsKey(material);
}
return false;
}
public static Color ColorOf(string material)
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
if (material != null && Colors.TryGetValue(material, out var value))
{
return new Color(value[0], value[1], value[2]);
}
return new Color(0.5f, 0.5f, 0.5f);
}
}
internal sealed class ShrineUses
{
public int Max { get; }
public int UseIndex { get; private set; }
public int Left => Max - UseIndex;
public bool Spent => UseIndex >= Max;
public ShrineUses(int uses)
{
Max = ((uses >= 0) ? uses : 0);
}
public bool TrySpend()
{
if (Spent)
{
return false;
}
UseIndex++;
return true;
}
}
[HarmonyPatch(typeof(StructureSpawner), "Start")]
internal static class StructureSpawnerStartPatch
{
private static void Postfix(StructureSpawner __instance)
{
try
{
BloodWorld.PlaceOnce(__instance);
}
catch (Exception ex)
{
BloodPlugin.Log.LogError((object)("Shrine of Blood placement failed: " + ex));
}
}
}
internal enum PlaceKind
{
NewWorld,
NewMap,
AlreadyPlaced,
MissingManager
}
internal static class WorldGate
{
public static PlaceKind Decide(bool hasManager, bool sameManager, bool sameSeed)
{
if (!hasManager)
{
return PlaceKind.MissingManager;
}
if (sameManager && sameSeed)
{
return PlaceKind.AlreadyPlaced;
}
if (sameManager)
{
return PlaceKind.NewMap;
}
return PlaceKind.NewWorld;
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "MuckShrineOfBlood";
public const string PLUGIN_NAME = "Shrine of Blood";
public const string PLUGIN_VERSION = "0.1.0";
}