Decompiled source of Stone Tweaks v1.0.0

BepInEx/plugins/StoneTweaks/StoneTweaks.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using LithoCost.Core;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("StoneTweaks")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("StoneTweaks")]
[assembly: AssemblyTitle("StoneTweaks")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 LithoCost
{
	[BepInPlugin("com.lithocost.valheim", "Stone Tweaks", "1.0.0")]
	public sealed class LithoCostPlugin : BaseUnityPlugin
	{
		private sealed class PieceRule
		{
			internal readonly ConfigEntry<bool> Enabled;

			internal readonly ConfigEntry<int> Amount;

			internal PieceRule(ConfigEntry<bool> enabled, ConfigEntry<int> amount)
			{
				Enabled = enabled;
				Amount = amount;
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private static class ObjectDbAwakePatch
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				RequestScan();
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class ZNetSceneAwakePatch
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				RequestScan();
			}
		}

		public const string PluginGuid = "com.lithocost.valheim";

		public const string PluginName = "Stone Tweaks";

		public const string PluginVersion = "1.0.0";

		private static LithoCostPlugin? _instance;

		private readonly Dictionary<string, ConfigEntry<int>> _constructionAmounts = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, PieceRule> _pileRules = new Dictionary<string, PieceRule>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, Requirement[]> _originalRequirements = new Dictionary<string, Requirement[]>(StringComparer.OrdinalIgnoreCase);

		private ConfigEntry<string>? _resourceKeywords;

		private LegacyConfigMigration? _legacyConfig;

		private Coroutine? _scanRoutine;

		private bool _hasPendingConfigSave;

		private bool _isSpanish;

		private int _displayOrder;

		private void Awake()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			_instance = this;
			new Harmony("com.lithocost.valheim").PatchAll(typeof(LithoCostPlugin).Assembly);
		}

		internal static void RequestScan()
		{
			if (!((Object)(object)_instance == (Object)null) && _instance._scanRoutine == null)
			{
				_instance._scanRoutine = ((MonoBehaviour)_instance).StartCoroutine(_instance.ScanRoutine());
			}
		}

		private IEnumerator ScanRoutine()
		{
			yield return (object)new WaitForSeconds(2f);
			DetectLanguage();
			_legacyConfig = LegacyConfigMigration.ReadAndRemove(((BaseUnityPlugin)this).Config);
			_hasPendingConfigSave = _legacyConfig.RemovedLegacyEntries;
			BindGeneralConfig();
			((BaseUnityPlugin)this).Logger.LogInfo((object)Text("Stone Tweaks loaded. Material tabs group matching building pieces; structures cost one original rock material by default.", "Stone Tweaks está cargado. Las pestañas agrupan piezas por material; las estructuras cuestan una unidad de su material original por defecto."));
			for (int attempt = 0; attempt < 8; attempt++)
			{
				DiscoverNewPieces();
				yield return (object)new WaitForSeconds(1f);
			}
			SavePendingConfig();
			_scanRoutine = null;
		}

		private void DetectLanguage()
		{
			string text = null;
			try
			{
				Localization instance = Localization.instance;
				text = ((instance != null) ? instance.GetSelectedLanguage() : null);
			}
			catch
			{
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				_isSpanish = text.StartsWith("Spanish", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Español", StringComparison.OrdinalIgnoreCase) || text.StartsWith("es", StringComparison.OrdinalIgnoreCase);
			}
			else
			{
				_isSpanish = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Equals("es", StringComparison.OrdinalIgnoreCase) || CultureInfo.CurrentCulture.TwoLetterISOLanguageName.Equals("es", StringComparison.OrdinalIgnoreCase);
			}
		}

		private string Text(string english, string spanish)
		{
			return _isSpanish ? spanish : english;
		}

		private void BindGeneralConfig()
		{
			_resourceKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("General", "RockResourceKeywords", "stone,marble,grausten,rock,crystal,obsidian", Text("Comma-separated words used to identify rocky materials by their prefab name. Add terms for modded rock or crystal resources.", "Palabras separadas por comas para detectar materiales rocosos por su ID interno. Añade términos de rocas o cristales de otros mods."));
		}

		private void DiscoverNewPieces()
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<Piece> allBuildPieces;
			try
			{
				allBuildPieces = instance.GetAllBuildPieces(true);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)(Text("Could not read Valheim's build-piece list yet: ", "Todavía no se pudo leer la lista de piezas de construcción de Valheim: ") + ex.Message));
				return;
			}
			if (allBuildPieces == null)
			{
				return;
			}
			foreach (Piece item in allBuildPieces)
			{
				if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).gameObject == (Object)null || item.m_resources == null)
				{
					continue;
				}
				string name = ((Object)((Component)item).gameObject).name;
				if (!PieceClassifier.TryClassify(item, name, out var kind))
				{
					continue;
				}
				if (!_originalRequirements.TryGetValue(name, out Requirement[] value))
				{
					value = (Requirement[])item.m_resources.Clone();
					_originalRequirements.Add(name, value);
				}
				List<Requirement> list = value.Where((Requirement requirement) => IsRockResource(GetResourcePrefabName(requirement))).ToList();
				if (list.Count == 0)
				{
					continue;
				}
				string localizedPieceName = GetLocalizedPieceName(item, name);
				foreach (IGrouping<string, Requirement> item2 in list.GroupBy<Requirement, string>((Requirement requirement) => GetResourcePrefabName(requirement), StringComparer.OrdinalIgnoreCase))
				{
					string key = item2.Key;
					int originalAmount = item2.Sum((Requirement requirement) => Math.Max(0, requirement.m_amount));
					string ruleKey = MakeRuleKey(name, key);
					if (kind == RockPieceKind.Pile)
					{
						EnsurePileRule(name, key, originalAmount, localizedPieceName, ruleKey);
					}
					else
					{
						EnsureConstructionAmount(name, key, localizedPieceName, ruleKey);
					}
				}
				ApplyRules(item, value, kind);
			}
		}

		private void EnsureConstructionAmount(string prefabName, string resourcePrefab, string displayName, string ruleKey)
		{
			if (!_constructionAmounts.ContainsKey(ruleKey))
			{
				LegacyPieceSettings legacyPieceSettings = GetLegacyPieceSettings(prefabName);
				int val = ((legacyPieceSettings != null && legacyPieceSettings.Enabled) ? legacyPieceSettings.Amount : (GetLegacyMaterialAmount(resourcePrefab) ?? 1));
				string displayName2 = displayName + " (" + prefabName + ")";
				string description = Text("Amount of the original " + PieceClassifier.GetMaterialDisplayName(resourcePrefab, spanish: false) + " used by this building piece. Set to 0 to remove this resource cost.", "Cantidad de " + PieceClassifier.GetMaterialDisplayName(resourcePrefab, spanish: true) + " original que cuesta esta pieza. Usa 0 para quitar este recurso.");
				string text = "Build_" + prefabName + "_" + resourcePrefab + "_Amount";
				_constructionAmounts.Add(ruleKey, ((BaseUnityPlugin)this).Config.Bind<int>(PieceClassifier.GetMaterialGroup(resourcePrefab), text, Math.Max(0, val), CreateDescription(description, displayName2)));
				_hasPendingConfigSave = true;
			}
		}

		private void EnsurePileRule(string prefabName, string resourcePrefab, int originalAmount, string displayName, string ruleKey)
		{
			if (!_pileRules.ContainsKey(ruleKey))
			{
				LegacyPieceSettings legacyPieceSettings = GetLegacyPileSettings(prefabName, resourcePrefab) ?? GetLegacyPieceSettings(prefabName);
				bool flag = legacyPieceSettings?.Enabled ?? false;
				int num = legacyPieceSettings?.Amount ?? originalAmount;
				string text = displayName + " (" + prefabName + ")";
				string description = Text("Off by default, so this pile keeps its original cost. Enable to change the quantity of its original material.", "Desactivado por defecto para conservar el coste original del montón. Actívalo para cambiar la cantidad de su material original.");
				string description2 = Text("Custom quantity for this material in the pile. The original cost is used while the override is disabled.", "Cantidad personalizada de este material para el montón. Se conserva el coste original mientras la opción esté desactivada.");
				string text2 = "Pile_" + prefabName + "_" + resourcePrefab;
				_pileRules.Add(ruleKey, new PieceRule(((BaseUnityPlugin)this).Config.Bind<bool>(PieceClassifier.GetMaterialGroup(resourcePrefab), text2 + "_Override", flag, CreateDescription(description, text + Text(" — custom cost", " — coste personalizado"))), ((BaseUnityPlugin)this).Config.Bind<int>(PieceClassifier.GetMaterialGroup(resourcePrefab), text2 + "_Amount", Math.Max(0, flag ? num : originalAmount), CreateDescription(description2, text + Text(" — amount", " — cantidad")))));
				_hasPendingConfigSave = true;
			}
		}

		private int? GetLegacyMaterialAmount(string resourcePrefab)
		{
			int value;
			return (_legacyConfig != null && _legacyConfig.MaterialAmounts.TryGetValue(resourcePrefab, out value)) ? new int?(value) : ((int?)null);
		}

		private LegacyPieceSettings? GetLegacyPieceSettings(string prefabName)
		{
			LegacyPieceSettings value;
			return (_legacyConfig != null && _legacyConfig.PieceSettings.TryGetValue(prefabName, out value)) ? value : null;
		}

		private LegacyPieceSettings? GetLegacyPileSettings(string prefabName, string resourcePrefab)
		{
			string key = MakeRuleKey(prefabName, resourcePrefab);
			LegacyPieceSettings value;
			return (_legacyConfig != null && _legacyConfig.PileSettings.TryGetValue(key, out value)) ? value : null;
		}

		private void ApplyRules(Piece piece, Requirement[] original, RockPieceKind kind)
		{
			string name = ((Object)((Component)piece).gameObject).name;
			List<Requirement> list = new List<Requirement>(original.Length);
			foreach (Requirement val in original)
			{
				string resourcePrefabName = GetResourcePrefabName(val);
				if (!IsRockResource(resourcePrefabName) || resourcePrefabName == null)
				{
					list.Add(val);
					continue;
				}
				string key = MakeRuleKey(name, resourcePrefabName);
				ConfigEntry<int> value2;
				if (kind == RockPieceKind.Pile)
				{
					if (!_pileRules.TryGetValue(key, out PieceRule value) || !value.Enabled.Value)
					{
						list.Add(val);
					}
					else
					{
						AddAdjustedRequirement(list, val, value.Amount.Value);
					}
				}
				else if (_constructionAmounts.TryGetValue(key, out value2))
				{
					AddAdjustedRequirement(list, val, value2.Value);
				}
				else
				{
					list.Add(val);
				}
			}
			piece.m_resources = list.ToArray();
		}

		private static void AddAdjustedRequirement(List<Requirement> updated, Requirement requirement, int amount)
		{
			amount = Math.Max(0, amount);
			if (amount > 0)
			{
				updated.Add(CreateAdjustedRequirement(requirement, amount));
			}
		}

		private static Requirement CreateAdjustedRequirement(Requirement original, int amount)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			return new Requirement
			{
				m_resItem = original.m_resItem,
				m_amount = amount,
				m_extraAmountOnlyOneIngredient = 0,
				m_amountPerLevel = 0,
				m_upgraderResource = original.m_upgraderResource,
				m_recover = original.m_recover
			};
		}

		private ConfigDescription CreateDescription(string description, string displayName)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					DispName = displayName,
					Order = _displayOrder--
				}
			});
		}

		private static string MakeRuleKey(string prefabName, string resourcePrefab)
		{
			return prefabName + "\u001f" + resourcePrefab;
		}

		private void SavePendingConfig()
		{
			if (_hasPendingConfigSave)
			{
				((BaseUnityPlugin)this).Config.Save();
				_hasPendingConfigSave = false;
			}
		}

		private bool IsRockResource(string? resourcePrefab)
		{
			if (string.IsNullOrWhiteSpace(resourcePrefab) || _resourceKeywords == null)
			{
				return false;
			}
			string[] source = _resourceKeywords.Value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			return source.Any((string keyword) => !string.IsNullOrWhiteSpace(keyword) && resourcePrefab.IndexOf(keyword.Trim(), StringComparison.OrdinalIgnoreCase) >= 0);
		}

		private static string? GetResourcePrefabName(Requirement? requirement)
		{
			if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null || (Object)(object)((Component)requirement.m_resItem).gameObject == (Object)null)
			{
				return null;
			}
			return ((Object)((Component)requirement.m_resItem).gameObject).name;
		}

		private static string GetLocalizedPieceName(Piece piece, string fallback)
		{
			if (string.IsNullOrWhiteSpace(piece.m_name))
			{
				return fallback;
			}
			try
			{
				Localization instance = Localization.instance;
				if (instance != null)
				{
					string text = instance.Localize(piece.m_name);
					if (!string.IsNullOrWhiteSpace(text))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			return piece.m_name;
		}

		private string Text(string english, string spanish, params object[] arguments)
		{
			return string.Format(CultureInfo.InvariantCulture, _isSpanish ? spanish : english, arguments);
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public string? DispName;

		public int? Order;
	}
}
namespace LithoCost.Core
{
	internal sealed class LegacyConfigMigration
	{
		internal readonly Dictionary<string, int> MaterialAmounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		internal readonly Dictionary<string, LegacyPieceSettings> PieceSettings = new Dictionary<string, LegacyPieceSettings>(StringComparer.OrdinalIgnoreCase);

		internal readonly Dictionary<string, LegacyPieceSettings> PileSettings = new Dictionary<string, LegacyPieceSettings>(StringComparer.OrdinalIgnoreCase);

		internal bool RemovedLegacyEntries;

		internal static LegacyConfigMigration ReadAndRemove(ConfigFile config)
		{
			LegacyConfigMigration legacyConfigMigration = new LegacyConfigMigration();
			if (!(typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(config) is Dictionary<ConfigDefinition, string> dictionary))
			{
				return legacyConfigMigration;
			}
			foreach (KeyValuePair<ConfigDefinition, string> item in dictionary.ToList())
			{
				ConfigDefinition key = item.Key;
				string section = key.Section;
				if (section.StartsWith("Material: ", StringComparison.OrdinalIgnoreCase))
				{
					string key2 = section.Substring("Material: ".Length);
					if (key.Key.Equals("Amount", StringComparison.OrdinalIgnoreCase) && TryReadAmount(item.Value, out var amount))
					{
						legacyConfigMigration.MaterialAmounts[key2] = amount;
					}
					Remove(dictionary, key, legacyConfigMigration);
				}
				else if (section.StartsWith("Piece: ", StringComparison.OrdinalIgnoreCase))
				{
					string key3 = section.Substring("Piece: ".Length);
					ReadPieceSetting(legacyConfigMigration.PieceSettings, key3, key.Key, item.Value);
					Remove(dictionary, key, legacyConfigMigration);
				}
				else if (section.StartsWith("Construction/", StringComparison.OrdinalIgnoreCase))
				{
					ReadConstructionSection(legacyConfigMigration, section, key.Key, item.Value);
					Remove(dictionary, key, legacyConfigMigration);
				}
				else if (section.StartsWith("Piles/", StringComparison.OrdinalIgnoreCase))
				{
					ReadPileSection(legacyConfigMigration, section, key.Key, item.Value);
					Remove(dictionary, key, legacyConfigMigration);
				}
			}
			return legacyConfigMigration;
		}

		private static void ReadConstructionSection(LegacyConfigMigration migration, string section, string key, string value)
		{
			string[] array = section.Split('/');
			if (array.Length == 3 && key.Equals("Amount", StringComparison.OrdinalIgnoreCase) && TryReadAmount(value, out var amount))
			{
				migration.MaterialAmounts[array[2]] = amount;
			}
			else if (array.Length >= 6 && array[3].Equals("Pieces", StringComparison.OrdinalIgnoreCase))
			{
				string key2 = string.Join("/", array.Skip(5));
				ReadPieceSetting(migration.PieceSettings, key2, key, value);
			}
		}

		private static void ReadPileSection(LegacyConfigMigration migration, string section, string key, string value)
		{
			string[] array = section.Split('/');
			if (array.Length >= 4)
			{
				string text = array[2];
				string text2 = string.Join("/", array.Skip(3));
				string key2 = text2 + "\u001f" + text;
				ReadPieceSetting(migration.PileSettings, key2, key, value);
			}
		}

		private static void ReadPieceSetting(IDictionary<string, LegacyPieceSettings> target, string key, string settingName, string value)
		{
			if (!target.TryGetValue(key, out LegacyPieceSettings value2))
			{
				value2 = new LegacyPieceSettings();
				target.Add(key, value2);
			}
			int amount;
			if (settingName.Equals("Enabled", StringComparison.OrdinalIgnoreCase) && bool.TryParse(value, out var result))
			{
				value2.Enabled = result;
			}
			else if (settingName.Equals("Amount", StringComparison.OrdinalIgnoreCase) && TryReadAmount(value, out amount))
			{
				value2.Amount = amount;
			}
		}

		private static bool TryReadAmount(string value, out int amount)
		{
			if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out amount))
			{
				amount = Math.Max(0, amount);
				return true;
			}
			return false;
		}

		private static void Remove(IDictionary<ConfigDefinition, string> orphanedEntries, ConfigDefinition definition, LegacyConfigMigration migration)
		{
			orphanedEntries.Remove(definition);
			migration.RemovedLegacyEntries = true;
		}
	}
	internal sealed class LegacyPieceSettings
	{
		internal bool Enabled;

		internal int Amount = 1;
	}
	internal enum RockPieceKind
	{
		Structure,
		Pile
	}
	internal static class PieceClassifier
	{
		private static readonly string[] FunctionalPrefabTerms = new string[46]
		{
			"forge", "furnace", "oven", "smelter", "kiln", "wisp", "fountain", "chimney", "hearth", "fireplace",
			"firepit", "bonfire", "brazier", "torch", "workbench", "stonecutter", "cauldron", "artisan", "windmill", "mill",
			"smoke", "ward", "portal", "altar", "statue", "throne", "chest", "bed", "chair", "table",
			"bench", "shelf", "sign", "banner", "carpet", "lamp", "lantern", "sconce", "ballista", "catapult",
			"trap", "cart", "ship", "boat", "trophy", "feast"
		};

		private static readonly string[] StructuralPrefabTerms = new string[21]
		{
			"wall", "floor", "roof", "beam", "pole", "pillar", "column", "arch", "stair", "foundation",
			"support", "gate", "door", "bridge", "ramp", "platform", "railing", "shingle", "tile", "balcony",
			"window"
		};

		internal static bool TryClassify(Piece piece, string prefabName, out RockPieceKind kind)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Invalid comparison between Unknown and I4
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Invalid comparison between Unknown and I4
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			kind = RockPieceKind.Structure;
			string value = prefabName.ToLowerInvariant();
			if (ContainsAny(value, FunctionalPrefabTerms))
			{
				return false;
			}
			if (ContainsAny(value, "pile", "stack", "heap"))
			{
				kind = RockPieceKind.Pile;
				return true;
			}
			if ((int)piece.m_category == 2 || (int)piece.m_category == 3)
			{
				return true;
			}
			return (int)piece.m_category == 5 && ContainsAny(value, StructuralPrefabTerms);
		}

		internal static string GetMaterialFolder(string resourcePrefab)
		{
			string text = resourcePrefab.ToLowerInvariant();
			if (text.Contains("grausten") || text.Contains("ashland"))
			{
				return "Grausten";
			}
			if (text.Contains("marble"))
			{
				return "Marble";
			}
			if (text.Contains("crystal"))
			{
				return "Crystal";
			}
			if (text.Contains("obsidian"))
			{
				return "Obsidian";
			}
			if (text.Contains("stone") || text.Contains("rock"))
			{
				return "Stone";
			}
			return "Other";
		}

		internal static string GetMaterialGroup(string resourcePrefab)
		{
			return GetMaterialFolder(resourcePrefab).ToUpperInvariant();
		}

		internal static string GetMaterialDisplayName(string resourcePrefab, bool spanish)
		{
			return GetMaterialFolder(resourcePrefab) switch
			{
				"Stone" => spanish ? "piedra" : "stone", 
				"Marble" => spanish ? "mármol negro" : "black marble", 
				"Grausten" => "Grausten", 
				"Crystal" => spanish ? "cristal" : "crystal", 
				"Obsidian" => spanish ? "obsidiana" : "obsidian", 
				_ => spanish ? "material rocoso" : "rock material", 
			};
		}

		internal static string GetStructureFolder(string prefabName)
		{
			string text = prefabName.ToLowerInvariant();
			if (text.Contains("floor"))
			{
				return "Floors";
			}
			if (text.Contains("roof") || text.Contains("shingle"))
			{
				return "Roofs";
			}
			if (text.Contains("wall"))
			{
				return "Walls";
			}
			if (text.Contains("beam") || text.Contains("pole"))
			{
				return "Beams";
			}
			if (text.Contains("pillar") || text.Contains("column"))
			{
				return "Pillars";
			}
			if (text.Contains("stair"))
			{
				return "Stairs";
			}
			if (text.Contains("arch"))
			{
				return "Arches";
			}
			if (text.Contains("gate") || text.Contains("door"))
			{
				return "Doors";
			}
			if (text.Contains("bridge") || text.Contains("ramp"))
			{
				return "BridgesAndRamps";
			}
			if (text.Contains("foundation") || text.Contains("support"))
			{
				return "Supports";
			}
			if (text.Contains("platform") || text.Contains("railing") || text.Contains("balcony"))
			{
				return "Platforms";
			}
			if (text.Contains("window"))
			{
				return "Windows";
			}
			return "OtherStructures";
		}

		private static bool ContainsAny(string value, params string[] terms)
		{
			return terms.Any((string term) => value.Contains(term));
		}
	}
}