Decompiled source of TerrainRewards v1.1.1

TerrainRewards.dll

Decompiled 5 days 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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("TerrainRewards")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("TerrainRewards")]
[assembly: AssemblyTitle("TerrainRewards")]
[assembly: AssemblyVersion("1.0.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 static readonly Dictionary<string, ConfigEntry<string>> BiomeEntries = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);

		private static ConfigEntry<string> _allBiomes;

		private const string RewardKey = "Rewards v2";

		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", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "BlackForest", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "Swamp", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "Mountain", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "Plains", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "Mistlands", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "AshLands", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "DeepNorth", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
			BindBiome(config, "Ocean", "", "Unlimited semicolon-separated entries using ItemPrefab:Amount:ChancePercent. Each entry rolls independently. Decimal chances such as 0.5 are supported.");
		}

		private static void BindBiome(ConfigFile config, string biome, string defaults, string description)
		{
			BiomeEntries[Normalize(biome)] = config.Bind<string>("Rewards - " + biome, "Rewards v2", defaults, description);
		}

		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);
			foreach (string text in array)
			{
				string text2 = text.Trim();
				string[] array2 = text2.Split(':');
				if (array2.Length != 3)
				{
					Warn(source, text2, "expected ItemPrefab:Amount:ChancePercent");
					continue;
				}
				string text3 = 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(text3) || !flag || !flag2)
				{
					Warn(source, text2, "item, amount, or chance could not be parsed");
				}
				else if (result < 1 || result2 < 0f || result2 > 100f)
				{
					Warn(source, text2, "amount must be >= 1 and chance must be 0-100");
				}
				else
				{
					destination.Add(new RewardDefinition(text3, 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)
				{
					log.LogError((object)"Harmony could not resolve TerrainOp.Awake.");
				}
			}
			return methodBase;
		}

		private static void Postfix(TerrainOp __instance)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ConfigEntry<bool> enabled = TerrainRewardsPlugin.Enabled;
				if (enabled == null || !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);
				string[] obj = new string[11]
				{
					"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=",
					null,
					null,
					null
				};
				GameObject spawnOnPlaced = __instance.m_spawnOnPlaced;
				obj[8] = ((spawnOnPlaced != null) ? ((Object)spawnOnPlaced).name : null) ?? "null";
				obj[9] = ", ";
				obj[10] = $"spawnAtMaxLevelDepth={__instance.m_spawnAtMaxLevelDepth}";
				TerrainRewardsPlugin.Debug(string.Concat(obj));
				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;
				}
				Vector3 val = position + Vector3.up * settings.m_levelOffset;
				if (Heightmap.AtMaxLevelDepth(val) && !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", "0.4.1")]
	public sealed class TerrainRewardsPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.danielcannady.terrainrewards";

		public const string PluginName = "Terrain Rewards";

		public const string PluginVersion = "0.4.1";

		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_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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 0.4.1 loaded. Patch target: " + methodBase.DeclaringType?.FullName + "." + methodBase.Name + "; " + $"postfix installed: {flag}"));
		}

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

		internal static void Debug(string message)
		{
			ConfigEntry<bool> debugLogging = DebugLogging;
			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_0134: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<bool> enabled = Enabled;
			if (enabled == null || !enabled.Value || (Object)(object)player == (Object)null)
			{
				return;
			}
			string biomeName = BiomeDetector.GetBiomeName(player);
			IReadOnlyList<RewardDefinition> rewardsForBiome = RewardConfig.GetRewardsForBiome(biomeName);
			Debug($"Biome='{biomeName}', configured rewards={rewardsForBiome.Count}");
			foreach (RewardDefinition item in rewardsForBiome)
			{
				float num = Random.value * 100f;
				bool flag = num < item.ChancePercent;
				Debug($"Roll {item.ItemName}: rolled={num:0.00}, " + $"chance={item.ChancePercent:0.##}, success={flag}");
				if (!flag)
				{
					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 prefab '" + item.ItemName + "' was not found. Use its internal prefab name."));
					}
					continue;
				}
				int num2 = SpawnRewardDrops(val, item.Amount, dropPosition);
				Debug($"SpawnDrop {item.ItemName} x{item.Amount}: " + $"spawned={num2}");
				if (num2 > 0 && ShowMessages.Value)
				{
					((Character)player).Message((MessageType)1, $"Dropped {num2} {item.ItemName}", 0, (Sprite)null);
				}
			}
		}

		private static int SpawnRewardDrops(GameObject prefab, int amount, Vector3 dropPosition)
		{
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: 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)
			//IL_00a2: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: 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 + "' has no ItemDrop component."));
				}
				return 0;
			}
			int num = 1;
			if (component.m_itemData?.m_shared != null)
			{
				num = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize);
			}
			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 + (float)num4 * 0.05f, val.y);
				GameObject val3 = Object.Instantiate<GameObject>(prefab, val2, Quaternion.identity);
				ItemDrop component2 = val3.GetComponent<ItemDrop>();
				if ((Object)(object)component2 == (Object)null)
				{
					Object.Destroy((Object)(object)val3);
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("Spawned reward '" + ((Object)prefab).name + "' had no ItemDrop component."));
					}
					break;
				}
				component2.m_itemData.m_stack = num5;
				num3 += num5;
				num2 -= num5;
				num4++;
			}
			return num3;
		}
	}
}