Decompiled source of Growbed v1.1.2

plugins/Growbed.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Jotunn;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Growbed")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.2.0")]
[assembly: AssemblyInformationalVersion("1.1.2+e6079e67199dba7fb242b71edab3cc3940d217bd")]
[assembly: AssemblyProduct("Growbed")]
[assembly: AssemblyTitle("Growbed")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.2.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 GrowbedMod
{
	internal static class CropVisuals
	{
		internal class Template
		{
			public GameObject Root;

			public string Source;

			public int Renderers;

			public float Radius;

			public float Height;
		}

		private static readonly Dictionary<string, Template> Templates = new Dictionary<string, Template>();

		private static GameObject s_holder;

		public static void Reset()
		{
			Templates.Clear();
			if ((Object)(object)s_holder != (Object)null)
			{
				Object.Destroy((Object)(object)s_holder);
				s_holder = null;
			}
		}

		public static float ScaleFor(Template template, CropInfo crop, int stage, float slotSpacing)
		{
			return Mathf.Clamp(GrowbedConfig.PlantScale?.Value ?? 1f, 0.1f, 3f);
		}

		public static Template For(CropInfo crop, int stage)
		{
			if (crop == null)
			{
				return null;
			}
			string text = ((stage >= 4) ? crop.MaturePrefab : crop.YoungPrefab);
			bool flag = crop.Perennial && stage < 4;
			if (string.IsNullOrEmpty(text))
			{
				text = crop.MaturePrefab;
			}
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			string key = (flag ? (text + "|bare") : text);
			if (Templates.TryGetValue(key, out var value))
			{
				return value;
			}
			Template template;
			try
			{
				template = Build(text, flag, crop.CropPrefab);
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("CropVisuals: could not copy '" + text + "' (" + ex.Message + "); that crop falls back to the generic bed mesh."));
				template = null;
			}
			Templates[key] = template;
			return template;
		}

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

		private static Template Build(string prefabName, bool bare = false, string fruit = null)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_0076: 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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabName) : null);
			if ((Object)(object)val == (Object)null)
			{
				Logger.LogWarning((object)("CropVisuals: no prefab '" + prefabName + "'; that crop falls back to the generic bed mesh."));
				return null;
			}
			GameObject val2 = new GameObject(prefabName + "_visual");
			val2.transform.SetParent(Holder().transform, false);
			GameObject val3 = Object.Instantiate<GameObject>(val, val2.transform);
			val3.transform.localPosition = Vector3.zero;
			val3.transform.localRotation = Quaternion.identity;
			val3.transform.localScale = val.transform.localScale;
			GameObject hideWhenPicked = val3.GetComponent<Pickable>()?.m_hideWhenPicked;
			FlattenLods(val3);
			Strip(val3);
			if (bare)
			{
				ShowPickedVariant(val3, fruit, hideWhenPicked);
			}
			if (DrawnRenderers(val3).Count == 0)
			{
				Logger.LogWarning((object)("CropVisuals: '" + prefabName + "' has no renderers to borrow; that crop falls back to the generic bed mesh."));
				Object.Destroy((Object)(object)val2);
				return null;
			}
			Bounds? val4 = LocalBounds(val3);
			float radius = 0f;
			float height = 0f;
			if (val4.HasValue)
			{
				Bounds value = val4.Value;
				if (((Bounds)(ref value)).min.y > 0.02f)
				{
					val3.transform.localPosition = new Vector3(0f, 0f - ((Bounds)(ref value)).min.y, 0f);
				}
				Vector2 val5 = new Vector2(((Bounds)(ref value)).min.x, ((Bounds)(ref value)).min.z);
				float magnitude = ((Vector2)(ref val5)).magnitude;
				val5 = new Vector2(((Bounds)(ref value)).max.x, ((Bounds)(ref value)).max.z);
				radius = Mathf.Max(magnitude, ((Vector2)(ref val5)).magnitude);
				height = Mathf.Max(0f, ((Bounds)(ref value)).max.y);
			}
			return new Template
			{
				Root = val2,
				Source = prefabName,
				Renderers = DrawnRenderers(val3).Count,
				Radius = radius,
				Height = height
			};
		}

		private static void Strip(GameObject root)
		{
			for (int i = 0; i < 4; i++)
			{
				bool flag = false;
				Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
				foreach (Component val in componentsInChildren)
				{
					if (!((Object)(object)val == (Object)null) && !(val is Transform) && !Keeps(val))
					{
						try
						{
							Object.DestroyImmediate((Object)(object)val);
							flag = true;
						}
						catch (Exception)
						{
						}
					}
				}
				if (!flag)
				{
					break;
				}
			}
		}

		private static void ShowPickedVariant(GameObject root, string fruit, GameObject hideWhenPicked)
		{
			if (HideVanillaFruit(root, hideWhenPicked))
			{
				Logger.LogInfo((object)("CropVisuals: '" + ((Object)root).name + "' unripe form hides its Pickable's '" + ((Object)hideWhenPicked).name + "'."));
			}
			else if (SwapWholeModel(root))
			{
				Logger.LogInfo((object)("CropVisuals: '" + ((Object)root).name + "' unripe form is its own picked model."));
			}
			else
			{
				int num = HideFruitMeshes(root, fruit);
				if (num > 0)
				{
					Logger.LogInfo((object)($"CropVisuals: '{((Object)root).name}' unripe form hides {num} " + "'" + fruit + "' mesh(es)."));
				}
				else
				{
					Logger.LogInfo((object)("CropVisuals: '" + ((Object)root).name + "' has no separate ripe form; it looks the same at every stage."));
				}
			}
		}

		private static bool HideVanillaFruit(GameObject root, GameObject hideWhenPicked)
		{
			if ((Object)(object)hideWhenPicked == (Object)null || (Object)(object)hideWhenPicked == (Object)(object)root)
			{
				return false;
			}
			int num = hideWhenPicked.GetComponentsInChildren<Renderer>(true).Length;
			if (num == 0 || num >= DrawnRenderers(root).Count)
			{
				return false;
			}
			hideWhenPicked.SetActive(false);
			return true;
		}

		private static bool SwapWholeModel(GameObject root)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			List<Transform> list = new List<Transform>();
			Transform[] componentsInChildren = root.GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				if (((Object)val).name.IndexOf("picked", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(val);
				}
			}
			foreach (Transform item in list)
			{
				if ((Object)(object)item.parent != (Object)null)
				{
					foreach (Transform item2 in item.parent)
					{
						Transform val2 = item2;
						if ((Object)(object)val2 != (Object)(object)item && (Object)(object)((Component)val2).GetComponentInChildren<Renderer>(true) != (Object)null)
						{
							((Component)val2).gameObject.SetActive(false);
						}
					}
				}
				((Component)item).gameObject.SetActive(true);
			}
			return list.Count > 0;
		}

		private static int HideFruitMeshes(GameObject root, string fruit)
		{
			if (string.IsNullOrEmpty(fruit) || fruit.Length < 5)
			{
				return 0;
			}
			string value = fruit.Substring(0, 5).ToLowerInvariant();
			List<Renderer> list = DrawnRenderers(root);
			List<Renderer> list2 = new List<Renderer>();
			foreach (Renderer item in list)
			{
				Mesh val = MeshOf(item);
				if ((((Object)(object)val != (Object)null) ? ((Object)val).name : string.Empty).ToLowerInvariant().StartsWith(value) || ((Object)item).name.ToLowerInvariant().StartsWith(value))
				{
					list2.Add(item);
				}
			}
			if (list2.Count == 0 || list2.Count == list.Count)
			{
				return 0;
			}
			foreach (Renderer item2 in list2)
			{
				((Component)item2).gameObject.SetActive(false);
			}
			return list2.Count;
		}

		private static bool Keeps(Component component)
		{
			if (!(component is MeshFilter) && !(component is MeshRenderer))
			{
				return component is SkinnedMeshRenderer;
			}
			return true;
		}

		private static void FlattenLods(GameObject root)
		{
			LODGroup[] componentsInChildren = root.GetComponentsInChildren<LODGroup>(true);
			foreach (LODGroup val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				LOD[] lODs = val.GetLODs();
				HashSet<Renderer> hashSet = new HashSet<Renderer>();
				if (lODs.Length != 0 && lODs[0].renderers != null)
				{
					Renderer[] renderers = lODs[0].renderers;
					foreach (Renderer val2 in renderers)
					{
						if ((Object)(object)val2 != (Object)null)
						{
							hashSet.Add(val2);
						}
					}
				}
				for (int k = 1; k < lODs.Length; k++)
				{
					if (lODs[k].renderers == null)
					{
						continue;
					}
					Renderer[] renderers = lODs[k].renderers;
					foreach (Renderer val3 in renderers)
					{
						if ((Object)(object)val3 != (Object)null && !hashSet.Contains(val3))
						{
							Object.DestroyImmediate((Object)(object)val3);
						}
					}
				}
				Object.DestroyImmediate((Object)(object)val);
			}
		}

		private static List<Renderer> DrawnRenderers(GameObject root)
		{
			List<Renderer> list = new List<Renderer>();
			Renderer[] componentsInChildren = root.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null || !val.enabled)
				{
					continue;
				}
				bool flag = false;
				Transform val2 = ((Component)val).transform;
				while ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)root.transform.parent)
				{
					if (!((Component)val2).gameObject.activeSelf)
					{
						flag = true;
						break;
					}
					val2 = val2.parent;
				}
				if (!flag)
				{
					list.Add(val);
				}
			}
			return list;
		}

		private static Bounds? LocalBounds(GameObject root)
		{
			//IL_0002: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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)
			//IL_00c3: 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)
			Bounds value = default(Bounds);
			bool flag = false;
			Bounds val5 = default(Bounds);
			foreach (Renderer item in DrawnRenderers(root))
			{
				Mesh val = MeshOf(item);
				if (!((Object)(object)val == (Object)null))
				{
					Bounds bounds = val.bounds;
					Matrix4x4 val2 = root.transform.worldToLocalMatrix * ((Component)item).transform.localToWorldMatrix;
					Vector3 val3 = ((Matrix4x4)(ref val2)).MultiplyPoint3x4(((Bounds)(ref bounds)).center);
					Vector3 val4 = ((Matrix4x4)(ref val2)).MultiplyVector(((Bounds)(ref bounds)).extents);
					((Vector3)(ref val4))..ctor(Mathf.Abs(val4.x), Mathf.Abs(val4.y), Mathf.Abs(val4.z));
					((Bounds)(ref val5))..ctor(val3, val4 * 2f);
					if (flag)
					{
						((Bounds)(ref value)).Encapsulate(val5);
						continue;
					}
					value = val5;
					flag = true;
				}
			}
			if (!flag)
			{
				return null;
			}
			return value;
		}

		private static Mesh MeshOf(Renderer renderer)
		{
			SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null);
			if (val != null)
			{
				return val.sharedMesh;
			}
			MeshFilter component = ((Component)renderer).GetComponent<MeshFilter>();
			if (!((Object)(object)component != (Object)null))
			{
				return null;
			}
			return component.sharedMesh;
		}
	}
	internal class Growbed : MonoBehaviour, Hoverable, Interactable
	{
		private const string ZdoSeed = "growbed_seed";

		private const string ZdoPlantedAt = "growbed_planted";

		private const string ZdoSlotsUsed = "growbed_slots";

		private const string RpcPlant = "Growbed_Plant";

		private const string RpcHarvest = "Growbed_Harvest";

		private const string RpcAddBushes = "Growbed_AddBushes";

		private const string RpcFx = "Growbed_Fx";

		private const int FxPlant = 0;

		private const int FxHarvest = 1;

		public const int MatureStage = 4;

		public string TierPrefabName;

		[Header("Effects (optional — reuses vanilla cues when assigned)")]
		public EffectList m_plantEffects = new EffectList();

		public EffectList m_harvestEffects = new EffectList();

		[Header("Hover text")]
		[Tooltip("Metres above the piece origin to float the hover text.")]
		public float m_hoverOffset = 0.8f;

		[Header("Where crops appear")]
		[Tooltip("Origin for spawned harvest drops. Defaults to the piece transform.")]
		public Transform m_dropPoint;

		private ZNetView m_nview;

		private GrowbedTier m_tier;

		private readonly List<GameObject> m_stageVisuals = new List<GameObject>();

		private readonly List<List<GameObject>> m_slotVisuals = new List<List<GameObject>>();

		private readonly List<Transform> m_slotAnchors = new List<Transform>();

		private readonly List<GameObject> m_livePlants = new List<GameObject>();

		private float m_slotSpacing;

		private int m_lastAppliedStage = -1;

		private int m_lastAppliedSlots = -1;

		private string m_lastAppliedSeed;

		private bool m_ready;

		private float m_nextVisualCheck;

		private string m_plantMenu;

		private float m_plantMenuExpiry;

		private const int MaxHotbarOptions = 3;

		private static string s_selectedSeed;

		private static readonly HashSet<string> LoggedVisuals = new HashSet<string>();

		private float SlotSpacing
		{
			get
			{
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				if (m_slotSpacing > 0f)
				{
					return m_slotSpacing;
				}
				m_slotSpacing = Mathf.Min(m_tier.FootprintX, m_tier.FootprintZ);
				for (int i = 0; i < m_slotAnchors.Count; i++)
				{
					for (int j = i + 1; j < m_slotAnchors.Count; j++)
					{
						float num = Vector3.Distance(m_slotAnchors[i].localPosition, m_slotAnchors[j].localPosition);
						if (num > 0.01f && num < m_slotSpacing)
						{
							m_slotSpacing = num;
						}
					}
				}
				return m_slotSpacing;
			}
		}

		private string SeedPrefab
		{
			get
			{
				if (!m_nview.IsValid())
				{
					return string.Empty;
				}
				return m_nview.GetZDO().GetString("growbed_seed", string.Empty);
			}
		}

		private int SlotsUsed
		{
			get
			{
				if (!m_nview.IsValid())
				{
					return 0;
				}
				return m_nview.GetZDO().GetInt("growbed_slots", 0);
			}
		}

		private long PlantedAt
		{
			get
			{
				if (!m_nview.IsValid())
				{
					return 0L;
				}
				return m_nview.GetZDO().GetLong("growbed_planted", 0L);
			}
		}

		public bool IsEmpty
		{
			get
			{
				if (!string.IsNullOrEmpty(SeedPrefab))
				{
					return SlotsUsed <= 0;
				}
				return true;
			}
		}

		private CropInfo Crop => SeedRegistry.ByPrefab(SeedPrefab);

		private bool HoldsBushes => Crop?.Perennial ?? false;

		private bool HasRoomForBushes
		{
			get
			{
				if (HoldsBushes)
				{
					return SlotsUsed < m_tier.Slots;
				}
				return false;
			}
		}

		private static double Now
		{
			get
			{
				if (!((Object)(object)ZNet.instance != (Object)null))
				{
					return 0.0;
				}
				return ZNet.instance.GetTimeSeconds();
			}
		}

		private float GrowTime => GrowTimeFor(Crop);

		public bool IsMature => GetStage() >= 4;

		private float SecondsRemaining => Mathf.Max(0f, GrowTime - (float)(Now - (double)PlantedAt));

		private void Awake()
		{
			m_nview = ((Component)this).GetComponent<ZNetView>();
			m_tier = GrowbedPlugin.TierFor(TierPrefabName ?? ((Object)((Component)this).gameObject).name);
			if (m_tier == null)
			{
				Logger.LogError((object)("Growbed on '" + ((Object)((Component)this).gameObject).name + "' has no matching tier row."));
				return;
			}
			CollectStageVisuals();
			BuildSlotAnchors();
			if (!((Object)(object)m_nview == (Object)null) && m_nview.IsValid())
			{
				m_ready = true;
				m_nview.Register<string, int>("Growbed_Plant", (Action<long, string, int>)RPC_Plant);
				m_nview.Register<string, int>("Growbed_AddBushes", (Action<long, string, int>)RPC_AddBushes);
				m_nview.Register<int>("Growbed_Harvest", (Action<long, int>)RPC_Harvest);
				m_nview.Register<int>("Growbed_Fx", (Action<long, int>)RPC_Fx);
				ApplyVisuals(GetStage(), force: true);
			}
		}

		private void CollectStageVisuals()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			m_stageVisuals.Clear();
			m_slotVisuals.Clear();
			for (int i = 1; i <= 4; i++)
			{
				Transform val = FindDeepChild(((Component)this).transform, $"stage_{i}");
				m_stageVisuals.Add(((Object)(object)val != (Object)null) ? ((Component)val).gameObject : null);
				List<GameObject> list = new List<GameObject>();
				if ((Object)(object)val != (Object)null)
				{
					Dictionary<int, GameObject> dictionary = new Dictionary<int, GameObject>();
					foreach (Transform item in val)
					{
						Transform val2 = item;
						int num = SlotIndex(((Object)val2).name);
						if (num >= 0 && !dictionary.ContainsKey(num))
						{
							dictionary[num] = ((Component)val2).gameObject;
						}
					}
					GameObject value;
					for (int j = 0; dictionary.TryGetValue(j, out value); j++)
					{
						list.Add(value);
					}
					if (dictionary.Count > list.Count)
					{
						Logger.LogWarning((object)($"{((Object)((Component)this).gameObject).name} stage_{i}: slot indices are not " + $"contiguous from 0, so {dictionary.Count - list.Count} slot(s) " + "will never be drawn."));
					}
				}
				m_slotVisuals.Add(list);
			}
		}

		private void BuildSlotAnchors()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			m_slotAnchors.Clear();
			int num = -1;
			for (int i = 0; i < m_slotVisuals.Count; i++)
			{
				if (num < 0 || m_slotVisuals[i].Count > m_slotVisuals[num].Count)
				{
					num = i;
				}
			}
			if (num < 0 || m_slotVisuals[num].Count == 0)
			{
				return;
			}
			GameObject val = new GameObject("growbed_slots");
			val.transform.SetParent(((Component)this).transform, false);
			foreach (GameObject item in m_slotVisuals[num])
			{
				GameObject val2 = new GameObject(((Object)item).name + "_anchor");
				val2.transform.SetParent(val.transform, false);
				val2.transform.localPosition = ((Component)this).transform.InverseTransformPoint(item.transform.position);
				m_slotAnchors.Add(val2.transform);
			}
		}

		private void OnDestroy()
		{
			ClearLivePlants();
		}

		private void ClearLivePlants()
		{
			foreach (GameObject livePlant in m_livePlants)
			{
				if ((Object)(object)livePlant != (Object)null)
				{
					Object.Destroy((Object)(object)livePlant);
				}
			}
			m_livePlants.Clear();
		}

		private static int SlotIndex(string name)
		{
			if (name == null || !name.StartsWith("slot_", StringComparison.Ordinal))
			{
				return -1;
			}
			string text = name.Substring("slot_".Length);
			int num = text.IndexOf('.');
			if (num >= 0)
			{
				text = text.Substring(0, num);
			}
			if (!int.TryParse(text, out var result))
			{
				return -1;
			}
			return result;
		}

		private static Transform FindDeepChild(Transform root, string name)
		{
			if (((Object)root).name == name)
			{
				return root;
			}
			for (int i = 0; i < root.childCount; i++)
			{
				Transform val = FindDeepChild(root.GetChild(i), name);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		private float GrowTimeFor(CropInfo crop)
		{
			float num = crop?.BaseGrowTime ?? 3000f;
			float num2 = ((crop != null && crop.Perennial) ? GrowbedConfig.BushGrowTimeMultiplier.Value : GrowbedConfig.GlobalGrowTimeMultiplier.Value);
			return Mathf.Max(1f, num * m_tier.GrowTimeMultiplier * num2);
		}

		public int GetStage()
		{
			if (IsEmpty)
			{
				return 0;
			}
			float num = Mathf.Clamp01((float)((Now - (double)PlantedAt) / (double)GrowTime));
			if (num >= 1f)
			{
				return 4;
			}
			return 1 + Mathf.Clamp(Mathf.FloorToInt(num * 3f), 0, 2);
		}

		public string GetHoverName()
		{
			return Localization.instance.Localize(m_tier.NameToken);
		}

		public float GetHoverOffset()
		{
			return m_hoverOffset;
		}

		public string GetHoverText()
		{
			if (!m_ready)
			{
				return string.Empty;
			}
			string nameToken = m_tier.NameToken;
			if (IsEmpty)
			{
				return Localization.instance.Localize($"{nameToken} ( 0 / {m_tier.Slots} )\n" + PlantMenu());
			}
			CropInfo crop = Crop;
			string text = ((crop != null) ? crop.CropNameToken : SeedPrefab);
			int slotsUsed = SlotsUsed;
			string text2 = (HasRoomForBushes ? "\n[<color=yellow><b>L.Shift + $KEY_Use</b></color>] $piece_growbed_add_bushes" : string.Empty);
			if (IsMature)
			{
				int num = YieldFor(crop, slotsUsed);
				string text3 = $"{nameToken}\n{text} ( {slotsUsed} / {m_tier.Slots} ) — " + $"$piece_growbed_ready ({num})\n" + "[<color=yellow><b>$KEY_Use</b></color>] $piece_growbed_harvest" + text2;
				if (m_tier.AllowReplant && GrowbedConfig.EnableReplant.Value && !HoldsBushes)
				{
					text3 += "\n[<color=yellow><b>L.Shift + $KEY_Use</b></color>] $piece_growbed_harvest_replant";
				}
				return Localization.instance.Localize(text3);
			}
			return Localization.instance.Localize($"{nameToken}\n{text} ( {slotsUsed} / {m_tier.Slots} ) — " + "$piece_growbed_growing " + FormatTime(SecondsRemaining) + text2);
		}

		private string PlantMenu()
		{
			if (m_plantMenu == null || Time.time >= m_plantMenuExpiry)
			{
				m_plantMenu = BuildPlantMenu();
				m_plantMenuExpiry = Time.time + 0.25f;
			}
			return m_plantMenu;
		}

		private string BuildPlantMenu()
		{
			Player localPlayer = Player.m_localPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val == null)
			{
				return "$piece_growbed_need_seeds";
			}
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>();
			List<ItemData> list2 = PlantableItems((Humanoid)(object)localPlayer);
			CropInfo cropInfo = SeedRegistry.ByItem(SelectedSeed((Humanoid)(object)localPlayer));
			if (cropInfo != null)
			{
				hashSet.Add(cropInfo.SeedPrefab);
				AddOption(list, "$KEY_Use", cropInfo, val);
			}
			if (list2.Count > 1)
			{
				list.Add("[<color=yellow><b>L.Shift + $KEY_Use</b></color>] " + $"$piece_growbed_next_seed ({list2.Count})");
			}
			if (!ZInput.IsGamepadActive() && ZInput.instance != null)
			{
				for (int i = 0; i < val.GetWidth(); i++)
				{
					if (list.Count > 3)
					{
						break;
					}
					CropInfo cropInfo2 = SeedRegistry.ByItem(val.GetItemAt(i, 0));
					if (cropInfo2 != null && hashSet.Add(cropInfo2.SeedPrefab))
					{
						string boundKeyString = ZInput.instance.GetBoundKeyString($"Hotbar{i + 1}", true);
						if (!string.IsNullOrEmpty(boundKeyString))
						{
							AddOption(list, boundKeyString, cropInfo2, val);
						}
					}
				}
			}
			if (list.Count <= 0)
			{
				return "$piece_growbed_need_seeds";
			}
			return string.Join("\n", list.ToArray());
		}

		private void AddOption(List<string> lines, string key, CropInfo crop, Inventory inventory)
		{
			int num = inventory.CountItems(crop.SeedNameToken, -1, true);
			int num2 = Mathf.Min(m_tier.Slots, num / crop.SeedCost);
			if (num2 > 0)
			{
				string text = ((num2 < m_tier.Slots) ? $"{num2}/{m_tier.Slots}" : num2.ToString());
				lines.Add("[<color=yellow><b>" + key + "</b></color>] " + text + " × " + crop.SeedNameToken + " " + $"→ {YieldFor(crop, num2)} {crop.CropNameToken} " + "$piece_growbed_in " + FormatTime(GrowTimeFor(crop)));
			}
		}

		private static string FormatTime(float seconds)
		{
			int num = Mathf.CeilToInt(seconds);
			int num2 = num / 3600;
			int num3 = num % 3600 / 60;
			int num4 = num % 60;
			if (num2 <= 0)
			{
				return $"{num3}:{num4:00}";
			}
			return $"{num2}:{num3:00}:{num4:00}";
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			if (hold)
			{
				return false;
			}
			if (!m_ready)
			{
				return false;
			}
			if (alt && HasRoomForBushes)
			{
				return AddBushes(user);
			}
			if (IsMature)
			{
				bool replant = alt && m_tier.AllowReplant && GrowbedConfig.EnableReplant.Value && !HoldsBushes;
				return Harvest(user, replant);
			}
			if (!IsEmpty)
			{
				((Character)user).Message((MessageType)2, Localization.instance.Localize("$piece_growbed_growing " + FormatTime(SecondsRemaining)), 0, (Sprite)null, false);
				return true;
			}
			if (alt)
			{
				return SelectNextSeed(user);
			}
			return PlantFromInventory(user, SelectedSeed(user));
		}

		private static List<ItemData> PlantableItems(Humanoid user)
		{
			List<ItemData> list = new List<ItemData>();
			Inventory val = ((user != null) ? user.GetInventory() : null);
			if (val == null)
			{
				return list;
			}
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ItemData allItem in val.GetAllItems())
			{
				CropInfo cropInfo = SeedRegistry.ByItem(allItem);
				if (cropInfo != null && hashSet.Add(cropInfo.SeedPrefab) && val.CountItems(cropInfo.SeedNameToken, -1, true) >= cropInfo.SeedCost)
				{
					list.Add(allItem);
				}
			}
			return list;
		}

		private static ItemData SelectedSeed(Humanoid user)
		{
			List<ItemData> list = PlantableItems(user);
			if (list.Count == 0)
			{
				return null;
			}
			return ((IEnumerable<ItemData>)list).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => SeedRegistry.ByItem(i).SeedPrefab == s_selectedSeed)) ?? list[0];
		}

		private bool SelectNextSeed(Humanoid user)
		{
			List<ItemData> list = PlantableItems(user);
			if (list.Count == 0)
			{
				((Character)user).Message((MessageType)2, "$piece_growbed_need_seeds", 0, (Sprite)null, false);
				return true;
			}
			int num = list.IndexOf(SelectedSeed(user));
			s_selectedSeed = SeedRegistry.ByItem(list[(num + 1) % list.Count]).SeedPrefab;
			m_plantMenu = null;
			return true;
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			if (!m_ready)
			{
				return false;
			}
			if (IsEmpty)
			{
				return PlantFromInventory(user, item);
			}
			if (HasRoomForBushes && SeedRegistry.ByItem(item)?.SeedPrefab == SeedPrefab)
			{
				return AddBushes(user);
			}
			return false;
		}

		private bool PlantFromInventory(Humanoid user, ItemData candidate)
		{
			CropInfo cropInfo = SeedRegistry.ByItem(candidate);
			if (cropInfo == null)
			{
				((Character)user).Message((MessageType)2, "$piece_growbed_need_seeds", 0, (Sprite)null, false);
				return true;
			}
			Inventory inventory = user.GetInventory();
			if (inventory == null)
			{
				return false;
			}
			int num = inventory.CountItems(cropInfo.SeedNameToken, -1, true);
			int num2 = Mathf.Min(m_tier.Slots, num / cropInfo.SeedCost);
			if (num2 <= 0)
			{
				((Character)user).Message((MessageType)2, "$piece_growbed_need_seeds", 0, (Sprite)null, false);
				return true;
			}
			inventory.RemoveItem(cropInfo.SeedNameToken, num2 * cropInfo.SeedCost, -1, true);
			m_nview.InvokeRPC("Growbed_Plant", new object[2] { cropInfo.SeedPrefab, num2 });
			((Character)user).Message((MessageType)2, Localization.instance.Localize($"$piece_growbed_planted {cropInfo.SeedNameToken} ({num2})"), 0, (Sprite)null, false);
			return true;
		}

		private bool AddBushes(Humanoid user)
		{
			CropInfo crop = Crop;
			Inventory inventory = user.GetInventory();
			if (crop == null || inventory == null)
			{
				return false;
			}
			int num = m_tier.Slots - SlotsUsed;
			int num2 = inventory.CountItems(crop.SeedNameToken, -1, true);
			int num3 = Mathf.Min(num, num2 / crop.SeedCost);
			if (num3 <= 0)
			{
				((Character)user).Message((MessageType)2, "$piece_growbed_need_seeds", 0, (Sprite)null, false);
				return true;
			}
			inventory.RemoveItem(crop.SeedNameToken, num3 * crop.SeedCost, -1, true);
			m_nview.InvokeRPC("Growbed_AddBushes", new object[2] { crop.SeedPrefab, num3 });
			((Character)user).Message((MessageType)2, Localization.instance.Localize($"$piece_growbed_planted {crop.SeedNameToken} ({num3})"), 0, (Sprite)null, false);
			return true;
		}

		private void RPC_AddBushes(long sender, string seedPrefab, int slots)
		{
			if (!m_nview.IsOwner())
			{
				return;
			}
			CropInfo cropInfo = SeedRegistry.ByPrefab(seedPrefab);
			if (cropInfo == null)
			{
				Logger.LogWarning((object)$"Growbed refused unknown seed '{seedPrefab}' from {sender}.");
				return;
			}
			int num = m_tier.Slots - SlotsUsed;
			if (!cropInfo.Perennial || SeedPrefab != seedPrefab || num <= 0)
			{
				SpawnItems(cropInfo.SeedPrefab, slots * cropInfo.SeedCost, -1);
				return;
			}
			int num2 = Mathf.Clamp(slots, 1, num);
			m_nview.GetZDO().Set("growbed_slots", SlotsUsed + num2);
			if (slots > num2)
			{
				SpawnItems(cropInfo.SeedPrefab, (slots - num2) * cropInfo.SeedCost, -1);
			}
			m_nview.InvokeRPC(ZNetView.Everybody, "Growbed_Fx", new object[1] { 0 });
		}

		private void RPC_Plant(long sender, string seedPrefab, int slots)
		{
			if (!m_nview.IsOwner())
			{
				return;
			}
			CropInfo cropInfo = SeedRegistry.ByPrefab(seedPrefab);
			if (cropInfo == null)
			{
				Logger.LogWarning((object)$"Growbed refused unknown seed '{seedPrefab}' from {sender}.");
				return;
			}
			slots = Mathf.Clamp(slots, 1, m_tier.Slots);
			if (!IsEmpty)
			{
				Logger.LogDebug((object)($"Growbed already planted; refunding {slots * cropInfo.SeedCost} " + $"{cropInfo.SeedPrefab} to {sender}."));
				SpawnItems(cropInfo.SeedPrefab, slots * cropInfo.SeedCost, -1);
				return;
			}
			ZDO zDO = m_nview.GetZDO();
			zDO.Set("growbed_seed", seedPrefab);
			zDO.Set("growbed_slots", slots);
			zDO.Set("growbed_planted", (long)Now);
			m_nview.InvokeRPC(ZNetView.Everybody, "Growbed_Fx", new object[1] { 0 });
		}

		private static int BaseYieldPerSlot(CropInfo crop)
		{
			Pickable val = SeedRegistry.PickableFor(crop);
			if ((Object)(object)val == (Object)null || (Object)(object)val.m_itemPrefab == (Object)null)
			{
				return crop.YieldPerSlot;
			}
			if (val.m_dontScale || (Object)(object)Game.instance == (Object)null)
			{
				return val.m_amount;
			}
			return Mathf.Max(val.m_minAmountScaled, Game.instance.ScaleDrops(val.m_itemPrefab, val.m_amount));
		}

		private int YieldFor(CropInfo crop, int slots)
		{
			if (crop == null)
			{
				return 0;
			}
			float num = (float)(BaseYieldPerSlot(crop) * slots) * m_tier.YieldMultiplier * GrowbedConfig.GlobalYieldMultiplier.Value;
			return Mathf.Max(1, Mathf.RoundToInt(num));
		}

		private static int RollPickBonus(Humanoid user, CropInfo crop, int slots)
		{
			//IL_0029: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			if (GrowbedConfig.EnablePickBonus.Value)
			{
				Player val = (Player)(object)((user is Player) ? user : null);
				if (val != null)
				{
					Pickable val2 = SeedRegistry.PickableFor(crop);
					if ((Object)(object)val2 == (Object)null || (int)val2.m_pickRaiseSkill == 0)
					{
						return 0;
					}
					int num = 0;
					for (int i = 0; i < slots; i++)
					{
						((Character)val).RaiseSkill(val2.m_pickRaiseSkill, 1f);
						if (Random.value < ((Character)val).GetSkillFactor(val2.m_pickRaiseSkill) * val2.m_maxLevelBonusChance)
						{
							num += val2.m_bonusYieldAmount;
						}
					}
					return num;
				}
			}
			return 0;
		}

		private bool Harvest(Humanoid user, bool replant)
		{
			CropInfo crop = Crop;
			int slotsUsed = SlotsUsed;
			int num = RollPickBonus(user, crop, slotsUsed);
			m_nview.InvokeRPC("Growbed_Harvest", new object[1] { num });
			if (num > 0)
			{
				ShowBonusText(num);
			}
			if (replant && crop != null)
			{
				TryReplant(user, crop, slotsUsed);
			}
			return true;
		}

		private void TryReplant(Humanoid user, CropInfo crop, int slots)
		{
			Inventory inventory = user.GetInventory();
			if (inventory != null)
			{
				int num = inventory.CountItems(crop.SeedNameToken, -1, true);
				int num2 = Mathf.Min(slots, num / crop.SeedCost);
				if (num2 <= 0)
				{
					((Character)user).Message((MessageType)2, "$piece_growbed_replant_no_seeds", 0, (Sprite)null, false);
					return;
				}
				inventory.RemoveItem(crop.SeedNameToken, num2 * crop.SeedCost, -1, true);
				m_nview.InvokeRPC("Growbed_Plant", new object[2] { crop.SeedPrefab, num2 });
				((Character)user).Message((MessageType)2, Localization.instance.Localize($"$piece_growbed_replanted {crop.SeedNameToken} ({num2})"), 0, (Sprite)null, false);
			}
		}

		private void RPC_Harvest(long sender, int bonus)
		{
			if (!m_nview.IsOwner() || !IsMature)
			{
				return;
			}
			CropInfo crop = Crop;
			int slotsUsed = SlotsUsed;
			int num = YieldFor(crop, slotsUsed) + Mathf.Max(0, bonus);
			ZDO zDO = m_nview.GetZDO();
			if (crop != null && crop.Perennial)
			{
				zDO.Set("growbed_planted", (long)Now);
			}
			else
			{
				zDO.Set("growbed_seed", string.Empty);
				zDO.Set("growbed_slots", 0);
				zDO.Set("growbed_planted", 0L);
			}
			m_nview.InvokeRPC(ZNetView.Everybody, "Growbed_Fx", new object[1] { 1 });
			if (crop != null && num > 0)
			{
				for (int i = 0; i < slotsUsed; i++)
				{
					int amount = num / slotsUsed + ((i < num % slotsUsed) ? 1 : 0);
					SpawnItems(crop.CropPrefab, amount, i);
				}
				SpawnExtraDrops(crop, slotsUsed);
			}
		}

		private void SpawnExtraDrops(CropInfo crop, int slots)
		{
			Pickable val = SeedRegistry.PickableFor(crop);
			if (val?.m_extraDrops == null || val.m_extraDrops.IsEmpty())
			{
				return;
			}
			for (int i = 0; i < slots; i++)
			{
				foreach (ItemData dropListItem in val.m_extraDrops.GetDropListItems())
				{
					if ((Object)(object)dropListItem?.m_dropPrefab != (Object)null)
					{
						SpawnItems(((Object)dropListItem.m_dropPrefab).name, dropListItem.m_stack, i);
					}
				}
			}
		}

		private void ShowBonusText(int bonus)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)DamageText.instance == (Object)null))
			{
				Vector3 val = (((Object)(object)m_dropPoint != (Object)null) ? m_dropPoint : ((Component)this).transform).position + Vector3.up * 0.6f;
				DamageText.instance.ShowText((TextType)7, val, $"+{bonus}", true);
			}
		}

		private void RPC_Fx(long sender, int code)
		{
			//IL_0019: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			switch (code)
			{
			case 0:
			{
				EffectList plantEffects = m_plantEffects;
				if (plantEffects != null)
				{
					plantEffects.Create(((Component)this).transform.position, ((Component)this).transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
				}
				break;
			}
			case 1:
			{
				EffectList harvestEffects = m_harvestEffects;
				if (harvestEffects != null)
				{
					harvestEffects.Create(((Component)this).transform.position, ((Component)this).transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
				}
				break;
			}
			}
		}

		private void SpawnItems(string itemPrefabName, int amount, int slot)
		{
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: 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_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			if (amount <= 0)
			{
				return;
			}
			ObjectDB instance = ObjectDB.instance;
			object obj = ((instance != null) ? instance.GetItemPrefab(itemPrefabName) : null);
			if (obj == null)
			{
				ZNetScene instance2 = ZNetScene.instance;
				obj = ((instance2 != null) ? instance2.GetPrefab(itemPrefabName) : null);
			}
			GameObject val = (GameObject)obj;
			if ((Object)(object)val == (Object)null)
			{
				Logger.LogError((object)("Growbed cannot find item prefab '" + itemPrefabName + "'; drop lost."));
				return;
			}
			int num = Mathf.Max(1, val.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_maxStackSize ?? 1);
			Vector3 val2 = ((slot >= 0 && slot < m_slotAnchors.Count && (Object)(object)m_slotAnchors[slot] != (Object)null) ? m_slotAnchors[slot] : (((Object)(object)m_dropPoint != (Object)null) ? m_dropPoint : ((Component)this).transform)).position + Vector3.up * 0.5f;
			int num2 = amount;
			int num3 = 0;
			while (num2 > 0)
			{
				int num4 = Mathf.Min(num2, num);
				num2 -= num4;
				Vector2 val3 = Random.insideUnitCircle * 0.2f;
				Vector3 val4 = val2 + new Vector3(val3.x, 0.5f * (float)num3, val3.y);
				GameObject obj2 = Object.Instantiate<GameObject>(val, val4, Quaternion.Euler(0f, (float)Random.Range(0, 360), 0f));
				ItemDrop component = obj2.GetComponent<ItemDrop>();
				if ((Object)(object)component != (Object)null)
				{
					component.SetStack(num4);
					ItemDrop.OnCreateNew(component, false);
				}
				Rigidbody component2 = obj2.GetComponent<Rigidbody>();
				if ((Object)(object)component2 != (Object)null)
				{
					component2.linearVelocity = Vector3.up * 4f;
				}
				num3++;
			}
		}

		private void Update()
		{
			if (m_ready && !(Time.time < m_nextVisualCheck))
			{
				m_nextVisualCheck = Time.time + 0.25f;
				ApplyVisuals(GetStage(), force: false);
			}
		}

		private void ApplyVisuals(int stage, bool force)
		{
			int slotsUsed = SlotsUsed;
			string seedPrefab = SeedPrefab;
			if (force || stage != m_lastAppliedStage || slotsUsed != m_lastAppliedSlots || !(seedPrefab == m_lastAppliedSeed))
			{
				m_lastAppliedStage = stage;
				m_lastAppliedSlots = slotsUsed;
				m_lastAppliedSeed = seedPrefab;
				bool flag = ApplyBorrowedPlants(stage, slotsUsed);
				ApplyBundledStages((!flag) ? stage : 0, slotsUsed);
			}
		}

		private bool ApplyBorrowedPlants(int stage, int slotsUsed)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: 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_00c4: Unknown result type (might be due to invalid IL or missing references)
			ClearLivePlants();
			if (stage <= 0 || slotsUsed <= 0 || m_slotAnchors.Count == 0)
			{
				return false;
			}
			CropInfo crop = Crop;
			CropVisuals.Template template = CropVisuals.For(crop, stage);
			if (template == null)
			{
				return false;
			}
			float num = CropVisuals.ScaleFor(template, crop, stage, SlotSpacing);
			LogVisualOnce(template, stage, num);
			int num2 = Mathf.Min(slotsUsed, m_slotAnchors.Count);
			for (int i = 0; i < num2; i++)
			{
				GameObject val = Object.Instantiate<GameObject>(template.Root, m_slotAnchors[i]);
				val.transform.localPosition = Vector3.zero;
				val.transform.localRotation = Quaternion.Euler(0f, (float)i * 67f % 360f, 0f);
				val.transform.localScale = Vector3.one * num;
				val.SetActive(true);
				m_livePlants.Add(val);
			}
			return true;
		}

		private static void LogVisualOnce(CropVisuals.Template template, int stage, float scale)
		{
			string item = $"{template.Source}|{stage}";
			if (LoggedVisuals.Add(item))
			{
				Logger.LogInfo((object)($"Growbed stage {stage}: drawing '{template.Source}' " + $"({template.Renderers} renderer(s), {template.Height:0.000}m tall, " + $"radius {template.Radius:0.000}m) at scale {scale:0.00} " + $"-> {template.Height * scale:0.000}m above the soil."));
			}
		}

		private void ApplyBundledStages(int stage, int slotsUsed)
		{
			for (int i = 0; i < m_stageVisuals.Count; i++)
			{
				bool flag = stage > 0 && i + 1 == stage;
				GameObject val = m_stageVisuals[i];
				if ((Object)(object)val != (Object)null && val.activeSelf != flag)
				{
					val.SetActive(flag);
				}
				if (!flag)
				{
					continue;
				}
				List<GameObject> list = m_slotVisuals[i];
				for (int j = 0; j < list.Count; j++)
				{
					bool flag2 = j < slotsUsed;
					if (list[j].activeSelf != flag2)
					{
						list[j].SetActive(flag2);
					}
				}
			}
		}
	}
	internal static class GrowbedConfig
	{
		private class TierSettings
		{
			private readonly GrowbedTier m_tier;

			private readonly ConfigEntry<int> m_slots;

			private readonly ConfigEntry<float> m_growTimeMultiplier;

			private readonly ConfigEntry<float> m_yieldMultiplier;

			private readonly ConfigEntry<bool> m_allowReplant;

			private readonly ConfigEntry<float> m_health;

			private readonly ConfigEntry<string> m_craftingStation;

			private readonly ConfigEntry<string> m_buildCost;

			private readonly ConfigEntry<float> m_footprintX;

			private readonly ConfigEntry<float> m_footprintZ;

			public TierSettings(ConfigFile config, GrowbedTier tier)
			{
				//IL_007e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0088: Expected O, but got Unknown
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d3: Expected O, but got Unknown
				//IL_0114: Unknown result type (might be due to invalid IL or missing references)
				//IL_011e: Expected O, but got Unknown
				//IL_0151: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Expected O, but got Unknown
				//IL_019c: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a6: Expected O, but got Unknown
				//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ec: Expected O, but got Unknown
				//IL_0224: Unknown result type (might be due to invalid IL or missing references)
				//IL_022e: Expected O, but got Unknown
				//IL_026f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0279: Expected O, but got Unknown
				//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
				//IL_02c4: Expected O, but got Unknown
				m_tier = tier;
				string text = tier.PrefabName.Replace("piece_growbed_", string.Empty);
				string text2 = "Tier - " + char.ToUpperInvariant(text[0]) + text.Substring(1);
				m_slots = config.Bind<int>(text2, "Slots", tier.Slots, new ConfigDescription("How many plants this bed holds. Takes effect on game restart.\nThe model only carries so many slot meshes: raising this above what the art provides still grows and yields the extra plants, but the surplus will not be drawn.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 64), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_growTimeMultiplier = config.Bind<float>(text2, "GrowTimeMultiplier", tier.GrowTimeMultiplier, new ConfigDescription("Multiplies the crop's own vanilla grow time. 1.0 is parity with a vanilla plant; above 1.0 is slower. The defaults are above 1.0 on purpose — a bed packs roughly twice vanilla's density, so vanilla-speed growth would double throughput per square metre.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 20f), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_yieldMultiplier = config.Bind<float>(text2, "YieldMultiplier", tier.YieldMultiplier, new ConfigDescription("Multiplies harvested stack size for this tier only. Stacks with Balance.YieldMultiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 20f), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_allowReplant = config.Bind<bool>(text2, "AllowReplant", tier.AllowReplant, new ConfigDescription("Offers harvest-and-replant on alt-interact, taking the seeds from the harvester's inventory. Needs Balance.EnableReplant too.", (AcceptableValueBase)null, new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_health = config.Bind<float>(text2, "Health", tier.Health, new ConfigDescription("Structural health, as WearNTear sees it. Restart to apply.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100000f), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_craftingStation = config.Bind<string>(text2, "CraftingStation", tier.CraftingStation ?? string.Empty, new ConfigDescription("Station prefab name required to build, e.g. 'forge' or 'piece_workbench'. Empty means buildable anywhere. Restart to apply.", (AcceptableValueBase)null, new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_buildCost = config.Bind<string>(text2, "BuildCost", Format(tier.Requirements), new ConfigDescription("Build cost as 'Prefab:Amount' pairs separated by commas, e.g. 'Wood:10,Stone:4'. Prefab names are the game's item ids. Restart to apply; an unparseable value keeps the built-in cost.", (AcceptableValueBase)null, new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_footprintX = config.Bind<float>(text2, "FootprintX", tier.FootprintX, new ConfigDescription("Bed width in metres. With Slots this is the real density lever, so change it alongside GrowTimeMultiplier. Restart to apply.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
				m_footprintZ = config.Bind<float>(text2, "FootprintZ", tier.FootprintZ, new ConfigDescription("Bed depth in metres. Restart to apply.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						IsAdminOnly = true
					}
				}));
			}

			public void ApplyTo()
			{
				m_tier.Slots = m_slots.Value;
				m_tier.GrowTimeMultiplier = m_growTimeMultiplier.Value;
				m_tier.YieldMultiplier = m_yieldMultiplier.Value;
				m_tier.AllowReplant = m_allowReplant.Value;
				m_tier.Health = m_health.Value;
				string text = m_craftingStation.Value?.Trim();
				m_tier.CraftingStation = (string.IsNullOrEmpty(text) ? null : text);
				Dictionary<string, int> dictionary = Parse(m_buildCost.Value, m_tier.PrefabName);
				if (dictionary != null)
				{
					m_tier.Requirements = dictionary;
				}
				m_tier.FootprintX = m_footprintX.Value;
				m_tier.FootprintZ = m_footprintZ.Value;
			}

			private static string Format(Dictionary<string, int> requirements)
			{
				return string.Join(",", requirements.Select((KeyValuePair<string, int> pair) => $"{pair.Key}:{pair.Value}"));
			}

			private static Dictionary<string, int> Parse(string raw, string tierName)
			{
				if (string.IsNullOrEmpty(raw) || string.IsNullOrEmpty(raw.Trim()))
				{
					return new Dictionary<string, int>();
				}
				Dictionary<string, int> dictionary = new Dictionary<string, int>();
				string[] array = raw.Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length != 0)
					{
						string[] array2 = text.Split(new char[1] { ':' });
						if (array2.Length != 2)
						{
							Logger.LogWarning((object)(tierName + ": BuildCost entry '" + text + "' is not 'Prefab:Amount'; keeping the built-in build cost for this tier."));
							return null;
						}
						string text2 = array2[0].Trim();
						if (text2.Length == 0 || !int.TryParse(array2[1].Trim(), out var result) || result < 1)
						{
							Logger.LogWarning((object)(tierName + ": BuildCost entry '" + text + "' needs a prefab name and a whole amount of at least 1; keeping the built-in build cost."));
							return null;
						}
						dictionary[text2] = result;
					}
				}
				return dictionary;
			}
		}

		public static ConfigEntry<float> GlobalGrowTimeMultiplier;

		public static ConfigEntry<float> BushGrowTimeMultiplier;

		public static ConfigEntry<float> GlobalYieldMultiplier;

		public static ConfigEntry<bool> EnablePickBonus;

		public static ConfigEntry<bool> EnableReplant;

		public static ConfigEntry<string> BlockedSeeds;

		public static ConfigEntry<float> PlantScale;

		private static readonly List<TierSettings> Tiers = new List<TierSettings>();

		public static void Bind(ConfigFile config)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Expected O, but got Unknown
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Expected O, but got Unknown
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Expected O, but got Unknown
			GlobalGrowTimeMultiplier = config.Bind<float>("Balance", "GrowTimeMultiplier", 0.75f, new ConfigDescription("Multiplies a crop bed's grow time on top of its tier multiplier, where 1.0 is the plant's own vanilla time. The default 0.75 is what a bed sells: the same crop, a quarter sooner. Raise it to 1.0 for strict parity with a field tile.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				}
			}));
			BushGrowTimeMultiplier = config.Bind<float>("Balance", "BushGrowTimeMultiplier", 0.5f, new ConfigDescription("GrowTimeMultiplier's counterpart for bushes — berries, mushrooms and anything else a bed keeps rather than pulls up. It replaces GrowTimeMultiplier for those rather than stacking with it, because a bush is paced by its respawn timer, which vanilla measures in in-game days and which makes an unscaled bush bed feel dead.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				}
			}));
			GlobalYieldMultiplier = config.Bind<float>("Balance", "YieldMultiplier", 1f, new ConfigDescription("Multiplies harvested stack size. 1.0 keeps parity with vanilla per-plant yield, which is the intended balance point. It scales the base yield only; the skill bonus from EnablePickBonus is added on top unscaled, so a lucky pick is worth the same as it is in a field.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				}
			}));
			EnablePickBonus = config.Bind<bool>("Balance", "EnablePickBonus", true, new ConfigDescription("Harvesting rolls the crop's own skill bonus once per slot and raises the skill picking it would have raised — Farming, for every vanilla crop. Off makes a bed pay a flat yield and grant no skill.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				}
			}));
			EnableReplant = config.Bind<bool>("Balance", "EnableReplant", true, new ConfigDescription("Master switch for the harvest-and-replant secondary interaction. A tier also needs its own AllowReplant set.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				}
			}));
			BlockedSeeds = config.Bind<string>("Balance", "BlockedSeeds", "Wood,Stone,Flint,Coal,Resin,Tar,Obsidian,Guck,SurtlingCore,IronOre,CopperOre,TinOre,SilverOre,BlackMetalScrap,FlametalOre", new ConfigDescription("Comma-separated item prefabs a bed refuses to plant, matched against the item it costs. Mods that make loose resources placeable — branches, flint, ore — register them the same way they register berry bushes, and a growbed has no business farming firewood. Clear it to allow everything.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				}
			}));
			PlantScale = config.Bind<float>("Appearance", "PlantScale", 1f, new ConfigDescription("Size of the plants drawn in a bed, where 1.0 is the size the game draws them in the ground. Lower it if full-size bushes crowd each other in a packed bed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdminOnly = false
				}
			}));
			Tiers.Clear();
			GrowbedTier[] all = GrowbedTier.All;
			foreach (GrowbedTier tier in all)
			{
				Tiers.Add(new TierSettings(config, tier));
			}
		}

		public static void ApplyTierOverrides()
		{
			foreach (TierSettings tier in Tiers)
			{
				tier.ApplyTo();
			}
		}
	}
	internal class ConfigurationManagerAttributes
	{
		public bool? IsAdminOnly;

		public bool? Browsable;

		public bool? ReadOnly;

		public int? Order;

		public string Category;
	}
	[BepInPlugin("com.noodle.growbed", "Growbed", "1.1.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	internal class GrowbedPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.noodle.growbed";

		public const string PluginName = "Growbed";

		public const string PluginVersion = "1.1.2";

		private const string AssetBundleName = "growbed";

		private static readonly Dictionary<string, GrowbedTier> TiersByPrefab = new Dictionary<string, GrowbedTier>();

		private static readonly Dictionary<string, GameObject> RegisteredPrefabs = new Dictionary<string, GameObject>();

		private AssetBundle m_bundle;

		private static ManualLogSource Log;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			GrowbedConfig.Bind(((BaseUnityPlugin)this).Config);
			GrowbedConfig.ApplyTierOverrides();
			GrowbedTier[] all = GrowbedTier.All;
			foreach (GrowbedTier growbedTier in all)
			{
				TiersByPrefab[growbedTier.PrefabName] = growbedTier;
			}
			AddLocalizations();
			RegisterPieces();
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable;
			PieceManager.OnPiecesRegistered += VerifyPiecesRegistered;
			Log.LogInfo((object)"Growbed 1.1.2 loaded.");
		}

		private void OnVanillaPrefabsAvailable()
		{
			if (TryBuildSeedRegistry())
			{
				PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
			}
		}

		private static bool TryBuildSeedRegistry()
		{
			if (!SeedRegistry.IsBuilt)
			{
				return SeedRegistry.Build();
			}
			return true;
		}

		private void VerifyPiecesRegistered()
		{
			if (!TryBuildSeedRegistry())
			{
				Log.LogError((object)"Seed table still empty after the pieces registered. Growbeds will place but refuse every seed. ZNetScene was expected to exist by now.");
			}
			DressTrim();
			CropVisuals.Reset();
			PieceTable pieceTable = PieceManager.Instance.GetPieceTable(PieceTables.Hammer);
			if ((Object)(object)pieceTable == (Object)null)
			{
				Log.LogError((object)"Hammer piece table not found; no growbed will be buildable.");
				return;
			}
			GrowbedTier[] all = GrowbedTier.All;
			foreach (GrowbedTier tier in all)
			{
				if (pieceTable.m_pieces.Any((GameObject go) => (Object)(object)go != (Object)null && ((Object)go).name.StartsWith(tier.PrefabName, StringComparison.Ordinal)))
				{
					Log.LogInfo((object)(tier.PrefabName + ": in the hammer menu."));
				}
				else
				{
					Log.LogError((object)(tier.PrefabName + ": NOT in the hammer menu. It was registered but never placed in the piece table."));
				}
			}
		}

		public static GrowbedTier TierFor(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			foreach (KeyValuePair<string, GrowbedTier> item in TiersByPrefab)
			{
				if (prefabName.StartsWith(item.Key, StringComparison.Ordinal))
				{
					return item.Value;
				}
			}
			return null;
		}

		private void RegisterPieces()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Expected O, but got Unknown
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Expected O, but got Unknown
			m_bundle = LoadBundle();
			if ((Object)(object)m_bundle == (Object)null)
			{
				Log.LogError((object)"Asset bundle 'growbed' is not embedded in this assembly, so no growbed can be registered and none will appear in the hammer. Build it from the Unity project under unity/ (see unity/README.md) and rebuild the mod.");
				return;
			}
			GrowbedTier[] all = GrowbedTier.All;
			foreach (GrowbedTier growbedTier in all)
			{
				GameObject val = LoadTierPrefab(growbedTier);
				if ((Object)(object)val == (Object)null)
				{
					Log.LogError((object)("Could not load prefab for " + growbedTier.PrefabName + "; skipping tier."));
					continue;
				}
				PreparePrefab(val, growbedTier);
				if (PieceManager.Instance.AddPiece(new CustomPiece(val, true, new PieceConfig
				{
					Name = growbedTier.NameToken,
					Description = growbedTier.DescriptionToken,
					PieceTable = PieceTables.Hammer,
					Category = "Misc",
					CraftingStation = growbedTier.CraftingStation,
					Icon = BuildIcon(val, growbedTier),
					Requirements = BuildRequirements(growbedTier)
				})))
				{
					RegisteredPrefabs[growbedTier.PrefabName] = val;
					Log.LogInfo((object)("Registered " + growbedTier.PrefabName + " " + $"({growbedTier.Slots} slots, {growbedTier.GrowTimeMultiplier:0.##}x grow time, " + "station: " + (growbedTier.CraftingStation ?? "none") + ")"));
				}
				else
				{
					Log.LogError((object)("PieceManager refused " + growbedTier.PrefabName + "; it will not appear in the hammer. The Jotunn error logged just above this line gives the reason."));
				}
			}
		}

		private static Sprite BuildIcon(GameObject prefab, GrowbedTier tier)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			Sprite val = prefab.GetComponent<Piece>()?.m_icon;
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			Sprite val2 = EmbeddedIcon(tier.PrefabName);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2;
			}
			Log.LogWarning((object)("No embedded icon for " + tier.PrefabName + "; using a flat swatch."));
			return FlatIcon(tier.IconTint);
		}

		private static Sprite EmbeddedIcon(string prefabName)
		{
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			string text = "Growbed.icons." + prefabName + ".rawtex";
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(text);
			if (stream == null)
			{
				return null;
			}
			byte[] array = new byte[stream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = stream.Read(array, i, array.Length - i);
				if (num <= 0)
				{
					break;
				}
			}
			if (array.Length < 8)
			{
				Log.LogWarning((object)(text + " is truncated."));
				return null;
			}
			int num2 = BitConverter.ToInt32(array, 0);
			int num3 = BitConverter.ToInt32(array, 4);
			long num4 = 8 + (long)num2 * (long)num3 * 4;
			if (num2 <= 0 || num3 <= 0 || array.Length < num4)
			{
				Log.LogWarning((object)($"{text} header says {num2}x{num3} but holds " + $"{array.Length} bytes; expected {num4}."));
				return null;
			}
			Color32[] array2 = (Color32[])(object)new Color32[num2 * num3];
			for (int j = 0; j < array2.Length; j++)
			{
				int num5 = 8 + j * 4;
				array2[j] = new Color32(array[num5], array[num5 + 1], array[num5 + 2], array[num5 + 3]);
			}
			Texture2D val = new Texture2D(num2, num3, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1
			};
			val.SetPixels32(array2);
			val.Apply();
			return Sprite.Create(val, new Rect(0f, 0f, (float)num2, (float)num3), new Vector2(0.5f, 0.5f));
		}

		private static Sprite FlatIcon(Color fill)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_00b1: 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_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1
			};
			Color val2 = default(Color);
			((Color)(ref val2))..ctor(0.09f, 0.08f, 0.06f, 1f);
			Color[] array = (Color[])(object)new Color[4096];
			for (int i = 0; i < 64; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					bool flag = j < 3 || i < 3 || j >= 61 || i >= 61;
					array[i * 64 + j] = (flag ? val2 : fill);
				}
			}
			val.SetPixels(array);
			val.Apply();
			return Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f));
		}

		private static RequirementConfig[] BuildRequirements(GrowbedTier tier)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			List<RequirementConfig> list = new List<RequirementConfig>();
			foreach (KeyValuePair<string, int> requirement in tier.Requirements)
			{
				list.Add(new RequirementConfig(requirement.Key, requirement.Value, 0, true));
			}
			return list.ToArray();
		}

		private static void PreparePrefab(GameObject prefab, GrowbedTier tier)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			(prefab.GetComponent<ZNetView>() ?? prefab.AddComponent<ZNetView>()).m_persistent = true;
			Piece obj = prefab.GetComponent<Piece>() ?? prefab.AddComponent<Piece>();
			obj.m_name = tier.NameToken;
			obj.m_description = tier.DescriptionToken;
			obj.m_canBeRemoved = true;
			obj.m_groundPiece = false;
			obj.m_clipEverything = false;
			WearNTear obj2 = prefab.GetComponent<WearNTear>() ?? prefab.AddComponent<WearNTear>();
			obj2.m_health = tier.Health;
			obj2.m_materialType = tier.Material;
			obj2.m_supports = false;
			obj2.m_noSupportWear = true;
			obj2.m_noRoofWear = true;
			(prefab.GetComponent<Growbed>() ?? prefab.AddComponent<Growbed>()).TierPrefabName = tier.PrefabName;
		}

		private static void DressTrim()
		{
			GrowbedTier[] all = GrowbedTier.All;
			foreach (GrowbedTier growbedTier in all)
			{
				if (growbedTier.TrimMaterialSources == null || growbedTier.TrimMaterialSources.Length == 0 || !RegisteredPrefabs.TryGetValue(growbedTier.PrefabName, out var value) || (Object)(object)value == (Object)null)
				{
					continue;
				}
				Transform val = FindDeep(value.transform, "trim");
				Renderer val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<Renderer>() : null);
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				string text = null;
				Material val3 = null;
				string[] trimMaterialSources = growbedTier.TrimMaterialSources;
				foreach (string text2 in trimMaterialSources)
				{
					val3 = BorrowMaterial(text2);
					if ((Object)(object)val3 != (Object)null)
					{
						text = text2;
						break;
					}
				}
				if ((Object)(object)val3 == (Object)null)
				{
					Log.LogWarning((object)(growbedTier.PrefabName + ": no material to borrow from any of '" + string.Join("', '", growbedTier.TrimMaterialSources) + "'; the trim stays timber-coloured."));
					continue;
				}
				Material[] array = (Material[])(object)new Material[val2.sharedMaterials.Length];
				for (int k = 0; k < array.Length; k++)
				{
					array[k] = val3;
				}
				val2.sharedMaterials = array;
				ManualLogSource log = Log;
				string[] obj = new string[8]
				{
					growbedTier.PrefabName,
					": trim dressed in '",
					((Object)val3).name,
					"' (shader '",
					null,
					null,
					null,
					null
				};
				Shader shader = val3.shader;
				obj[4] = ((shader != null) ? ((Object)shader).name : null);
				obj[5] = "') from ";
				obj[6] = text;
				obj[7] = ".";
				log.LogInfo((object)string.Concat(obj));
			}
		}

		private static Material BorrowMaterial(string prefabName)
		{
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabName) : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Renderer[] componentsInChildren = val.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val2 in componentsInChildren)
			{
				if (!(val2 is ParticleSystemRenderer))
				{
					Material sharedMaterial = val2.sharedMaterial;
					if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null) && ((Object)sharedMaterial.shader).name.IndexOf("snow", StringComparison.OrdinalIgnoreCase) < 0)
					{
						return sharedMaterial;
					}
				}
			}
			return null;
		}

		private static Transform FindDeep(Transform root, string name)
		{
			if (((Object)root).name == name)
			{
				return root;
			}
			for (int i = 0; i < root.childCount; i++)
			{
				Transform val = FindDeep(root.GetChild(i), name);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		private AssetBundle LoadBundle()
		{
			try
			{
				return AssetUtils.LoadAssetBundleFromResources("growbed", Assembly.GetExecutingAssembly());
			}
			catch (Exception ex)
			{
				Log.LogDebug((object)("Bundle load failed: " + ex.Message));
				return null;
			}
		}

		private GameObject LoadTierPrefab(GrowbedTier tier)
		{
			AssetBundle bundle = m_bundle;
			GameObject obj = ((bundle != null) ? bundle.LoadAsset<GameObject>(tier.PrefabName) : null);
			if ((Object)(object)obj == (Object)null)
			{
				Log.LogError((object)("Bundle has no asset named '" + tier.PrefabName + "'."));
			}
			return obj;
		}

		private static void AddLocalizations()
		{
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			Dictionary<string, string> dictionary = new Dictionary<string, string>
			{
				{ "piece_growbed_wood", "Wooden Growbed" },
				{ "piece_growbed_wood_desc", "A simple planting trough." },
				{ "piece_growbed_bronze", "Bronze-Banded Growbed" },
				{ "piece_growbed_bronze_desc", "Bronze fittings keep the soil warm." },
				{ "piece_growbed_iron", "Iron Growbed" },
				{ "piece_growbed_iron_desc", "A heated iron frame that hurries the soil along." },
				{ "piece_growbed_silver", "Silvered Growbed" },
				{ "piece_growbed_silver_desc", "Grows quickly, and can be replanted in one motion." },
				{ "piece_growbed_in", "in" },
				{ "piece_growbed_harvest", "Harvest" },
				{ "piece_growbed_harvest_replant", "Harvest and replant" },
				{ "piece_growbed_add_bushes", "Add bushes" },
				{ "piece_growbed_growing", "growing —" },
				{ "piece_growbed_ready", "ready" },
				{ "piece_growbed_planted", "Planted" },
				{ "piece_growbed_replanted", "Replanted" },
				{ "piece_growbed_need_seeds", "You have no seeds to plant" },
				{ "piece_growbed_next_seed", "Change seed" },
				{ "piece_growbed_replant_no_seeds", "Harvested, but no seeds left to replant" }
			};
			string text = "English";
			localization.AddTranslation(ref text, dictionary);
		}
	}
	internal class GrowbedTier
	{
		public string PrefabName;

		public string NameToken;

		public string DescriptionToken;

		public int Slots;

		public float GrowTimeMultiplier;

		public float YieldMultiplier = 1f;

		public bool AllowReplant;

		public Color IconTint;

		public float Health;

		public MaterialType Material;

		public string[] TrimMaterialSources;

		public string CraftingStation;

		public int StationLevel;

		public Dictionary<string, int> Requirements;

		public float FootprintX;

		public float FootprintZ;

		public static readonly GrowbedTier[] All = new GrowbedTier[4]
		{
			new GrowbedTier
			{
				PrefabName = "piece_growbed_wood",
				TrimMaterialSources = new string[2] { "Stone", "stone_wall_2x1" },
				NameToken = "$piece_growbed_wood",
				DescriptionToken = "$piece_growbed_wood_desc",
				Slots = 4,
				GrowTimeMultiplier = 1f,
				AllowReplant = false,
				IconTint = new Color(0.42f, 0.31f, 0.19f),
				Health = 400f,
				Material = (MaterialType)0,
				CraftingStation = null,
				StationLevel = 0,
				Requirements = new Dictionary<string, int>
				{
					{ "Wood", 10 },
					{ "Stone", 4 }
				},
				FootprintX = 2f,
				FootprintZ = 1f
			},
			new GrowbedTier
			{
				PrefabName = "piece_growbed_bronze",
				TrimMaterialSources = new string[1] { "Bronze" },
				NameToken = "$piece_growbed_bronze",
				DescriptionToken = "$piece_growbed_bronze_desc",
				Slots = 6,
				GrowTimeMultiplier = 1f,
				AllowReplant = false,
				IconTint = new Color(0.55f, 0.36f, 0.17f),
				Health = 600f,
				Material = (MaterialType)0,
				CraftingStation = "forge",
				StationLevel = 1,
				Requirements = new Dictionary<string, int>
				{
					{ "Bronze", 4 },
					{ "Wood", 10 }
				},
				FootprintX = 2f,
				FootprintZ = 1.5f
			},
			new GrowbedTier
			{
				PrefabName = "piece_growbed_iron",
				TrimMaterialSources = new string[1] { "Iron" },
				NameToken = "$piece_growbed_iron",
				DescriptionToken = "$piece_growbed_iron_desc",
				Slots = 8,
				GrowTimeMultiplier = 1f,
				AllowReplant = false,
				IconTint = new Color(0.34f, 0.35f, 0.37f),
				Health = 800f,
				Material = (MaterialType)2,
				CraftingStation = "forge",
				StationLevel = 2,
				Requirements = new Dictionary<string, int>
				{
					{ "Iron", 6 },
					{ "Chain", 2 }
				},
				FootprintX = 2.5f,
				FootprintZ = 1.5f
			},
			new GrowbedTier
			{
				PrefabName = "piece_growbed_silver",
				TrimMaterialSources = new string[1] { "Silver" },
				NameToken = "$piece_growbed_silver",
				DescriptionToken = "$piece_growbed_silver_desc",
				Slots = 12,
				GrowTimeMultiplier = 1f,
				AllowReplant = true,
				IconTint = new Color(0.72f, 0.74f, 0.78f),
				Health = 1000f,
				Material = (MaterialType)2,
				CraftingStation = "forge",
				StationLevel = 4,
				Requirements = new Dictionary<string, int>
				{
					{ "Silver", 8 },
					{ "WolfPelt", 4 }
				},
				FootprintX = 3f,
				FootprintZ = 2f
			}
		};
	}
	internal class CropInfo
	{
		public string SeedPrefab;

		public string CropPrefab;

		public string YoungPrefab;

		public string MaturePrefab;

		public int SeedCost;

		public int YieldPerSlot;

		public float BaseGrowTime;

		public string SeedNameToken;

		public string CropNameToken;

		public bool Perennial;
	}
	internal static class SeedRegistry
	{
		private static readonly Dictionary<string, CropInfo> BySeedPrefab = new Dictionary<string, CropInfo>();

		private static readonly Dictionary<string, CropInfo> BySeedToken = new Dictionary<string, CropInfo>();

		private static readonly List<string> Blocked = new List<string>();

		public static bool IsBuilt { get; private set; }

		public static int Count
		{
			get
			{
				EnsureBuilt();
				return BySeedPrefab.Count;
			}
		}

		public static IEnumerable<CropInfo> Crops
		{
			get
			{
				EnsureBuilt();
				return BySeedPrefab.Values;
			}
		}

		public static bool Build()
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				Logger.LogDebug((object)"SeedRegistry.Build: no ZNetScene yet; will retry.");
				return false;
			}
			BySeedPrefab.Clear();
			BySeedToken.Clear();
			Blocked.Clear();
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null))
				{
					Plant component = prefab.GetComponent<Plant>();
					if (!((Object)(object)component == (Object)null))
					{
						Add(FromPlant(component, prefab.GetComponent<Piece>()));
					}
				}
			}
			List<string> list = BySeedPrefab.Keys.OrderBy((string k) => k).ToList();
			foreach (GameObject prefab2 in ZNetScene.instance.m_prefabs)
			{
				if (!((Object)(object)prefab2 == (Object)null) && !((Object)(object)prefab2.GetComponent<Plant>() != (Object)null))
				{
					Pickable component2 = prefab2.GetComponent<Pickable>();
					if (!((Object)(object)component2 == (Object)null) && !((Object)(object)prefab2.GetComponent<TreeBase>() != (Object)null) && !((Object)(object)prefab2.GetComponentInChildren<TreeBase>(true) != (Object)null))
					{
						Add(FromPickable(component2, prefab2.GetComponent<Piece>()));
					}
				}
			}
			IsBuilt = true;
			List<string> list2 = (from c in BySeedPrefab.Values
				where c.Perennial
				select c.SeedPrefab into k
				orderby k
				select k).ToList();
			Logger.LogInfo((object)($"SeedRegistry: {list.Count} plantable crops discovered " + "(" + string.Join(", ", list) + ")"));
			if (Blocked.Count > 0)
			{
				Logger.LogInfo((object)($"SeedRegistry: {Blocked.Count} blocked by config " + "(" + string.Join(", ", Blocked.OrderBy((string k) => k)) + ")"));
			}
			Logger.LogInfo((object)((list2.Count > 0) ? ($"SeedRegistry: {list2.Count} plantable bushes discovered " + "(" + string.Join(", ", list2) + ")") : "SeedRegistry: no plantable bushes found. Mods like Plant Everything add these; without one, beds take seeds only."));
			return true;
		}

		private static void EnsureBuilt()
		{
			if (!IsBuilt)
			{
				Build();
			}
		}

		private static void Add(CropInfo info)
		{
			if (info != null && !BySeedPrefab.ContainsKey(info.SeedPrefab))
			{
				if (IsBlocked(info.SeedPrefab))
				{
					Blocked.Add(info.SeedPrefab);
					return;
				}
				BySeedPrefab[info.SeedPrefab] = info;
				BySeedToken[info.SeedNameToken] = info;
			}
		}

		private static CropInfo FromPickable(Pickable pickable, Piece piece)
		{
			if ((Object)(object)piece == (Object)null || piece.m_resources == null || piece.m_resources.Length == 0)
			{
				return null;
			}
			Requirement val = piece.m_resources[0];
			if (val == null || (Object)(object)val.m_resItem == (Object)null)
			{
				return null;
			}
			if ((Object)(object)pickable.m_itemPrefab == (Object)null)
			{
				return null;
			}
			if (pickable.m_respawnTimeMinutes <= 0f)
			{
				return null;
			}
			ItemDrop resItem = val.m_resItem;
			ItemDrop component = pickable.m_itemPrefab.GetComponent<ItemDrop>();
			return new CropInfo
			{
				SeedPrefab = ((Object)((Component)resItem).gameObject).name,
				CropPrefab = ((Object)pickable.m_itemPrefab).name,
				YoungPrefab = ((Object)((Component)pickable).gameObject).name,
				MaturePrefab = ((Object)((Component)pickable).gameObject).name,
				SeedCost = Mathf.Max(1, val.m_amount),
				YieldPerSlot = Mathf.Max(1, pickable.m_amount),
				BaseGrowTime = Mathf.Max(1f, pickable.m_respawnTimeMinutes * 60f),
				SeedNameToken = (resItem.m_itemData?.m_shared?.m_name ?? ((Object)((Component)resItem).gameObject).name),
				CropNameToken = (component?.m_itemData?.m_shared?.m_name ?? ((Object)pickable.m_itemPrefab).name),
				Perennial = true
			};
		}

		private static CropInfo FromPlant(Plant plant, Piece piece)
		{
			string name = ((Object)((Component)plant).gameObject).name;
			if ((Object)(object)piece == (Object)null || piece.m_resources == null || piece.m_resources.Length == 0)
			{
				return null;
			}
			Requirement val = piece.m_resources[0];
			if (val == null || (Object)(object)val.m_resItem == (Object)null)
			{
				return null;
			}
			ItemDrop resItem = val.m_resItem;
			if (plant.m_grownPrefabs == null || plant.m_grownPrefabs.Length == 0)
			{
				return null;
			}
			Pickable val2 = null;
			GameObject[] grownPrefabs = plant.m_grownPrefabs;
			foreach (GameObject val3 in grownPrefabs)
			{
				if (!((Object)(object)val3 == (Object)null))
				{
					val2 = val3.GetComponent<Pickable>();
					if ((Object)(object)val2 != (Object)null)
					{
						break;
					}
				}
			}
			if ((Object)(object)val2 == (Object)null || (Object)(object)val2.m_itemPrefab == (Object)null)
			{
				return null;
			}
			float num = plant.m_growTime;
			if (plant.m_growTimeMax > plant.m_growTime)
			{
				num = (plant.m_growTime + plant.m_growTimeMax) * 0.5f;
			}
			ItemDrop component = val2.m_itemPrefab.GetComponent<ItemDrop>();
			return new CropInfo
			{
				SeedPrefab = ((Object)((Component)resItem).gameObject).name,
				CropPrefab = ((Object)val2.m_itemPrefab).name,
				YoungPrefab = name,
				MaturePrefab = ((Object)((Component)val2).gameObject).name,
				SeedCost = Mathf.Max(1, val.m_amount),
				YieldPerSlot = Mathf.Max(1, val2.m_amount),
				BaseGrowTime = Mathf.Max(1f, num),
				SeedNameToken = (resItem.m_itemData?.m_shared?.m_name ?? ((Object)((Component)resItem).gameObject).name),
				CropNameToken = (component?.m_itemData?.m_shared?.m_name ?? ((Object)val2.m_itemPrefab).name)
			};
		}

		private static bool IsBlocked(string seedPrefab)
		{
			string text = GrowbedConfig.BlockedSeeds?.Value;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			string[] array = text.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				if (string.Equals(array[i].Trim(), seedPrefab, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public static Pickable PickableFor(CropInfo crop)
		{
			if (crop == null || string.IsNullOrEmpty(crop.MaturePrefab))
			{
				return null;
			}
			ZNetScene instance = ZNetScene.instance;
			GameObject val = ((instance != null) ? instance.GetPrefab(crop.MaturePrefab) : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.GetComponent<Pickable>();
		}

		public static CropInfo ByPrefab(string seedPrefabName)
		{
			if (string.IsNullOrEmpty(seedPrefabName))
			{
				return null;
			}
			EnsureBuilt();
			if (!BySeedPrefab.TryGetValue(seedPrefabName, out var value))
			{
				return null;
			}
			return value;
		}

		public static CropInfo ByItem(ItemData item)
		{
			if (item?.m_shared == null)
			{
				return null;
			}
			EnsureBuilt();
			if (BySeedToken.TryGetValue(item.m_shared.m_name, out var value))
			{
				return value;
			}
			if (!((Object)(object)item.m_dropPrefab != (Object)null))
			{
				return null;
			}
			return ByPrefab(((Object)item.m_dropPrefab).name);
		}

		public static bool IsSeed(ItemData item)
		{
			return ByItem(item) != null;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}