Decompiled source of StationSpeed v0.2.2

StationSpeed.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace StationSpeed;

[BepInPlugin("j1ga.stationspeed", "Station Speed", "0.2.2")]
public class StationSpeedPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Smelter), "Awake")]
	private static class Smelter_Awake_Patch
	{
		private static void Postfix(Smelter __instance)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.Active)
			{
				return;
			}
			try
			{
				instance.ApplySmelter(__instance, awake: true);
			}
			catch (Exception e)
			{
				instance.Fail("Smelter.Awake", e);
			}
		}
	}

	[HarmonyPatch(typeof(CookingStation), "Awake")]
	private static class CookingStation_Awake_Patch
	{
		private static void Postfix(CookingStation __instance)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.Active)
			{
				return;
			}
			try
			{
				instance.ApplyCookingStation(__instance, awake: true);
			}
			catch (Exception e)
			{
				instance.Fail("CookingStation.Awake", e);
			}
		}
	}

	[HarmonyPatch(typeof(Beehive), "Awake")]
	private static class Beehive_Awake_Patch
	{
		private static void Postfix(Beehive __instance)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.Active)
			{
				return;
			}
			try
			{
				instance.ApplyBeehive(__instance, awake: true);
			}
			catch (Exception e)
			{
				instance.Fail("Beehive.Awake", e);
			}
		}
	}

	[HarmonyPatch(typeof(SapCollector), "Awake")]
	private static class SapCollector_Awake_Patch
	{
		private static void Postfix(SapCollector __instance)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.Active)
			{
				return;
			}
			try
			{
				instance.ApplySapCollector(__instance, awake: true);
			}
			catch (Exception e)
			{
				instance.Fail("SapCollector.Awake", e);
			}
		}
	}

	[HarmonyPatch(typeof(Fermenter), "RPC_AddItem")]
	private static class Fermenter_RPC_AddItem_Patch
	{
		private static void Prefix(Fermenter __instance, out long __state)
		{
			__state = StartTime(__instance);
		}

		private static void Postfix(Fermenter __instance, long __state)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.OnFermenterAdded(__instance, __state);
			}
			catch (Exception e)
			{
				instance.Fail("Fermenter.RPC_AddItem", e);
			}
		}
	}

	[HarmonyPatch(typeof(Plant), "Awake")]
	private static class Plant_Awake_Patch
	{
		private static void Prefix(Plant __instance, out long __state)
		{
			__state = PlantTime(__instance);
		}

		private static void Postfix(Plant __instance, long __state)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.OnPlantAwake(__instance, __state);
			}
			catch (Exception e)
			{
				instance.Fail("Plant.Awake", e);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "Awake")]
	private static class ZNet_Awake_Patch
	{
		private static void Postfix()
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.RegisterRpc();
			}
			catch (Exception e)
			{
				instance.Fail("ZNet.Awake", e);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private static class ZNet_RPC_PeerInfo_Patch
	{
		private static void Postfix(ZNet __instance, ZRpc rpc)
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || instance._disabledByErrors)
			{
				return;
			}
			try
			{
				instance.OnPeerInfo(__instance, rpc);
			}
			catch (Exception e)
			{
				instance.Fail("ZNet.RPC_PeerInfo", e);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	private static class ZNet_OnDestroy_Patch
	{
		private static void Postfix()
		{
			StationSpeedPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				((MonoBehaviour)instance).StopAllCoroutines();
				instance.ClearServerValues();
				instance.ResetScan();
				instance._indexedScene = null;
				instance._saplingByPrefab.Clear();
			}
			catch (Exception e)
			{
				instance.Fail("ZNet.OnDestroy", e);
			}
		}
	}

	private const float MinMultiplier = 0.1f;

	private const float MaxMultiplier = 100f;

	public const string Guid = "j1ga.stationspeed";

	public const string Name = "Station Speed";

	public const string Version = "0.2.2";

	private const int MaxErrors = 25;

	private const int ScanPerFrame = 20000;

	private const double FreshSeconds = 300.0;

	private const double ClockSlackSeconds = 30.0;

	private const string RpcName = "j1ga.stationspeed.config";

	private const int PacketVersion = 1;

	private ConfigEntry<bool> _cfgEnabled;

	private ConfigEntry<bool> _cfgDebug;

	private ConfigEntry<float> _cfgSmelter;

	private ConfigEntry<float> _cfgBlastFurnace;

	private ConfigEntry<float> _cfgCharcoalKiln;

	private ConfigEntry<float> _cfgSpinningWheel;

	private ConfigEntry<float> _cfgWindmill;

	private ConfigEntry<float> _cfgEitrRefinery;

	private ConfigEntry<float> _cfgOtherSmelter;

	private ConfigEntry<float> _cfgCookingStation;

	private ConfigEntry<float> _cfgOven;

	private ConfigEntry<float> _cfgOtherCooking;

	private ConfigEntry<bool> _cfgScaleOvenFuel;

	private ConfigEntry<float> _cfgFermenter;

	private ConfigEntry<float> _cfgCrops;

	private ConfigEntry<float> _cfgSaplings;

	private ConfigEntry<float> _cfgBeehive;

	private ConfigEntry<float> _cfgSapCollector;

	private ConfigEntry<string> _cfgOverrides;

	private readonly Dictionary<string, float> _overrides = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

	private ConfigEntry<bool> _cfgHostShift;

	private ConfigEntry<float> _cfgHostInterval;

	private ConfigEntry<bool> _cfgSync;

	private readonly List<ConfigEntry<float>> _multipliers = new List<ConfigEntry<float>>();

	private readonly Dictionary<string, bool> _saplingByPrefab = new Dictionary<string, bool>();

	public static StationSpeedPlugin Instance;

	private Harmony _harmony;

	private MethodInfo _plantGrowTime;

	private int _errorCount;

	private bool _disabledByErrors;

	private readonly HashSet<string> _loggedErrors = new HashSet<string>();

	private static readonly int MarkHash = StringExtensionMethods.GetStableHashCode("j1ga.stationspeed.shifted");

	private FieldInfo _fiObjectsByID;

	private readonly Dictionary<int, Fermenter> _fermenterPrefabs = new Dictionary<int, Fermenter>();

	private readonly Dictionary<int, Plant> _plantPrefabs = new Dictionary<int, Plant>();

	private ZNetScene _indexedScene;

	private readonly List<ZDO> _scan = new List<ZDO>();

	private int _scanCursor = -1;

	private float _nextScan;

	private bool _growTimeMismatch;

	private int _hostShifted;

	private int _lastScanCount;

	private bool _growTimeReported;

	private bool _serverSynced;

	private bool _serverEnabled = true;

	private bool _serverScaleOvenFuel = true;

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

	private readonly Dictionary<string, float> _serverOverrides = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

	private bool _syncDirty;

	private bool Enabled
	{
		get
		{
			if (!_serverSynced)
			{
				return _cfgEnabled.Value;
			}
			return _serverEnabled;
		}
	}

	private bool ScaleOvenFuel
	{
		get
		{
			if (!_serverSynced)
			{
				return _cfgScaleOvenFuel.Value;
			}
			return _serverScaleOvenFuel;
		}
	}

	private bool Active
	{
		get
		{
			if (!_disabledByErrors)
			{
				return Enabled;
			}
			return false;
		}
	}

	private bool HostActive
	{
		get
		{
			if (_fiObjectsByID == null || !Active || !_cfgHostShift.Value)
			{
				return false;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null && instance.IsServer() && ZDOMan.instance != null)
			{
				return (Object)(object)ZNetScene.instance != (Object)null;
			}
			return false;
		}
	}

	private void RegisterCommands()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		new ConsoleCommand("stationspeed", "Station Speed. 'stationspeed status' lists the multipliers in effect, 'stationspeed rescan' applies them to the loaded stations again", (ConsoleEvent)delegate(ConsoleEventArgs args)
		{
			RunCommand(args);
		}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}

	private static void Say(ConsoleEventArgs args, string text)
	{
		if (args != null && (Object)(object)args.Context != (Object)null)
		{
			args.Context.AddString(text);
		}
	}

	private void RunCommand(ConsoleEventArgs args)
	{
		try
		{
			string text = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "");
			if (text == "status")
			{
				Say(args, StatusText());
			}
			else if (text == "rescan")
			{
				int num = Reapply();
				if (HostActive)
				{
					_nextScan = 0f;
				}
				Say(args, "Multipliers applied to " + num + " loaded stations" + (HostActive ? "; host pass starts now." : "."));
			}
			else
			{
				Say(args, "stationspeed status | rescan");
			}
		}
		catch (Exception ex)
		{
			Say(args, "stationspeed: " + ex.Message);
			Fail("command", ex);
		}
	}

	private string StatusValues()
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("smelter x").Append(F(Kind(_cfgSmelter))).Append(", blast furnace x")
			.Append(F(Kind(_cfgBlastFurnace)))
			.Append(", kiln x")
			.Append(F(Kind(_cfgCharcoalKiln)))
			.Append(", spinning wheel x")
			.Append(F(Kind(_cfgSpinningWheel)))
			.Append(", windmill x")
			.Append(F(Kind(_cfgWindmill)))
			.Append(", eitr refinery x")
			.Append(F(Kind(_cfgEitrRefinery)))
			.Append(", other smelters x")
			.Append(F(Kind(_cfgOtherSmelter)))
			.Append(", cooking x")
			.Append(F(Kind(_cfgCookingStation)))
			.Append(", oven x")
			.Append(F(Kind(_cfgOven)))
			.Append(ScaleOvenFuel ? " (fuel too)" : " (fuel as vanilla)")
			.Append(", other cooking x")
			.Append(F(Kind(_cfgOtherCooking)))
			.Append(", fermenter x")
			.Append(F(Kind(_cfgFermenter)))
			.Append(", crops x")
			.Append(F(Kind(_cfgCrops)))
			.Append(", saplings x")
			.Append(F(Kind(_cfgSaplings)))
			.Append(", beehive x")
			.Append(F(Kind(_cfgBeehive)))
			.Append(", sap x")
			.Append(F(Kind(_cfgSapCollector)));
		Dictionary<string, float> dictionary = (_serverSynced ? _serverOverrides : _overrides);
		if (dictionary.Count > 0)
		{
			stringBuilder.Append("; overrides:");
			foreach (KeyValuePair<string, float> item in dictionary)
			{
				stringBuilder.Append(' ').Append(item.Key).Append('=')
					.Append(F(item.Value));
			}
		}
		stringBuilder.Append('.');
		return stringBuilder.ToString();
	}

	private string StatusText()
	{
		if (_disabledByErrors)
		{
			return "Station Speed is inert after errors, see the log.";
		}
		StringBuilder stringBuilder = new StringBuilder();
		if (_serverSynced)
		{
			stringBuilder.Append(_serverEnabled ? "Settings from the server: " : "Disabled by the server. Its settings: ");
		}
		else if (!_cfgEnabled.Value)
		{
			return "Station Speed is disabled in the config.";
		}
		stringBuilder.Append(StatusValues());
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance != (Object)null && instance.IsServer())
		{
			if (HostActive)
			{
				stringBuilder.Append(" Host: ").Append(_hostShifted).Append(" shifted for players without the mod, ")
					.Append(_lastScanCount)
					.Append(" objects per pass every ")
					.Append(F(_cfgHostInterval.Value))
					.Append(" s.");
			}
			else
			{
				stringBuilder.Append(" Host shift is off.");
			}
			stringBuilder.Append(_cfgSync.Value ? " Settings are sent to the clients." : " Settings are not sent to the clients.");
		}
		return stringBuilder.ToString();
	}

	private ConfigEntry<float> BindMultiplier(string section, string key, string what)
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Expected O, but got Unknown
		ConfigEntry<float> val = ((BaseUnityPlugin)this).Config.Bind<float>(section, key, 1f, new ConfigDescription("Speed multiplier for " + what + ". 2 = twice as fast, 0.5 = twice as slow.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), new object[0]));
		_multipliers.Add(val);
		return val;
	}

	private void BindConfig()
	{
		//IL_029b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a5: Expected O, but got Unknown
		_cfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("01 General", "Enabled", true, "Master switch. Loaded stations go back to vanilla speed at once; the fermenters and plants already shifted stay shifted.");
		_cfgDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("01 General", "Debug", false, "Log every station the mod touches.");
		_cfgSmelter = BindMultiplier("02 Smelters", "Smelter", "the smelter (prefab smelter)");
		_cfgBlastFurnace = BindMultiplier("02 Smelters", "BlastFurnace", "the blast furnace (blastfurnace)");
		_cfgCharcoalKiln = BindMultiplier("02 Smelters", "CharcoalKiln", "the charcoal kiln (charcoal_kiln)");
		_cfgSpinningWheel = BindMultiplier("02 Smelters", "SpinningWheel", "the spinning wheel (piece_spinningwheel)");
		_cfgWindmill = BindMultiplier("02 Smelters", "Windmill", "the windmill (windmill). The wind still scales its speed on top, as in vanilla");
		_cfgEitrRefinery = BindMultiplier("02 Smelters", "EitrRefinery", "the eitr refinery (eitrrefinery)");
		_cfgOtherSmelter = BindMultiplier("02 Smelters", "OtherSmelter", "any other station built on the Smelter component, e.g. from other mods");
		_cfgCookingStation = BindMultiplier("03 Cooking", "CookingStation", "the cooking station and the iron cooking station");
		_cfgOven = BindMultiplier("03 Cooking", "Oven", "the stone oven (piece_oven)");
		_cfgOtherCooking = BindMultiplier("03 Cooking", "OtherCooking", "any other station built on the CookingStation component");
		_cfgScaleOvenFuel = ((BaseUnityPlugin)this).Config.Bind<bool>("03 Cooking", "ScaleOvenFuel", true, "Burn the oven's own fuel faster by the same multiplier, so that the wood per loaf stays as in vanilla (smelters do this by themselves). Off: the fuel lasts the vanilla time and faster baking also means cheaper baking.");
		_cfgFermenter = BindMultiplier("04 Timestamp stations", "Fermenter", "the fermenter. Applied by moving the start time when the item is added, so a player without the mod sees the same readiness");
		_cfgCrops = BindMultiplier("04 Timestamp stations", "Crops", "everything planted with the cultivator that does not grow into a tree: carrots, turnips, onions, barley, flax, mushrooms, seeds, vines. Applied by moving the planting time, same as the fermenter");
		_cfgSaplings = BindMultiplier("04 Timestamp stations", "Saplings", "planted trees: beech, fir, pine, birch, oak, ancient, yggdrasil shoot and any other sapling that grows into a tree");
		_cfgBeehive = BindMultiplier("05 Collectors", "Beehive", "the beehive");
		_cfgSapCollector = BindMultiplier("05 Collectors", "SapCollector", "the sap extractor");
		_cfgOverrides = ((BaseUnityPlugin)this).Config.Bind<string>("06 Overrides", "ByPrefab", "", "Per-prefab multipliers that win over the kind settings above: prefab=multiplier, comma separated. Example: smelter=3,piece_beehive=0.5,Oak_Sapling=4");
		_cfgOverrides.SettingChanged += delegate
		{
			ReadOverrides(_cfgOverrides.Value, _overrides);
		};
		ReadOverrides(_cfgOverrides.Value, _overrides);
		_cfgHostShift = ((BaseUnityPlugin)this).Config.Bind<bool>("07 Host", "HostShift", true, "On the host (or a dedicated server with the mod): every few seconds look through the world's fermenters and plants for a start time that was just written and not shifted yet - an item added or a seed planted by a player without the mod - and shift it. Off: only the owner's own shift at the moment of adding.");
		_cfgHostInterval = ((BaseUnityPlugin)this).Config.Bind<float>("07 Host", "HostScanInterval", 5f, new ConfigDescription("Seconds between two passes over the world's objects on the host. A pass is spread over several frames.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), new object[0]));
		_cfgSync = ((BaseUnityPlugin)this).Config.Bind<bool>("08 Sync", "SyncConfig", true, "The server sends its multipliers, overrides, ScaleOvenFuel and Enabled to every connecting client with the mod, and again when they change; the client uses them instead of its own file while connected. Off on the server: every client uses its own file. Has no effect on a client.");
		((BaseUnityPlugin)this).Config.SettingChanged += delegate
		{
			_syncDirty = true;
		};
	}

	private static void ReadOverrides(string text, Dictionary<string, float> into)
	{
		into.Clear();
		if (string.IsNullOrEmpty(text))
		{
			return;
		}
		string[] array = text.Split(',');
		foreach (string text2 in array)
		{
			string text3 = text2.Trim();
			int num = text3.IndexOf('=');
			if (num > 0 && float.TryParse(text3.Substring(num + 1).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				into[text3.Substring(0, num).Trim()] = Mathf.Clamp(result, 0.1f, 100f);
			}
		}
	}

	private float Kind(ConfigEntry<float> kind)
	{
		if (_serverSynced && _serverValues.TryGetValue(((ConfigEntryBase)kind).Definition.Key, out var value))
		{
			return Mathf.Clamp(value, 0.1f, 100f);
		}
		return kind.Value;
	}

	private float Multiplier(string prefab, ConfigEntry<float> kind)
	{
		Dictionary<string, float> dictionary = (_serverSynced ? _serverOverrides : _overrides);
		if (dictionary.TryGetValue(prefab, out var value))
		{
			return value;
		}
		return Kind(kind);
	}

	private ConfigEntry<float> SmelterKind(string prefab)
	{
		return (ConfigEntry<float>)(prefab switch
		{
			"smelter" => _cfgSmelter, 
			"blastfurnace" => _cfgBlastFurnace, 
			"charcoal_kiln" => _cfgCharcoalKiln, 
			"piece_spinningwheel" => _cfgSpinningWheel, 
			"windmill" => _cfgWindmill, 
			"eitrrefinery" => _cfgEitrRefinery, 
			_ => _cfgOtherSmelter, 
		});
	}

	private ConfigEntry<float> CookingKind(string prefab)
	{
		switch (prefab)
		{
		case "piece_cookingstation":
		case "piece_cookingstation_iron":
			return _cfgCookingStation;
		case "piece_oven":
			return _cfgOven;
		default:
			return _cfgOtherCooking;
		}
	}

	private ConfigEntry<float> PlantKind(Plant p, string prefab)
	{
		if (!_saplingByPrefab.TryGetValue(prefab, out var value))
		{
			value = false;
			if ((Object)(object)p != (Object)null && p.m_grownPrefabs != null)
			{
				GameObject[] grownPrefabs = p.m_grownPrefabs;
				foreach (GameObject val in grownPrefabs)
				{
					if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent<TreeBase>() != (Object)null)
					{
						value = true;
						break;
					}
				}
			}
			_saplingByPrefab[prefab] = value;
		}
		if (!value)
		{
			return _cfgCrops;
		}
		return _cfgSaplings;
	}

	private void Awake()
	{
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Expected O, but got Unknown
		try
		{
			Instance = this;
			BindConfig();
			RegisterCommands();
			_plantGrowTime = AccessTools.Method(typeof(Plant), "GetGrowTime", (Type[])null, (Type[])null);
			if (_plantGrowTime == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Plant.GetGrowTime was not found; plant growth will not be changed.");
			}
			FindHostFields();
			_harmony = new Harmony("j1ga.stationspeed");
			_harmony.PatchAll(typeof(StationSpeedPlugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Station Speed 0.2.2 loaded.");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Awake failed, mod is inert: " + ex));
			_disabledByErrors = true;
		}
	}

	private void Update()
	{
		if (_disabledByErrors)
		{
			return;
		}
		try
		{
			if (_syncDirty)
			{
				FlushSync();
			}
			UpdateHost();
		}
		catch (Exception e)
		{
			Fail("Update", e);
			ResetScan();
		}
	}

	private void OnDestroy()
	{
		try
		{
			((MonoBehaviour)this).StopAllCoroutines();
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("OnDestroy: " + ex.Message));
		}
		if ((Object)(object)Instance == (Object)(object)this)
		{
			Instance = null;
		}
	}

	private static string PrefabName(GameObject go)
	{
		if ((Object)(object)go == (Object)null)
		{
			return "";
		}
		string name = ((Object)go).name;
		int num = name.IndexOf("(Clone)", StringComparison.Ordinal);
		if (num <= 0)
		{
			return name;
		}
		return name.Substring(0, num);
	}

	private static T PrefabComponent<T>(string prefab) where T : Component
	{
		ZNetScene instance = ZNetScene.instance;
		if ((Object)(object)instance == (Object)null || string.IsNullOrEmpty(prefab))
		{
			return default(T);
		}
		GameObject prefab2 = instance.GetPrefab(prefab);
		if (!((Object)(object)prefab2 != (Object)null))
		{
			return default(T);
		}
		return prefab2.GetComponent<T>();
	}

	private static bool ShiftStart(ZDO zdo, int key, float duration, float k)
	{
		if (zdo == null || k <= 0f || k == 1f)
		{
			return false;
		}
		long num = zdo.GetLong(key, 0L);
		if (num > 0)
		{
			DateTime maxValue = DateTime.MaxValue;
			if (num <= maxValue.Ticks)
			{
				double num2 = duration - duration / k;
				long ticks = new DateTime(num).AddSeconds(0.0 - num2).Ticks;
				zdo.Set(key, ticks);
				zdo.Set(MarkHash, ticks);
				return true;
			}
		}
		return false;
	}

	private static string F(float v)
	{
		return v.ToString("0.##", CultureInfo.InvariantCulture);
	}

	private static string Pos(Vector3 p)
	{
		return "(" + p.x.ToString("0", CultureInfo.InvariantCulture) + ", " + p.y.ToString("0", CultureInfo.InvariantCulture) + ", " + p.z.ToString("0", CultureInfo.InvariantCulture) + ")";
	}

	private void Debug(string text)
	{
		if (_cfgDebug.Value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)text);
		}
	}

	private void Fail(string where, Exception e)
	{
		string text = where + ": " + e.GetType().Name + ": " + e.Message;
		if (_loggedErrors.Add(text))
		{
			((BaseUnityPlugin)this).Logger.LogError((object)(text + "\n" + e.StackTrace));
		}
		if (++_errorCount >= 25 && !_disabledByErrors)
		{
			_disabledByErrors = true;
			((BaseUnityPlugin)this).Logger.LogError((object)"Too many errors, Station Speed is now inert until the game restarts.");
		}
	}

	private void FindHostFields()
	{
		FieldInfo field = typeof(ZDOMan).GetField("m_objectsByID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (field != null && typeof(Dictionary<ZDOID, ZDO>).IsAssignableFrom(field.FieldType))
		{
			_fiObjectsByID = field;
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)"ZDOMan.m_objectsByID was not found or has an unexpected type; the host will not shift fermenters and plants of players without the mod.");
		}
	}

	private void UpdateHost()
	{
		if (!HostActive)
		{
			if (_scanCursor >= 0)
			{
				ResetScan();
			}
			return;
		}
		if (_scanCursor < 0)
		{
			if (Time.time < _nextScan)
			{
				return;
			}
			if (!BeginScan())
			{
				_nextScan = Time.time + _cfgHostInterval.Value;
				return;
			}
		}
		StepScan();
	}

	private void ResetScan()
	{
		_scan.Clear();
		_scanCursor = -1;
	}

	private bool BeginScan()
	{
		IndexPrefabs();
		if (_fermenterPrefabs.Count == 0 && _plantPrefabs.Count == 0)
		{
			return false;
		}
		if (!(_fiObjectsByID.GetValue(ZDOMan.instance) is Dictionary<ZDOID, ZDO> dictionary))
		{
			return false;
		}
		_scan.Clear();
		if (_scan.Capacity < dictionary.Count)
		{
			_scan.Capacity = dictionary.Count;
		}
		foreach (ZDO value in dictionary.Values)
		{
			_scan.Add(value);
		}
		_scanCursor = 0;
		return true;
	}

	private void StepScan()
	{
		ZNet instance = ZNet.instance;
		ZDOMan instance2 = ZDOMan.instance;
		DateTime time = instance.GetTime();
		int num = Math.Min(_scan.Count, _scanCursor + 20000);
		while (_scanCursor < num)
		{
			Examine(_scan[_scanCursor], time, instance2);
			_scanCursor++;
		}
		if (_scanCursor >= _scan.Count)
		{
			_lastScanCount = _scan.Count;
			ResetScan();
			_nextScan = Time.time + _cfgHostInterval.Value;
		}
	}

	private void IndexPrefabs()
	{
		ZNetScene instance = ZNetScene.instance;
		if ((Object)(object)instance == (Object)(object)_indexedScene || instance.m_prefabs == null)
		{
			return;
		}
		_fermenterPrefabs.Clear();
		_plantPrefabs.Clear();
		foreach (GameObject prefab in instance.m_prefabs)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				continue;
			}
			Fermenter component = prefab.GetComponent<Fermenter>();
			if ((Object)(object)component != (Object)null)
			{
				_fermenterPrefabs[instance.GetPrefabHash(prefab)] = component;
				continue;
			}
			Plant component2 = prefab.GetComponent<Plant>();
			if ((Object)(object)component2 != (Object)null)
			{
				_plantPrefabs[instance.GetPrefabHash(prefab)] = component2;
			}
		}
		_indexedScene = instance;
		Debug("host: " + _fermenterPrefabs.Count + " fermenter and " + _plantPrefabs.Count + " plant prefabs indexed");
	}

	private void Examine(ZDO zdo, DateTime now, ZDOMan man)
	{
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
		if (zdo == null)
		{
			return;
		}
		int prefab = zdo.GetPrefab();
		if (prefab == 0)
		{
			return;
		}
		if (_fermenterPrefabs.TryGetValue(prefab, out var value))
		{
			if (Fresh(zdo, ZDOVars.s_startTime, now))
			{
				string name = ((Object)((Component)value).gameObject).name;
				float num = Multiplier(name, _cfgFermenter);
				if (num != 1f && man.GetZDO(zdo.m_uid) == zdo && ShiftStart(zdo, ZDOVars.s_startTime, value.m_fermentationDuration, num))
				{
					_hostShifted++;
					Debug("host: " + name + " " + Pos(zdo.GetPosition()) + ": ready in " + F(value.m_fermentationDuration / num) + " s instead of " + F(value.m_fermentationDuration) + " (x" + F(num) + ")");
				}
			}
		}
		else
		{
			if (_growTimeMismatch || !_plantPrefabs.TryGetValue(prefab, out var value2) || !Fresh(zdo, ZDOVars.s_plantTime, now))
			{
				return;
			}
			string name2 = ((Object)((Component)value2).gameObject).name;
			float num2 = Multiplier(name2, PlantKind(value2, name2));
			if (num2 != 1f && man.GetZDO(zdo.m_uid) == zdo)
			{
				float num3 = GrowTime(zdo, value2);
				if (ShiftStart(zdo, ZDOVars.s_plantTime, num3, num2))
				{
					_hostShifted++;
					Debug("host: " + name2 + " " + Pos(zdo.GetPosition()) + ": grows in " + F(num3 / num2) + " s instead of " + F(num3) + " (x" + F(num2) + ")");
				}
			}
		}
	}

	private static bool Fresh(ZDO zdo, int key, DateTime now)
	{
		long num = zdo.GetLong(key, 0L);
		if (num > 0)
		{
			DateTime maxValue = DateTime.MaxValue;
			if (num <= maxValue.Ticks)
			{
				if (zdo.GetLong(MarkHash, 0L) == num)
				{
					return false;
				}
				double totalSeconds = (now - new DateTime(num)).TotalSeconds;
				if (totalSeconds > -30.0)
				{
					return totalSeconds < 300.0;
				}
				return false;
			}
		}
		return false;
	}

	private static float GrowTime(ZDO zdo, Plant prefab)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		int num = zdo.GetInt(ZDOVars.s_seed, 0);
		if (num == 0)
		{
			num = (int)(((ZDOID)(ref zdo.m_uid)).ID + ((ZDOID)(ref zdo.m_uid)).UserID);
		}
		State state = Random.state;
		Random.InitState(num);
		float value = Random.value;
		Random.state = state;
		return Mathf.Lerp(prefab.m_growTime, prefab.m_growTimeMax, value);
	}

	private bool ApplySmelter(Smelter s, bool awake)
	{
		if ((Object)(object)s == (Object)null)
		{
			return false;
		}
		string text = PrefabName(((Component)s).gameObject);
		float num = (Active ? Multiplier(text, SmelterKind(text)) : 1f);
		Smelter val = PrefabComponent<Smelter>(text);
		if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)s)
		{
			s.m_secPerProduct = val.m_secPerProduct / num;
		}
		else
		{
			if (!awake || num == 1f)
			{
				return false;
			}
			s.m_secPerProduct /= num;
		}
		if (num != 1f)
		{
			Debug(text + ": " + F(s.m_secPerProduct) + " s per product (x" + F(num) + ")");
		}
		return true;
	}

	private bool ApplyCookingStation(CookingStation c, bool awake)
	{
		if ((Object)(object)c == (Object)null || c.m_conversion == null)
		{
			return false;
		}
		string text = PrefabName(((Component)c).gameObject);
		float num = (Active ? Multiplier(text, CookingKind(text)) : 1f);
		CookingStation val = PrefabComponent<CookingStation>(text);
		if ((Object)(object)val == (Object)(object)c || ((Object)(object)val != (Object)null && object.ReferenceEquals(val.m_conversion, c.m_conversion)))
		{
			val = null;
		}
		if ((Object)(object)val == (Object)null && (!awake || num == 1f))
		{
			return false;
		}
		for (int i = 0; i < c.m_conversion.Count; i++)
		{
			ItemConversion val2 = c.m_conversion[i];
			if (val2 == null)
			{
				continue;
			}
			if ((Object)(object)val != (Object)null)
			{
				if (val.m_conversion != null && i < val.m_conversion.Count && val.m_conversion[i] != null)
				{
					val2.m_cookTime = val.m_conversion[i].m_cookTime / num;
				}
			}
			else
			{
				val2.m_cookTime /= num;
			}
		}
		if (c.m_useFuel)
		{
			float num2 = ((Active && ScaleOvenFuel) ? num : 1f);
			if ((Object)(object)val != (Object)null)
			{
				c.m_secPerFuel = Mathf.Max(1, Mathf.RoundToInt((float)val.m_secPerFuel / num2));
			}
			else if (num2 != 1f)
			{
				c.m_secPerFuel = Mathf.Max(1, Mathf.RoundToInt((float)c.m_secPerFuel / num2));
			}
		}
		if (num != 1f)
		{
			Debug(text + ": " + c.m_conversion.Count + " recipes (x" + F(num) + ")" + (c.m_useFuel ? (", fuel " + c.m_secPerFuel + " s per unit") : ""));
		}
		return true;
	}

	private bool ApplyBeehive(Beehive b, bool awake)
	{
		if ((Object)(object)b == (Object)null)
		{
			return false;
		}
		string text = PrefabName(((Component)b).gameObject);
		float num = (Active ? Multiplier(text, _cfgBeehive) : 1f);
		Beehive val = PrefabComponent<Beehive>(text);
		if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)b)
		{
			b.m_secPerUnit = val.m_secPerUnit / num;
		}
		else
		{
			if (!awake || num == 1f)
			{
				return false;
			}
			b.m_secPerUnit /= num;
		}
		if (num != 1f)
		{
			Debug(text + ": " + F(b.m_secPerUnit) + " s per unit (x" + F(num) + ")");
		}
		return true;
	}

	private bool ApplySapCollector(SapCollector s, bool awake)
	{
		if ((Object)(object)s == (Object)null)
		{
			return false;
		}
		string text = PrefabName(((Component)s).gameObject);
		float num = (Active ? Multiplier(text, _cfgSapCollector) : 1f);
		SapCollector val = PrefabComponent<SapCollector>(text);
		if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)s)
		{
			s.m_secPerUnit = val.m_secPerUnit / num;
		}
		else
		{
			if (!awake || num == 1f)
			{
				return false;
			}
			s.m_secPerUnit /= num;
		}
		if (num != 1f)
		{
			Debug(text + ": " + F(s.m_secPerUnit) + " s per unit (x" + F(num) + ")");
		}
		return true;
	}

	private int Reapply()
	{
		if ((Object)(object)ZNetScene.instance == (Object)null)
		{
			return 0;
		}
		int num = 0;
		Smelter[] array = Object.FindObjectsByType<Smelter>((FindObjectsSortMode)0);
		foreach (Smelter s in array)
		{
			if (ApplySmelter(s, awake: false))
			{
				num++;
			}
		}
		CookingStation[] array2 = Object.FindObjectsByType<CookingStation>((FindObjectsSortMode)0);
		foreach (CookingStation c in array2)
		{
			if (ApplyCookingStation(c, awake: false))
			{
				num++;
			}
		}
		Beehive[] array3 = Object.FindObjectsByType<Beehive>((FindObjectsSortMode)0);
		foreach (Beehive b in array3)
		{
			if (ApplyBeehive(b, awake: false))
			{
				num++;
			}
		}
		SapCollector[] array4 = Object.FindObjectsByType<SapCollector>((FindObjectsSortMode)0);
		foreach (SapCollector s2 in array4)
		{
			if (ApplySapCollector(s2, awake: false))
			{
				num++;
			}
		}
		return num;
	}

	private static ZNetView View(Component c)
	{
		if (!((Object)(object)c != (Object)null))
		{
			return null;
		}
		return c.GetComponent<ZNetView>();
	}

	private static long StartTime(Fermenter f)
	{
		ZNetView val = View((Component)(object)f);
		if ((Object)(object)val == (Object)null || val.GetZDO() == null)
		{
			return 0L;
		}
		return val.GetZDO().GetLong(ZDOVars.s_startTime, 0L);
	}

	private void OnFermenterAdded(Fermenter f, long before)
	{
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		if (!Active || (Object)(object)f == (Object)null)
		{
			return;
		}
		ZNetView val = View((Component)(object)f);
		if ((Object)(object)val == (Object)null || val.GetZDO() == null || !val.IsOwner())
		{
			return;
		}
		ZDO zDO = val.GetZDO();
		long num = zDO.GetLong(ZDOVars.s_startTime, 0L);
		if (num != 0 && num != before)
		{
			string text = PrefabName(((Component)f).gameObject);
			float num2 = Multiplier(text, _cfgFermenter);
			if (ShiftStart(zDO, ZDOVars.s_startTime, f.m_fermentationDuration, num2))
			{
				Debug(text + " " + Pos(((Component)f).transform.position) + ": ready in " + F(f.m_fermentationDuration / num2) + " s instead of " + F(f.m_fermentationDuration) + " (x" + F(num2) + ")");
			}
		}
	}

	private static long PlantTime(Plant p)
	{
		ZNetView val = View((Component)(object)p);
		if ((Object)(object)val == (Object)null || val.GetZDO() == null)
		{
			return -1L;
		}
		return val.GetZDO().GetLong(ZDOVars.s_plantTime, 0L);
	}

	private void OnPlantAwake(Plant p, long before)
	{
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		if (!Active || (Object)(object)p == (Object)null || before != 0 || _plantGrowTime == null)
		{
			return;
		}
		ZNetView val = View((Component)(object)p);
		if ((Object)(object)val == (Object)null || val.GetZDO() == null || !val.IsOwner())
		{
			return;
		}
		ZDO zDO = val.GetZDO();
		if (zDO.GetLong(ZDOVars.s_plantTime, 0L) == 0)
		{
			return;
		}
		string text = PrefabName(((Component)p).gameObject);
		float num = Multiplier(text, PlantKind(p, text));
		if (num != 1f)
		{
			float num2 = (float)_plantGrowTime.Invoke(p, null);
			CheckGrowTime(zDO, p, num2);
			if (ShiftStart(zDO, ZDOVars.s_plantTime, num2, num))
			{
				Debug(text + " " + Pos(((Component)p).transform.position) + ": grows in " + F(num2 / num) + " s instead of " + F(num2) + " (x" + F(num) + ")");
			}
		}
	}

	private void CheckGrowTime(ZDO zdo, Plant p, float vanilla)
	{
		if (!_growTimeReported)
		{
			_growTimeReported = true;
			float num = GrowTime(zdo, p);
			_growTimeMismatch = Mathf.Abs(num - vanilla) > 0.01f;
			if (_growTimeMismatch)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Plant grow time computed by the mod (" + F(num) + " s) differs from the game's (" + F(vanilla) + " s) for " + ((Object)((Component)p).gameObject).name + "; the host would shift plants of players without the mod by the wrong amount, so it will not shift plants."));
			}
			else
			{
				Debug("plant grow time check: " + ((Object)((Component)p).gameObject).name + " " + F(vanilla) + " s, same as the game's");
			}
		}
	}

	private void RegisterRpc()
	{
		if (ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.Register<ZPackage>("j1ga.stationspeed.config", (Action<long, ZPackage>)OnConfigPacket);
		}
	}

	private void FlushSync()
	{
		_syncDirty = false;
		int num = Reapply();
		if (num > 0)
		{
			Debug("multipliers re-applied to " + num + " loaded stations");
		}
		ZNet instance = ZNet.instance;
		if (!((Object)(object)instance == (Object)null) && instance.IsServer() && _cfgSync.Value && ZRoutedRpc.instance != null && instance.GetPeers().Count != 0)
		{
			SendConfig(0L);
			Debug("settings sent to " + instance.GetPeers().Count + " peers");
		}
	}

	private ZPackage BuildPacket()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(1);
		val.Write(_cfgEnabled.Value);
		val.Write(_multipliers.Count);
		foreach (ConfigEntry<float> multiplier in _multipliers)
		{
			val.Write(((ConfigEntryBase)multiplier).Definition.Key);
			val.Write(multiplier.Value);
		}
		val.Write(_cfgOverrides.Value ?? "");
		val.Write(_cfgScaleOvenFuel.Value);
		return val;
	}

	private void SendConfig(long target)
	{
		ZRoutedRpc.instance.InvokeRoutedRPC(target, "j1ga.stationspeed.config", new object[1] { BuildPacket() });
	}

	private void OnPeerInfo(ZNet znet, ZRpc rpc)
	{
		if (!znet.IsServer() || !_cfgSync.Value || rpc == null)
		{
			return;
		}
		foreach (ZNetPeer peer in znet.GetPeers())
		{
			if (peer != null && peer.m_rpc == rpc)
			{
				if (peer.m_uid != 0)
				{
					((MonoBehaviour)this).StartCoroutine(SendConfigLater(peer.m_uid));
				}
				break;
			}
		}
	}

	private IEnumerator SendConfigLater(long uid)
	{
		yield return (object)new WaitForSeconds(1f);
		ZNet znet = ZNet.instance;
		if ((Object)(object)znet == (Object)null || !znet.IsServer() || ZRoutedRpc.instance == null || !_cfgSync.Value)
		{
			yield break;
		}
		ZNetPeer peer = znet.GetPeer(uid);
		if (peer != null)
		{
			try
			{
				SendConfig(uid);
				Debug("settings sent to " + peer.m_playerName);
			}
			catch (Exception e)
			{
				Fail("send config", e);
			}
		}
	}

	private void OnConfigPacket(long sender, ZPackage pkg)
	{
		try
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer() || pkg == null)
			{
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer == null || sender != serverPeer.m_uid)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Settings packet from peer " + sender + ", which is not the server; ignored."));
				return;
			}
			int num = pkg.ReadInt();
			if (num != 1)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("The server runs a Station Speed with packet version " + num + ", this one understands " + 1 + "; using the local settings."));
				return;
			}
			bool serverEnabled = pkg.ReadBool();
			int num2 = pkg.ReadInt();
			if (num2 < 0 || num2 > 1000)
			{
				return;
			}
			Dictionary<string, float> dictionary = new Dictionary<string, float>();
			for (int i = 0; i < num2; i++)
			{
				string text = pkg.ReadString();
				float value = pkg.ReadSingle();
				if (!string.IsNullOrEmpty(text))
				{
					dictionary[text] = value;
				}
			}
			string text2 = pkg.ReadString();
			bool serverScaleOvenFuel = pkg.ReadBool();
			_serverValues.Clear();
			foreach (KeyValuePair<string, float> item in dictionary)
			{
				_serverValues[item.Key] = item.Value;
			}
			ReadOverrides(text2, _serverOverrides);
			_serverEnabled = serverEnabled;
			_serverScaleOvenFuel = serverScaleOvenFuel;
			_serverSynced = true;
			int num3 = Reapply();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Settings from the server: " + StatusValues() + ((num3 > 0) ? (" Re-applied to " + num3 + " loaded stations.") : "")));
		}
		catch (Exception e)
		{
			Fail("config packet", e);
		}
	}

	private void ClearServerValues()
	{
		_serverSynced = false;
		_serverValues.Clear();
		_serverOverrides.Clear();
		_serverEnabled = true;
		_serverScaleOvenFuel = true;
	}
}