Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Growbed v1.1.2
plugins/Growbed.dll
Decompiled 3 days agousing 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) { } } }