Decompiled source of SmolCraft v0.1.0

SmolCraft.dll

Decompiled 18 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
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(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("SmolCraft")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("SmolCraft")]
[assembly: AssemblyTitle("SmolCraft")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SmolCraft
{
	internal static class ConfigSync
	{
		private const string RpcName = "SmolCraft_SyncConfig";

		private const int PackageVersion = 1;

		private static bool _srvEnabled;

		private static bool _srvSmokeless;

		private static bool _srvRaiseSmoke;

		private static readonly Dictionary<string, float> SrvScales = new Dictionary<string, float>();

		internal static bool HasServerConfig { get; private set; }

		internal static bool EffectiveEnabled => HasServerConfig ? _srvEnabled : SmolCraftPlugin.ModEnabled.Value;

		internal static bool EffectiveSmokeless => HasServerConfig ? _srvSmokeless : SmolCraftPlugin.Smokeless.Value;

		internal static bool EffectiveRaiseSmokeExit => HasServerConfig ? _srvRaiseSmoke : SmolCraftPlugin.RaiseSmokeExit.Value;

		internal static IEnumerable<string> ServerStationNames => SrvScales.Keys;

		internal static bool TryGetServerScale(string prefab, out float scale)
		{
			scale = 1f;
			return HasServerConfig && SrvScales.TryGetValue(prefab, out scale);
		}

		internal static void OnNewConnection_Postfix(ZNetPeer peer)
		{
			try
			{
				if (peer != null)
				{
					ZRpc rpc = peer.m_rpc;
					if (rpc != null)
					{
						rpc.Register<ZPackage>("SmolCraft_SyncConfig", (Action<ZRpc, ZPackage>)RPC_ReceiveConfig);
					}
				}
			}
			catch (Exception ex)
			{
				SmolCraftPlugin.Log.LogWarning((object)("Could not register sync RPC: " + ex.Message));
			}
		}

		internal static void RPC_PeerInfo_Postfix(ZNet __instance, ZRpc rpc)
		{
			if ((Object)(object)__instance == (Object)null || rpc == null || !__instance.IsServer() || !SmolCraftPlugin.EnforceServerConfig.Value)
			{
				return;
			}
			try
			{
				rpc.Invoke("SmolCraft_SyncConfig", new object[1] { BuildPackage() });
				SmolCraftPlugin.Log.LogInfo((object)"Sent SmolCraft settings to joining player.");
			}
			catch (Exception ex)
			{
				SmolCraftPlugin.Log.LogWarning((object)("Could not send settings to joining player: " + ex.Message));
			}
		}

		internal static void ZNet_OnDestroy_Postfix()
		{
			if (HasServerConfig)
			{
				HasServerConfig = false;
				SrvScales.Clear();
				StationRegistry.ForgetServerOnly();
				SmokePatches.ClearCache();
				StationRegistry.ApplyAll();
				SmolCraftPlugin.Log.LogInfo((object)"Left server; using local SmolCraft settings again.");
			}
		}

		internal static void BroadcastIfServer()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || !SmolCraftPlugin.EnforceServerConfig.Value)
			{
				return;
			}
			int num = 0;
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer?.m_rpc != null)
				{
					try
					{
						peer.m_rpc.Invoke("SmolCraft_SyncConfig", new object[1] { BuildPackage() });
						num++;
					}
					catch (Exception ex)
					{
						SmolCraftPlugin.Log.LogWarning((object)("Sync to a peer failed: " + ex.Message));
					}
				}
			}
			if (num > 0)
			{
				SmolCraftPlugin.Log.LogInfo((object)$"Pushed updated SmolCraft settings to {num} player(s).");
			}
		}

		private static ZPackage BuildPackage()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(SmolCraftPlugin.ModEnabled.Value);
			val.Write(SmolCraftPlugin.Smokeless.Value);
			val.Write(SmolCraftPlugin.RaiseSmokeExit.Value);
			List<StationInfo> list = new List<StationInfo>(StationRegistry.Local);
			val.Write(list.Count);
			foreach (StationInfo item in list)
			{
				val.Write(item.PrefabName);
				val.Write(item.Scale.Value);
			}
			return val;
		}

		private static void RPC_ReceiveConfig(ZRpc rpc, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num != 1)
				{
					SmolCraftPlugin.Log.LogWarning((object)($"Server SmolCraft settings format v{num} (expected v{1}). " + "Update SmolCraft so it matches the server. Using local settings."));
					return;
				}
				_srvEnabled = pkg.ReadBool();
				_srvSmokeless = pkg.ReadBool();
				_srvRaiseSmoke = pkg.ReadBool();
				SrvScales.Clear();
				int num2 = pkg.ReadInt();
				for (int i = 0; i < num2; i++)
				{
					string key = pkg.ReadString();
					float value = pkg.ReadSingle();
					SrvScales[key] = value;
				}
				HasServerConfig = true;
				StationRegistry.EnsureServerStations();
				SmokePatches.ClearCache();
				StationRegistry.ApplyAll();
				SmolCraftPlugin.Log.LogInfo((object)$"Using server SmolCraft settings ({num2} stations). Local slider changes are ignored while connected.");
			}
			catch (Exception ex)
			{
				SmolCraftPlugin.Log.LogWarning((object)("Bad SmolCraft settings package from server: " + ex.Message));
			}
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? ShowRangeAsPercent;

		public Action<ConfigEntryBase> CustomDrawer;

		public bool? Browsable;

		public string Category;

		public object DefaultValue;

		public bool? HideDefaultButton;

		public bool? HideSettingName;

		public string Description;

		public string DispName;

		public int? Order;

		public bool? ReadOnly;

		public bool? IsAdvanced;

		public Func<object, string> ObjToStr;

		public Func<string, object> StrToObj;
	}
	internal static class Patches
	{
		internal static void ApplyAll(Harmony harmony)
		{
			TryPatch(harmony, typeof(ZNetScene), "Awake", null, Hook(typeof(Patches), "ZNetScene_Awake_Postfix", 0));
			TryPatch(harmony, typeof(ZNet), "OnNewConnection", null, Hook(typeof(ConfigSync), "OnNewConnection_Postfix"));
			TryPatch(harmony, typeof(ZNet), "RPC_PeerInfo", null, Hook(typeof(ConfigSync), "RPC_PeerInfo_Postfix"));
			TryPatch(harmony, typeof(ZNet), "OnDestroy", null, Hook(typeof(ConfigSync), "ZNet_OnDestroy_Postfix"));
			TryPatch(harmony, typeof(SmokeSpawner), "Spawn", Hook(typeof(SmokePatches), "Spawn_Prefix"));
			TryPatch(harmony, typeof(SmokeSpawner), "IsBlocked", Hook(typeof(SmokePatches), "IsBlocked_Prefix"));
		}

		private static void ZNetScene_Awake_Postfix(ZNetScene __instance)
		{
			try
			{
				StationRegistry.OnZNetSceneAwake(__instance);
			}
			catch (Exception arg)
			{
				SmolCraftPlugin.Log.LogError((object)$"Station discovery failed: {arg}");
			}
		}

		private static HarmonyMethod Hook(Type host, string name, int priority = -1)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(host, name, (Type[])null, (Type[])null);
			HarmonyMethod val = new HarmonyMethod(methodInfo);
			if (priority >= 0)
			{
				val.priority = priority;
			}
			return val;
		}

		private static void TryPatch(Harmony harmony, Type type, string methodName, HarmonyMethod prefix = null, HarmonyMethod postfix = null)
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				SmolCraftPlugin.Log.LogWarning((object)("Could not find " + type.Name + "." + methodName + " — this part of SmolCraft is disabled (a Valheim update probably renamed it)."));
				return;
			}
			try
			{
				harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				SmolCraftPlugin.Log.LogError((object)("Failed to patch " + type.Name + "." + methodName + ": " + ex.Message));
			}
		}
	}
	internal static class SmokePatches
	{
		private static readonly Dictionary<int, bool> OwnerCache = new Dictionary<int, bool>();

		internal static void ClearCache()
		{
			OwnerCache.Clear();
		}

		private static bool Suppress(SmokeSpawner spawner)
		{
			if ((Object)(object)spawner == (Object)null)
			{
				return false;
			}
			if (!ConfigSync.EffectiveEnabled || !ConfigSync.EffectiveSmokeless)
			{
				return false;
			}
			int instanceID = ((Object)spawner).GetInstanceID();
			if (OwnerCache.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			ZNetView componentInParent = ((Component)spawner).GetComponentInParent<ZNetView>();
			value = (Object)(object)componentInParent != (Object)null && StationRegistry.IsManaged(StationRegistry.PrefabNameOf(((Component)componentInParent).gameObject));
			OwnerCache[instanceID] = value;
			return value;
		}

		internal static bool Spawn_Prefix(SmokeSpawner __instance)
		{
			return !Suppress(__instance);
		}

		internal static bool IsBlocked_Prefix(SmokeSpawner __instance, ref bool __result)
		{
			if (!Suppress(__instance))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[BepInPlugin("BhimaJenkins.SmolCraft", "SmolCraft", "0.1.0")]
	public class SmolCraftPlugin : BaseUnityPlugin
	{
		public const string ModGuid = "BhimaJenkins.SmolCraft";

		public const string ModName = "SmolCraft";

		public const string ModVersion = "0.1.0";

		internal static SmolCraftPlugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> ModEnabled;

		internal static ConfigEntry<bool> Smokeless;

		internal static ConfigEntry<bool> RaiseSmokeExit;

		internal static ConfigEntry<bool> EnforceServerConfig;

		internal static ConfigEntry<string> CustomStations;

		internal static ConfigEntry<bool> LogPieceNames;

		internal const string CustomSection = "6 - Custom Stations";

		private Harmony _harmony;

		private FileSystemWatcher _watcher;

		private volatile bool _reloadRequested;

		private bool _applyRequested;

		private bool _customListDirty;

		private float _applyTimer;

		internal static ConfigFile Cfg => ((BaseUnityPlugin)Instance).Config;

		private void Awake()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Expected O, but got Unknown
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: 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_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, new ConfigDescription("Master switch. Turn off to put every station back to vanilla size.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 40
				}
			}));
			Smokeless = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Smokeless Stations", false, new ConfigDescription("Stations managed by SmolCraft stop producing smoke and can never be 'blocked' by smoke. Use this if a shrunk station keeps going out indoors. Only affects SmolCraft stations (campfires, hearths, braziers etc. are untouched).", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 30
				}
			}));
			RaiseSmokeExit = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Keep Smoke Exit At Vanilla Height", true, new ConfigDescription("Shrinking a smelter also shrinks its chimney, and smoke puffs are a fixed size, so they jam inside a small flue and the station stops ('needs ventilation'). With this on, the point where smoke comes out stays at its vanilla height so puffs spawn above the shrunk chimney and float away normally. Turn off for a purist look (smoke from the tiny chimney).", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 20
				}
			}));
			EnforceServerConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enforce Server Settings", true, new ConfigDescription("SERVER ONLY. When on, the server sends its SmolCraft settings to every player that joins and overrides theirs, so everyone sees the same sizes. Ignored on clients.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 10
				}
			}));
			CustomStations = ((BaseUnityPlugin)this).Config.Bind<string>("6 - Custom Stations", "Extra Station Prefabs", "", new ConfigDescription("Add any piece SmolCraft didn't pick up on its own (modded or vanilla). Comma-separated prefab names; each one gets its own size slider in this section. Optionally add a starting size after a colon, e.g.  piece_bigbench:0.6, MyModForge:65%  . Names are case-sensitive — use 'Log Buildable Piece Names' to find them. Removing a name puts that piece back to vanilla size.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 100
				}
			}));
			LogPieceNames = ((BaseUnityPlugin)this).Config.Bind<bool>("6 - Custom Stations", "Log Buildable Piece Names", false, new ConfigDescription("When on (in a world), writes every buildable piece's prefab name, in-game name and whether SmolCraft manages it to the BepInEx log (LogOutput.log / console). Turn on, check the log, turn off.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 90
				}
			}));
			CustomStations.SettingChanged += delegate
			{
				_customListDirty = true;
				RequestApply(1f);
			};
			LogPieceNames.SettingChanged += delegate
			{
				if (LogPieceNames.Value)
				{
					StationRegistry.LogBuildablePieces();
				}
			};
			ModEnabled.SettingChanged += OnAnySettingChanged;
			Smokeless.SettingChanged += OnAnySettingChanged;
			RaiseSmokeExit.SettingChanged += OnAnySettingChanged;
			EnforceServerConfig.SettingChanged += OnAnySettingChanged;
			_harmony = new Harmony("BhimaJenkins.SmolCraft");
			Patches.ApplyAll(_harmony);
			SetupConfigWatcher();
			Log.LogInfo((object)"SmolCraft 0.1.0 loaded.");
		}

		internal void OnAnySettingChanged(object sender, EventArgs e)
		{
			SmokePatches.ClearCache();
			RequestApply();
		}

		internal void RequestApply(float delay = 0.25f)
		{
			_applyRequested = true;
			_applyTimer = Math.Max(_applyTimer, delay);
		}

		private void Update()
		{
			if (_reloadRequested)
			{
				_reloadRequested = false;
				try
				{
					((BaseUnityPlugin)this).Config.Reload();
					Log.LogInfo((object)"Config file changed on disk, reloaded.");
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("Could not reload config: " + ex.Message));
				}
				RequestApply();
			}
			if (!_applyRequested)
			{
				return;
			}
			_applyTimer -= Time.unscaledDeltaTime;
			if (_applyTimer <= 0f)
			{
				_applyRequested = false;
				_applyTimer = 0f;
				if (_customListDirty)
				{
					_customListDirty = false;
					StationRegistry.SyncCustomStations();
				}
				StationRegistry.ApplyAll();
				ConfigSync.BroadcastIfServer();
			}
		}

		private void SetupConfigWatcher()
		{
			try
			{
				_watcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath));
				_watcher.Changed += delegate
				{
					_reloadRequested = true;
				};
				_watcher.Created += delegate
				{
					_reloadRequested = true;
				};
				_watcher.Renamed += delegate
				{
					_reloadRequested = true;
				};
				_watcher.IncludeSubdirectories = false;
				_watcher.EnableRaisingEvents = true;
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Config file watcher unavailable (" + ex.Message + "); edits will apply on restart."));
			}
		}

		private void OnDestroy()
		{
			_watcher?.Dispose();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal enum StationCategory
	{
		None,
		SmeltersAndKilns,
		CraftingStations,
		StationUpgrades,
		Production,
		Custom
	}
	internal class StationInfo
	{
		public string PrefabName;

		public string DisplayName;

		public StationCategory Category;

		public GameObject Prefab;

		public Vector3 OriginalScale;

		public Vector3[] SmokeOffsets;

		public ConfigEntry<float> Scale;

		public float MinScale;

		public bool IsCustom;

		public bool IsServerOnly;

		public bool HasSmoke => SmokeOffsets != null && SmokeOffsets.Length != 0;
	}
	internal static class StationRegistry
	{
		private const float SmelterMinScale = 0.4f;

		private const float OtherMinScale = 0.3f;

		private const float DefaultShrunk = 0.7f;

		private const float DefaultVanilla = 1f;

		private static readonly string[] DefaultShrinkKeywords = new string[9] { "smelter", "blastfurnace", "blast_furnace", "kiln", "eitrrefinery", "eitr_refinery", "refinery", "foundry", "frigid" };

		private static readonly Dictionary<string, StationInfo> Stations = new Dictionary<string, StationInfo>(StringComparer.Ordinal);

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

		private static PropertyInfo _locInstance;

		private static MethodInfo _localize;

		internal static IEnumerable<StationInfo> Local => Stations.Values.Where((StationInfo s) => s.Scale != null);

		internal static bool IsManaged(string prefabName)
		{
			return prefabName != null && Stations.ContainsKey(prefabName);
		}

		internal static void OnZNetSceneAwake(ZNetScene scene)
		{
			if ((Object)(object)scene == (Object)null || scene.m_prefabs == null)
			{
				return;
			}
			int num = 0;
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				if (Stations.TryGetValue(((Object)prefab).name, out var value))
				{
					value.Prefab = prefab;
					continue;
				}
				Piece component = prefab.GetComponent<Piece>();
				if (!((Object)(object)component == (Object)null))
				{
					StationCategory stationCategory = Classify(prefab, component);
					if (stationCategory != StationCategory.None)
					{
						bool flag = MatchesShrinkKeyword(IdOf(prefab, component));
						Register(prefab, component, stationCategory, flag ? 0.7f : 1f, custom: false, serverOnly: false);
						num++;
					}
				}
			}
			if (num > 0)
			{
				SmolCraftPlugin.Log.LogInfo((object)$"Found {num} new station(s) automatically.");
			}
			SyncCustomStations();
			EnsureServerStations();
			if (SmolCraftPlugin.LogPieceNames.Value)
			{
				LogBuildablePieces();
			}
			SmokePatches.ClearCache();
			ApplyAll();
		}

		private static string IdOf(GameObject prefab, Piece piece)
		{
			return (((Object)prefab).name + " " + (((Object)(object)piece != (Object)null) ? (piece.m_name ?? "") : "")).ToLowerInvariant();
		}

		private static bool MatchesShrinkKeyword(string text)
		{
			string[] defaultShrinkKeywords = DefaultShrinkKeywords;
			foreach (string value in defaultShrinkKeywords)
			{
				if (text.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		private static StationCategory Classify(GameObject prefab, Piece piece)
		{
			if (MatchesShrinkKeyword(IdOf(prefab, piece)) || (Object)(object)prefab.GetComponent<Smelter>() != (Object)null)
			{
				return StationCategory.SmeltersAndKilns;
			}
			if ((Object)(object)prefab.GetComponent<CraftingStation>() != (Object)null)
			{
				return StationCategory.CraftingStations;
			}
			if ((Object)(object)prefab.GetComponent<StationExtension>() != (Object)null)
			{
				return StationCategory.StationUpgrades;
			}
			if ((Object)(object)prefab.GetComponent<CookingStation>() != (Object)null || (Object)(object)prefab.GetComponent<Fermenter>() != (Object)null)
			{
				return StationCategory.Production;
			}
			return StationCategory.None;
		}

		private static string SectionName(StationCategory c)
		{
			return c switch
			{
				StationCategory.SmeltersAndKilns => "2 - Smelters, Kilns & Refineries", 
				StationCategory.CraftingStations => "3 - Crafting Stations", 
				StationCategory.StationUpgrades => "4 - Station Upgrades", 
				StationCategory.Production => "5 - Cooking & Fermenting", 
				StationCategory.Custom => "6 - Custom Stations", 
				_ => "9 - Other", 
			};
		}

		private static string Localized(Piece piece, string fallback)
		{
			if ((Object)(object)piece == (Object)null)
			{
				return fallback;
			}
			string name = piece.m_name;
			if (string.IsNullOrEmpty(name))
			{
				return fallback;
			}
			try
			{
				if (_localize == null)
				{
					Type type = AccessTools.TypeByName("Localization");
					_locInstance = ((type == null) ? null : AccessTools.Property(type, "instance"));
					_localize = ((type == null) ? null : AccessTools.Method(type, "Localize", new Type[1] { typeof(string) }, (Type[])null));
				}
				object obj = _locInstance?.GetValue(null, null);
				if (obj != null && _localize != null)
				{
					string text = _localize.Invoke(obj, new object[1] { name }) as string;
					if (!string.IsNullOrEmpty(text) && !text.StartsWith("$") && !text.StartsWith("["))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			return fallback;
		}

		private static string SafeKey(string s)
		{
			char[] array = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' };
			foreach (char oldChar in array)
			{
				s = s.Replace(oldChar, '_');
			}
			return s;
		}

		private static StationInfo Register(GameObject prefab, Piece piece, StationCategory category, float defaultScale, bool custom, bool serverOnly)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Expected O, but got Unknown
			Transform root = prefab.transform;
			SmokeSpawner[] componentsInChildren = prefab.GetComponentsInChildren<SmokeSpawner>(true);
			StationInfo stationInfo = new StationInfo
			{
				PrefabName = ((Object)prefab).name,
				Category = category,
				Prefab = prefab,
				OriginalScale = root.localScale,
				SmokeOffsets = componentsInChildren.Select((SmokeSpawner s) => root.InverseTransformPoint(((Component)s).transform.position)).ToArray(),
				IsCustom = custom,
				IsServerOnly = serverOnly
			};
			stationInfo.MinScale = ((category switch
			{
				StationCategory.Custom => stationInfo.HasSmoke, 
				StationCategory.SmeltersAndKilns => true, 
				_ => false, 
			}) ? 0.4f : 0.3f);
			string text = Localized(piece, ((Object)prefab).name);
			if (!UsedDisplayNames.Add(text))
			{
				text = text + " (" + ((Object)prefab).name + ")";
			}
			stationInfo.DisplayName = text;
			if (!serverOnly)
			{
				string text2 = text + " [" + ((Object)prefab).name + "] — size multiplier. 1.0 = vanilla, " + $"{stationInfo.MinScale:0.0} = smallest allowed.";
				if (stationInfo.HasSmoke)
				{
					text2 += " This station makes smoke: if it keeps stopping indoors, leave 'Keep Smoke Exit At Vanilla Height' on or enable 'Smokeless Stations'.";
				}
				stationInfo.Scale = SmolCraftPlugin.Cfg.Bind<float>(SectionName(category), SafeKey(((Object)prefab).name), Mathf.Clamp(defaultScale, stationInfo.MinScale, 1f), new ConfigDescription(text2, (AcceptableValueBase)(object)new AcceptableValueRange<float>(stationInfo.MinScale, 1f), new object[1]
				{
					new ConfigurationManagerAttributes
					{
						DispName = text,
						ShowRangeAsPercent = true
					}
				}));
				stationInfo.Scale.SettingChanged += SmolCraftPlugin.Instance.OnAnySettingChanged;
			}
			Stations[((Object)prefab).name] = stationInfo;
			return stationInfo;
		}

		private static Dictionary<string, float> ParseCustomList()
		{
			Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.Ordinal);
			string text = SmolCraftPlugin.CustomStations.Value ?? "";
			string[] array = text.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text2 in array)
			{
				string text3 = text2.Trim();
				if (text3.Length == 0)
				{
					continue;
				}
				float value = 1f;
				int num = text3.IndexOf(':');
				if (num >= 0)
				{
					string s = text3.Substring(num + 1).Trim().TrimEnd(new char[1] { '%' });
					text3 = text3.Substring(0, num).Trim();
					if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
					{
						value = ((result > 1.5f) ? (result / 100f) : result);
					}
				}
				if (text3.Length > 0)
				{
					dictionary[text3] = value;
				}
			}
			return dictionary;
		}

		private static GameObject FindPrefab(ZNetScene scene, string name)
		{
			if (scene?.m_prefabs == null)
			{
				return null;
			}
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab != (Object)null && ((Object)prefab).name == name)
				{
					return prefab;
				}
			}
			return null;
		}

		internal static void SyncCustomStations()
		{
			Dictionary<string, float> wanted = ParseCustomList();
			foreach (StationInfo item in Stations.Values.Where((StationInfo i) => i.IsCustom && !wanted.ContainsKey(i.PrefabName)).ToList())
			{
				Forget(item, removeConfig: true);
				SmolCraftPlugin.Log.LogInfo((object)("Removed custom station '" + item.PrefabName + "' (back to vanilla size)."));
			}
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			foreach (KeyValuePair<string, float> item2 in wanted)
			{
				if (Stations.TryGetValue(item2.Key, out var value))
				{
					if (!value.IsServerOnly)
					{
						if (!value.IsCustom)
						{
							SmolCraftPlugin.Log.LogInfo((object)("'" + item2.Key + "' is already handled automatically — its slider is in \"" + SectionName(value.Category) + "\"."));
						}
						continue;
					}
					Forget(value, removeConfig: false);
				}
				GameObject val = FindPrefab(instance, item2.Key);
				if ((Object)(object)val == (Object)null)
				{
					SmolCraftPlugin.Log.LogWarning((object)("Custom station '" + item2.Key + "' not found. Prefab names are case-sensitive — turn on 'Log Buildable Piece Names' to see the exact names."));
					continue;
				}
				Register(val, val.GetComponent<Piece>(), StationCategory.Custom, item2.Value, custom: true, serverOnly: false);
				SmolCraftPlugin.Log.LogInfo((object)("Added custom station '" + item2.Key + "'."));
			}
			SmokePatches.ClearCache();
		}

		internal static void EnsureServerStations()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || !ConfigSync.HasServerConfig)
			{
				return;
			}
			foreach (string serverStationName in ConfigSync.ServerStationNames)
			{
				if (!Stations.ContainsKey(serverStationName))
				{
					GameObject val = FindPrefab(instance, serverStationName);
					if (!((Object)(object)val == (Object)null))
					{
						Register(val, val.GetComponent<Piece>(), StationCategory.Custom, 1f, custom: false, serverOnly: true);
					}
				}
			}
			SmokePatches.ClearCache();
		}

		internal static void ForgetServerOnly()
		{
			foreach (StationInfo item in Stations.Values.Where((StationInfo i) => i.IsServerOnly).ToList())
			{
				Forget(item, removeConfig: false);
			}
		}

		private static void Forget(StationInfo info, bool removeConfig)
		{
			ResetToVanilla(info);
			Stations.Remove(info.PrefabName);
			UsedDisplayNames.Remove(info.DisplayName);
			if (removeConfig && info.Scale != null)
			{
				info.Scale.SettingChanged -= SmolCraftPlugin.Instance.OnAnySettingChanged;
				SmolCraftPlugin.Cfg.Remove(((ConfigEntryBase)info.Scale).Definition);
				SmolCraftPlugin.Cfg.Save();
			}
			SmokePatches.ClearCache();
		}

		private static void ResetToVanilla(StationInfo info)
		{
			if ((Object)(object)info.Prefab != (Object)null)
			{
				ApplyToRoot(info.Prefab.transform, info, 1f);
			}
			ForEachPlaced(delegate(ZNetView nview, StationInfo i)
			{
				if (i == info)
				{
					ApplyToRoot(((Component)nview).transform, info, 1f);
				}
			});
		}

		internal static void LogBuildablePieces()
		{
			ZNetScene instance = ZNetScene.instance;
			if (instance?.m_prefabs == null)
			{
				SmolCraftPlugin.Log.LogInfo((object)"Load into a world to list piece names.");
				return;
			}
			List<string> list = new List<string>();
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null))
				{
					Piece component = prefab.GetComponent<Piece>();
					if (!((Object)(object)component == (Object)null))
					{
						StationInfo value;
						string arg = (Stations.TryGetValue(((Object)prefab).name, out value) ? ("managed → " + SectionName(value.Category)) : "not managed");
						list.Add(string.Format("  {0,-40} {1,-30} {2}", ((Object)prefab).name, Localized(component, "?"), arg));
					}
				}
			}
			list.Sort(StringComparer.OrdinalIgnoreCase);
			SmolCraftPlugin.Log.LogInfo((object)($"Buildable pieces ({list.Count}). Copy the first column into 'Extra Station Prefabs':\n" + string.Join("\n", list)));
		}

		internal static float EffectiveScale(StationInfo info)
		{
			if (!ConfigSync.EffectiveEnabled)
			{
				return 1f;
			}
			float scale;
			float num = (ConfigSync.TryGetServerScale(info.PrefabName, out scale) ? scale : ((info.Scale == null) ? 1f : info.Scale.Value));
			return Mathf.Clamp(num, info.MinScale, 1f);
		}

		internal static void ApplyAll()
		{
			if (Stations.Count == 0)
			{
				return;
			}
			Dictionary<StationInfo, float> scales = new Dictionary<StationInfo, float>(Stations.Count);
			foreach (StationInfo value in Stations.Values)
			{
				float num = EffectiveScale(value);
				scales[value] = num;
				if ((Object)(object)value.Prefab != (Object)null)
				{
					ApplyToRoot(value.Prefab.transform, value, num);
				}
			}
			int count = 0;
			ForEachPlaced(delegate(ZNetView nview, StationInfo info)
			{
				ApplyToRoot(((Component)nview).transform, info, scales[info]);
				count++;
			});
			if (count > 0)
			{
				SmolCraftPlugin.Log.LogDebug((object)$"Rescaled {count} placed station(s).");
			}
		}

		private static void ForEachPlaced(Action<ZNetView, StationInfo> action)
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			ZNetView[] array = Object.FindObjectsByType<ZNetView>((FindObjectsSortMode)0);
			foreach (ZNetView val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = PrefabNameOf(((Component)val).gameObject);
					if (text != null && Stations.TryGetValue(text, out var value))
					{
						action(val, value);
					}
				}
			}
		}

		private static void ApplyToRoot(Transform root, StationInfo info, float s)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: 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)
			root.localScale = info.OriginalScale * s;
			if (!info.HasSmoke)
			{
				return;
			}
			bool effectiveRaiseSmokeExit = ConfigSync.EffectiveRaiseSmokeExit;
			SmokeSpawner[] componentsInChildren = ((Component)root).GetComponentsInChildren<SmokeSpawner>(true);
			int num = Math.Min(componentsInChildren.Length, info.SmokeOffsets.Length);
			for (int i = 0; i < num; i++)
			{
				Transform transform = ((Component)componentsInChildren[i]).transform;
				if (!((Object)(object)transform == (Object)(object)root))
				{
					Vector3 val = info.SmokeOffsets[i];
					Vector3 val2 = (Vector3)((effectiveRaiseSmokeExit && s > 0.01f) ? new Vector3(val.x, val.y / s, val.z) : val);
					transform.position = root.TransformPoint(val2);
				}
			}
		}

		internal static string PrefabNameOf(GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				return null;
			}
			string name = ((Object)go).name;
			int num = name.IndexOf("(Clone)", StringComparison.Ordinal);
			return (num >= 0) ? name.Substring(0, num).Trim() : name;
		}
	}
}