Decompiled source of DivineTemples v0.1.1

plugins/DivineTemples.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using DivineTemples.Patches;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Configuration;
using SimpleInjector;
using TMPro;
using TrainworksReloaded.Base.Extensions;
using TrainworksReloaded.Core;
using TrainworksReloaded.Core.Extensions;
using TrainworksReloaded.Core.Interfaces;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DivineTemples")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+fa610b8c2f16f9a00f4a247c15d918b5fe7950fa")]
[assembly: AssemblyProduct("DivineTemples")]
[assembly: AssemblyTitle("DivineTemples")]
[assembly: AssemblyVersion("0.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DivineTemples
{
	public sealed class DivineTempleNodeData : MapNodeData
	{
		public sealed class Factory : IFactory<MapNodeData>
		{
			public string FactoryKey => "divine_temple";

			public MapNodeData? GetValue()
			{
				return (MapNodeData?)(object)ScriptableObject.CreateInstance<DivineTempleNodeData>();
			}
		}

		public const int TempleBranch = -1;

		internal static DivineTempleNodeData? Node;

		internal static StoryEventData? Event;

		private static readonly MethodInfo ActiveSaveDataGetter = AccessTools.PropertyGetter(typeof(SaveManager), "ActiveSaveData");

		internal static SaveData SaveDataOf(SaveManager saveManager)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			return (SaveData)ActiveSaveDataGetter.Invoke(saveManager, null);
		}

		public static StoryEventState? FindState(SaveManager saveManager, int distance)
		{
			if (!((Object)(object)Event == (Object)null))
			{
				SaveData val = SaveDataOf(saveManager);
				if (val != null)
				{
					foreach (StoryEventState activeStoryEventState in val.GetActiveStoryEventStates(distance))
					{
						if (activeStoryEventState.GetBranch() == -1 && (Object)(object)activeStoryEventState.StoryEventData == (Object)(object)Event)
						{
							return activeStoryEventState;
						}
					}
					return null;
				}
			}
			return null;
		}

		public override bool CanBeTriggered(Location location, SaveManager saveManager)
		{
			StoryEventState val = FindState(saveManager, location.Distance);
			if (val != null)
			{
				return !((EventState)val).Claimed;
			}
			return false;
		}

		public override bool HasBeenVisited(Location location, SaveManager saveManager)
		{
			return !((MapNodeData)this).CanBeTriggered(location, saveManager);
		}

		public override void TriggerNode(Location location, SaveManager saveManager, ScreenManager screenManager, Action? callback = null)
		{
			((MapNodeData)this).TriggerNode(location, saveManager, screenManager, callback);
			StoryEventState state = FindState(saveManager, location.Distance);
			if (state == null || ((EventState)state).Claimed)
			{
				callback?.Invoke();
				return;
			}
			TempleCosts.Enter(saveManager, location.Distance);
			screenManager.ShowStoryEventScreen(state.StoryEventData, saveManager.IsRegionRun, (Action)delegate
			{
				((EventState)state).MarkSeen(saveManager);
				saveManager.Save(true, false);
			});
		}
	}
	[BepInPlugin("DivineTemples", "DivineTemples", "0.1.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string MoreEventsGuid = "MoreEvents_Reloaded.Plugin";

		internal static ManualLogSource Logger = new ManualLogSource("DivineTemples");

		private const float ConfigPollSeconds = 0.5f;

		private float nextConfigCheck;

		private DateTime configWrittenAt;

		public void Awake()
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			Logger = ((BaseUnityPlugin)this).Logger;
			DivineTemplePatches.Init(((BaseUnityPlugin)this).Config);
			TempleCosts.Init(((BaseUnityPlugin)this).Config);
			Railhead.GetBuilder().Configure("DivineTemples", (Action<IConfigurationBuilder>)delegate(IConfigurationBuilder c)
			{
				ConfigurationExtensions.AddMergedJsonFile(c, new List<string> { "json/plugin.json" });
			});
			Railend.ConfigurePreAction((Action<Container>)delegate(Container c)
			{
				c.Collection.Append<IFactory<MapNodeData>, DivineTempleNodeData.Factory>((Lifestyle)(object)Lifestyle.Singleton);
			});
			Railend.ConfigurePostAction((Action<Container>)delegate(Container c)
			{
				((IDictionary<string, MapNodeData>)c.GetInstance<IRegister<MapNodeData>>()).TryGetValue(StringExtensions.GetId("DivineTemples", "MapNode", "DivineTemple"), out MapNodeData value);
				((IDictionary<string, StoryEventData>)c.GetInstance<IRegister<StoryEventData>>()).TryGetValue(StringExtensions.GetId("MoreEvents_Reloaded.Plugin", "StoryEvent", "DivineAltar"), out StoryEventData value2);
				((IDictionary<string, Sprite>)c.GetInstance<IRegister<Sprite>>()).TryGetValue(StringExtensions.GetId("DivineTemples", "Sprite", "DivineTemple_Base"), out Sprite value3);
				DivineTemplePatches.NodeBase = value3;
				if ((Object)(object)value3 == (Object)null)
				{
					Logger.LogWarning((object)"Divine Temple node base sprite missing; temples will stand on bare ground.");
				}
				DivineTempleNodeData.Node = value as DivineTempleNodeData;
				DivineTempleNodeData.Event = value2;
				if ((Object)(object)DivineTempleNodeData.Node == (Object)null || (Object)(object)DivineTempleNodeData.Event == (Object)null)
				{
					Logger.LogError((object)$"Divine Temple node or Divine Altar event missing (node: {value}, event: {value2}); no temples will be placed.");
				}
				else
				{
					IRegister<Sprite> instance = c.GetInstance<IRegister<Sprite>>();
					((IDictionary<string, Sprite>)instance).TryGetValue(StringExtensions.GetId("DivineTemples", "Sprite", "DivineTemple_Rollover"), out Sprite value4);
					((IDictionary<string, Sprite>)instance).TryGetValue(StringExtensions.GetId("DivineTemples", "Sprite", "DivineTemple_Sheen"), out Sprite value5);
					TempleIcon.Rollover = value4;
					TempleIcon.Sheen = value5;
					StoryEventPoolData val = ((IEnumerable<StoryEventPoolData>)Resources.FindObjectsOfTypeAll<StoryEventPoolData>()).FirstOrDefault((Func<StoryEventPoolData, bool>)((StoryEventPoolData p) => !string.IsNullOrEmpty(((MapNodeData)p).GetNodeSelectedSfxCue())));
					if ((Object)(object)val != (Object)null)
					{
						AccessTools.Field(typeof(MapNodeData), "nodeSelectedSfxCue").SetValue(DivineTempleNodeData.Node, ((MapNodeData)val).GetNodeSelectedSfxCue());
					}
				}
			});
			new Harmony("DivineTemples").PatchAll();
			configWrittenAt = LastWrittenAt();
			Logger.LogInfo((object)"Plugin DivineTemples is loaded!");
		}

		public void Update()
		{
			if (Time.unscaledTime < nextConfigCheck)
			{
				return;
			}
			nextConfigCheck = Time.unscaledTime + 0.5f;
			DateTime dateTime = LastWrittenAt();
			if (!(dateTime == configWrittenAt))
			{
				configWrittenAt = dateTime;
				try
				{
					((BaseUnityPlugin)this).Config.Reload();
				}
				catch (Exception ex)
				{
					Logger.LogDebug((object)("Could not reload the config yet (" + ex.Message + "); retrying."));
					configWrittenAt = default(DateTime);
					return;
				}
				DivineTemplePatches.RepositionTemples();
			}
		}

		private DateTime LastWrittenAt()
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				return default(DateTime);
			}
			return File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
		}
	}
	internal enum Currency
	{
		Gold,
		PyreHealth
	}
	internal enum CurrencySetting
	{
		Gold,
		PyreHealth,
		Random
	}
	internal readonly struct TempleCost
	{
		internal Currency Currency { get; }

		internal int Amount { get; }

		internal TempleCost(Currency currency, int amount)
		{
			Currency = currency;
			Amount = amount;
		}
	}
	internal static class TempleCosts
	{
		internal static ConfigEntry<bool> Enabled = null;

		internal static ConfigEntry<CurrencySetting> PaidIn = null;

		internal static ConfigEntry<int> UnitInfusionPrice = null;

		internal static ConfigEntry<int> SpellUpgradePrice = null;

		internal static ConfigEntry<int> PyreHealthPer100Gold = null;

		private static SaveManager? visiting;

		private static int visitingDistance;

		private static PlayerManager? playerManager;

		private static readonly FieldRef<InkWriter, PlayerManager> InkWriterPlayerManager = AccessTools.FieldRefAccess<InkWriter, PlayerManager>("_playerManager");

		public static void Init(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Costs", "ChargeForChoices", true, "Whether a temple asks for payment. Off leaves the Divine Altar free, as More Events Reloaded serves it.");
			PaidIn = config.Bind<CurrencySetting>("Costs", "Currency", CurrencySetting.Random, "What a temple charges in. Random gives each choice one of the two, settled when the run starts, from the run's seed.");
			UnitInfusionPrice = config.Bind<int>("Costs", "UnitInfusionPrice", 100, "Gold price of fusing one unit's soul into another.");
			SpellUpgradePrice = config.Bind<int>("Costs", "SpellUpgradePrice", 50, "Gold price of a spell upgrade, which is every pact the altar offers.");
			PyreHealthPer100Gold = config.Bind<int>("Costs", "PyreHealthPer100Gold", 10, "How much pyre health stands in for 100 gold. At 10, a 100 gold price becomes 10 pyre health.");
			SelfCheck();
		}

		internal static void Enter(SaveManager saveManager, int distance)
		{
			visiting = saveManager;
			visitingDistance = distance;
		}

		internal static void Leave()
		{
			visiting = null;
		}

		internal static TempleCost? CostOf(StoryChoiceItem item)
		{
			try
			{
				return PriceOf(item);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogWarning((object)$"Could not price a Divine Temple choice, leaving it free: {arg}");
				return null;
			}
		}

		private static TempleCost? PriceOf(StoryChoiceItem item)
		{
			if (!Enabled.Value || (Object)(object)visiting == (Object)null || (Object)(object)item == (Object)null)
			{
				return null;
			}
			string text = RewardKeyOf(item);
			if (text == null)
			{
				return null;
			}
			int num = GoldPriceOf(text);
			if (num <= 0)
			{
				return null;
			}
			return PicksPyreHealth(text) ? new TempleCost(Currency.PyreHealth, Mathf.Max(1, Mathf.RoundToInt((float)(num * PyreHealthPer100Gold.Value) / 100f))) : new TempleCost(Currency.Gold, num);
		}

		internal static bool CanPay(TempleCost cost)
		{
			if ((Object)(object)visiting == (Object)null)
			{
				return true;
			}
			if (cost.Currency != Currency.Gold)
			{
				return visiting.GetTowerHP() > cost.Amount;
			}
			return visiting.GetGold() >= cost.Amount;
		}

		internal static void Pay(TempleCost cost, SaveManager saveManager, StoryManager storyManager)
		{
			if (cost.Currency == Currency.Gold)
			{
				saveManager.AdjustGold(-cost.Amount, false, false);
				Plugin.Logger.LogInfo((object)$"Divine Temple charged {cost.Amount} gold; {saveManager.GetGold()} left.");
				return;
			}
			PlayerManager val = PlayerManagerFrom(storyManager);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"No PlayerManager to take pyre health with; the temple reward was free.");
				return;
			}
			val.AdjustTowerHP(-cost.Amount, true);
			Plugin.Logger.LogInfo((object)$"Divine Temple charged {cost.Amount} pyre health; {saveManager.GetTowerHP()} left.");
		}

		internal static string Describe(TempleCost cost, TMP_Text label)
		{
			string text = ((cost.Currency == Currency.Gold) ? "Gold" : "Health");
			string arg = ((cost.Currency == Currency.Gold) ? "gold" : "pyre health");
			if (!HasSprite(label, text))
			{
				return $"{cost.Amount} {arg}";
			}
			return $"{cost.Amount}<sprite name=\"{text}\">";
		}

		private static string? RewardKeyOf(StoryChoiceItem item)
		{
			try
			{
				return item.GetDeckRewardInfo()?.dataKey;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogDebug((object)("No reward to price on this choice yet (" + ex.Message + ")."));
				return null;
			}
		}

		private static int GoldPriceOf(string rewardKey)
		{
			SaveManager? obj = visiting;
			object obj2;
			if (obj == null)
			{
				obj2 = null;
			}
			else
			{
				AllGameData allGameData = obj.GetAllGameData();
				obj2 = ((allGameData != null) ? allGameData.FindRewardDataByName(rewardKey) : null);
			}
			GrantableRewardData val = (GrantableRewardData)obj2;
			return Mathf.Max(0, (val is EnhancerRewardData) ? SpellUpgradePrice.Value : UnitInfusionPrice.Value);
		}

		private static bool PicksPyreHealth(string rewardKey)
		{
			if (PaidIn.Value != CurrencySetting.Random)
			{
				return PaidIn.Value == CurrencySetting.PyreHealth;
			}
			int seed;
			if (!((Object)(object)visiting == (Object)null))
			{
				SaveData obj = DivineTempleNodeData.SaveDataOf(visiting);
				seed = ((obj != null) ? ((BaseSaveData)obj).GetSeed() : 0);
			}
			else
			{
				seed = 0;
			}
			return Roll(seed, visitingDistance, rewardKey) % 2 == 0;
		}

		internal static int Roll(int seed, int distance, string rewardKey)
		{
			uint num = 2166136261u;
			foreach (byte item in BitConverter.GetBytes(seed).Concat(BitConverter.GetBytes(distance)))
			{
				num = (num ^ item) * 16777619;
			}
			foreach (char c in rewardKey)
			{
				num = (num ^ (byte)c) * 16777619;
			}
			return (int)(num & 0x7FFFFFFF);
		}

		private static PlayerManager? PlayerManagerFrom(StoryManager storyManager)
		{
			InkWriter val = (((Object)(object)storyManager == (Object)null) ? null : storyManager.InkWriter);
			if ((Object)(object)val != (Object)null)
			{
				PlayerManager val2 = InkWriterPlayerManager.Invoke(val);
				if (val2 != null && (Object)(object)val2 != (Object)null)
				{
					return val2;
				}
			}
			if ((Object)(object)playerManager == (Object)null)
			{
				playerManager = Object.FindObjectOfType<PlayerManager>();
			}
			return playerManager;
		}

		private static bool HasSprite(TMP_Text label, string spriteName)
		{
			if (!Holds(((Object)(object)label == (Object)null) ? null : label.spriteAsset, spriteName))
			{
				return Holds(TMP_Settings.defaultSpriteAsset, spriteName);
			}
			return true;
		}

		private static bool Holds(TMP_SpriteAsset? asset, string spriteName)
		{
			if ((Object)(object)asset == (Object)null)
			{
				return false;
			}
			List<TMP_SpriteCharacter> spriteCharacterTable = asset.spriteCharacterTable;
			if (spriteCharacterTable != null && spriteCharacterTable.Any((TMP_SpriteCharacter sprite) => sprite.name == spriteName))
			{
				return true;
			}
			return asset.fallbackSpriteAssets?.Any((TMP_SpriteAsset fallback) => (Object)(object)fallback != (Object)null && (fallback.spriteCharacterTable?.Any((TMP_SpriteCharacter sprite) => sprite.name == spriteName) ?? false)) ?? false;
		}

		private static void SelfCheck()
		{
			Check(Roll(7, 1, "PactReward") == Roll(7, 1, "PactReward"), "a choice keeps its currency across visits");
			Check(Roll(7, 1, "PactReward") != Roll(7, 1, "OtherReward"), "two choices are priced apart");
			Check(Roll(7, 1, "PactReward") != Roll(7, 2, "PactReward"), "two rings are priced apart");
			Check(Roll(7, 1, "PactReward") != Roll(8, 1, "PactReward"), "two runs are priced apart");
		}

		private static void Check(bool ok, string what)
		{
			if (!ok)
			{
				throw new InvalidOperationException("DivineTemples self-check failed: " + what);
			}
		}
	}
	internal static class TempleIcon
	{
		internal const string EnabledIconPath = "Art root/IconSprite_Enabled";

		internal const string GlowPath = "Enabled FX";

		internal static Sprite? Rollover;

		internal static Sprite? Sheen;

		private static bool done;

		internal static void SetUp(MapNodeData node)
		{
			if (done)
			{
				return;
			}
			done = true;
			MapNodeIcon mapIconPrefab = node.GetMapIconPrefab();
			if ((Object)(object)mapIconPrefab == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"The Divine Temple has no map icon prefab; its art keeps the FX of the node it was copied from.");
				return;
			}
			RestoreAnimators(mapIconPrefab);
			Material val = EnabledMaterialOf(((Component)mapIconPrefab).transform);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"No enabled icon material on the Divine Temple prefab; its hover glow keeps the shape it was copied from.");
			}
			else
			{
				ApplyFxMasks(val);
			}
		}

		internal static void SetUpDrawn(MapNodeUI node)
		{
			Material val = EnabledMaterialOf(((Component)node).transform);
			if ((Object)(object)val != (Object)null)
			{
				ApplyFxMasks(val);
			}
			MapNodeIcon[] componentsInChildren = ((Component)node).GetComponentsInChildren<MapNodeIcon>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Transform obj = ((Component)componentsInChildren[i]).transform.Find("Enabled FX");
				if (obj != null)
				{
					((Component)obj).gameObject.SetActive(false);
				}
			}
		}

		private static Material? EnabledMaterialOf(Transform root)
		{
			object obj = ((Component)root).GetComponentsInChildren<MapNodeIcon>(true).Select(delegate(MapNodeIcon icon)
			{
				Transform obj3 = ((Component)icon).transform.Find("Art root/IconSprite_Enabled");
				return (obj3 == null) ? null : ((Component)obj3).GetComponent<Image>();
			}).FirstOrDefault((Func<Image, bool>)((Image candidate) => (Object)(object)candidate != (Object)null));
			if (obj == null)
			{
				Transform obj2 = root.Find("Art root/IconSprite_Enabled");
				obj = ((obj2 != null) ? ((Component)obj2).GetComponent<Image>() : null);
			}
			Image val = (Image)obj;
			if (!((Object)(object)val == (Object)null))
			{
				return ((Graphic)val).material;
			}
			return null;
		}

		private static void ApplyFxMasks(Material material)
		{
			if ((Object)(object)Rollover == (Object)null || (Object)(object)Sheen == (Object)null)
			{
				Plugin.Logger.LogWarning((object)$"Divine Temple FX masks missing (rollover: {Rollover}, sheen: {Sheen}); its hover glow keeps the shape it was copied from.");
				return;
			}
			material.SetTexture("_Layer2Mask", (Texture)(object)Rollover.texture);
			material.SetTexture("_Layer3Mask", (Texture)(object)Sheen.texture);
			material.SetFloat("_Layer4Enabled", 0f);
			material.SetFloat("_Layer5Enabled", 0f);
		}

		private static void RestoreAnimators(MapNodeIcon icon)
		{
			Animator[] componentsInChildren = ((Component)icon).GetComponentsInChildren<Animator>(true);
			if (componentsInChildren.Length == 0 || componentsInChildren.All((Animator val3) => (Object)(object)val3.runtimeAnimatorController != (Object)null))
			{
				return;
			}
			MapNodeIcon val = (from data in Resources.FindObjectsOfTypeAll<MapNodeData>()
				select data.GetMapIconPrefab()).FirstOrDefault((Func<MapNodeIcon, bool>)((MapNodeIcon candidate) => (Object)(object)candidate != (Object)null && (Object)(object)candidate != (Object)(object)icon && ((Component)candidate).GetComponentsInChildren<Animator>(true).Any((Animator val3) => (Object)(object)val3.runtimeAnimatorController != (Object)null)));
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"Found no vanilla map icon to take animator controllers from; the temple's FX layers stay as they are.");
				return;
			}
			Animator[] array = componentsInChildren;
			foreach (Animator animator in array)
			{
				if (!((Object)(object)animator.runtimeAnimatorController != (Object)null))
				{
					Animator val2 = ((IEnumerable<Animator>)((Component)val).GetComponentsInChildren<Animator>(true)).FirstOrDefault((Func<Animator, bool>)((Animator candidate) => ((Object)candidate).name == ((Object)animator).name));
					if ((Object)(object)((val2 != null) ? val2.runtimeAnimatorController : null) == (Object)null)
					{
						Plugin.Logger.LogWarning((object)("'" + ((Object)animator).name + "' has no counterpart on '" + ((Object)val).name + "'; its FX stay as they are."));
					}
					else
					{
						animator.runtimeAnimatorController = val2.runtimeAnimatorController;
						Plugin.Logger.LogInfo((object)("'" + ((Object)animator).name + "' took its animator controller from '" + ((Object)val).name + "'."));
					}
				}
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "DivineTemples";

		public const string PLUGIN_NAME = "DivineTemples";

		public const string PLUGIN_VERSION = "0.1.1";
	}
}
namespace DivineTemples.Patches
{
	internal static class DivineTemplePatches
	{
		[HarmonyPatch(typeof(SaveManager), "SetupNodes")]
		private static class PlaceTemples
		{
			private static void Postfix(SaveManager __instance)
			{
				//IL_012b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0132: Expected O, but got Unknown
				//IL_0104: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)DivineTempleNodeData.Node == (Object)null || (Object)(object)DivineTempleNodeData.Event == (Object)null)
				{
					return;
				}
				RunState runState = __instance.GetRunState();
				SaveData val = DivineTempleNodeData.SaveDataOf(__instance);
				foreach (int item in from d in Distances
					where d < runState.GetRunLength()
					orderby d
					select d)
				{
					if (runState.GetNumBranchesAtDistance(item) < 2)
					{
						Plugin.Logger.LogInfo((object)$"Ring {item + 1} is a single track, so it has no centre spot; skipped.");
						continue;
					}
					List<NodeDataAtLocation> mapNodeDataForBranch = MapSection.GetMapNodeDataForBranch(item, (BranchSelection)0, __instance);
					List<NodeDataAtLocation> mapNodeDataForBranch2 = MapSection.GetMapNodeDataForBranch(item, (BranchSelection)1, __instance);
					List<NodeDataAtLocation> list = MapSection.ExtractSharedMapNodeData(mapNodeDataForBranch, mapNodeDataForBranch2, 1);
					if (list.Count > 0)
					{
						ManualLogSource logger = Plugin.Logger;
						object arg = item + 1;
						MapNodeData data = list[0].data;
						logger.LogInfo((object)$"Ring {arg} centre is already taken by '{((data != null) ? ((Object)data).name : null)}'; skipped.");
					}
					else
					{
						StoryEventState val2 = new StoryEventState(DivineTempleNodeData.Event);
						val2.SetActiveIndex(item, -1, 0);
						val.AddStoryEvent(val2);
						Plugin.Logger.LogInfo((object)$"Divine Temple placed at ring {item + 1}");
					}
				}
			}
		}

		[HarmonyPatch(typeof(MapSection), "GetMapNodeDataForBranch")]
		private static class ShowTemple
		{
			private static void Postfix(int mapDistance, BranchSelection branchSelection, SaveManager saveManager, List<NodeDataAtLocation> __result)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Invalid comparison between Unknown and I4
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Invalid comparison between Unknown and I4
				//IL_0036: Unknown result type (might be due to invalid IL or missing references)
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Expected I4, but got Unknown
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0057: Expected O, but got Unknown
				//IL_0057: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)DivineTempleNodeData.Node == (Object)null) && (int)branchSelection != -1)
				{
					RunState runState = saveManager.GetRunState();
					if ((int)branchSelection < ((runState != null) ? runState.GetNumBranchesAtDistance(mapDistance) : 0) && DivineTempleNodeData.FindState(saveManager, mapDistance) != null)
					{
						__result.Add(new NodeDataAtLocation
						{
							data = (MapNodeData)(object)DivineTempleNodeData.Node,
							location = new Location(mapDistance, (int)branchSelection, 0)
						});
					}
				}
			}
		}

		[HarmonyPatch(typeof(MapNodeUI), "RefreshState")]
		private static class KeepTempleIcon
		{
			private static void Postfix(MapNodeUI __instance)
			{
				if ((Object)(object)__instance.GetData() == (Object)(object)DivineTempleNodeData.Node)
				{
					TempleIcon.SetUpDrawn(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(MapSection), "SetUpNodes")]
		private static class DrawTempleInCentre
		{
			private static void Postfix(MapSection __instance, int ___mapDistance, Camera ___mapCamera, SaveManager ___saveManager, List<MapNodeUI> ___leftMapNodes, List<MapNodeUI> ___rightMapNodes, List<MapNodeUI> ___sharedMapNodes, List<MapNodeUI> ___pactMapNodes, Dictionary<BranchSelection, List<MapNodeUI>> ___mapNodesByBranch)
			{
				//IL_010e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0113: Unknown result type (might be due to invalid IL or missing references)
				//IL_0114: Unknown result type (might be due to invalid IL or missing references)
				//IL_0119: Unknown result type (might be due to invalid IL or missing references)
				//IL_011d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0122: Unknown result type (might be due to invalid IL or missing references)
				//IL_0127: Unknown result type (might be due to invalid IL or missing references)
				//IL_012c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0135: Unknown result type (might be due to invalid IL or missing references)
				//IL_013f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0144: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
				//IL_0100: Expected O, but got Unknown
				//IL_0209: Unknown result type (might be due to invalid IL or missing references)
				//IL_027b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0298: Unknown result type (might be due to invalid IL or missing references)
				//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
				//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
				//IL_0308: Unknown result type (might be due to invalid IL or missing references)
				MapNodeData temple = (MapNodeData)(object)DivineTempleNodeData.Node;
				if ((Object)(object)temple == (Object)null || (Object)(object)___saveManager == (Object)null || DivineTempleNodeData.FindState(___saveManager, ___mapDistance) == null || ___leftMapNodes.Count == 0 || ___rightMapNodes.Count == 0)
				{
					return;
				}
				MapNodeUI val = ___mapNodesByBranch.Values.SelectMany((List<MapNodeUI> nodes) => nodes).FirstOrDefault((Func<MapNodeUI, bool>)((MapNodeUI n) => (Object)(object)n.GetData() == (Object)(object)temple));
				string text = "centre slot '" + ((val != null) ? ((Object)val).name : null) + "'";
				if ((Object)(object)val == (Object)null)
				{
					text = "clone";
					val = CloneRouteNode(___leftMapNodes[0]);
					if (!___mapNodesByBranch.TryGetValue((BranchSelection)(-1), out List<MapNodeUI> value))
					{
						value = new List<MapNodeUI>();
						___mapNodesByBranch.Add((BranchSelection)(-1), value);
					}
					val.Set(temple, new Location(___mapDistance, -1, value.Count), ___mapCamera, ___saveManager);
					value.Add(val);
				}
				RectTransform val2 = (RectTransform)((Component)__instance).transform;
				Rect rect = val2.rect;
				Vector3 position = ((Transform)val2).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center));
				Vector3 position2 = TempleTarget(__instance, ___mapDistance, ((Component)val).transform.position.z);
				ManualLogSource logger = Plugin.Logger;
				string text2 = $"Ring {___mapDistance + 1} temple on {text}: {___sharedMapNodes.Count} centre, {___pactMapNodes.Count} pact slots, ";
				string arg = (((Object)(object)temple.GetMapIconPrefab() == (Object)null) ? "MISSING" : "ok");
				object arg2 = ((Component)val).gameObject.activeInHierarchy;
				Transform parent = ((Component)val).transform.parent;
				logger.LogInfo((object)(text2 + $"icon {arg}, shown {arg2}, under '{((parent != null) ? ((Object)parent).name : null)}'."));
				Plugin.Logger.LogInfo((object)($"Ring {___mapDistance + 1} positions: node {At(___mapCamera, ((Component)val).transform.position)}, " + "centre slots " + string.Join(" ", ___sharedMapNodes.Select((MapNodeUI slot) => At(___mapCamera, ((Component)slot).transform.position))) + ", pact slots " + string.Join(" ", ___pactMapNodes.Select((MapNodeUI slot) => At(___mapCamera, ((Component)slot).transform.position))) + ", routes " + At(___mapCamera, Middle(___leftMapNodes)) + " " + At(___mapCamera, Middle(___rightMapNodes)) + ", " + $"ring middle {At(___mapCamera, position)} -> moving to {At(___mapCamera, position2)} (VerticalOffsetRing{___mapDistance + 1} {OffsetFor(___mapDistance):0.#})."));
				((Component)val).transform.position = position2;
				((Component)val).transform.SetAsLastSibling();
				ShowBase(val);
				TempleIcon.SetUp(temple);
				TempleIcon.SetUpDrawn(val);
			}

			private static void ShowBase(MapNodeUI node)
			{
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Expected O, but got Unknown
				//IL_0057: 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_0069: Unknown result type (might be due to invalid IL or missing references)
				//IL_006a: 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_0076: Unknown result type (might be due to invalid IL or missing references)
				//IL_0077: Unknown result type (might be due to invalid IL or missing references)
				//IL_007d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0083: Unknown result type (might be due to invalid IL or missing references)
				//IL_008e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0098: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ae: 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)
				//IL_00be: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)NodeBase == (Object)null) && !((Object)(object)((Component)node).transform.Find("DivineTempleBase") != (Object)null))
				{
					GameObject val = new GameObject("DivineTempleBase", new Type[2]
					{
						typeof(RectTransform),
						typeof(Image)
					});
					RectTransform val2 = (RectTransform)val.transform;
					((Transform)val2).SetParent(((Component)node).transform, false);
					Vector2 anchorMin = (val2.anchorMax = ((Component)node).GetComponent<RectTransform>().pivot);
					val2.anchorMin = anchorMin;
					val2.pivot = new Vector2(0.5f, 0.5f);
					val2.anchoredPosition = new Vector2(0f, 5f);
					((Transform)val2).localScale = Vector3.one;
					Rect rect = NodeBase.rect;
					val2.sizeDelta = ((Rect)(ref rect)).size;
					Image component = val.GetComponent<Image>();
					component.sprite = NodeBase;
					((Graphic)component).raycastTarget = false;
					((Transform)val2).SetAsFirstSibling();
				}
			}

			private static MapNodeUI CloneRouteNode(MapNodeUI template)
			{
				MapNodeUI val = Object.Instantiate<MapNodeUI>(template, ((Component)template).transform.parent);
				((Object)val).name = "DivineTempleNode";
				MapNodeIcon[] componentsInChildren = ((Component)val).GetComponentsInChildren<MapNodeIcon>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					Object.DestroyImmediate((Object)(object)((Component)componentsInChildren[i]).gameObject);
				}
				return val;
			}

			private static Vector3 Middle(List<MapNodeUI> nodes)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				return nodes.Aggregate(Vector3.zero, (Vector3 total, MapNodeUI node) => total + ((Component)node).transform.position) / (float)nodes.Count;
			}

			private static string At(Camera? camera, Vector3 position)
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: 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_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)camera == (Object)null)
				{
					return $"{position}";
				}
				Vector3 val = camera.WorldToViewportPoint(position);
				return $"{position}[{val.x:0.00},{val.y:0.00}]";
			}
		}

		internal const float DefaultVerticalOffset = -85f;

		private static readonly Dictionary<int, float> MeasuredOffsets = new Dictionary<int, float>
		{
			[2] = 170f,
			[5] = -105f,
			[6] = -85f
		};

		internal static HashSet<int> Distances = new HashSet<int>();

		internal static Sprite? NodeBase;

		private const string NodeBaseName = "DivineTempleBase";

		private const float NodeBaseLift = 5f;

		internal static Dictionary<int, ConfigEntry<float>> Offsets = new Dictionary<int, ConfigEntry<float>>();

		private static readonly FieldRef<MapSection, int> SectionDistance = AccessTools.FieldRefAccess<MapSection, int>("mapDistance");

		public static void Init(ConfigFile config)
		{
			Distances = ParseRings(config.Bind<string>("General", "Rings", "2,5,6", "Comma-separated ring numbers that get a Divine Temple in the centre of the map, provided that spot is free. Ring 1 is the stretch before the first battle. Each ring listed here gets its own VerticalOffsetRing<N> entry below, the next time the game starts.").Value);
			Offsets = Distances.OrderBy((int distance) => distance).ToDictionary((int distance) => distance, (int distance) => config.Bind<float>("General", $"VerticalOffsetRing{distance + 1}", MeasuredOffsetFor(distance + 1), $"How far the ring {distance + 1} temple sits from the middle of its ring, in map units: a ring is 750 tall, and a node is 120 across. Negative moves it down the ring, towards the battle you came from."));
			SelfCheck();
		}

		private static float MeasuredOffsetFor(int ring)
		{
			if (!MeasuredOffsets.TryGetValue(ring, out var value))
			{
				return -85f;
			}
			return value;
		}

		internal static float OffsetFor(int distance)
		{
			if (!Offsets.TryGetValue(distance, out ConfigEntry<float> value))
			{
				return -85f;
			}
			return value.Value;
		}

		internal static HashSet<int> ParseRings(string text)
		{
			int result;
			return (from part in text.Split(',')
				select (!int.TryParse(part.Trim(), out result) || result <= 0) ? (-1) : (result - 1) into distance
				where distance >= 0
				select distance).ToHashSet();
		}

		private static Vector3 TempleTarget(MapSection section, int mapDistance, float z)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)((Component)section).transform;
			Rect rect = val.rect;
			Vector3 result = ((Transform)val).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center + new Vector2(0f, OffsetFor(mapDistance))));
			result.z = z;
			return result;
		}

		internal static void RepositionTemples()
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			MapNodeData temple = (MapNodeData)(object)DivineTempleNodeData.Node;
			if ((Object)(object)temple == (Object)null)
			{
				return;
			}
			MapSection[] array = Object.FindObjectsOfType<MapSection>();
			foreach (MapSection val in array)
			{
				MapNodeUI val2 = ((IEnumerable<MapNodeUI>)((Component)val).GetComponentsInChildren<MapNodeUI>(true)).FirstOrDefault((Func<MapNodeUI, bool>)((MapNodeUI candidate) => (Object)(object)candidate.GetData() == (Object)(object)temple));
				if (!((Object)(object)val2 == (Object)null))
				{
					int num = SectionDistance.Invoke(val);
					((Component)val2).transform.position = TempleTarget(val, num, ((Component)val2).transform.position.z);
					((Component)val2).transform.SetAsLastSibling();
					Plugin.Logger.LogInfo((object)$"Ring {num + 1} temple moved to VerticalOffsetRing{num + 1} {OffsetFor(num):0.#}.");
				}
			}
		}

		private static void SelfCheck()
		{
			Check(ParseRings("1,3,5,7").SetEquals(new int[4] { 0, 2, 4, 6 }), "ring numbers are 1-based");
			Check(ParseRings(" 2 , x, 0, -1, 9 ").SetEquals(new int[2] { 1, 8 }), "junk, zero and negatives are dropped");
			Check(ParseRings("").Count == 0, "an empty list places nothing");
			Check(Distances.All((int distance) => Offsets.ContainsKey(distance)), "every configured ring has an offset entry");
			Check(OffsetFor(-1) == -85f, "a ring with no entry falls back to the default offset");
			Check(MeasuredOffsetFor(2) == 170f && MeasuredOffsetFor(4) == -85f, "a measured ring starts at its own height, an unmeasured one at the default");
		}

		private static void Check(bool ok, string what)
		{
			if (!ok)
			{
				throw new InvalidOperationException("DivineTemples self-check failed: " + what);
			}
		}
	}
	internal static class TempleCostPatches
	{
		[HarmonyPatch(typeof(StoryChoiceItem), "Set")]
		private static class ShowCost
		{
			private static void Postfix(StoryChoiceItem __instance, TMP_Text ___optionalRewardLabel)
			{
				TempleCost? templeCost = TempleCosts.CostOf(__instance);
				if (templeCost.HasValue)
				{
					TempleCost valueOrDefault = templeCost.GetValueOrDefault();
					if ((Object)(object)___optionalRewardLabel != (Object)null)
					{
						string text = (((Component)___optionalRewardLabel).gameObject.activeSelf ? (___optionalRewardLabel.text + "  ") : string.Empty);
						((Component)___optionalRewardLabel).gameObject.SetActive(true);
						___optionalRewardLabel.text = text + TempleCosts.Describe(valueOrDefault, ___optionalRewardLabel);
					}
					if (!TempleCosts.CanPay(valueOrDefault))
					{
						__instance.SetInteractable(false, false);
					}
				}
			}
		}

		[HarmonyPatch(typeof(StoryEventScreen), "MarkButtonsInteractableIfConditionsPassed")]
		private static class KeepUnaffordableDisabled
		{
			private static void Postfix(bool enable, List<StoryChoiceItem> ___currentChoiceItems)
			{
				if (!enable || ___currentChoiceItems == null)
				{
					return;
				}
				foreach (StoryChoiceItem ___currentChoiceItem in ___currentChoiceItems)
				{
					TempleCost? templeCost = TempleCosts.CostOf(___currentChoiceItem);
					if (templeCost.HasValue)
					{
						TempleCost valueOrDefault = templeCost.GetValueOrDefault();
						if (!TempleCosts.CanPay(valueOrDefault))
						{
							___currentChoiceItem.SetInteractable(false, false);
						}
					}
				}
			}
		}

		[HarmonyPatch(typeof(StoryChoiceItem), "PresentDeckReward")]
		private static class ChargeForReward
		{
			private static void Prefix(StoryChoiceItem __instance, SaveManager saveManager, StoryManager storyManager, ref Action<bool> rewardGrantedCallback)
			{
				TempleCost? templeCost = TempleCosts.CostOf(__instance);
				if (!templeCost.HasValue)
				{
					return;
				}
				TempleCost cost = templeCost.GetValueOrDefault();
				Action<bool> granted = rewardGrantedCallback;
				rewardGrantedCallback = delegate(bool didCollectReward)
				{
					if (didCollectReward)
					{
						TempleCosts.Pay(cost, saveManager, storyManager);
					}
					granted?.Invoke(didCollectReward);
				};
			}
		}

		[HarmonyPatch(typeof(StoryEventScreen), "ReturnToMap")]
		private static class ForgetTemple
		{
			private static void Postfix()
			{
				TempleCosts.Leave();
			}
		}

		[HarmonyPatch(typeof(StoryEventScreen), "SetupStory")]
		private static class ForgetOtherEvents
		{
			private static void Postfix(StoryEventData storyData)
			{
				if ((Object)(object)storyData != (Object)(object)DivineTempleNodeData.Event)
				{
					TempleCosts.Leave();
				}
			}
		}
	}
}