Decompiled source of MoreFishAPI v1.0.0

BepInEx/plugins/MoreFishAPI.dll

Decompiled 20 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Managing.Object;
using FishNet.Object;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
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("MoreFishAPI")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MoreFishAPI")]
[assembly: AssemblyTitle("MoreFishAPI")]
[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 MoreFishAPI
{
	public class FishDefinition
	{
		public string Id { get; set; }

		public string DisplayName { get; set; }

		public Mesh Mesh { get; set; }

		public GameObject Prefab { get; set; }

		public string Template { get; set; } = "";

		public int Worth { get; set; } = 10;

		public float MeshScale { get; set; } = 1f;

		public float Rarity { get; set; } = 1f;

		public Vector3 MeshRotation { get; set; } = Vector3.zero;

		public bool AutoFit { get; set; } = true;

		public bool AllowDrip { get; set; }

		public Texture Texture { get; set; }

		public Material Material { get; set; }

		public int WorthMin { get; set; } = -1;

		public int WorthMax { get; set; } = -1;

		public int Health { get; set; } = -1;

		public bool? Endangered { get; set; }

		public bool? Hostile { get; set; }

		public bool KeepTemplateSkin { get; set; }

		public float HeadPosition { get; set; } = -1f;

		public float PreviewScale { get; set; } = -1f;

		public float FlopIntervalMin { get; set; } = -1f;

		public float FlopIntervalMax { get; set; } = -1f;

		public float FlopStrength { get; set; } = -1f;

		public float SinkForce { get; set; } = -1f;

		public int FoodValue { get; set; } = -1;

		public int HealValue { get; set; } = -1;

		public string BaitFilter { get; set; } = "";

		public bool InJournal { get; set; } = true;

		public string SourcePlugin { get; internal set; }

		public byte RuntimeItemId { get; internal set; }

		public bool Live { get; internal set; }

		public Material RuntimeMaterial { get; internal set; }
	}
	public static class MoreFish
	{
		private static readonly Dictionary<string, FishDefinition> _byId = new Dictionary<string, FishDefinition>(StringComparer.OrdinalIgnoreCase);

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

		internal static string Forced;

		public static bool IsAvailable => true;

		public static int RegisteredCount => _byId.Count;

		public static IReadOnlyCollection<FishDefinition> All => _byId.Values;

		public static bool IsForcing => Forced != null;

		public static event Action OnFishRebuilt;

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static bool Register(FishDefinition fish)
		{
			if (fish == null || string.IsNullOrEmpty(fish.Id))
			{
				Plugin.Log.LogError((object)"A fish was registered with no id, ignoring it.");
				return false;
			}
			if ((Object)(object)fish.Mesh == (Object)null && (Object)(object)fish.Prefab == (Object)null)
			{
				Plugin.Log.LogError((object)("Fish '" + fish.Id + "' was registered with neither a Mesh nor a Prefab, ignoring it."));
				return false;
			}
			if ((Object)(object)fish.Prefab != (Object)null && (Object)(object)fish.Prefab.GetComponentInChildren<MeshFilter>(true) == (Object)null)
			{
				Plugin.Log.LogError((object)("Fish '" + fish.Id + "': the prefab '" + ((Object)fish.Prefab).name + "' has no MeshFilter anywhere in it, so there is nothing to show. Ignoring it."));
				return false;
			}
			if (_byId.ContainsKey(fish.Id))
			{
				Plugin.Log.LogWarning((object)("Fish '" + fish.Id + "' is already registered, ignoring the duplicate."));
				return false;
			}
			fish.SourcePlugin = Caller();
			if (Injector.AlreadyBuilt)
			{
				Plugin.Log.LogError((object)("Fish '" + fish.Id + "' from " + fish.SourcePlugin + " was registered too late. Custom fish are built once at startup, so register from your plugin's Awake. This fish will not appear."));
				return false;
			}
			if (string.IsNullOrEmpty(fish.DisplayName))
			{
				fish.DisplayName = fish.Id;
			}
			fish.Worth = Mathf.Max(0, fish.Worth);
			if (fish.WorthMin >= 0 && fish.WorthMax >= fish.WorthMin)
			{
				fish.Worth = Mathf.Max(1, (fish.WorthMin + fish.WorthMax) / 2);
			}
			fish.Rarity = Mathf.Max(0.0001f, fish.Rarity);
			fish.MeshScale = Mathf.Max(0.01f, fish.MeshScale);
			if (fish.InJournal && !fish.AllowDrip)
			{
				Plugin.Log.LogWarning((object)("Fish '" + fish.Id + "' is in the journal but can never roll shiny, so the game's catch every shiny check can never pass while it is installed. Set AllowDrip true, or InJournal false."));
			}
			_byId[fish.Id] = fish;
			Plugin.Log.LogInfo((object)("Registered fish '" + fish.DisplayName + "' as " + fish.Id + " from " + fish.SourcePlugin + "."));
			Injector.MarkDirty();
			return true;
		}

		public static bool Register(string id, string displayName, Mesh mesh, int worth, float rarity = 1f)
		{
			return Register(new FishDefinition
			{
				Id = id,
				DisplayName = displayName,
				Mesh = mesh,
				Worth = worth,
				Rarity = rarity
			});
		}

		public static bool RegisterFromBundle(string id, string displayName, AssetBundle bundle, string meshName, int worth, float rarity = 1f)
		{
			if ((Object)(object)bundle == (Object)null)
			{
				Plugin.Log.LogError((object)("Fish '" + id + "' was given a null asset bundle."));
				return false;
			}
			Mesh val = bundle.LoadAsset<Mesh>(meshName);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogError((object)("Fish '" + id + "': the bundle has no mesh called '" + meshName + "'. It contains: " + string.Join(", ", bundle.GetAllAssetNames())));
				return false;
			}
			return Register(id, displayName, val, worth, rarity);
		}

		public static AssetBundle LoadBundle(string absolutePath)
		{
			if (string.IsNullOrEmpty(absolutePath))
			{
				return null;
			}
			if (_bundles.TryGetValue(absolutePath, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			try
			{
				if (!File.Exists(absolutePath))
				{
					Plugin.Log.LogError((object)("No asset bundle at " + absolutePath));
					return null;
				}
				AssetBundle val = AssetBundle.LoadFromFile(absolutePath);
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogError((object)("Unity refused to load the asset bundle at " + absolutePath + ". This usually means the bundle is already loaded, that it was built for a different platform, or that it was built with a Unity version the game cannot read."));
					return null;
				}
				_bundles[absolutePath] = val;
				return val;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to load asset bundle: " + ex));
				return null;
			}
		}

		public static bool ForceNextCatch(string id)
		{
			FishDefinition fishDefinition = Get(id);
			if (fishDefinition == null)
			{
				Plugin.Log.LogWarning((object)("Cannot force '" + id + "', no fish is registered under that id."));
				return false;
			}
			if (!fishDefinition.Live)
			{
				Plugin.Log.LogWarning((object)("Cannot force '" + id + "', it is registered but was not added to the game."));
				return false;
			}
			Forced = fishDefinition.Id;
			Plugin.Log.LogInfo((object)("The next thing caught on any rod will be '" + fishDefinition.DisplayName + "'."));
			return true;
		}

		public static void ClearForcedCatch()
		{
			if (Forced != null)
			{
				Plugin.Log.LogInfo((object)"Forced catch cleared.");
				Forced = null;
			}
		}

		public static bool IsRegistered(string id)
		{
			if (!string.IsNullOrEmpty(id))
			{
				return _byId.ContainsKey(id);
			}
			return false;
		}

		public static FishDefinition Get(string id)
		{
			if (string.IsNullOrEmpty(id) || !_byId.TryGetValue(id, out var value))
			{
				return null;
			}
			return value;
		}

		public static List<FishDefinition> Ordered()
		{
			List<FishDefinition> list = new List<FishDefinition>(_byId.Values);
			list.Sort((FishDefinition a, FishDefinition b) => string.CompareOrdinal(a.Id, b.Id));
			return list;
		}

		internal static void RaiseRebuilt()
		{
			Delegate[] array = MoreFish.OnFishRebuilt?.GetInvocationList();
			if (array == null)
			{
				return;
			}
			Delegate[] array2 = array;
			foreach (Delegate obj in array2)
			{
				try
				{
					((Action)obj)();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"A listener of OnFishRebuilt threw, the others still ran: {arg}");
				}
			}
		}

		private static string Caller()
		{
			try
			{
				StackTrace stackTrace = new StackTrace();
				for (int i = 1; i < stackTrace.FrameCount; i++)
				{
					Type type = stackTrace.GetFrame(i).GetMethod()?.DeclaringType;
					if (type != null && type.Assembly != typeof(MoreFish).Assembly)
					{
						return type.Assembly.GetName().Name;
					}
				}
			}
			catch (Exception)
			{
			}
			return "unknown";
		}
	}
	internal static class BaitTables
	{
		private static readonly List<object> _added = new List<object>();

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

		public static Fishable SampleFor(string id)
		{
			if (string.IsNullOrEmpty(id) || !_samples.TryGetValue(id, out var value))
			{
				return null;
			}
			return value;
		}

		public static void Inject(List<FishDefinition> fish, FieldInfo allBaitsField, FieldInfo weightsField, FieldInfo weightFishableField, FieldInfo weightValueField, FieldInfo fishableItemField)
		{
			if (allBaitsField.GetValue(null) is IList { Count: not 0 } list)
			{
				int num = 0;
				List<string> list2 = new List<string>();
				List<string> list3 = new List<string>();
				foreach (object item in list)
				{
					BaitInfo val = (BaitInfo)((item is BaitInfo) ? item : null);
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					list2.Add(((Object)val).name);
					if (!(weightsField.GetValue(val) is IList list4))
					{
						continue;
					}
					if (list4.Count == 0)
					{
						list3.Add(((Object)val).name);
						continue;
					}
					float num2 = Baseline(list4, weightValueField);
					foreach (FishDefinition item2 in fish)
					{
						if (item2.RuntimeItemId != 0 && Matches(item2, val))
						{
							Item val2 = GameInfo.IDToItem(item2.RuntimeItemId);
							if (!((Object)(object)val2 == (Object)null))
							{
								Fishable val3 = ScriptableObject.CreateInstance<Fishable>();
								((Object)val3).name = "MoreFish_" + item2.Id;
								fishableItemField.SetValue(val3, val2);
								Object.DontDestroyOnLoad((Object)(object)val3);
								object obj = Activator.CreateInstance(typeof(ItemInfoWeight), nonPublic: true);
								weightFishableField.SetValue(obj, val3);
								weightValueField.SetValue(obj, num2 * item2.Rarity * Mathf.Max(0f, Plugin.RarityMultiplier.Value));
								list4.Add(obj);
								_added.Add(obj);
								_samples[item2.Id] = val3;
								num++;
							}
						}
					}
				}
				Plugin.Log.LogInfo((object)string.Format("Custom fish added to catch tables {0} time(s). Bait names for BaitFilter: {1}", num, string.Join(", ", list2)));
				if (list3.Count > 0)
				{
					Plugin.Log.LogInfo((object)string.Format("Skipped {0} bait(s) that catch nothing in the base game, so custom fish cannot be caught with no bait equipped: {1}", list3.Count, string.Join(", ", list3)));
				}
			}
			else
			{
				Plugin.Log.LogWarning((object)"No baits are loaded yet, so custom fish were not added to any catch table.");
			}
		}

		private static float Baseline(IList weights, FieldInfo weightValueField)
		{
			float num = 0f;
			int num2 = 0;
			foreach (object weight in weights)
			{
				if (!_added.Contains(weight) && weightValueField.GetValue(weight) is float num3 && num3 > 0f)
				{
					num += num3;
					num2++;
				}
			}
			if (num2 <= 0)
			{
				return 1f;
			}
			return num / (float)num2;
		}

		private static bool Matches(FishDefinition fish, BaitInfo bait)
		{
			if (string.IsNullOrEmpty(fish.BaitFilter))
			{
				return true;
			}
			string text = ((Object)bait).name.Replace(" ", "").ToLowerInvariant();
			string[] array = fish.BaitFilter.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim().Replace(" ", "").ToLowerInvariant();
				if (text2.Length > 0 && text.Contains(text2))
				{
					return true;
				}
			}
			return false;
		}
	}
	internal class IdMapFile
	{
		public int Version = 2;

		public Dictionary<string, byte> Assigned = new Dictionary<string, byte>();
	}
	internal static class IdMap
	{
		public const byte Lowest = 128;

		public const byte Highest = 250;

		public const int Slots = 123;

		private static IdMapFile _file;

		private static bool _loaded;

		public static string Path => System.IO.Path.Combine(Application.persistentDataPath, "MoreFishAPI", "fishids.json");

		private static void Load()
		{
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			try
			{
				if (File.Exists(Path))
				{
					IdMapFile idMapFile = JsonConvert.DeserializeObject<IdMapFile>(File.ReadAllText(Path));
					if (idMapFile?.Assigned != null)
					{
						_file = idMapFile;
						return;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not read the fish id record, it will be rewritten: " + ex.Message));
			}
			_file = new IdMapFile();
		}

		private static void Save()
		{
			try
			{
				Directory.CreateDirectory(System.IO.Path.GetDirectoryName(Path));
				string text = Path + ".writing";
				File.WriteAllText(text, JsonConvert.SerializeObject((object)_file, (Formatting)1));
				if (File.Exists(Path))
				{
					File.Replace(text, Path, null);
				}
				else
				{
					File.Move(text, Path);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not write the fish id record: " + ex.Message));
			}
		}

		public static byte Preferred(string id)
		{
			uint num = 2166136261u;
			string text = id.ToLowerInvariant();
			foreach (char c in text)
			{
				num ^= c;
				num *= 16777619;
			}
			return (byte)(128 + num % 123);
		}

		public static Dictionary<string, byte> Assign(List<FishDefinition> fish, IDictionary vanillaItems)
		{
			Load();
			Dictionary<string, byte> dictionary = new Dictionary<string, byte>(StringComparer.OrdinalIgnoreCase);
			HashSet<byte> hashSet = new HashSet<byte>();
			foreach (FishDefinition item in fish)
			{
				byte b = Preferred(item.Id);
				byte b2 = 0;
				for (int i = 0; i < 123; i++)
				{
					byte b3 = (byte)(128 + (b - 128 + i) % 123);
					if (!hashSet.Contains(b3) && !vanillaItems.Contains(b3))
					{
						b2 = b3;
						break;
					}
				}
				if (b2 == 0)
				{
					Plugin.Log.LogError((object)$"No free item id remains for fish '{item.Id}' from {item.SourcePlugin}. Only {123} custom fish can exist across all installed mods.");
					continue;
				}
				dictionary[item.Id] = b2;
				hashSet.Add(b2);
				if (b2 != b)
				{
					Plugin.Log.LogWarning((object)$"Fish '{item.Id}' wanted id {b} but it was taken, so it got {b2}. If other players have a different set of fish mods, this fish may not match on their machine.");
				}
			}
			ReportDrift(dictionary);
			return dictionary;
		}

		private static void ReportDrift(Dictionary<string, byte> assigned)
		{
			bool flag = false;
			foreach (KeyValuePair<string, byte> item in assigned)
			{
				if (_file.Assigned.TryGetValue(item.Key, out var value) && value != item.Value)
				{
					Plugin.Log.LogWarning((object)$"Fish '{item.Key}' had id {value} last time and has id {item.Value} now. Anything of this fish already in a save will not come back correctly.");
				}
				if (!_file.Assigned.ContainsKey(item.Key) || _file.Assigned[item.Key] != item.Value)
				{
					_file.Assigned[item.Key] = item.Value;
					flag = true;
				}
			}
			if (flag)
			{
				Save();
			}
		}

		public static string Checksum(Dictionary<string, byte> assigned)
		{
			List<string> list = new List<string>(assigned.Keys);
			list.Sort(StringComparer.OrdinalIgnoreCase);
			uint num = 2166136261u;
			foreach (string item in list)
			{
				string text = item.ToLowerInvariant();
				foreach (char c in text)
				{
					num ^= c;
					num *= 16777619;
				}
				num ^= assigned[item];
				num *= 16777619;
			}
			return num.ToString("X8");
		}
	}
	internal static class Injector
	{
		private const ushort CollectionId = 41;

		private static FieldInfo _allItems;

		private static FieldInfo _idToSpawnable;

		private static FieldInfo _nameToSpawnable;

		private static FieldInfo _allCreatures;

		private static FieldInfo _allBaits;

		private static FieldInfo _itemId;

		private static FieldInfo _itemWorth;

		private static FieldInfo _itemMesh;

		private static FieldInfo _dripMesh;

		private static FieldInfo _excludeFromJournal;

		private static FieldInfo _baitWeights;

		private static FieldInfo _weightFishable;

		private static FieldInfo _weightValue;

		private static FieldInfo _fishableItem;

		private static readonly Dictionary<byte, FishDefinition> _byRuntimeId = new Dictionary<byte, FishDefinition>();

		private static GameObject _nursery;

		private static readonly List<GameObject> _placeholders = new List<GameObject>();

		private static bool _resolved;

		private static bool _dirty;

		private static bool _attempted;

		private static bool _succeeded;

		private static bool _warnedTemplate;

		private static bool _listedCandidates;

		private static float _firstTry;

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

		public static bool Succeeded => _succeeded;

		public static bool AlreadyBuilt => _attempted;

		public static FishDefinition FromItem(Item item)
		{
			if (!((Object)(object)item != (Object)null) || !_byRuntimeId.TryGetValue(item.ID, out var value))
			{
				return null;
			}
			return value;
		}

		public static bool KnowsId(byte id)
		{
			return _byRuntimeId.ContainsKey(id);
		}

		public static bool Resolve()
		{
			if (_resolved)
			{
				return _allItems != null;
			}
			_resolved = true;
			_allItems = AccessTools.Field(typeof(GameInfo), "_allItems");
			_idToSpawnable = AccessTools.Field(typeof(GameInfo), "_idToSpawnable");
			_nameToSpawnable = AccessTools.Field(typeof(GameInfo), "_nameToSpawnable");
			_allCreatures = AccessTools.Field(typeof(GameInfo), "_allCreatures");
			_allBaits = AccessTools.Field(typeof(GameInfo), "_allBaits");
			_itemId = AccessTools.Field(typeof(Item), "_id");
			_itemWorth = AccessTools.Field(typeof(Item), "_worth");
			_itemMesh = AccessTools.Field(typeof(Item), "_mesh");
			_dripMesh = AccessTools.Field(typeof(Creature), "_dripMesh");
			_excludeFromJournal = AccessTools.Field(typeof(Creature), "_excludeFromJournal");
			_baitWeights = AccessTools.Field(typeof(BaitInfo), "_itemWeights");
			_weightFishable = AccessTools.Field(typeof(ItemInfoWeight), "fishable");
			_weightValue = AccessTools.Field(typeof(ItemInfoWeight), "_weight");
			_fishableItem = AccessTools.Field(typeof(Fishable), "_itemToSpawn");
			foreach (KeyValuePair<string, FieldInfo> item in new Dictionary<string, FieldInfo>
			{
				{ "GameInfo._allItems", _allItems },
				{ "GameInfo._allCreatures", _allCreatures },
				{ "GameInfo._allBaits", _allBaits },
				{ "Item._id", _itemId },
				{ "BaitInfo._itemWeights", _baitWeights },
				{ "ItemInfoWeight.fishable", _weightFishable },
				{ "ItemInfoWeight._weight", _weightValue },
				{ "Fishable._itemToSpawn", _fishableItem }
			})
			{
				if (item.Value == null)
				{
					Plugin.Log.LogError((object)("Could not find " + item.Key + ". Custom fish cannot be added to this version of the game."));
					_allItems = null;
					return false;
				}
			}
			if (_allItems.FieldType.IsGenericType)
			{
				Type type = _allItems.FieldType.GetGenericArguments()[0];
				if (type != typeof(byte))
				{
					Plugin.Log.LogError((object)("The game now keys its item list by " + type.Name + " rather than byte. Custom fish would be added in a way the game cannot see, so none will be added."));
					_allItems = null;
					return false;
				}
			}
			return true;
		}

		public static void MarkDirty()
		{
			if (_attempted)
			{
				Plugin.Log.LogWarning((object)"Backstop: a fish reached MarkDirty after the custom fish were built. MoreFish.Register already refuses late registration, so reaching here means something called this directly.");
			}
			else
			{
				_dirty = true;
			}
		}

		public static void TryBuild()
		{
			if (!_dirty || _attempted || !Resolve() || !(_allItems.GetValue(null) is IDictionary { Count: not 0 } dictionary) || (Object)(object)InstanceFinder.NetworkManager == (Object)null)
			{
				return;
			}
			Creature val = FindTemplate(dictionary);
			if ((Object)(object)val == (Object)null)
			{
				if (_firstTry <= 0f)
				{
					_firstTry = Time.realtimeSinceStartup;
				}
				if (!_warnedTemplate && Time.realtimeSinceStartup - _firstTry > 10f)
				{
					_warnedTemplate = true;
					_attempted = true;
					Plugin.Log.LogError((object)"No ordinary fish could be found to use as a template, so no custom fish were added.");
				}
				return;
			}
			_dirty = false;
			_attempted = true;
			try
			{
				_succeeded = Build(dictionary, val);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to add custom fish: " + ex));
			}
			finally
			{
				MoreFish.RaiseRebuilt();
			}
		}

		public static Creature TemplateFor(FishDefinition fish, Creature fallback)
		{
			string text = ((!string.IsNullOrEmpty(fish.Template)) ? fish.Template : Plugin.TemplateFish.Value);
			if (!string.IsNullOrEmpty(text) && _candidates.TryGetValue(text, out var value))
			{
				return value;
			}
			if (!string.IsNullOrEmpty(fish.Template))
			{
				Plugin.Log.LogWarning((object)("Fish '" + fish.Id + "' asked for template '" + fish.Template + "', which this game does not have. Using '" + ((Object)fallback).name + "' instead."));
			}
			return fallback;
		}

		private static Creature FindTemplate(IDictionary items)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			List<Creature> list = new List<Creature>();
			foreach (DictionaryEntry item in items)
			{
				object? value = item.Value;
				Item val = (Item)((value is Item) ? value : null);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Creature component = ((Component)val).GetComponent<Creature>();
				if (!((Object)(object)component == (Object)null) && (int)component.BossType == 0 && !component.ExcludeFromJournal)
				{
					bool flag = ((Component)val).GetComponentsInChildren<MeshFilter>(true).Length != 0 || ((Component)val).GetComponentsInChildren<SkinnedMeshRenderer>(true).Length != 0;
					if (!((Object)(object)((Component)val).GetComponent<NetworkObject>() == (Object)null) && flag)
					{
						list.Add(component);
					}
				}
			}
			if (list.Count == 0)
			{
				return null;
			}
			_candidates.Clear();
			foreach (Creature item2 in list)
			{
				_candidates[((Object)item2).name] = item2;
			}
			if (!_listedCandidates)
			{
				_listedCandidates = true;
				List<string> list2 = new List<string>();
				foreach (Creature item3 in list)
				{
					list2.Add(((Object)item3).name);
				}
				list2.Sort(string.CompareOrdinal);
				Plugin.Log.LogInfo((object)("Fish available as a template: " + string.Join(", ", list2)));
				foreach (Creature item4 in list)
				{
					Bounds val2 = CombinedBounds(((Component)item4).gameObject);
					Component[] array;
					if (((Component)item4).GetComponentsInChildren<MeshFilter>(true).Length == 0)
					{
						Component[] componentsInChildren = (Component[])(object)((Component)item4).GetComponentsInChildren<SkinnedMeshRenderer>(true);
						array = componentsInChildren;
					}
					else
					{
						Component[] componentsInChildren = (Component[])(object)((Component)item4).GetComponentsInChildren<MeshFilter>(true);
						array = componentsInChildren;
					}
					Component[] array2 = array;
					_ = array2.LongLength;
					string text = ((item4 is AttackingFish) ? "hostile" : ((item4 is Fish) ? "fish" : "creature"));
					int num = (int)(AccessTools.Field(typeof(Creature), "_maxHp")?.GetValue(item4) ?? ((object)0));
					bool flag2 = (bool)(AccessTools.Field(typeof(Creature), "_isEndangered")?.GetValue(item4) ?? ((object)false));
					Plugin.Log.LogInfo((object)$"  {((Object)item4).name}: {text}, {array2.Length} part(s), combined {((Bounds)(ref val2)).size}, hp {num}, endangered {flag2}, worth {((Item)item4).DefaultWorth}");
				}
			}
			string value2 = Plugin.TemplateFish.Value;
			if (!string.IsNullOrEmpty(value2))
			{
				foreach (Creature item5 in list)
				{
					if (string.Equals(((Object)item5).name, value2, StringComparison.OrdinalIgnoreCase))
					{
						return item5;
					}
				}
				Plugin.Log.LogWarning((object)("No fish called '" + value2 + "' exists, so one will be chosen automatically. See the list above for valid names."));
			}
			Creature val3 = null;
			int num2 = int.MaxValue;
			foreach (Creature item6 in list)
			{
				int num3 = ((Component)item6).GetComponentsInChildren<MeshFilter>(true).Length;
				if (num3 != 0 && num3 < num2)
				{
					val3 = item6;
					num2 = num3;
				}
			}
			return val3 ?? list[0];
		}

		public static Bounds CombinedBounds(GameObject root)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: 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_006e: 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_007d: 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_011a: 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_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			Bounds result = default(Bounds);
			((Bounds)(ref result))..ctor(Vector3.zero, Vector3.zero);
			MeshFilter[] componentsInChildren = root.GetComponentsInChildren<MeshFilter>(true);
			Bounds val2 = default(Bounds);
			foreach (MeshFilter val in componentsInChildren)
			{
				if (!((Object)(object)val.sharedMesh == (Object)null))
				{
					Bounds bounds = val.sharedMesh.bounds;
					((Bounds)(ref val2))..ctor(((Bounds)(ref bounds)).center + ((Component)val).transform.localPosition, Vector3.Scale(((Bounds)(ref bounds)).size, ((Component)val).transform.localScale));
					if (!flag)
					{
						flag = true;
						result = val2;
					}
					else
					{
						((Bounds)(ref result)).Encapsulate(val2);
					}
				}
			}
			SkinnedMeshRenderer[] componentsInChildren2 = root.GetComponentsInChildren<SkinnedMeshRenderer>(true);
			Bounds val4 = default(Bounds);
			foreach (SkinnedMeshRenderer val3 in componentsInChildren2)
			{
				if (!((Object)(object)val3.sharedMesh == (Object)null))
				{
					Bounds bounds2 = val3.sharedMesh.bounds;
					((Bounds)(ref val4))..ctor(((Bounds)(ref bounds2)).center + ((Component)val3).transform.localPosition, Vector3.Scale(((Bounds)(ref bounds2)).size, ((Component)val3).transform.localScale));
					if (!flag)
					{
						flag = true;
						result = val4;
					}
					else
					{
						((Bounds)(ref result)).Encapsulate(val4);
					}
				}
			}
			return result;
		}

		private static GameObject Nursery()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if ((Object)(object)_nursery == (Object)null)
			{
				_nursery = new GameObject("MoreFishAPI_Templates");
				Object.DontDestroyOnLoad((Object)(object)_nursery);
				_nursery.SetActive(false);
			}
			return _nursery;
		}

		private static bool Build(IDictionary items, Creature template)
		{
			List<FishDefinition> list = MoreFish.Ordered();
			if (list.Count == 0)
			{
				return false;
			}
			Dictionary<string, byte> dictionary = IdMap.Assign(list, items);
			if (dictionary.Count == 0)
			{
				Plugin.Log.LogError((object)"No item ids could be assigned, so no custom fish were added.");
				return false;
			}
			PrefabObjects prefabObjects = InstanceFinder.NetworkManager.GetPrefabObjects<SinglePrefabObjects>((ushort)41, true);
			if ((Object)(object)prefabObjects == (Object)null)
			{
				Plugin.Log.LogError((object)"FishNet has no prefab collection available, so custom fish cannot be made spawnable.");
				return false;
			}
			Dictionary<byte, GameObject> dictionary2 = new Dictionary<byte, GameObject>();
			List<string> list2 = new List<string>();
			foreach (FishDefinition item in list)
			{
				if (dictionary.TryGetValue(item.Id, out var value))
				{
					Creature val = TemplateFor(item, template);
					GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, Nursery().transform);
					((Object)val2).name = "MoreFish_" + item.Id;
					Item component = val2.GetComponent<Item>();
					Creature component2 = val2.GetComponent<Creature>();
					_itemId.SetValue(component, value);
					_itemWorth?.SetValue(component, item.Worth);
					_itemMesh?.SetValue(component, item.Mesh);
					_dripMesh?.SetValue(component2, item.Mesh);
					bool flag = item.InJournal && Plugin.AddToJournal.Value;
					_excludeFromJournal?.SetValue(component2, !flag);
					ApplyLook(val2, item, val);
					ApplyBehaviour(val2, item, component2, val);
					if ((Object)(object)val2.GetComponent<NetworkObject>() == (Object)null)
					{
						Plugin.Log.LogError((object)("Fish '" + item.Id + "' has no NetworkObject and cannot be spawned. Skipping it."));
						Object.Destroy((Object)(object)val2);
					}
					else
					{
						dictionary2[value] = val2;
						item.RuntimeItemId = value;
						list2.Add($"{item.Id}={value} on {((Object)val).name}");
					}
				}
			}
			if (dictionary2.Count == 0)
			{
				Plugin.Log.LogError((object)"No custom fish could be prepared, so nothing was added to the game.");
				return false;
			}
			if (!SeedCollection(prefabObjects, dictionary2))
			{
				prefabObjects.Clear();
				foreach (GameObject value2 in dictionary2.Values)
				{
					Object.Destroy((Object)(object)value2);
				}
				foreach (GameObject placeholder in _placeholders)
				{
					Object.Destroy((Object)(object)placeholder);
				}
				_placeholders.Clear();
				Plugin.Log.LogError((object)"Custom fish were not added, because they could not be registered safely for multiplayer.");
				return false;
			}
			Publish(items, dictionary2, list, dictionary);
			string text = IdMap.Checksum(dictionary);
			Plugin.Log.LogInfo((object)string.Format("Added {0} custom fish. Ids: {1}", dictionary2.Count, string.Join(", ", list2)));
			Plugin.Log.LogInfo((object)("Fish set checksum " + text + ". Everyone in a lobby must show the same checksum, or fish will not match."));
			return true;
		}

		private static bool SeedCollection(PrefabObjects collection, Dictionary<byte, GameObject> built)
		{
			if (collection.GetObjectCount() > 0)
			{
				Plugin.Log.LogWarning((object)$"FishNet collection {(ushort)41} already holds {collection.GetObjectCount()} prefab(s) that this mod did not add. Replacing them, which may break whatever put them there.");
			}
			collection.Clear();
			List<NetworkObject> list = new List<NetworkObject>(123);
			for (int i = 0; i < 123; i++)
			{
				byte b = (byte)(128 + i);
				if (built.TryGetValue(b, out var value))
				{
					list.Add(value.GetComponent<NetworkObject>());
				}
				else
				{
					list.Add(Placeholder(b).GetComponent<NetworkObject>());
				}
			}
			collection.AddObjects(list, false, true);
			for (int j = 0; j < list.Count; j++)
			{
				if ((Object)(object)list[j] == (Object)null || list[j].SpawnableCollectionId != 41 || list[j].PrefabId != j)
				{
					Plugin.Log.LogError((object)$"FishNet did not take ownership of slot {j}, so custom fish are not safe to spawn.");
					return false;
				}
			}
			return true;
		}

		private static GameObject Placeholder(byte id)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			GameObject val = new GameObject("MoreFish_Unused_" + id);
			val.transform.SetParent(Nursery().transform, false);
			val.AddComponent<NetworkObject>();
			_placeholders.Add(val);
			return val;
		}

		private static void Publish(IDictionary items, Dictionary<byte, GameObject> built, List<FishDefinition> ordered, Dictionary<string, byte> assigned)
		{
			IDictionary dictionary = _idToSpawnable?.GetValue(null) as IDictionary;
			IDictionary dictionary2 = _nameToSpawnable?.GetValue(null) as IDictionary;
			IList list = _allCreatures.GetValue(null) as IList;
			foreach (FishDefinition item in ordered)
			{
				if (!assigned.TryGetValue(item.Id, out var value) || !built.TryGetValue(value, out var value2))
				{
					continue;
				}
				Item component = value2.GetComponent<Item>();
				Creature component2 = value2.GetComponent<Creature>();
				items[value] = component;
				if (dictionary != null && !dictionary.Contains(value))
				{
					dictionary[value] = component;
				}
				if (dictionary2 != null)
				{
					string key = "morefish_" + item.Id.Replace(" ", "").ToLowerInvariant();
					if (!dictionary2.Contains(key))
					{
						dictionary2[key] = component;
					}
				}
				if (item.InJournal && Plugin.AddToJournal.Value && list != null && !list.Contains(component2))
				{
					list.Add(component2);
				}
				item.Live = true;
				_byRuntimeId[value] = item;
			}
			BaitTables.Inject(ordered, _allBaits, _baitWeights, _weightFishable, _weightValue, _fishableItem);
		}

		private static int LongestAxis(Vector3 size)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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_000e: 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)
			if (size.x >= size.y && size.x >= size.z)
			{
				return 0;
			}
			if (!(size.y >= size.z))
			{
				return 2;
			}
			return 1;
		}

		private static Vector3 AlignAxes(Bounds source, Bounds target)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			int num = LongestAxis(((Bounds)(ref source)).size);
			int num2 = LongestAxis(((Bounds)(ref target)).size);
			if (num == num2)
			{
				return Vector3.zero;
			}
			if (num == 1 && num2 == 0)
			{
				return new Vector3(0f, 0f, -90f);
			}
			if (num == 0 && num2 == 1)
			{
				return new Vector3(0f, 0f, 90f);
			}
			if (num == 1 && num2 == 2)
			{
				return new Vector3(90f, 0f, 0f);
			}
			if (num == 2 && num2 == 1)
			{
				return new Vector3(-90f, 0f, 0f);
			}
			if (num == 2 && num2 == 0)
			{
				return new Vector3(0f, 90f, 0f);
			}
			if (num == 0 && num2 == 2)
			{
				return new Vector3(0f, -90f, 0f);
			}
			return Vector3.zero;
		}

		private static Mesh Bake(Mesh source, float scale, Quaternion turn)
		{
			//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_0045: Expected O, but got Unknown
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: 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)
			if (!source.isReadable)
			{
				Plugin.Log.LogWarning((object)("Mesh '" + ((Object)source).name + "' is not marked Read/Write in its asset bundle, so its size and orientation cannot be corrected. Tick Read/Write when building the bundle."));
				return source;
			}
			Mesh val = new Mesh
			{
				name = ((Object)source).name + "_fitted"
			};
			Vector3[] vertices = source.vertices;
			for (int i = 0; i < vertices.Length; i++)
			{
				vertices[i] = turn * vertices[i] * scale;
			}
			val.vertices = vertices;
			Vector3[] normals = source.normals;
			if (normals != null && normals.Length == vertices.Length)
			{
				for (int j = 0; j < normals.Length; j++)
				{
					normals[j] = turn * normals[j];
				}
				val.normals = normals;
			}
			val.uv = source.uv;
			if (source.uv2 != null && source.uv2.Length == vertices.Length)
			{
				val.uv2 = source.uv2;
			}
			if (source.colors32 != null && source.colors32.Length == vertices.Length)
			{
				val.colors32 = source.colors32;
			}
			val.subMeshCount = source.subMeshCount;
			for (int k = 0; k < source.subMeshCount; k++)
			{
				val.SetTriangles(source.GetTriangles(k), k);
			}
			val.RecalculateBounds();
			if (normals == null || normals.Length != vertices.Length)
			{
				val.RecalculateNormals();
			}
			val.RecalculateTangents();
			Object.DontDestroyOnLoad((Object)(object)val);
			return val;
		}

		private static void Quiet(GameObject clone)
		{
			//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)
			object? obj = AccessTools.Field(typeof(Creature), "_enableOnShiny")?.GetValue(clone.GetComponent<Creature>());
			GameObject val = (GameObject)((obj is GameObject) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				val.SetActive(false);
			}
			Light[] componentsInChildren = clone.GetComponentsInChildren<Light>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				((Behaviour)componentsInChildren[i]).enabled = false;
			}
			ParticleSystem[] componentsInChildren2 = clone.GetComponentsInChildren<ParticleSystem>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				EmissionModule emission = componentsInChildren2[i].emission;
				((EmissionModule)(ref emission)).enabled = false;
			}
		}

		private static void SetFloat(object target, string field, float value)
		{
			if (!(value < 0f) && target != null)
			{
				AccessTools.Field(target.GetType(), field)?.SetValue(target, value);
			}
		}

		private static void SetInt(object target, string field, int value)
		{
			if (value >= 0 && target != null)
			{
				AccessTools.Field(target.GetType(), field)?.SetValue(target, value);
			}
		}

		private static void ApplyBehaviour(GameObject clone, FishDefinition fish, Creature creature, Creature basis)
		{
			SetFloat(creature, "_headPos", fish.HeadPosition);
			SetFloat(clone.GetComponent<Item>(), "_inventoryMeshScale", fish.PreviewScale);
			if (!fish.KeepTemplateSkin)
			{
				AccessTools.Field(typeof(Item), "_skinPreset")?.SetValue(clone.GetComponent<Item>(), null);
			}
			SetInt(creature, "_maxHp", fish.Health);
			SetInt(creature, "_fullnessToRestore", fish.FoodValue);
			SetInt(creature, "_hpToRestore", fish.HealValue);
			if (fish.Endangered.HasValue)
			{
				AccessTools.Field(typeof(Creature), "_isEndangered")?.SetValue(creature, fish.Endangered.Value);
			}
			Fish component = clone.GetComponent<Fish>();
			if ((Object)(object)component != (Object)null)
			{
				SetFloat(component, "_minTime", fish.FlopIntervalMin);
				SetFloat(component, "_maxTime", fish.FlopIntervalMax);
				SetFloat(component, "_rotForce", fish.FlopStrength);
				SetFloat(component, "_towardsWaterForce", fish.SinkForce);
			}
			AttackingFish component2 = clone.GetComponent<AttackingFish>();
			if (fish.Hostile.HasValue && fish.Hostile.Value && (Object)(object)component2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)("Fish '" + fish.Id + "' asked to be hostile, but '" + ((Object)basis).name + "' is not an attacking creature. Hostility is built into the game's own creature and cannot be added, so pick a template that already attacks."));
			}
			if (fish.Hostile.HasValue && !fish.Hostile.Value && (Object)(object)component2 != (Object)null)
			{
				SetFloat(component2, "_towardsPlayerForce", 0f);
				SetFloat(component2, "_airTowardsPlayerForce", 0f);
				SetFloat(component2, "_underwaterAttackForceMulti", 0f);
				AccessTools.Field(typeof(AttackingFish), "_timeBetweenDamage")?.SetValue(component2, 999999f);
				Plugin.Log.LogInfo((object)("Fish '" + fish.Id + "' built on the attacking creature '" + ((Object)basis).name + "' but was made harmless."));
			}
		}

		private static void AdoptRenderers(GameObject clone, GameObject visual, bool keepSkin)
		{
			List<Renderer> list = new List<Renderer>(visual.GetComponentsInChildren<Renderer>(true));
			if (list.Count != 0)
			{
				Item component = clone.GetComponent<Item>();
				if (AccessTools.Field(typeof(Item), "_renderers")?.GetValue(component) is List<Renderer> list2)
				{
					list2.Clear();
					list2.AddRange(list);
				}
				if (keepSkin && AccessTools.Field(typeof(Item), "_skinRenderers")?.GetValue(component) is List<Renderer> list3)
				{
					list3.Clear();
					list3.AddRange(list);
				}
			}
		}

		private static void GraftPrefab(GameObject clone, FishDefinition fish, Creature basis)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: 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_00e1: Unknown result type (might be due to invalid IL or missing references)
			MeshFilter[] componentsInChildren = clone.GetComponentsInChildren<MeshFilter>(true);
			SkinnedMeshRenderer[] componentsInChildren2 = clone.GetComponentsInChildren<SkinnedMeshRenderer>(true);
			Transform val = ((componentsInChildren.Length != 0) ? ((Component)componentsInChildren[0]).transform : ((componentsInChildren2.Length != 0) ? ((Component)componentsInChildren2[0]).transform : clone.transform));
			MeshFilter[] array = componentsInChildren;
			for (int i = 0; i < array.Length; i++)
			{
				Renderer component = ((Component)array[i]).GetComponent<Renderer>();
				if ((Object)(object)component != (Object)null)
				{
					component.enabled = false;
				}
			}
			SkinnedMeshRenderer[] array2 = componentsInChildren2;
			for (int i = 0; i < array2.Length; i++)
			{
				((Renderer)array2[i]).enabled = false;
			}
			GameObject val2 = Object.Instantiate<GameObject>(fish.Prefab, val);
			((Object)val2).name = "MoreFish_Visual";
			val2.transform.localPosition = Vector3.zero;
			val2.transform.localRotation = Quaternion.Euler(fish.MeshRotation);
			val2.transform.localScale = Vector3.one * Mathf.Max(0.0001f, fish.MeshScale);
			List<string> list = new List<string>();
			Behaviour[] componentsInChildren3 = val2.GetComponentsInChildren<Behaviour>(true);
			foreach (Behaviour val3 in componentsInChildren3)
			{
				if (!((Object)(object)val3 == (Object)null) && !(val3 is Animator) && !(val3 is Animation))
				{
					val3.enabled = false;
					list.Add(((object)val3).GetType().Name);
				}
			}
			if (list.Count > 0)
			{
				Plugin.Log.LogInfo((object)string.Format("'{0}': disabled {1} component(s) on the supplied prefab: {2}. Animators are left running.", fish.Id, list.Count, string.Join(", ", list)));
			}
			Collider[] componentsInChildren4 = val2.GetComponentsInChildren<Collider>(true);
			for (int i = 0; i < componentsInChildren4.Length; i++)
			{
				Object.Destroy((Object)(object)componentsInChildren4[i]);
			}
			AdoptRenderers(clone, val2, fish.KeepTemplateSkin);
			MeshFilter componentInChildren = val2.GetComponentInChildren<MeshFilter>(true);
			if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.sharedMesh != (Object)null)
			{
				_itemMesh?.SetValue(clone.GetComponent<Item>(), componentInChildren.sharedMesh);
				_dripMesh?.SetValue(clone.GetComponent<Creature>(), componentInChildren.sharedMesh);
				Renderer component2 = ((Component)componentInChildren).GetComponent<Renderer>();
				if ((Object)(object)component2 != (Object)null && (Object)(object)component2.sharedMaterial != (Object)null)
				{
					fish.RuntimeMaterial = component2.sharedMaterial;
				}
			}
			Plugin.Log.LogInfo((object)("'" + fish.Id + "': grafted prefab '" + ((Object)fish.Prefab).name + "' onto template '" + ((Object)basis).name + "' exactly as authored, no auto fitting."));
		}

		private static void ApplyLook(GameObject clone, FishDefinition fish, Creature basis)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: 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_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: 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_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Expected O, but got Unknown
			if ((Object)(object)fish.Prefab != (Object)null)
			{
				GraftPrefab(clone, fish, basis);
				if (!fish.AllowDrip)
				{
					Quiet(clone);
				}
				return;
			}
			MeshFilter[] componentsInChildren = clone.GetComponentsInChildren<MeshFilter>(true);
			if (componentsInChildren.Length == 0)
			{
				Plugin.Log.LogError((object)("Fish '" + fish.Id + "' supplied a Mesh, but template '" + ((Object)basis).name + "' is built from a skinned mesh and cannot take one. Supply a Prefab instead, or pick a different Template."));
				return;
			}
			_ = componentsInChildren[0].sharedMesh;
			Bounds target = CombinedBounds(clone);
			float num = 1f;
			Vector3 val = Vector3.zero;
			Bounds bounds;
			if (fish.AutoFit)
			{
				Vector3 size = ((Bounds)(ref target)).size;
				if (((Vector3)(ref size)).magnitude > 0.0001f)
				{
					bounds = fish.Mesh.bounds;
					size = ((Bounds)(ref bounds)).size;
					float magnitude = ((Vector3)(ref size)).magnitude;
					if (magnitude > 0.0001f)
					{
						size = ((Bounds)(ref target)).size;
						num = ((Vector3)(ref size)).magnitude / magnitude;
					}
					val = AlignAxes(fish.Mesh.bounds, target);
				}
			}
			Quaternion turn = Quaternion.Euler(val) * Quaternion.Euler(fish.MeshRotation);
			Mesh val2 = Bake(fish.Mesh, num * fish.MeshScale, turn);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if (i == 0)
				{
					componentsInChildren[i].sharedMesh = val2;
					continue;
				}
				Renderer component = ((Component)componentsInChildren[i]).GetComponent<Renderer>();
				if ((Object)(object)component != (Object)null)
				{
					component.enabled = false;
				}
			}
			_itemMesh?.SetValue(clone.GetComponent<Item>(), val2);
			_dripMesh?.SetValue(clone.GetComponent<Creature>(), val2);
			if (!fish.AllowDrip)
			{
				Quiet(clone);
			}
			ManualLogSource log = Plugin.Log;
			object[] obj = new object[6] { fish.Id, null, null, null, null, null };
			bounds = fish.Mesh.bounds;
			obj[1] = ((Bounds)(ref bounds)).size;
			obj[2] = ((Bounds)(ref target)).size;
			obj[3] = num;
			obj[4] = val;
			bounds = val2.bounds;
			obj[5] = ((Bounds)(ref bounds)).size;
			log.LogInfo((object)string.Format("'{0}': mesh {1} vs whole template {2}, fitted by {3:F3}, turned by {4}, final size {5}.", obj));
			if ((Object)(object)fish.Material != (Object)null)
			{
				fish.RuntimeMaterial = fish.Material;
			}
			if ((Object)(object)fish.Material == (Object)null && (Object)(object)fish.Texture == (Object)null)
			{
				return;
			}
			Renderer[] componentsInChildren2 = clone.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val3 in componentsInChildren2)
			{
				if ((Object)(object)val3 == (Object)null || val3.sharedMaterials.Length == 0)
				{
					continue;
				}
				Material[] array = (Material[])(object)new Material[val3.sharedMaterials.Length];
				for (int k = 0; k < array.Length; k++)
				{
					if ((Object)(object)fish.Material != (Object)null)
					{
						array[k] = fish.Material;
						continue;
					}
					Material val4 = val3.sharedMaterials[k];
					if ((Object)(object)val4 == (Object)null)
					{
						continue;
					}
					Material val5 = new Material(val4);
					string[] array2 = new string[3] { "_BaseMap", "_MainTex", "_BaseColorMap" };
					foreach (string text in array2)
					{
						if (val5.HasProperty(text))
						{
							val5.SetTexture(text, fish.Texture);
						}
					}
					if (val5.HasProperty("_Use_Skin"))
					{
						val5.SetInt("_Use_Skin", 0);
					}
					if (val5.HasProperty("_Rainbow_Skin"))
					{
						val5.SetInt("_Rainbow_Skin", 0);
					}
					Object.DontDestroyOnLoad((Object)(object)val5);
					array[k] = val5;
				}
				val3.sharedMaterials = array;
				if ((Object)(object)fish.RuntimeMaterial == (Object)null && array.Length != 0)
				{
					fish.RuntimeMaterial = array[0];
				}
			}
		}
	}
	[BepInPlugin("dazed.howtofish.morefishapi", "MoreFishAPI", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "dazed.howtofish.morefishapi";

		public const string Name = "MoreFishAPI";

		public const string Version = "1.0.0";

		public static ManualLogSource Log = Logger.CreateLogSource("MoreFishAPI");

		public static ConfigEntry<bool> AddToJournal;

		public static ConfigEntry<float> RarityMultiplier;

		public static ConfigEntry<string> TemplateFish;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			AddToJournal = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AddToJournal", true, "List custom fish in the journal alongside the game's own catches, so they count toward what you have seen. Turn this off to keep the journal purely vanilla.");
			RarityMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RarityMultiplier", 1f, "Scales how often custom fish are caught compared to what their author asked for. Below 1 makes them rarer, above 1 more common. 0 stops them appearing without unregistering them.");
			TemplateFish = ((BaseUnityPlugin)this).Config.Bind<string>("General", "TemplateFish", "Cod", "Which of the game's own fish a custom fish is built from when it does not name its own. This decides body shape, physics and how it behaves on the line. Every valid name is listed in the log at startup. Leave blank to let the mod pick one.");
			if (!Injector.Resolve())
			{
				Log.LogError((object)"Aborting: the game's item lists do not look the way this mod expects.");
				((Behaviour)this).enabled = false;
			}
			else
			{
				_harmony = new Harmony("dazed.howtofish.morefishapi");
				Patches.ApplyAll(_harmony);
				Log.LogInfo((object)"MoreFishAPI 1.0.0 loaded. Modders: call MoreFishAPI.MoreFish.Register to add a fish.");
			}
		}

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

		private void Update()
		{
			Injector.TryBuild();
			if (Injector.AlreadyBuilt)
			{
				((Behaviour)this).enabled = false;
			}
		}
	}
	internal static class Patches
	{
		private static readonly Dictionary<Renderer, Material> _painted = new Dictionary<Renderer, Material>();

		private static int _pruneAt = 64;

		internal static void ApplyAll(Harmony harmony)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Expected O, but got Unknown
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(Item), "GetName", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Patches), "GetNamePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				Plugin.Log.LogWarning((object)"Could not hook item naming, so custom fish will show the template fish's name.");
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayerInventory), "LoadFromSave", (Type[])null, (Type[])null);
			if (methodInfo2 != null)
			{
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Patches), "LoadFromSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				Plugin.Log.LogWarning((object)"Could not hook inventory loading. A save holding a fish this game does not have may lose the rest of that inventory.");
			}
			MethodInfo methodInfo3 = AccessTools.Method(typeof(Creature), "SetDrip", (Type[])null, (Type[])null);
			if (methodInfo3 != null)
			{
				harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(Patches), "SetDripPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			MethodInfo methodInfo4 = AccessTools.Method(typeof(CreatureManager), "GetRandomItem", (Type[])null, (Type[])null);
			if (methodInfo4 != null)
			{
				harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(Patches), "GetRandomItemPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				Plugin.Log.LogWarning((object)"Could not hook catch selection, so MoreFish.ForceNextCatch will not work.");
			}
			MethodInfo methodInfo5 = AccessTools.Method(typeof(InventorySlot), "ApplySkin", (Type[])null, (Type[])null);
			if (methodInfo5 != null)
			{
				harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "InventorySlotSkinPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			MethodInfo methodInfo6 = AccessTools.Method(typeof(JournalSlot), "SetCreature", (Type[])null, (Type[])null);
			if (methodInfo6 != null)
			{
				harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "JournalSlotPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			Plugin.Log.LogInfo((object)"Hooked item naming, inventory loading, the shiny roll, catch selection and item previews.");
		}

		private static void LoadFromSavePrefix(PlayerInventory __instance)
		{
			try
			{
				object? obj = AccessTools.Field(typeof(PlayerInventory), "_toLoadFrom")?.GetValue(__instance);
				SavedPlayer val = (SavedPlayer)((obj is SavedPlayer) ? obj : null);
				if (val != null)
				{
					int num = Strip(val.InventoryItems);
					if (val.HeldItem != null && val.HeldItem.Exists && !Known(val.HeldItem.ItemID))
					{
						val.HeldItem.Exists = false;
						num++;
					}
					if (num > 0)
					{
						Plugin.Log.LogWarning((object)$"This save holds {num} item(s) from a fish mod that is not installed. They have been skipped so the rest of the inventory still loads.");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not check the saved inventory: " + ex));
			}
		}

		private static int Strip(List<SavedItem> items)
		{
			if (items == null)
			{
				return 0;
			}
			int num = 0;
			for (int num2 = items.Count - 1; num2 >= 0; num2--)
			{
				if (items[num2] != null && items[num2].Exists && !Known(items[num2].ItemID))
				{
					items.RemoveAt(num2);
					num++;
				}
			}
			return num;
		}

		private static bool Known(byte id)
		{
			return (Object)(object)GameInfo.GetSpawnable(id) != (Object)null;
		}

		private static void PaintPreview(object slot, string rendererField, Item item)
		{
			object? obj = AccessTools.Field(slot.GetType(), rendererField)?.GetValue(slot);
			Renderer val = (Renderer)((obj is Renderer) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				object? obj2 = AccessTools.Field(slot.GetType(), "_filter")?.GetValue(slot);
				MeshFilter val2 = (MeshFilter)((obj2 is MeshFilter) ? obj2 : null);
				val = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<Renderer>() : null);
			}
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			FishDefinition fishDefinition = Injector.FromItem(item);
			Material value;
			if ((Object)(object)fishDefinition?.RuntimeMaterial == (Object)null)
			{
				_painted.Remove(val);
			}
			else if (!_painted.TryGetValue(val, out value) || !((Object)(object)value == (Object)(object)fishDefinition.RuntimeMaterial))
			{
				val.sharedMaterial = fishDefinition.RuntimeMaterial;
				_painted[val] = fishDefinition.RuntimeMaterial;
				if (_painted.Count > _pruneAt)
				{
					Prune();
				}
			}
		}

		private static void Prune()
		{
			List<Renderer> list = new List<Renderer>();
			foreach (KeyValuePair<Renderer, Material> item in _painted)
			{
				if ((Object)(object)item.Key == (Object)null)
				{
					list.Add(item.Key);
				}
			}
			foreach (Renderer item2 in list)
			{
				_painted.Remove(item2);
			}
			_pruneAt = Mathf.Max(64, _painted.Count * 2);
		}

		private static void InventorySlotSkinPostfix(InventorySlot __instance, Item item)
		{
			PaintPreview(__instance, "_renderer", item);
		}

		private static void JournalSlotPostfix(JournalSlot __instance, Creature creature)
		{
			PaintPreview(__instance, "_renderer", (Item)(object)creature);
		}

		private static bool GetRandomItemPrefix(ref Fishable __result)
		{
			if (MoreFish.Forced == null)
			{
				return true;
			}
			Fishable val = BaitTables.SampleFor(MoreFish.Forced);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)("'" + MoreFish.Forced + "' was forced but has no catch entry, so the game will pick normally."));
				MoreFish.Forced = null;
				return true;
			}
			Plugin.Log.LogInfo((object)("Forcing the catch to '" + MoreFish.Forced + "'."));
			MoreFish.Forced = null;
			__result = val;
			return false;
		}

		private static bool SetDripPrefix(Creature __instance)
		{
			return Injector.FromItem((Item)(object)__instance)?.AllowDrip ?? true;
		}

		private static bool GetNamePrefix(Item __instance, ref string __result)
		{
			FishDefinition fishDefinition = Injector.FromItem(__instance);
			if (fishDefinition == null)
			{
				return true;
			}
			__result = fishDefinition.DisplayName;
			return false;
		}
	}
}