Decompiled source of TerrainRewards v1.2.1

TerrainRewards.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.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.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("TerrainRewards")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+20f6b88632816e0fdc02f6b7f601f158e31db963")]
[assembly: AssemblyProduct("TerrainRewards")]
[assembly: AssemblyTitle("TerrainRewards")]
[assembly: AssemblyVersion("1.2.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 TerrainRewards
{
	internal static class BiomeDetector
	{
		private static readonly MethodInfo GetCurrentBiomeMethod = AccessTools.Method(typeof(Player), "GetCurrentBiome", (Type[])null, (Type[])null);

		internal static string GetBiomeName(Player player)
		{
			if ((Object)(object)player == (Object)null || GetCurrentBiomeMethod == null)
			{
				TerrainRewardsPlugin.Debug("GetCurrentBiome method was unavailable.");
				return string.Empty;
			}
			try
			{
				return GetCurrentBiomeMethod.Invoke(player, null)?.ToString() ?? string.Empty;
			}
			catch (Exception ex)
			{
				TerrainRewardsPlugin.Log.LogWarning((object)("Unable to determine current biome: " + ex.Message));
				return string.Empty;
			}
		}
	}
	internal static class RewardConfig
	{
		private const string RewardKey = "Rewards v2";

		private const string RewardDescription = "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.";

		private static readonly Dictionary<string, ConfigEntry<string>> BiomeEntries = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);

		private static ConfigEntry<string> _allBiomes;

		internal static void Bind(ConfigFile config)
		{
			_allBiomes = config.Bind<string>("Rewards - All Biomes", "Rewards v2", "Stone:2:25", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "Meadows", "");
			BindBiome(config, "BlackForest", "");
			BindBiome(config, "Swamp", "");
			BindBiome(config, "Mountain", "");
			BindBiome(config, "Plains", "");
			BindBiome(config, "Mistlands", "");
			BindBiome(config, "AshLands", "");
			BindBiome(config, "DeepNorth", "");
			BindBiome(config, "Ocean", "");
		}

		private static void BindBiome(ConfigFile config, string biome, string defaults)
		{
			BiomeEntries[Normalize(biome)] = config.Bind<string>("Rewards - " + biome, "Rewards v2", defaults, "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
		}

		internal static IReadOnlyList<RewardDefinition> GetRewardsForBiome(string biome)
		{
			List<RewardDefinition> list = new List<RewardDefinition>();
			Parse(_allBiomes.Value, list, "All Biomes");
			if (BiomeEntries.TryGetValue(Normalize(biome), out var value))
			{
				Parse(value.Value, list, biome);
			}
			return list;
		}

		private static void Parse(string raw, List<RewardDefinition> destination, string source)
		{
			if (string.IsNullOrWhiteSpace(raw))
			{
				return;
			}
			string[] array = raw.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				string[] array2 = text.Split(new char[1] { ':' }, StringSplitOptions.None);
				if (array2.Length != 3)
				{
					Warn(source, text, "expected ItemPrefab:Amount:ChancePercent");
					continue;
				}
				string text2 = array2[0].Trim();
				int result;
				bool flag = int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
				float result2;
				bool flag2 = float.TryParse(array2[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result2);
				if (string.IsNullOrWhiteSpace(text2) || !flag || !flag2)
				{
					Warn(source, text, "item, amount, or chance could not be parsed");
				}
				else if (result < 1 || result2 < 0f || result2 > 100f)
				{
					Warn(source, text, "amount must be >= 1 and chance must be 0-100");
				}
				else
				{
					destination.Add(new RewardDefinition(text2, result, result2));
				}
			}
		}

		private static void Warn(string source, string entry, string reason)
		{
			TerrainRewardsPlugin.Log.LogWarning((object)("Invalid reward '" + entry + "' in '" + source + "': " + reason + "."));
		}

		private static string Normalize(string value)
		{
			return (value ?? string.Empty).Replace(" ", string.Empty).Replace("_", string.Empty).Replace("-", string.Empty)
				.Trim();
		}
	}
	internal sealed class RewardDefinition
	{
		public string ItemName { get; }

		public int Amount { get; }

		public float ChancePercent { get; }

		public RewardDefinition(string itemName, int amount, float chancePercent)
		{
			ItemName = itemName;
			Amount = amount;
			ChancePercent = chancePercent;
		}
	}
	[HarmonyPatch]
	internal static class TerrainOpAwakePatch
	{
		internal static MethodBase ResolveTargetMethod()
		{
			return AccessTools.Method(typeof(TerrainOp), "Awake", (Type[])null, (Type[])null);
		}

		private static MethodBase TargetMethod()
		{
			MethodBase methodBase = ResolveTargetMethod();
			if (methodBase == null)
			{
				ManualLogSource log = TerrainRewardsPlugin.Log;
				if (log == null)
				{
					return methodBase;
				}
				log.LogError((object)"Harmony could not resolve TerrainOp.Awake.");
			}
			return methodBase;
		}

		private static void Postfix(TerrainOp __instance)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (TerrainRewardsPlugin.Enabled == null || !TerrainRewardsPlugin.Enabled.Value || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					TerrainRewardsPlugin.Debug("TerrainOp ignored: no local player.");
					return;
				}
				Settings settings = __instance.m_settings;
				if (settings == null)
				{
					TerrainRewardsPlugin.Debug("TerrainOp ignored: settings were null.");
					return;
				}
				Vector3 position = ((Component)__instance).transform.position;
				float num = Vector3.Distance(((Component)localPlayer).transform.position, position);
				TerrainRewardsPlugin.Debug($"TerrainOp.Awake postfix: position={position}, distance={num:0.00}, " + $"level={settings.m_level}, levelOffset={settings.m_levelOffset:0.###}, " + $"raise={settings.m_raise}, raiseDelta={settings.m_raiseDelta:0.###}, " + $"raisePower={settings.m_raisePower:0.###}, smooth={settings.m_smooth}, " + $"smoothPower={settings.m_smoothPower:0.###}, paintCleared={settings.m_paintCleared}, " + "spawnOnPlaced=" + (((Object)(object)__instance.m_spawnOnPlaced != (Object)null) ? ((Object)__instance.m_spawnOnPlaced).name : "null") + ", " + $"spawnAtMaxLevelDepth={__instance.m_spawnAtMaxLevelDepth}");
				if (num > TerrainRewardsPlugin.MaximumPlayerDistance.Value)
				{
					TerrainRewardsPlugin.Debug($"TerrainOp ignored: local player is {num:0.00}m away.");
					return;
				}
				if (!settings.m_raise)
				{
					TerrainRewardsPlugin.Debug("TerrainOp ignored: m_raise is false.");
					return;
				}
				if (settings.m_raiseDelta >= TerrainRewardsPlugin.MaximumDigRaiseDelta.Value)
				{
					TerrainRewardsPlugin.Debug($"TerrainOp ignored: raiseDelta {settings.m_raiseDelta:0.###} " + $"is not below {TerrainRewardsPlugin.MaximumDigRaiseDelta.Value:0.###}.");
					return;
				}
				if (Heightmap.AtMaxLevelDepth(position + Vector3.up * settings.m_levelOffset) && !TerrainRewardsPlugin.RewardAtMaximumDepth.Value)
				{
					TerrainRewardsPlugin.Debug("TerrainOp ignored: terrain is already at maximum dig depth.");
					return;
				}
				TerrainRewardsPlugin.Debug("TerrainOp accepted as a nearby negative-delta digging operation.");
				TerrainRewardsPlugin.AwardRewards(localPlayer, position);
			}
			catch (Exception arg)
			{
				ManualLogSource log = TerrainRewardsPlugin.Log;
				if (log != null)
				{
					log.LogError((object)$"Error while processing TerrainOp rewards: {arg}");
				}
			}
		}
	}
	[BepInPlugin("com.danielcannady.terrainrewards", "Terrain Rewards", "1.2.0")]
	public sealed class TerrainRewardsPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.danielcannady.terrainrewards";

		public const string PluginName = "Terrain Rewards";

		public const string PluginVersion = "1.2.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> DebugLogging;

		internal static ConfigEntry<bool> ShowMessages;

		internal static ConfigEntry<float> MaximumPlayerDistance;

		internal static ConfigEntry<float> MaximumDigRaiseDelta;

		internal static ConfigEntry<bool> RewardAtMaximumDepth;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable or disable Terrain Rewards.");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", true, "Write detailed TerrainOp and reward-roll information to LogOutput.log.");
			ShowMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowMessages", true, "Show a message when rewards drop from terrain.");
			MaximumPlayerDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "MaximumPlayerDistance", 8f, "Maximum distance in meters between the local player and a terrain operation.");
			MaximumDigRaiseDelta = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "MaximumDigRaiseDelta", -0.01f, "Only terrain operations with m_raise=true and a raise delta below this value count as digging. Current Valheim pickaxes use a negative raise delta.");
			RewardAtMaximumDepth = ((BaseUnityPlugin)this).Config.Bind<bool>("Detection", "RewardAtMaximumDepth", false, "Award rewards even when the terrain is already at maximum dig depth.");
			RewardConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("com.danielcannady.terrainrewards");
			_harmony.PatchAll();
			MethodBase methodBase = TerrainOpAwakePatch.ResolveTargetMethod();
			if (methodBase == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Could not find TerrainOp.Awake. Terrain rewards will not run.");
				return;
			}
			Patches patchInfo = Harmony.GetPatchInfo(methodBase);
			bool flag = false;
			if (patchInfo != null)
			{
				foreach (Patch postfix in patchInfo.Postfixes)
				{
					if (postfix.owner == "com.danielcannady.terrainrewards")
					{
						flag = true;
						break;
					}
				}
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Terrain Rewards 1.2.0 loaded. Patch target: " + methodBase.DeclaringType?.Name + "." + methodBase.Name + "; " + $"postfix installed: {flag}"));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		internal static void Debug(string message)
		{
			if (DebugLogging != null && DebugLogging.Value)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("[Debug] " + message));
				}
			}
		}

		internal static void AwardRewards(Player player, Vector3 dropPosition)
		{
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled == null || !Enabled.Value || (Object)(object)player == (Object)null)
			{
				return;
			}
			string biomeName = BiomeDetector.GetBiomeName(player);
			foreach (RewardDefinition item in RewardConfig.GetRewardsForBiome(biomeName))
			{
				float num = Random.value * 100f;
				Debug($"Reward roll: biome={biomeName}, item={item.ItemName}, amount={item.Amount}, chance={item.ChancePercent:0.###}, roll={num:0.###}");
				if (num >= item.ChancePercent)
				{
					continue;
				}
				ObjectDB instance = ObjectDB.instance;
				GameObject val = ((instance != null) ? instance.GetItemPrefab(item.ItemName) : null);
				if ((Object)(object)val == (Object)null)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogWarning((object)("Reward item prefab '" + item.ItemName + "' was not found."));
					}
				}
				else
				{
					int num2 = SpawnRewardDrops(val, item.Amount, dropPosition);
					if (num2 > 0 && ShowMessages != null && ShowMessages.Value)
					{
						((Character)player).Message((MessageType)1, $"Dropped {num2} {item.ItemName}", 0, (Sprite)null, false);
					}
				}
			}
		}

		private static int SpawnRewardDrops(GameObject prefab, int amount, Vector3 dropPosition)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: 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_00a1: Unknown result type (might be due to invalid IL or missing references)
			ItemDrop component = prefab.GetComponent<ItemDrop>();
			if ((Object)(object)component == (Object)null)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("Reward prefab '" + ((Object)prefab).name + "' does not contain an ItemDrop component."));
				}
				return 0;
			}
			int num = component.m_itemData.m_shared.m_maxStackSize;
			if (num < 1)
			{
				num = 1;
			}
			int num2 = amount;
			int num3 = 0;
			int num4 = 0;
			while (num2 > 0)
			{
				int num5 = Mathf.Min(num2, num);
				Vector2 val = Random.insideUnitCircle * 0.35f;
				Vector3 val2 = dropPosition + new Vector3(val.x, 0.35f + 0.05f * (float)num4, val.y);
				ItemDrop component2 = Object.Instantiate<GameObject>(prefab, val2, Quaternion.identity).GetComponent<ItemDrop>();
				if ((Object)(object)component2 != (Object)null)
				{
					component2.m_itemData.m_stack = num5;
					num3 += num5;
				}
				num2 -= num5;
				num4++;
			}
			return num3;
		}
	}
}