Decompiled source of FishQualityBonus v0.2.0

FishQualityBonus.dll

Decompiled 3 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
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: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("FishQualityBonus")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+804fb076ca0b968c5b34f351803c5079faafb169")]
[assembly: AssemblyProduct("FishQualityBonus")]
[assembly: AssemblyTitle("FishQualityBonus")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.0.0")]
[module: UnverifiableCode]
[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 FishQualityBonus
{
	internal struct RecipeFacts
	{
		public bool HasOutput;

		public bool RequireOnlyOneIngredient;

		public bool OutputIsEquipment;

		public bool IsMead;

		public bool MeadsIncluded;

		public bool ExplicitlyExcluded;

		public int FishRequirementCount;
	}
	internal static class BonusRules
	{
		internal static int ComputeBonus(FishPlan plan, int recipeAmount, int perQualityLevel, int speciesExtra)
		{
			if (recipeAmount < 0)
			{
				recipeAmount = 0;
			}
			int totalFish = plan.TotalFish;
			int num = plan.TotalQuality - totalFish;
			int num2 = ((totalFish > 0) ? (num * recipeAmount * perQualityLevel / totalFish) : 0);
			return Math.Max(0, num2 + Math.Max(0, speciesExtra));
		}

		internal static string IneligibleReason(RecipeFacts facts)
		{
			if (!facts.HasOutput)
			{
				return "no output item";
			}
			if (facts.RequireOnlyOneIngredient)
			{
				return "vanilla already scales this by ingredient quality";
			}
			if (facts.OutputIsEquipment)
			{
				return "output is equipment";
			}
			if (facts.IsMead && !facts.MeadsIncluded)
			{
				return "mead, and IncludeMeadRecipes is off";
			}
			if (facts.ExplicitlyExcluded)
			{
				return "listed in ExcludedRecipes";
			}
			if (facts.FishRequirementCount == 0)
			{
				return "uses no fish";
			}
			if (facts.FishRequirementCount > 1)
			{
				return "uses " + facts.FishRequirementCount + " different fish";
			}
			return null;
		}

		internal static Dictionary<string, int> BuildSpeciesTable(IEnumerable<KeyValuePair<string, int>> entries)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (entries == null)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, int> entry in entries)
			{
				if (!string.IsNullOrEmpty(entry.Key) && entry.Value > 0 && (!dictionary.TryGetValue(entry.Key, out var value) || value < entry.Value))
				{
					dictionary[entry.Key] = entry.Value;
				}
			}
			return dictionary;
		}

		internal static HashSet<string> ParseExclusions(string raw)
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (string.IsNullOrEmpty(raw))
			{
				return hashSet;
			}
			string[] array = raw.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					hashSet.Add(text);
				}
			}
			return hashSet;
		}
	}
	internal sealed class FishChoice
	{
		public Requirement Requirement;

		public FishPlan Plan;

		public int TotalNeeded;
	}
	internal static class FishBonus
	{
		private static HashSet<string> _excluded = new HashSet<string>();

		private static string _excludedRaw;

		internal static FishChoice Choose(Inventory inventory, Requirement[] requirements, int qualityLevel, int multiplier)
		{
			if (inventory == null || requirements == null)
			{
				return null;
			}
			if (!TryGetSingleFishRequirement(requirements, out var fish))
			{
				return null;
			}
			int num = fish.GetAmount(qualityLevel) * multiplier;
			if (num <= 0)
			{
				return null;
			}
			int[] countsByQuality = CountByQuality(inventory, fish);
			bool largestFirst = ModConfig.FishToSpend.Value == FishPreference.LargestFirst;
			if (!FishPlan.TryPick(countsByQuality, num, largestFirst, ModConfig.AllowMixedQualities.Value, out var plan))
			{
				return null;
			}
			return new FishChoice
			{
				Requirement = fish,
				Plan = plan,
				TotalNeeded = num
			};
		}

		private static int[] CountByQuality(Inventory inventory, Requirement fishReq)
		{
			SharedData shared = fishReq.m_resItem.m_itemData.m_shared;
			int num = ((shared.m_maxQuality < 1) ? 1 : shared.m_maxQuality);
			int[] array = new int[num + 1];
			for (int i = 0; i <= num; i++)
			{
				array[i] = inventory.CountItems(shared.m_name, i, true);
			}
			return array;
		}

		internal static string IneligibleReason(Recipe recipe)
		{
			return BonusRules.IneligibleReason(Describe(recipe));
		}

		internal static int BonusFor(Recipe recipe, FishChoice choice)
		{
			if (choice == null)
			{
				return 0;
			}
			if (IneligibleReason(recipe) != null)
			{
				return 0;
			}
			int speciesExtra = (ModConfig.UseSpeciesBonus.Value ? SpeciesBonusTable.ExtraFor(choice.Requirement.m_resItem.m_itemData.m_shared) : 0);
			return BonusRules.ComputeBonus(choice.Plan, recipe.m_amount, ModConfig.BonusPerQualityLevel.Value, speciesExtra);
		}

		internal static bool CanCraftMixed(Inventory inventory, Recipe recipe, int qualityLevel, int multiplier)
		{
			if (inventory == null || recipe?.m_resources == null)
			{
				return false;
			}
			if (!ModConfig.AllowMixedQualities.Value)
			{
				return false;
			}
			if (!TryGetSingleFishRequirement(recipe.m_resources, out var fish))
			{
				return false;
			}
			if (IneligibleReason(recipe) != null)
			{
				return false;
			}
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val in resources)
			{
				if (Object.op_Implicit((Object)(object)val.m_resItem))
				{
					int num = val.GetAmount(qualityLevel) * multiplier;
					if (num > 0 && ((val == fish) ? inventory.CountItems(val.m_resItem.m_itemData.m_shared.m_name, -1, true) : LargestStackByQuality(inventory, val)) < num)
					{
						return false;
					}
				}
			}
			return true;
		}

		private static int LargestStackByQuality(Inventory inventory, Requirement req)
		{
			SharedData shared = req.m_resItem.m_itemData.m_shared;
			int num = 0;
			for (int i = 1; i <= shared.m_maxQuality; i++)
			{
				int num2 = inventory.CountItems(shared.m_name, i, true);
				if (num2 > num)
				{
					num = num2;
				}
			}
			return num;
		}

		internal static bool IsMeadRecipe(Recipe recipe)
		{
			CraftingStation val = recipe?.m_craftingStation;
			if ((Object)(object)val != (Object)null)
			{
				return ((Object)val).name.IndexOf("MeadCauldron", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private static RecipeFacts Describe(Recipe recipe)
		{
			RecipeFacts result = default(RecipeFacts);
			if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null)
			{
				return result;
			}
			result.HasOutput = true;
			result.RequireOnlyOneIngredient = recipe.m_requireOnlyOneIngredient;
			result.OutputIsEquipment = recipe.m_item.m_itemData.IsEquipable();
			result.IsMead = IsMeadRecipe(recipe);
			result.MeadsIncluded = ModConfig.IncludeMeadRecipes.Value;
			result.ExplicitlyExcluded = IsExcluded(((Object)((Component)recipe.m_item).gameObject).name);
			result.FishRequirementCount = CountFishRequirements(recipe.m_resources);
			return result;
		}

		private static int CountFishRequirements(Requirement[] requirements)
		{
			if (requirements == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < requirements.Length; i++)
			{
				if (IsFish(requirements[i]))
				{
					num++;
				}
			}
			return num;
		}

		private static bool TryGetSingleFishRequirement(Requirement[] requirements, out Requirement fish)
		{
			fish = null;
			foreach (Requirement val in requirements)
			{
				if (IsFish(val))
				{
					if (fish != null)
					{
						fish = null;
						return false;
					}
					fish = val;
				}
			}
			return fish != null;
		}

		private static bool IsFish(Requirement req)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			SharedData val = req?.m_resItem?.m_itemData?.m_shared;
			if (val != null)
			{
				return (int)val.m_itemType == 21;
			}
			return false;
		}

		private static bool IsExcluded(string prefabName)
		{
			string text = ModConfig.ExcludedRecipes.Value ?? string.Empty;
			if (text != _excludedRaw)
			{
				_excludedRaw = text;
				_excluded = BonusRules.ParseExclusions(text);
			}
			return _excluded.Contains(prefabName);
		}
	}
	internal readonly struct FishPlan
	{
		private readonly int[] _byQuality;

		private readonly int _totalFish;

		private readonly int _totalQuality;

		internal int TotalFish => _totalFish;

		internal int TotalQuality => _totalQuality;

		internal int MaxQuality
		{
			get
			{
				if (_byQuality != null)
				{
					return _byQuality.Length - 1;
				}
				return -1;
			}
		}

		private FishPlan(int[] byQuality)
		{
			_byQuality = byQuality;
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < byQuality.Length; i++)
			{
				int num3 = byQuality[i];
				if (num3 > 0)
				{
					num += num3;
					int num4 = ((i < 1) ? 1 : i);
					num2 += num3 * num4;
				}
			}
			_totalFish = num;
			_totalQuality = num2;
		}

		internal int CountAt(int quality)
		{
			if (_byQuality == null || quality < 0 || quality >= _byQuality.Length)
			{
				return 0;
			}
			return _byQuality[quality];
		}

		internal static bool TryPick(IList<int> countsByQuality, int needed, bool largestFirst, bool allowMixed, out FishPlan plan)
		{
			plan = default(FishPlan);
			if (countsByQuality == null || countsByQuality.Count == 0 || needed <= 0)
			{
				return false;
			}
			int num = countsByQuality.Count - 1;
			int[] array = new int[countsByQuality.Count];
			int num2 = needed;
			for (int i = 0; i <= num; i++)
			{
				int num3 = (largestFirst ? (num - i) : i);
				int num4 = countsByQuality[num3];
				if (num4 <= 0)
				{
					continue;
				}
				if (!allowMixed)
				{
					if (num4 >= needed)
					{
						array[num3] = needed;
						plan = new FishPlan(array);
						return true;
					}
				}
				else
				{
					num2 -= (array[num3] = ((num4 < num2) ? num4 : num2));
					if (num2 == 0)
					{
						plan = new FishPlan(array);
						return true;
					}
				}
			}
			return false;
		}
	}
	internal static class FishRecipeReport
	{
		internal static bool Write(ObjectDB db)
		{
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Invalid comparison between Unknown and I4
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Invalid comparison between Unknown and I4
			if (!ModConfig.Enabled.Value || !ModConfig.LogRecipeReport.Value)
			{
				return false;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("===== FishQualityBonus report (" + db.m_recipes.Count + " recipes, " + SpeciesBonusTable.Count + " species bonuses derived) =====");
			stringBuilder.AppendLine("-- Fish items --");
			foreach (GameObject item in db.m_items)
			{
				if (!((Object)(object)item == (Object)null))
				{
					SharedData val = item.GetComponent<ItemDrop>()?.m_itemData?.m_shared;
					if (val != null && (int)val.m_itemType == 21)
					{
						stringBuilder.AppendLine("   " + ((Object)item).name.PadRight(24) + " maxQuality=" + val.m_maxQuality + " scaleWeightByQuality=" + val.m_scaleWeightByQuality + " speciesBonus=+" + SpeciesBonusTable.ExtraFor(val));
					}
				}
			}
			stringBuilder.AppendLine("-- Recipes vanilla already scales (m_requireOnlyOneIngredient) --");
			int num = 0;
			foreach (Recipe recipe in db.m_recipes)
			{
				if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null) && recipe.m_requireOnlyOneIngredient)
				{
					num++;
					stringBuilder.AppendLine("   " + ((Object)((Component)recipe.m_item).gameObject).name.PadRight(24) + " x" + recipe.m_amount + "   QualityMult=" + recipe.m_qualityResultAmountMultiplier + "   ingredientChoices=" + ((recipe.m_resources != null) ? recipe.m_resources.Length : 0));
				}
			}
			if (num == 0)
			{
				stringBuilder.AppendLine("   (none)");
			}
			stringBuilder.AppendLine("-- Recipes consuming a fish --");
			foreach (Recipe recipe2 in db.m_recipes)
			{
				if ((Object)(object)recipe2 == (Object)null || (Object)(object)recipe2.m_item == (Object)null || recipe2.m_resources == null)
				{
					continue;
				}
				bool flag = false;
				Requirement[] resources = recipe2.m_resources;
				for (int i = 0; i < resources.Length; i++)
				{
					SharedData val2 = resources[i]?.m_resItem?.m_itemData?.m_shared;
					if (val2 != null && (int)val2.m_itemType == 21)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					continue;
				}
				string text = FishBonus.IneligibleReason(recipe2);
				stringBuilder.AppendLine("   " + ((Object)((Component)recipe2.m_item).gameObject).name + " x" + recipe2.m_amount + "   OnlyOneIngredient=" + recipe2.m_requireOnlyOneIngredient + "   QualityMult=" + recipe2.m_qualityResultAmountMultiplier + "   Station=" + (Object.op_Implicit((Object)(object)recipe2.m_craftingStation) ? ((Object)recipe2.m_craftingStation).name : "(none)"));
				SharedData shared = recipe2.m_item.m_itemData.m_shared;
				stringBuilder.AppendLine("        output: type=" + ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + " maxStackSize=" + shared.m_maxStackSize + " equipable=" + recipe2.m_item.m_itemData.IsEquipable() + " mead=" + FishBonus.IsMeadRecipe(recipe2));
				stringBuilder.AppendLine("        --> " + ((text == null) ? "BONUS APPLIES" : ("skipped: " + text)));
				resources = recipe2.m_resources;
				foreach (Requirement val3 in resources)
				{
					SharedData val4 = val3?.m_resItem?.m_itemData?.m_shared;
					if (val4 != null)
					{
						stringBuilder.AppendLine("        needs " + ((Object)((Component)val3.m_resItem).gameObject).name.PadRight(20) + " x" + val3.m_amount + " (type=" + ((object)Unsafe.As<ItemType, ItemType>(ref val4.m_itemType)/*cast due to .constrained prefix*/).ToString() + ", maxQuality=" + val4.m_maxQuality + ", extraOnlyOne=" + val3.m_extraAmountOnlyOneIngredient + ")");
					}
				}
			}
			stringBuilder.Append("===== end report =====");
			FishQualityBonusPlugin.Log.LogInfo((object)stringBuilder.ToString());
			return true;
		}
	}
	public enum FishPreference
	{
		SmallestFirst,
		LargestFirst
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<int> BonusPerQualityLevel;

		internal static ConfigEntry<FishPreference> FishToSpend;

		internal static ConfigEntry<bool> AllowMixedQualities;

		internal static ConfigEntry<bool> UseSpeciesBonus;

		internal static ConfigEntry<bool> IncludeMeadRecipes;

		internal static ConfigEntry<string> ExcludedRecipes;

		internal static ConfigEntry<bool> LogRecipeReport;

		internal static void Init(ConfigFile cfg)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. Disables all mod functionality. When disabled the mod should behave exactly like vanilla. No side-effects, though we still capture information about receipts into internal memory.");
			BonusPerQualityLevel = cfg.Bind<int>("Bonus", "BonusPerQualityLevel", 3, new ConfigDescription("How much higher-quality fish are worth. The formula is:\n    size bonus = (fishQuality - 1) * amount * thisValue\nwhere 'amount' is what the recipe normally makes. Fish 'n' Bread makes 1, so with this bonus set to the default of 3, a quality 1/2/3/4/5 fish gives  you 1/4/7/10/13. That is the same scaling the game uses for its own Fish (raw) recipe.\nNote that UseSpeciesBonus is added on top and is not affected by this setting, so with both left at their defaults a quality-1 anglerfish still gives 3, and a quality-5 gives 15.\nWhen a craft spends fish of different sizes, 'fishQuality' is their average, rounded down.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			FishToSpend = cfg.Bind<FishPreference>("Bonus", "FishToSpend", FishPreference.SmallestFirst, "Which fish a recipe spends when you are carrying several qualities of the same species. Vanilla takes whichever one you picked up first, no matter where it sits in your inventory, so it is effectively random.\nWe work through your fish in this order and take them as we go, so SmallestFirst really does save your best catches: a two-fish craft with one small fish and three big ones spends the small one and only one big one.");
			AllowMixedQualities = cfg.Bind<bool>("Bonus", "AllowMixedQualities", true, "Allow a recipe to draw on several qualities of the same fish at once.\nBase game will not: it checks your biggest single-quality stack rather than your total, so two trollfish of different sizes cannot brew a Troll Endurance mead that needs two, even though the ingredient list shows 2 of 2 and looks happy. With this on, the craft goes through and the payout is based on the average size of the fish you spent.\nThis applies to the same recipes the bonus does, so ExcludedRecipes and IncludeMeadRecipes still leave a recipe entirely to vanilla. Set false to keep vanilla's rules and only change the payout.");
			UseSpeciesBonus = cfg.Bind<bool>("Bonus", "UseSpeciesBonus", true, "Also pay out for what kind of fish it is, not just how big it was. Valheim sorts its twelve fish into +0/+1/+2 tiers for the Fish (raw) recipe, and this reuses those same numbers, read from the game at load. Anglerfish is a +2, so Fish 'n' Bread gains 2 loaves. Vanilla does not scale this by quality, so it applies to a quality-1 fish too. Set false if you want the bonus to come purely from fish quality.");
			IncludeMeadRecipes = cfg.Bind<bool>("Bonus", "IncludeMeadRecipes", true, "Whether mead bases brewed at the mead cauldron get the bonus as well as food.\nVanilla has three that involve fish: MeadBaseBugRepellent, MeadBaseStrength and MeadBaseSwimmer.\nSet false to keep the mod to food recipes like Fish 'n' Bread. Equipment such as the fishing hat is never affected either way.");
			ExcludedRecipes = cfg.Bind<string>("Bonus", "ExcludedRecipes", "", "Optional. A comma-separated list of recipes to leave alone, named by the prefab that comes out of them. Use this when you want to skip one recipe rather than a whole group. Example: MeadBaseStrength,MeadBaseSwimmer");
			LogRecipeReport = cfg.Bind<bool>("Diagnostics", "LogRecipeReport", false, "Dump every fish and every fish-consuming recipe to the BepInEx log on load, with a note on whether the bonus should apply and why. This is a development aid and changes nothing in game.");
		}
	}
	[HarmonyPatch]
	internal static class ObjectDbHooks
	{
		private static int _lastRecipeCount = -1;

		private static int _lastReportedCount = -1;

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private static void AfterAwake(ObjectDB __instance)
		{
			Refresh(__instance);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
		private static void AfterCopyOtherDB(ObjectDB __instance)
		{
			Refresh(__instance);
		}

		private static void Refresh(ObjectDB db)
		{
			if (db?.m_recipes != null && db.m_recipes.Count != 0)
			{
				int count = db.m_recipes.Count;
				if (count != _lastRecipeCount)
				{
					_lastRecipeCount = count;
					SpeciesBonusTable.Build(db);
				}
				if (count != _lastReportedCount && FishRecipeReport.Write(db))
				{
					_lastReportedCount = count;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Recipe), "GetAmount")]
	internal static class Patch_Recipe_GetAmount
	{
		private static void Postfix(Recipe __instance, int quality, int craftMultiplier, ref int __result)
		{
			if (!ModConfig.Enabled.Value || __instance.m_requireOnlyOneIngredient)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				FishChoice choice = FishBonus.Choose(((Humanoid)localPlayer).GetInventory(), __instance.m_resources, quality, craftMultiplier);
				int num = FishBonus.BonusFor(__instance, choice);
				if (num > 0)
				{
					__result += num * craftMultiplier;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "ConsumeResources")]
	internal static class Patch_Player_ConsumeResources
	{
		private static bool Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier)
		{
			if (!ModConfig.Enabled.Value)
			{
				return true;
			}
			if (itemQuality >= 0)
			{
				return true;
			}
			Inventory inventory = ((Humanoid)__instance).GetInventory();
			FishChoice fishChoice = FishBonus.Choose(inventory, requirements, qualityLevel, multiplier);
			if (fishChoice == null)
			{
				return true;
			}
			foreach (Requirement val in requirements)
			{
				if (!Object.op_Implicit((Object)(object)val.m_resItem))
				{
					continue;
				}
				int num = val.GetAmount(qualityLevel) * multiplier;
				if (num <= 0)
				{
					continue;
				}
				string name = val.m_resItem.m_itemData.m_shared.m_name;
				if (val != fishChoice.Requirement)
				{
					inventory.RemoveItem(name, num, itemQuality, true);
					continue;
				}
				for (int j = 0; j <= fishChoice.Plan.MaxQuality; j++)
				{
					int num2 = fishChoice.Plan.CountAt(j);
					if (num2 > 0)
					{
						inventory.RemoveItem(name, num2, j, true);
					}
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
	internal static class Patch_Player_HaveRequirementItems
	{
		private static void Postfix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount, ref bool __result)
		{
			if (!__result && ModConfig.Enabled.Value && !discover && FishBonus.CanCraftMixed(((Humanoid)__instance).GetInventory(), piece, qualityLevel, amount))
			{
				__result = true;
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	internal static class Patch_InventoryGui_UpdateRecipe
	{
		private static void Postfix(InventoryGui __instance, Player player)
		{
			if (!ModConfig.Enabled.Value || (Object)(object)player == (Object)null || (Object)(object)__instance.m_recipeName == (Object)null)
			{
				return;
			}
			Recipe recipe = ((RecipeDataPair)(ref __instance.m_selectedRecipe)).Recipe;
			if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null) && !recipe.m_requireOnlyOneIngredient && ((RecipeDataPair)(ref __instance.m_selectedRecipe)).ItemData == null)
			{
				int num = CurrentCraftMultiplier(__instance);
				FishChoice choice = FishBonus.Choose(((Humanoid)player).GetInventory(), recipe.m_resources, 1, num);
				int num2 = FishBonus.BonusFor(recipe, choice);
				if (num2 > 0)
				{
					int num3 = (recipe.m_amount + num2) * num;
					string text = Localization.instance.Localize(recipe.m_item.m_itemData.m_shared.m_name);
					__instance.m_recipeName.text = text + " x" + num3;
				}
			}
		}

		private static int CurrentCraftMultiplier(InventoryGui gui)
		{
			if (!ZInput.GetButton("AltPlace") && !ZInput.GetButton("JoyLStick"))
			{
				return 1;
			}
			return gui.m_multiCraftAmount;
		}
	}
	[BepInPlugin("pandincus.fishqualitybonus", "FishQualityBonus", "0.2.0")]
	[BepInProcess("valheim.exe")]
	public class FishQualityBonusPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "pandincus.fishqualitybonus";

		public const string PluginName = "FishQualityBonus";

		public const string PluginVersion = "0.2.0";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Init(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("pandincus.fishqualitybonus");
			_harmony.PatchAll();
			Log.LogInfo((object)"FishQualityBonus v0.2.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class SpeciesBonusTable
	{
		private static Dictionary<string, int> _extraByFish = new Dictionary<string, int>();

		internal static int Count => _extraByFish.Count;

		internal static void Build(ObjectDB db)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Invalid comparison between Unknown and I4
			List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();
			if (db?.m_recipes != null)
			{
				foreach (Recipe recipe in db.m_recipes)
				{
					if ((Object)(object)recipe == (Object)null || !recipe.m_requireOnlyOneIngredient || recipe.m_resources == null)
					{
						continue;
					}
					Requirement[] resources = recipe.m_resources;
					foreach (Requirement val in resources)
					{
						SharedData val2 = val?.m_resItem?.m_itemData?.m_shared;
						if (val2 != null && (int)val2.m_itemType == 21)
						{
							list.Add(new KeyValuePair<string, int>(val2.m_name, val.m_extraAmountOnlyOneIngredient));
						}
					}
				}
			}
			_extraByFish = BonusRules.BuildSpeciesTable(list);
		}

		internal static int ExtraFor(SharedData fish)
		{
			if (fish == null)
			{
				return 0;
			}
			if (!_extraByFish.TryGetValue(fish.m_name, out var value))
			{
				return 0;
			}
			return value;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}