using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")] [assembly: AssemblyCompany("DSPNormalModeVegetation")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.4.3.0")] [assembly: AssemblyInformationalVersion("1.4.3")] [assembly: AssemblyProduct("DSPNormalModeVegetation")] [assembly: AssemblyTitle("DSPNormalModeVegetation")] [assembly: AssemblyVersion("1.4.3.0")] namespace DSPNormalModeVegetation; [BepInPlugin("Zincon.DSPNormalModeVegetation", "Icarus Gardening", "1.4.3")] [BepInProcess("DSPGAME.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "Zincon.DSPNormalModeVegetation"; public const string PluginName = "Icarus Gardening"; public const string PluginVersion = "1.4.3"; private Harmony harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { VegetationModePatches.ValidateTargets(); harmony = new Harmony("Zincon.DSPNormalModeVegetation"); harmony.PatchAll(typeof(VegetationModePatches)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Icarus Gardening 1.4.3 loaded. Native vegetation placement, removal, and scaling are available in regular games."); } catch (Exception ex) { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to enable normal-mode vegetation placement. No sandbox or abnormality flags were changed.\n" + ex)); } } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; Log = null; } } internal static class VegetationCategoryCatalog { internal const int FirstCategory = 1; internal const int LastCategory = 6; internal const int NativeVegetationCategory = 3; internal const int NativeRemovalCategory = 10; private const int LocalCategory = 1; private const int OrganicCategory = 2; private const int TerrestrialCategory = 3; private const int UninhabitableCategory = 4; private const int OtherCategory = 5; internal const int GalacticScaleCategory = 6; private const int LastVanillaThemeId = 25; private static readonly int[] BundledGalacticScaleVegetationIds = new int[34] { 25, 26, 32, 36, 37, 39, 41, 42, 43, 45, 46, 47, 101, 102, 103, 104, 106, 121, 124, 125, 126, 601, 602, 603, 604, 605, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1023 }; private static readonly Dictionary VanillaThemeCategoriesById = CreateVanillaThemeCategoriesById(); private static readonly Dictionary VanillaThemeCategoriesByName = CreateVanillaThemeCategoriesByName(); private static readonly HashSet VanillaGalacticScaleThemeNames = CreateVanillaGalacticScaleThemeNames(); private static readonly HashSet KnownCustomGalacticScaleThemeNames = CreateKnownCustomGalacticScaleThemeNames(); private static Dictionary> categoryVegetationIds; private static string lastClassificationDiagnostic; internal static bool IsCustomCategory(int category) { if (category >= 1) { return category <= 6; } return false; } internal static string GetTitle(int category) { bool isZHCN = Localization.isZHCN; switch (category) { case 1: if (!isZHCN) { return "Local Vegetation"; } return "本地植被"; case 2: if (!isZHCN) { return "Organic Planet Vegetation"; } return "有机行星植被"; case 3: if (!isZHCN) { return "Terrestrial Planets"; } return "类地行星"; case 4: if (!isZHCN) { return "Uninhabitable"; } return "不适宜生存"; case 5: if (!isZHCN) { return "Other"; } return "其它"; case 6: return "GalacticScale"; default: return string.Empty; } } internal static void Rebuild() { categoryVegetationIds = new Dictionary>(); for (int i = 1; i <= 6; i++) { categoryVegetationIds.Add(i, new HashSet()); } AddBundledGalacticScaleVegetation(categoryVegetationIds[6]); PlanetData localPlanet = GameMain.localPlanet; if (localPlanet != null && (Object)(object)LDB.themes != (Object)null) { AddThemeVegetation(((ProtoSet)(object)LDB.themes).Select(localPlanet.theme), categoryVegetationIds[1]); } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(KnownCustomGalacticScaleThemeNames, StringComparer.OrdinalIgnoreCase); HashSet vanillaVegetationSignatures = CollectVanillaVegetationSignatures(); int num = CollectDspAddPlanetThemeIds(hashSet); int num2 = CollectGalacticScaleLibrary(hashSet, hashSet2, categoryVegetationIds[6], vanillaVegetationSignatures); int[] array = new int[7]; int num3 = 0; List list = new List(); if ((Object)(object)LDB.themes != (Object)null && ((ProtoSet)(object)LDB.themes).dataArray != null) { ThemeProto[] dataArray = ((ProtoSet)(object)LDB.themes).dataArray; foreach (ThemeProto val in dataArray) { if (val == null) { continue; } bool flag = hashSet.Contains(((Proto)val).ID) || ContainsThemeName(hashSet2, ((Proto)val).Name) || ContainsThemeName(hashSet2, ((Proto)val).name); int num4 = (flag ? 6 : GetThemeCategory(val)); bool flag2 = !flag || IsCustomVegetationSet(GetThemeVegetationIds(val), vanillaVegetationSignatures); if (flag2) { AddThemeVegetation(val, categoryVegetationIds[num4]); if (flag) { num3++; } } array[num4]++; list.Add($"{((Proto)val).ID}:{GetThemeDiagnosticName(val)}" + "->" + GetDiagnosticCategoryName(num4) + $"[{CountThemeVegetation(val)}]" + ((!flag) ? string.Empty : (flag2 ? "(GS-custom)" : "(GS-clone)"))); } } string text = string.Join("; ", list.ToArray()); if (!string.Equals(text, lastClassificationDiagnostic, StringComparison.Ordinal)) { lastClassificationDiagnostic = text; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Vegetation theme classification: " + text)); } } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("Vegetation categories rebuilt from vanilla theme IDs and external registries: " + $"bundled-gs-vegetation={BundledGalacticScaleVegetationIds.Length}, " + $"vanilla-organic-themes={array[2]}, " + $"vanilla-terrestrial-themes={array[3]}, " + $"vanilla-uninhabitable-themes={array[4]}, " + $"other-themes={array[5]}, " + $"gs-themes={array[6]}, " + $"gs-custom-vegetation-themes={num3}, " + $"dspaddplanet-registry={num}, " + $"gs-library={num2}; " + $"local={categoryVegetationIds[1].Count}, " + $"organic={categoryVegetationIds[2].Count}, " + $"terrestrial={categoryVegetationIds[3].Count}, " + $"uninhabitable={categoryVegetationIds[4].Count}, " + $"other={categoryVegetationIds[5].Count}, " + $"galactic-scale={categoryVegetationIds[6].Count}.")); } } private static void AddBundledGalacticScaleVegetation(HashSet vegetationIds) { int[] bundledGalacticScaleVegetationIds = BundledGalacticScaleVegetationIds; foreach (int item in bundledGalacticScaleVegetationIds) { vegetationIds.Add(item); } } internal static HashSet CollectVegetationIds(int category) { if (categoryVegetationIds == null) { Rebuild(); } if (!categoryVegetationIds.TryGetValue(category, out var value)) { return new HashSet(); } return value; } private static int GetThemeCategory(ThemeProto theme) { if (theme == null) { return 5; } if (VanillaThemeCategoriesById.TryGetValue(((Proto)theme).ID, out var value) || TryGetVanillaThemeCategory(((Proto)theme).Name, out value) || TryGetVanillaThemeCategory(((Proto)theme).name, out value)) { return value; } if (ContainsThemeName(KnownCustomGalacticScaleThemeNames, ((Proto)theme).Name) || ContainsThemeName(KnownCustomGalacticScaleThemeNames, ((Proto)theme).name)) { return 6; } return 5; } private static bool TryGetVanillaThemeCategory(string themeName, out int category) { if (!string.IsNullOrWhiteSpace(themeName) && VanillaThemeCategoriesByName.TryGetValue(themeName.Trim(), out category)) { return true; } category = 5; return false; } private static Dictionary CreateVanillaThemeCategoriesById() { return new Dictionary { [1] = 2, [2] = 5, [3] = 5, [4] = 5, [5] = 5, [6] = 3, [7] = 4, [8] = 2, [9] = 4, [10] = 4, [11] = 3, [12] = 3, [13] = 4, [14] = 2, [15] = 2, [16] = 2, [17] = 4, [18] = 2, [19] = 3, [20] = 4, [21] = 5, [22] = 2, [23] = 3, [24] = 4, [25] = 2 }; } private static Dictionary CreateVanillaThemeCategoriesByName() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); AddThemes(dictionary, 2, "Ocean 1", "Ocean 2", "Ocean 3", "Ocean 4", "Ocean 5", "Ocean 6", "Desert 8", "Desert 11"); AddThemes(dictionary, 3, "Desert 1", "Desert 3", "Desert 4", "Desert 6", "Desert 9"); AddThemes(dictionary, 4, "Lava 1", "Volcanic 1", "Desert 5"); AddThemes(dictionary, 4, "Desert 2", "Ice 1", "Desert 7", "Desert 10"); AddThemes(dictionary, 5, "Gas 1", "Gas 2", "Gas 3", "Gas 4", "Gas 5"); return dictionary; } private static HashSet CreateVanillaGalacticScaleThemeNames() { return new HashSet(StringComparer.OrdinalIgnoreCase) { "Mediterranean", "GasGiant", "GasGiant2", "IceGiant", "IceGiant2", "AridDesert", "AshenGelisol", "OceanicJungle", "Lava", "IceGelisol", "Barren", "Gobi", "VolcanicAsh", "RedStone", "Prairie", "OceanWorld", "IceLake", "Hurricane", "SaltLake", "Sakura", "GasGiant3", "GasGiant4", "IceGiant3", "IceGiant4", "GasGiant5", "AridDesert2", "IceGelisol2", "IceGelisol3", "Savanna", "CrystalDesert", "FrozenTundra", "PandoraSwamp", "PandoraSwamp2" }; } private static HashSet CreateKnownCustomGalacticScaleThemeNames() { return new HashSet(StringComparer.OrdinalIgnoreCase) { "AridDesertsmol", "AridDesert2smol", "AshenGelisolsmol", "Barrensmol", "BarrenSatellite", "Beach", "BeachCold", "Center", "Comet", "Comet11", "Comet12", "Comet13", "Comet14", "CrystalDesertsmol", "DwarfPlanet", "FrozenTundrasmol", "GiganticForest", "GiganticForestCold", "Gobismol", "HotObsidian", "Hurricanesmol", "IceGelisolsmol", "IceGelisol2smol", "IceGelisol3smol", "IceLakesmol", "IceMalusol", "IceMalusmol", "Inferno", "Lavasmol", "MoltenWorld", "Obsidian", "OilGiant", "Pandora", "PandoraCold", "RedForest", "RedForestCold", "RemovedSmallPlanetTheme34", "RemovedSmallPlanetTheme35", "RemovedSmallPlanetTheme36", "SaltLakesmol", "SulfurSea", "VolcanicAshsmol", "AcidGreenhouse" }; } private static void AddThemes(Dictionary categories, int category, params string[] themeNames) { foreach (string key in themeNames) { categories.Add(key, category); } } private static int CollectDspAddPlanetThemeIds(HashSet themeIds) { try { if (!((FindLoadedType("DSPAddPlanet.GalacticScaleThemeRegistry")?.GetProperty("ThemeNameToId", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(null, null) is IEnumerable enumerable)) { return 0; } foreach (object item in enumerable) { if (TryReadIntMember(item, "Value", out var value) && value > 0) { themeIds.Add(value); } } return themeIds.Count; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not read DspAddplanet's GalacticScale theme registry: " + ex.Message)); } return 0; } } private static int CollectGalacticScaleLibrary(HashSet customThemeIds, HashSet customThemeNames, HashSet vegetationIds, HashSet vanillaVegetationSignatures) { try { if (!((FindLoadedType("GalacticScale.GSSettings")?.GetProperty("ThemeLibrary", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(null, null) is IEnumerable enumerable)) { return 0; } int num = 0; foreach (object item in enumerable) { object obj = ReadMember(item, "Key"); object obj2 = ReadMember(item, "Value"); if (obj2 == null) { continue; } num++; string text = obj as string; string text2 = ReadMember(obj2, "Name") as string; if ((!string.IsNullOrWhiteSpace(text) && !VanillaGalacticScaleThemeNames.Contains(text)) || (!string.IsNullOrWhiteSpace(text2) && !VanillaGalacticScaleThemeNames.Contains(text2))) { HashSet hashSet = new HashSet(); AddReflectedThemeVegetation(obj2, hashSet); if (IsCustomVegetationSet(hashSet, vanillaVegetationSignatures)) { vegetationIds.UnionWith(hashSet); } AddThemeName(customThemeNames, text); AddThemeName(customThemeNames, text2); if (TryReadIntMember(obj2, "LDBThemeId", out var value) && value > 25) { customThemeIds.Add(value); } } } return num; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not read GalacticScale's theme library: " + ex.Message)); } return 0; } } private static Type FindLoadedType(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } private static object ReadMember(object instance, string memberName) { if (instance == null) { return null; } Type type = instance.GetType(); PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(instance, null); } return type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance); } private static bool TryReadIntMember(object instance, string memberName, out int value) { if (ReadMember(instance, memberName) is int num) { value = num; return true; } value = 0; return false; } private static void AddReflectedThemeVegetation(object theme, HashSet vegetationIds) { for (int i = 0; i < 6; i++) { if (!(ReadMember(theme, "Vegetables" + i) is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { if (item is int num && num > 0) { vegetationIds.Add(num); } } } } private static void AddThemeName(HashSet names, string name) { if (!string.IsNullOrWhiteSpace(name)) { names.Add(name.Trim()); } } private static bool ContainsThemeName(HashSet names, string name) { if (!string.IsNullOrWhiteSpace(name)) { return names.Contains(name.Trim()); } return false; } private static string GetThemeDiagnosticName(ThemeProto theme) { string text = ((!string.IsNullOrWhiteSpace(((Proto)theme).Name)) ? ((Proto)theme).Name : ((Proto)theme).name); string text2 = ((!string.IsNullOrWhiteSpace(theme.displayName)) ? theme.displayName : theme.DisplayName); if (!string.IsNullOrWhiteSpace(text2) && !string.Equals(text, text2, StringComparison.Ordinal)) { return (text ?? "") + "/" + text2; } return text ?? ""; } private static string GetDiagnosticCategoryName(int category) { return category switch { 2 => "organic", 3 => "terrestrial", 4 => "uninhabitable", 6 => "galactic-scale", _ => "other", }; } private static int CountThemeVegetation(ThemeProto theme) { return GetThemeVegetationIds(theme).Count; } private static HashSet CollectVanillaVegetationSignatures() { HashSet hashSet = new HashSet(StringComparer.Ordinal); if (((ProtoSet)(object)LDB.themes)?.dataArray == null) { return hashSet; } ThemeProto[] dataArray = ((ProtoSet)(object)LDB.themes).dataArray; foreach (ThemeProto val in dataArray) { if (val != null && VanillaThemeCategoriesById.ContainsKey(((Proto)val).ID)) { hashSet.Add(GetVegetationSignature(GetThemeVegetationIds(val))); } } return hashSet; } private static bool IsCustomVegetationSet(HashSet vegetationIds, HashSet vanillaVegetationSignatures) { if (vegetationIds.Count > 0) { return !vanillaVegetationSignatures.Contains(GetVegetationSignature(vegetationIds)); } return false; } private static string GetVegetationSignature(HashSet vegetationIds) { int[] array = new int[vegetationIds.Count]; vegetationIds.CopyTo(array); Array.Sort(array); return string.Join(",", array); } private static HashSet GetThemeVegetationIds(ThemeProto theme) { HashSet hashSet = new HashSet(); AddThemeVegetation(theme, hashSet); return hashSet; } private static void AddThemeVegetation(ThemeProto theme, HashSet vegetationIds) { if (theme != null) { AddVegetationArray(theme.Vegetables0, vegetationIds); AddVegetationArray(theme.Vegetables1, vegetationIds); AddVegetationArray(theme.Vegetables2, vegetationIds); AddVegetationArray(theme.Vegetables3, vegetationIds); AddVegetationArray(theme.Vegetables4, vegetationIds); AddVegetationArray(theme.Vegetables5, vegetationIds); } } private static void AddVegetationArray(int[] source, HashSet vegetationIds) { if (source == null) { return; } foreach (int num in source) { if (num > 0) { vegetationIds.Add(num); } } } } internal static class VegetationCategoryUi { private static UISandboxMenu snapshotMenu; private static bool[] originalButtonActive; private static Sprite[] originalIcons; private static TipSettings[] originalTips; private static Text heading; private static string originalHeadingText; internal static bool TryApplyRegularChrome(UISandboxMenu menu, int selectedCategory, out string error) { //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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (!TryCapture(menu, out error)) { return false; } HashSet hashSet = new HashSet(); for (int i = 1; i < menu.categoryButtons.Length; i++) { UIButton val = menu.categoryButtons[i]; if ((Object)(object)val == (Object)null) { continue; } bool flag = VegetationCategoryCatalog.IsCustomCategory(i); bool flag2 = i == 10; ((Component)val).gameObject.SetActive(flag || flag2); val.highlighted = (flag2 ? (menu.currentCategory == 10) : (flag && menu.currentCategory == 3 && selectedCategory == i)); if (flag) { TipSettings tips = val.tips; tips.tipTitle = VegetationCategoryCatalog.GetTitle(i); tips.tipText = string.Empty; val.tips = tips; int representativeId; Sprite val2 = FindRepresentativeSprite(VegetationCategoryCatalog.CollectVegetationIds(i), hashSet, out representativeId); if ((Object)(object)val2 != (Object)null) { hashSet.Add(representativeId); } menu.categoryIcons[i].sprite = val2 ?? originalIcons[i]; } } heading.text = VegetationCategoryCatalog.GetTitle((!VegetationCategoryCatalog.IsCustomCategory(selectedCategory)) ? 1 : selectedCategory); return true; } internal static void RestoreNativeChrome(UISandboxMenu menu) { //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) if (snapshotMenu != menu || originalButtonActive == null || originalIcons == null || originalTips == null) { return; } int num = Math.Min(menu.categoryButtons.Length, originalButtonActive.Length); for (int i = 1; i < num; i++) { UIButton val = menu.categoryButtons[i]; if (!((Object)(object)val == (Object)null)) { ((Component)val).gameObject.SetActive(originalButtonActive[i]); val.tips = originalTips[i]; if (i < menu.categoryIcons.Length && (Object)(object)menu.categoryIcons[i] != (Object)null) { menu.categoryIcons[i].sprite = originalIcons[i]; } } } if ((Object)(object)heading != (Object)null) { heading.text = originalHeadingText; } } internal static void HideCategoryButtons(UISandboxMenu menu) { if (menu?.categoryButtons == null) { return; } for (int i = 1; i < menu.categoryButtons.Length; i++) { UIButton val = menu.categoryButtons[i]; if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(false); } } } internal static bool TrySetVegetationLayout(UISandboxMenu menu, Dictionary vegetationButtons, UIButton vegetationButtonPrefab, int selectedCategory, out string error) { //IL_0156: 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_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) if (!TryCapture(menu, out error)) { return false; } if (vegetationButtons == null || menu.localVegeButtons == null || menu.otherVegeButtons == null || (Object)(object)menu.localVegeGroup == (Object)null || (Object)(object)menu.otherVegeGroup == (Object)null || (Object)(object)menu.vegeScrollViewConent == (Object)null) { error = "UISandboxMenu vegetation button storage or layout groups were not found."; return false; } if (!VegetationCategoryCatalog.IsCustomCategory(selectedCategory)) { error = $"Unexpected vegetation category {selectedCategory}."; return false; } if (!EnsureBundledVegetationButtons(menu, vegetationButtons, vegetationButtonPrefab, out error)) { return false; } HashSet hashSet = VegetationCategoryCatalog.CollectVegetationIds(selectedCategory); List orderedButtonIds = GetOrderedButtonIds(vegetationButtons); menu.localVegeButtons.Clear(); menu.otherVegeButtons.Clear(); ((Component)menu.localVegeGroup).gameObject.SetActive(true); HideUnselectedVegetationGroup(menu); foreach (int item in orderedButtonIds) { UIButton val = vegetationButtons[item]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).GetComponent() == (Object)null) { error = $"Vegetation button {item} is incomplete."; return false; } if (hashSet.Contains(item)) { PlaceButton(val, menu.localVegeGroup, menu.localVegeButtons); } else { PlaceButton(val, menu.otherVegeGroup, menu.otherVegeButtons); } } menu.localVegeGroup.sizeDelta = new Vector2(menu.localVegeGroup.sizeDelta.x, (float)((menu.localVegeButtons.Count / 12 + 1) * 50 + 40)); menu.otherVegeGroup.sizeDelta = new Vector2(menu.otherVegeGroup.sizeDelta.x, (float)((menu.otherVegeButtons.Count / 12 + 1) * 50 + 40)); menu.vegeScrollViewConent.anchoredPosition = new Vector2(menu.vegeScrollViewConent.anchoredPosition.x, 0f); heading.text = VegetationCategoryCatalog.GetTitle(selectedCategory); error = null; return true; } private static bool EnsureBundledVegetationButtons(UISandboxMenu menu, Dictionary vegetationButtons, UIButton vegetationButtonPrefab, out string error) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 if ((Object)(object)vegetationButtonPrefab == (Object)null) { error = "The native vegetation button prefab was not found."; return false; } foreach (int item in VegetationCategoryCatalog.CollectVegetationIds(6)) { if (vegetationButtons.ContainsKey(item)) { continue; } VegeProto val = ((ProtoSet)(object)LDB.veges)?.Select(item); if (val == null || (Object)(object)val.iconSprite == (Object)null || (int)val.Type == 8) { continue; } UIButton val2 = Object.Instantiate(vegetationButtonPrefab, (Transform)(object)menu.otherVegeGroup); if ((Object)(object)val2 == (Object)null || ((Component)val2).transform.childCount == 0 || (Object)(object)((Component)((Component)val2).transform.GetChild(0)).GetComponent() == (Object)null) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)((Component)val2).gameObject); } error = $"Could not create the native vegetation button for {item}."; return false; } ((Component)((Component)val2).transform.GetChild(0)).GetComponent().sprite = val.iconSprite; val2.data = ((Proto)val).ID; ((Component)val2).gameObject.SetActive(true); val2.onClick += menu.OnChildButtonClick; vegetationButtons.Add(((Proto)val).ID, val2); } error = null; return true; } internal static void HideUnselectedVegetationGroup(UISandboxMenu menu) { if ((Object)(object)menu?.otherVegeGroup != (Object)null) { ((Component)menu.otherVegeGroup).gameObject.SetActive(false); } } internal static void UpdateScaleDisplay(int selectedCategory, bool hasSelectedVegetation, float scale) { if (!((Object)(object)heading == (Object)null) && VegetationCategoryCatalog.IsCustomCategory(selectedCategory)) { string title = VegetationCategoryCatalog.GetTitle(selectedCategory); heading.text = (hasSelectedVegetation ? (title + (Localization.isZHCN ? " | 倍率 " : " | Scale ") + scale.ToString("0.00") + "x") : title); } } private static bool TryCapture(UISandboxMenu menu, out string error) { //IL_0153: 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) if ((Object)(object)menu == (Object)null || menu.categoryButtons == null || menu.categoryButtons.Length <= 6 || menu.categoryIcons == null || menu.categoryIcons.Length <= 6 || menu.categoryTips == null || menu.categoryTips.Length <= 6 || (Object)(object)menu.localVegeGroup == (Object)null) { error = "UISandboxMenu category slots 1-6 or the vegetation group were not found."; return false; } for (int i = 1; i <= 6; i++) { if ((Object)(object)menu.categoryButtons[i] == (Object)null || (Object)(object)menu.categoryIcons[i] == (Object)null || (Object)(object)menu.categoryTips[i] == (Object)null) { error = $"UISandboxMenu category slot {i} is incomplete."; return false; } } if (snapshotMenu == menu && originalButtonActive != null && (Object)(object)heading != (Object)null) { error = null; return true; } Text val = FindHeading(menu.localVegeGroup); if ((Object)(object)val == (Object)null) { error = "The native local-vegetation heading was not found."; return false; } snapshotMenu = menu; originalButtonActive = new bool[menu.categoryButtons.Length]; originalIcons = (Sprite[])(object)new Sprite[menu.categoryButtons.Length]; originalTips = (TipSettings[])(object)new TipSettings[menu.categoryButtons.Length]; for (int j = 0; j < menu.categoryButtons.Length; j++) { UIButton val2 = menu.categoryButtons[j]; if ((Object)(object)val2 != (Object)null) { originalButtonActive[j] = ((Component)val2).gameObject.activeSelf; originalTips[j] = val2.tips; } if (j < menu.categoryIcons.Length && (Object)(object)menu.categoryIcons[j] != (Object)null) { originalIcons[j] = menu.categoryIcons[j].sprite; } } heading = val; originalHeadingText = heading.text; error = null; return true; } private static Text FindHeading(RectTransform group) { Text[] componentsInChildren = ((Component)group).GetComponentsInChildren(true); foreach (Text val in componentsInChildren) { if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform.parent == (Object)(object)group) { return val; } } return null; } private static List GetOrderedButtonIds(Dictionary buttons) { List list = new List(buttons.Count); HashSet hashSet = new HashSet(); if ((Object)(object)LDB.veges != (Object)null && ((ProtoSet)(object)LDB.veges).dataArray != null) { VegeProto[] dataArray = ((ProtoSet)(object)LDB.veges).dataArray; foreach (VegeProto val in dataArray) { if (val != null && buttons.ContainsKey(((Proto)val).ID) && hashSet.Add(((Proto)val).ID)) { list.Add(((Proto)val).ID); } } } foreach (int key in buttons.Keys) { if (hashSet.Add(key)) { list.Add(key); } } return list; } private static Sprite FindRepresentativeSprite(HashSet vegetationIds, HashSet excludedIds, out int representativeId) { representativeId = 0; if ((Object)(object)LDB.veges == (Object)null || ((ProtoSet)(object)LDB.veges).dataArray == null) { return null; } return FindRepresentativeSprite(vegetationIds, excludedIds, out representativeId, excludeUsed: true) ?? FindRepresentativeSprite(vegetationIds, excludedIds, out representativeId, excludeUsed: false); } private static Sprite FindRepresentativeSprite(HashSet vegetationIds, HashSet excludedIds, out int representativeId, bool excludeUsed) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 representativeId = 0; VegeProto[] dataArray = ((ProtoSet)(object)LDB.veges).dataArray; for (int i = 90; i < dataArray.Length; i++) { VegeProto val = dataArray[i]; if (val != null && vegetationIds.Contains(((Proto)val).ID) && (!excludeUsed || !excludedIds.Contains(((Proto)val).ID)) && (Object)(object)val.iconSprite != (Object)null && (int)val.Type != 8) { representativeId = ((Proto)val).ID; return val.iconSprite; } } return null; } private static void PlaceButton(UIButton button, RectTransform group, List destination) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) ((Component)button).transform.SetParent((Transform)(object)group); ((Component)button).GetComponent().anchoredPosition = new Vector2((float)(destination.Count % 12 * 50), (float)(-(destination.Count / 12) * 50 - 36)); destination.Add(button); } } [HarmonyPatch] internal static class VegetationModePatches { private static readonly MethodInfo SandboxToolsGetter = AccessTools.PropertyGetter(typeof(GameMain), "sandboxToolsEnabled"); private static readonly MethodInfo VegetationAvailabilityMethod = AccessTools.Method(typeof(VegetationModePatches), "IsVegetationToolAvailable", (Type[])null, (Type[])null); private static readonly MethodInfo VectorOneGetter = AccessTools.PropertyGetter(typeof(Vector3), "one"); private static readonly MethodInfo VegetationScaleVectorMethod = AccessTools.Method(typeof(VegetationModePatches), "GetVegetationScaleVector", (Type[])null, (Type[])null); private const float DefaultVegetationScaleStep = 0.1f; private const float ShiftVegetationScaleStep = 0.5f; private const float ControlVegetationScaleStep = 0.01f; private const float MinimumVegetationScale = 0.1f; private const float MaximumVegetationScale = 30f; private const float VegetationScaleRepeatDelay = 0.3f; private const float VegetationScaleRepeatInterval = 0.08f; private static bool runtimeDisabled; private static bool runtimeErrorLogged; private static int selectedVegetationCategory; private static int scaledVegetationProtoId; private static float vegetationScale = 1f; private static KeyCode heldVegetationScaleKey = (KeyCode)0; private static float nextVegetationScaleRepeatTime; internal static void ValidateTargets() { runtimeDisabled = false; runtimeErrorLogged = false; scaledVegetationProtoId = 0; vegetationScale = 1f; ResetVegetationScaleInput(); RequireMethod(typeof(UIFunctionPanel), "_OnUpdate"); RequireMethod(typeof(UIGame), "_OnUpdate"); RequireMethod(typeof(PlayerAction_Plant), "DetermineActive"); RequireMethod(typeof(UISandboxMenu), "_OnOpen"); RequireMethod(typeof(UISandboxMenu), "_OnUpdate"); RequireMethod(typeof(UISandboxMenu), "SetCurrentCategory"); RequireMethod(typeof(UISandboxMenu), "SetVegeLayout"); RequireMethod(typeof(UISandboxMenu), "OnCategoryButtonClick"); RequireMethod(typeof(UISandboxMenu), "OnChildButtonClick"); RequireMethod(typeof(PlayerAction_Plant), "SetHandVein"); RequireMethod(typeof(PlayerAction_Plant), "SetHandVege"); RequireMethod(typeof(PlayerAction_Plant), "SetRemovalMode"); RequireMethod(typeof(PlayerAction_Plant), "DeterminePlantPreview"); RequireMethod(typeof(PlayerAction_Plant), "DetermineRemovePreviews"); RequireMethod(typeof(PlayerAction_Plant), "PlantVeinFinally"); RequireMethod(typeof(PlayerAction_Plant), "PlantVegeFinally"); RequireMethod(typeof(PlayerControlGizmo), "SetPlantPreviewGizmo"); RequireField(typeof(UISandboxMenu), "vegeButtonDic"); RequireField(typeof(UISandboxMenu), "vegeButtonPrefab"); RequireField(typeof(UISandboxMenu), "lastTheme"); RequireField(typeof(UISandboxMenu), "actionPlant"); RequireField(typeof(UISandboxMenu), "veinFilterToggle"); RequireField(typeof(UISandboxMenu), "vegeFilterToggle"); RequireField(typeof(PlayerControlGizmo), "plantPreviewGizmo"); if (SandboxToolsGetter == null || VegetationAvailabilityMethod == null || VectorOneGetter == null || VegetationScaleVectorMethod == null) { throw new MissingMethodException("The sandbox permission getter or its replacement was not found."); } RequireSandboxCheckCount(typeof(UIFunctionPanel), "_OnUpdate", 3); RequireSandboxCheckCount(typeof(UIGame), "_OnUpdate", 1); RequireSandboxCheckCount(typeof(PlayerAction_Plant), "DetermineActive", 1); } private static void RequireMethod(Type type, string methodName) { if (AccessTools.Method(type, methodName, (Type[])null, (Type[])null) == null) { throw new MissingMethodException(type.FullName, methodName); } } private static void RequireField(Type type, string fieldName) { if (AccessTools.Field(type, fieldName) == null) { throw new MissingFieldException(type.FullName, fieldName); } } private static void RequireSandboxCheckCount(Type type, string methodName, int expectedCount) { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); int num = 0; foreach (CodeInstruction originalInstruction in PatchProcessor.GetOriginalInstructions((MethodBase)methodInfo, (ILGenerator)null)) { if ((originalInstruction.opcode == OpCodes.Call || originalInstruction.opcode == OpCodes.Callvirt) && object.Equals(originalInstruction.operand, SandboxToolsGetter)) { num++; } } if (num != expectedCount) { throw new InvalidOperationException($"{type.FullName}.{methodName} contained {num} sandbox checks; " + $"expected {expectedCount}."); } } private static bool IsVegetationToolAvailable() { if (GameMain.sandboxToolsEnabled) { return true; } if (runtimeDisabled) { return false; } GameData data = GameMain.data; if (data != null && data.gameDesc != null) { return !data.gameDesc.isSandboxMode; } return false; } private static bool IsVegetationOnlyMode() { if (IsVegetationToolAvailable()) { return !GameMain.sandboxToolsEnabled; } return false; } private static bool ShouldRestrictSandboxTools() { GameData data = GameMain.data; if (!GameMain.sandboxToolsEnabled && data != null && data.gameDesc != null) { return !data.gameDesc.isSandboxMode; } return false; } private static bool IsAllowedCategory(int category) { if (!ShouldRestrictSandboxTools()) { return true; } if (category != 0 && category != 10) { if (!runtimeDisabled) { return category == 3; } return false; } return true; } private static Vector3 GetVegetationScaleVector() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRestrictSandboxTools()) { return Vector3.one; } return Vector3.one * vegetationScale; } private static void ConfigureVegetationRemoval(PlayerAction_Plant actionPlant) { if (actionPlant != null && ShouldRestrictSandboxTools()) { actionPlant.veinRemovalFilter = false; actionPlant.vegeRemovalFilter = true; } } private static void UpdateVegetationScaleInput(PlayerAction_Plant actionPlant) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Invalid comparison between Unknown and I4 //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_0078: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRestrictSandboxTools() || actionPlant == null || actionPlant.handVegeProtoId <= 0 || actionPlant.handPlantPreview == null || VFInput.onGUI || VFInput.inputing || !Application.isFocused) { ResetVegetationScaleInput(); return; } bool key = Input.GetKey((KeyCode)113); bool key2 = Input.GetKey((KeyCode)101); if (key == key2) { ResetVegetationScaleInput(); return; } KeyCode val = (KeyCode)(key ? 113 : 101); bool increase = (int)val == 113; float unscaledTime = Time.unscaledTime; if (Input.GetKeyDown(val) && heldVegetationScaleKey != val) { heldVegetationScaleKey = val; nextVegetationScaleRepeatTime = unscaledTime + 0.3f; ApplyVegetationScaleStep(increase); } else if (heldVegetationScaleKey == val && !(unscaledTime < nextVegetationScaleRepeatTime)) { nextVegetationScaleRepeatTime = unscaledTime + 0.08f; ApplyVegetationScaleStep(increase); } } private static void ApplyVegetationScaleStep(bool increase) { bool controlHeld = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); bool shiftHeld = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); float vegetationScaleStep = GetVegetationScaleStep(controlHeld, shiftHeld); vegetationScale = AdjustVegetationScale(vegetationScale, increase, vegetationScaleStep); } private static float GetVegetationScaleStep(bool controlHeld, bool shiftHeld) { if (controlHeld) { return 0.01f; } if (!shiftHeld) { return 0.1f; } return 0.5f; } private static float AdjustVegetationScale(float currentScale, bool increase, float step) { return Mathf.Clamp(Mathf.Round((currentScale + (increase ? step : (0f - step))) * 100f) / 100f, 0.1f, 30f); } private static void ResetVegetationScaleInput() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) heldVegetationScaleKey = (KeyCode)0; nextVegetationScaleRepeatTime = 0f; } private static IEnumerable ReplaceSandboxChecks(IEnumerable instructions, int expectedCount, string targetName) { int replacedCount = 0; foreach (CodeInstruction instruction in instructions) { if ((instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) && object.Equals(instruction.operand, SandboxToolsGetter)) { instruction.opcode = OpCodes.Call; instruction.operand = VegetationAvailabilityMethod; replacedCount++; } yield return instruction; } if (replacedCount != expectedCount) { throw new InvalidOperationException($"{targetName} contained {replacedCount} sandbox checks; expected {expectedCount}. " + "The game method has changed, so the patch was not applied."); } } [HarmonyTranspiler] [HarmonyPatch(typeof(UIFunctionPanel), "_OnUpdate")] private static IEnumerable UIFunctionPanelOnUpdateTranspiler(IEnumerable instructions) { return ReplaceSandboxChecks(instructions, 3, "UIFunctionPanel._OnUpdate"); } [HarmonyPostfix] [HarmonyPatch(typeof(UIFunctionPanel), "_OnUpdate")] private static void UIFunctionPanelOnUpdatePostfix(UIFunctionPanel __instance) { if (IsVegetationOnlyMode()) { GameHistoryData history = GameMain.history; Player mainPlayer = GameMain.mainPlayer; bool flag = history != null && mainPlayer != null && !history.TechUnlocked(1001) && !mainPlayer.sailing && !__instance.showSandBoxMenu; if (__instance.needResearchTipGroup.activeSelf != flag) { __instance.needResearchTipGroup.SetActive(flag); } } } [HarmonyTranspiler] [HarmonyPatch(typeof(UIGame), "_OnUpdate")] private static IEnumerable UIGameOnUpdateTranspiler(IEnumerable instructions) { return ReplaceSandboxChecks(instructions, 1, "UIGame._OnUpdate"); } [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Plant), "DetermineActive")] private static IEnumerable DetermineActiveTranspiler(IEnumerable instructions) { return ReplaceSandboxChecks(instructions, 1, "PlayerAction_Plant.DetermineActive"); } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerAction_Plant), "DetermineActive")] private static void DetermineActivePostfix(PlayerAction_Plant __instance, ref bool __result) { if (ShouldRestrictSandboxTools() && __instance.handVeinProtoId > 0) { __instance.SetHandVein(0); __result = false; } } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerAction_Plant), "SetHandVege")] private static void SetHandVegePostfix(PlayerAction_Plant __instance, int vegeProtoId) { if (!ShouldRestrictSandboxTools()) { ResetVegetationScaleInput(); } else if (vegeProtoId <= 0 || __instance.handVegeProtoId <= 0) { scaledVegetationProtoId = 0; vegetationScale = 1f; ResetVegetationScaleInput(); } else if (scaledVegetationProtoId != __instance.handVegeProtoId) { scaledVegetationProtoId = __instance.handVegeProtoId; vegetationScale = 1f; ResetVegetationScaleInput(); } } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerAction_Plant), "DeterminePlantPreview")] private static void DeterminePlantPreviewPostfix(PlayerAction_Plant __instance) { UpdateVegetationScaleInput(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerAction_Plant), "DetermineRemovePreviews")] private static void DetermineRemovePreviewsPrefix(PlayerAction_Plant __instance) { ConfigureVegetationRemoval(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerControlGizmo), "SetPlantPreviewGizmo")] private static void SetPlantPreviewGizmoPostfix(ModelPreview preview, ModelGizmo ___plantPreviewGizmo) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (ShouldRestrictSandboxTools() && preview != null && preview.vegeProtoId > 0 && (Object)(object)___plantPreviewGizmo != (Object)null) { ((Component)___plantPreviewGizmo).transform.localScale = GetVegetationScaleVector(); } } [HarmonyPostfix] [HarmonyPatch(typeof(UISandboxMenu), "_OnOpen")] private static void SandboxMenuOnOpenPostfix(UISandboxMenu __instance) { selectedVegetationCategory = 0; ApplyMenuMode(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(UISandboxMenu), "_OnUpdate")] private static void SandboxMenuOnUpdatePostfix(UISandboxMenu __instance, PlayerAction_Plant ___actionPlant, UIToggle ___veinFilterToggle, UIToggle ___vegeFilterToggle) { if (IsVegetationOnlyMode() && __instance.currentCategory == 3 && VegetationCategoryCatalog.IsCustomCategory(selectedVegetationCategory)) { VegetationCategoryUi.HideUnselectedVegetationGroup(__instance); VegetationCategoryUi.UpdateScaleDisplay(selectedVegetationCategory, ___actionPlant != null && ___actionPlant.handVegeProtoId > 0, vegetationScale); } if (IsVegetationOnlyMode() && __instance.currentCategory == 10) { ConfigureVegetationRemoval(___actionPlant); if ((Object)(object)___veinFilterToggle != (Object)null) { ___veinFilterToggle.isOn = false; if ((Object)(object)___veinFilterToggle.toggle != (Object)null) { ((Selectable)___veinFilterToggle.toggle).interactable = false; } } if ((Object)(object)___vegeFilterToggle != (Object)null) { ___vegeFilterToggle.isOn = true; if ((Object)(object)___vegeFilterToggle.toggle != (Object)null) { ((Selectable)___vegeFilterToggle.toggle).interactable = false; } } } else if (!IsVegetationOnlyMode()) { if ((Object)(object)___veinFilterToggle?.toggle != (Object)null) { ((Selectable)___veinFilterToggle.toggle).interactable = true; } if ((Object)(object)___vegeFilterToggle?.toggle != (Object)null) { ((Selectable)___vegeFilterToggle.toggle).interactable = true; } } } private static void ApplyMenuMode(UISandboxMenu menu) { if (GameMain.sandboxToolsEnabled) { VegetationCategoryUi.RestoreNativeChrome(menu); return; } if (!IsVegetationOnlyMode()) { VegetationCategoryUi.HideCategoryButtons(menu); return; } VegetationCategoryCatalog.Rebuild(); if (!VegetationCategoryUi.TryApplyRegularChrome(menu, selectedVegetationCategory, out var error)) { DisableAtRuntime(menu, error); } } private static void DisableAtRuntime(UISandboxMenu menu, string reason) { runtimeDisabled = true; selectedVegetationCategory = 0; menu.SetCurrentCategory(0); VegetationCategoryUi.HideCategoryButtons(menu); if (!runtimeErrorLogged) { runtimeErrorLogged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("The native vegetation category UI was incompatible. Normal-mode vegetation placement has been disabled. " + reason)); } } } [HarmonyPrefix] [HarmonyPatch(typeof(UISandboxMenu), "SetCurrentCategory")] private static bool SetCurrentCategoryPrefix(int category) { return IsAllowedCategory(category); } [HarmonyPrefix] [HarmonyPatch(typeof(UISandboxMenu), "OnCategoryButtonClick")] private static bool OnCategoryButtonClickPrefix(UISandboxMenu __instance, int index) { if (!ShouldRestrictSandboxTools()) { return true; } if (index == 10) { selectedVegetationCategory = 0; return true; } if (runtimeDisabled || !VegetationCategoryCatalog.IsCustomCategory(index)) { return false; } bool flag = __instance.currentCategory == 3 && selectedVegetationCategory == index; selectedVegetationCategory = ((!flag) ? index : 0); __instance.SetCurrentCategory((!flag) ? 3 : 0); if (!IsVegetationOnlyMode()) { return false; } if (!VegetationCategoryUi.TryApplyRegularChrome(__instance, selectedVegetationCategory, out var error)) { DisableAtRuntime(__instance, error); } return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UISandboxMenu), "SetVegeLayout")] private static bool SetVegeLayoutPrefix(UISandboxMenu __instance, Dictionary ___vegeButtonDic, UIButton ___vegeButtonPrefab, ref int ___lastTheme) { if (!IsVegetationOnlyMode()) { return true; } ___lastTheme = 0; if (!VegetationCategoryUi.TrySetVegetationLayout(__instance, ___vegeButtonDic, ___vegeButtonPrefab, selectedVegetationCategory, out var error)) { DisableAtRuntime(__instance, error); } return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UISandboxMenu), "OnChildButtonClick")] private static bool OnChildButtonClickPrefix(UISandboxMenu __instance) { if (ShouldRestrictSandboxTools()) { if (!runtimeDisabled) { return __instance.currentCategory == 3; } return false; } return true; } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerAction_Plant), "SetHandVein")] private static bool SetHandVeinPrefix(int veinProtoId) { if (ShouldRestrictSandboxTools()) { return veinProtoId == 0; } return true; } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerAction_Plant), "SetRemovalMode")] private static void SetRemovalModePostfix(PlayerAction_Plant __instance) { ConfigureVegetationRemoval(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerAction_Plant), "PlantVeinFinally")] private static bool PlantVeinFinallyPrefix() { return !ShouldRestrictSandboxTools(); } [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Plant), "PlantVegeFinally")] private static IEnumerable PlantVegeFinallyTranspiler(IEnumerable instructions) { int replacedCount = 0; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, VectorOneGetter)) { instruction.opcode = OpCodes.Call; instruction.operand = VegetationScaleVectorMethod; replacedCount++; } yield return instruction; } if (replacedCount != 2) { throw new InvalidOperationException("PlayerAction_Plant.PlantVegeFinally contained " + $"{replacedCount} vegetation scale initializers; expected 2. " + "The game method has changed, so scalable vegetation was not enabled."); } } }