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> BiomeEntries = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static ConfigEntry _allBiomes; private const string RewardKey = "Rewards v2"; internal static void Bind(ConfigFile config) { _allBiomes = config.Bind("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("Rewards - " + biome, "Rewards v2", defaults, description); } internal static IReadOnlyList GetRewardsForBiome(string biome) { List list = new List(); 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 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 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 Enabled; internal static ConfigEntry DebugLogging; internal static ConfigEntry ShowMessages; internal static ConfigEntry MaximumPlayerDistance; internal static ConfigEntry MaximumDigRaiseDelta; internal static ConfigEntry 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("General", "Enabled", true, "Enable or disable Terrain Rewards."); DebugLogging = ((BaseUnityPlugin)this).Config.Bind("General", "DebugLogging", true, "Write detailed TerrainOp and reward-roll information to LogOutput.log."); ShowMessages = ((BaseUnityPlugin)this).Config.Bind("General", "ShowMessages", true, "Show a message when rewards drop from terrain."); MaximumPlayerDistance = ((BaseUnityPlugin)this).Config.Bind("Detection", "MaximumPlayerDistance", 8f, "Maximum distance in meters between the local player and a terrain operation."); MaximumDigRaiseDelta = ((BaseUnityPlugin)this).Config.Bind("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("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 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 enabled = Enabled; if (enabled == null || !enabled.Value || (Object)(object)player == (Object)null) { return; } string biomeName = BiomeDetector.GetBiomeName(player); IReadOnlyList 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(); 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(prefab, val2, Quaternion.identity); ItemDrop component2 = val3.GetComponent(); 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; } } }