Decompiled source of Inferno v0.3.2

plugins/Inferno.Core.dll

Decompiled 2 weeks 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 System.Text;
using System.Text.RegularExpressions;
using Inferno.Core.Areas;
using Inferno.Core.Catalog;
using Inferno.Core.Commands;
using Inferno.Core.Fuel;
using Inferno.Core.Permissions;
using Inferno.Core.Settings;
using Inferno.Core.Time;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("GrundleLord")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 GrundleLord. MIT License.")]
[assembly: AssemblyFileVersion("0.3.2.0")]
[assembly: AssemblyInformationalVersion("0.3.2+4c3e115387ee3266bdea5a26431267084f7f433c")]
[assembly: AssemblyProduct("Inferno")]
[assembly: AssemblyTitle("Inferno.Core")]
[assembly: InternalsVisibleTo("Inferno.Core.Tests")]
[assembly: AssemblyVersion("0.3.2.0")]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlyArray<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	int ICollection.Count => _items.Length;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			return _items[index];
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => _items.Length;

	T IReadOnlyList<T>.this[int index] => _items[index];

	int ICollection<T>.Count => _items.Length;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			return _items[index];
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlyArray(T[] items)
	{
		_items = items;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return ((IEnumerable)_items).GetEnumerator();
	}

	void ICollection.CopyTo(Array array, int index)
	{
		((ICollection)_items).CopyTo(array, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return ((IList)_items).Contains(value);
	}

	int IList.IndexOf(object value)
	{
		return ((IList)_items).IndexOf(value);
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return ((IEnumerable<T>)_items).GetEnumerator();
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return ((ICollection<T>)_items).Contains(item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		((ICollection<T>)_items).CopyTo(array, arrayIndex);
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		return ((IList<T>)_items).IndexOf(item);
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
	{
		object IEnumerator.Current => _item;

		T IEnumerator<T>.Current => _item;

		public Enumerator(T item)
		{
			_item = item;
		}

		bool IEnumerator.MoveNext()
		{
			if (!_moveNextCalled)
			{
				return _moveNextCalled = true;
			}
			return false;
		}

		void IEnumerator.Reset()
		{
			_moveNextCalled = false;
		}

		void IDisposable.Dispose()
		{
		}
	}

	int ICollection.Count => 1;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => 1;

	T IReadOnlyList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
	}

	int ICollection<T>.Count => 1;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlySingleElementList(T item)
	{
		_item = item;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection.CopyTo(Array array, int index)
	{
		array.SetValue(_item, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return EqualityComparer<T>.Default.Equals(_item, (T)value);
	}

	int IList.IndexOf(object value)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
		{
			return -1;
		}
		return 0;
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return EqualityComparer<T>.Default.Equals(_item, item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		array[arrayIndex] = _item;
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, item))
		{
			return -1;
		}
		return 0;
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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 Inferno.Core.Time
{
	public static class ClockSetting
	{
		public const int MaxHour = 24;

		public const int MaxMinute = 60;

		public static TimeOfDay ToTimeOfDay(int hour, int minute)
		{
			if ((hour < 0 || hour > 24) ? true : false)
			{
				throw new ArgumentOutOfRangeException("hour", hour, "Hour must be 0 to 24.");
			}
			if ((minute < 0 || minute > 60) ? true : false)
			{
				throw new ArgumentOutOfRangeException("minute", minute, "Minute must be 0 to 60.");
			}
			int num = (hour * 60 + minute) % 1440;
			return TimeOfDay.FromHourMinute(num / 60, num % 60);
		}

		public static bool TryParse(string? text, out TimeOfDay result)
		{
			result = default(TimeOfDay);
			string[] array = text?.Trim().Split(new char[1] { ':' });
			bool flag = array == null || array.Length != 2;
			if (!flag)
			{
				int length = array[0].Length;
				bool flag2 = ((length < 1 || length > 2) ? true : false);
				flag = flag2;
			}
			if (flag || array[1].Length != 2)
			{
				return false;
			}
			if (!TimeOfDay.TryParseDigits(array[0], out var value) || !TimeOfDay.TryParseDigits(array[1], out var value2) || value > 24 || value2 > 60)
			{
				return false;
			}
			result = ToTimeOfDay(value, value2);
			return true;
		}
	}
	public readonly struct DailySchedule : IEquatable<DailySchedule>
	{
		public static DailySchedule AlwaysOn => default(DailySchedule);

		public static DailySchedule Night => new DailySchedule(TimeOfDay.FromHourMinute(18, 0), TimeOfDay.FromHourMinute(6, 0));

		public static DailySchedule Day => new DailySchedule(TimeOfDay.FromHourMinute(6, 0), TimeOfDay.FromHourMinute(18, 0));

		public TimeOfDay OnTime { get; }

		public TimeOfDay OffTime { get; }

		public bool IsAlwaysOn => OnTime == OffTime;

		public DailySchedule(TimeOfDay onTime, TimeOfDay offTime)
		{
			OnTime = onTime;
			OffTime = offTime;
		}

		public bool IsOnAt(TimeOfDay now)
		{
			int minutesSinceMidnight = OnTime.MinutesSinceMidnight;
			int minutesSinceMidnight2 = OffTime.MinutesSinceMidnight;
			int minutesSinceMidnight3 = now.MinutesSinceMidnight;
			if (minutesSinceMidnight == minutesSinceMidnight2)
			{
				return true;
			}
			if (minutesSinceMidnight >= minutesSinceMidnight2)
			{
				if (minutesSinceMidnight3 < minutesSinceMidnight)
				{
					return minutesSinceMidnight3 < minutesSinceMidnight2;
				}
				return true;
			}
			if (minutesSinceMidnight3 >= minutesSinceMidnight)
			{
				return minutesSinceMidnight3 < minutesSinceMidnight2;
			}
			return false;
		}

		public override string ToString()
		{
			if (!IsAlwaysOn)
			{
				return $"{OnTime}-{OffTime}";
			}
			return "always on";
		}

		public bool Equals(DailySchedule other)
		{
			if (OnTime == other.OnTime)
			{
				return OffTime == other.OffTime;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is DailySchedule other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return OnTime.MinutesSinceMidnight * 1440 + OffTime.MinutesSinceMidnight;
		}

		public static bool operator ==(DailySchedule left, DailySchedule right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(DailySchedule left, DailySchedule right)
		{
			return !left.Equals(right);
		}
	}
	public static class GameClock
	{
		private const double RawSunrise = 0.15;

		private const double RawSunset = 0.85;

		public static TimeOfDay TimeAt(double worldTimeSeconds, long dayLengthSeconds)
		{
			return TimeOfDay.FromDayFraction(DisplayedDayFraction(worldTimeSeconds, dayLengthSeconds));
		}

		public static double DisplayedDayFraction(double worldTimeSeconds, long dayLengthSeconds)
		{
			if (!(worldTimeSeconds >= 0.0) || double.IsInfinity(worldTimeSeconds))
			{
				throw new ArgumentOutOfRangeException("worldTimeSeconds", worldTimeSeconds, "World time must be a finite value ≥ 0.");
			}
			if (dayLengthSeconds <= 0)
			{
				throw new ArgumentOutOfRangeException("dayLengthSeconds", dayLengthSeconds, "Day length must be > 0.");
			}
			return Rescale(worldTimeSeconds % (double)dayLengthSeconds / (double)dayLengthSeconds);
		}

		internal static double Rescale(double raw)
		{
			if (raw >= 0.15 && raw <= 0.85)
			{
				return 0.25 + (raw - 0.15) / 0.7 * 0.5;
			}
			if (raw < 0.5)
			{
				return raw / 0.15 * 0.25;
			}
			return 0.75 + (raw - 0.85) / 0.15000000000000002 * 0.25;
		}
	}
	public readonly struct TimeOfDay : IEquatable<TimeOfDay>
	{
		public const int MinutesPerDay = 1440;

		public int MinutesSinceMidnight { get; }

		public int Hour => MinutesSinceMidnight / 60;

		public int Minute => MinutesSinceMidnight % 60;

		private TimeOfDay(int minutesSinceMidnight)
		{
			MinutesSinceMidnight = minutesSinceMidnight;
		}

		public static TimeOfDay FromHourMinute(int hour, int minute)
		{
			if ((hour < 0 || hour > 23) ? true : false)
			{
				throw new ArgumentOutOfRangeException("hour", hour, "Hour must be 0 to 23.");
			}
			if ((minute < 0 || minute > 59) ? true : false)
			{
				throw new ArgumentOutOfRangeException("minute", minute, "Minute must be 0 to 59.");
			}
			return new TimeOfDay(hour * 60 + minute);
		}

		public static TimeOfDay FromDayFraction(double fraction)
		{
			if (!(fraction >= 0.0) || !(fraction <= 1.0))
			{
				throw new ArgumentOutOfRangeException("fraction", fraction, "Day fraction must be 0 to 1.");
			}
			return new TimeOfDay((int)Math.Floor(fraction * 1440.0) % 1440);
		}

		public static bool TryParse(string? text, out TimeOfDay result)
		{
			result = default(TimeOfDay);
			if (text == null)
			{
				return false;
			}
			string[] array = text.Trim().Split(new char[1] { ':' });
			bool flag = array.Length != 2;
			if (!flag)
			{
				int length = array[0].Length;
				bool flag2 = ((length < 1 || length > 2) ? true : false);
				flag = flag2;
			}
			if (flag || array[1].Length != 2)
			{
				return false;
			}
			if (!TryParseDigits(array[0], out var value) || !TryParseDigits(array[1], out var value2))
			{
				return false;
			}
			if (value > 23 || value2 > 59)
			{
				return false;
			}
			result = new TimeOfDay(value * 60 + value2);
			return true;
		}

		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture, "{0:00}:{1:00}", Hour, Minute);
		}

		public bool Equals(TimeOfDay other)
		{
			return MinutesSinceMidnight == other.MinutesSinceMidnight;
		}

		public override bool Equals(object? obj)
		{
			if (obj is TimeOfDay other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return MinutesSinceMidnight;
		}

		public static bool operator ==(TimeOfDay left, TimeOfDay right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(TimeOfDay left, TimeOfDay right)
		{
			return !left.Equals(right);
		}

		internal static bool TryParseDigits(string text, out int value)
		{
			value = 0;
			foreach (char c in text)
			{
				if ((c < '0' || c > '9') ? true : false)
				{
					return false;
				}
				value = value * 10 + (c - 48);
			}
			return true;
		}
	}
}
namespace Inferno.Core.Sync
{
	public static class MenuCommands
	{
		public static IReadOnlyList<string> ForItem(string prefabName, ItemSettings before, ItemSettings after)
		{
			if (string.IsNullOrWhiteSpace(prefabName) || prefabName.IndexOfAny(new char[4] { ' ', '\t', '\n', '\r' }) >= 0)
			{
				throw new ArgumentException("Prefab name must be a single word.", "prefabName");
			}
			if (before == null)
			{
				throw new ArgumentNullException("before");
			}
			if (after == null)
			{
				throw new ArgumentNullException("after");
			}
			List<string> list = new List<string>();
			if (before.BurnRateLevel != after.BurnRateLevel)
			{
				list.Add(Command("burnrate", prefabName, after.BurnRateLevel.ToString(CultureInfo.InvariantCulture)));
			}
			if (before.Schedule != after.Schedule)
			{
				list.Add(after.Schedule.IsAlwaysOn ? Command("schedule", prefabName, "off") : Command("schedule", prefabName, after.Schedule.OnTime.ToString(), after.Schedule.OffTime.ToString()));
			}
			if (before.Smoke != after.Smoke)
			{
				list.Add(Command("smoke", prefabName, OnOff(after.Smoke)));
			}
			if (before.AlwaysOn != after.AlwaysOn)
			{
				list.Add(Command("alwayson", prefabName, OnOff(after.AlwaysOn)));
			}
			return list;
		}

		public static IReadOnlyList<string> ForGeneral(GeneralSettings before, GeneralSettings after)
		{
			if (before == null)
			{
				throw new ArgumentNullException("before");
			}
			if (after == null)
			{
				throw new ArgumentNullException("after");
			}
			List<string> list = new List<string>();
			AddIfChanged(list, "adminonly", before.AdminOnly, after.AdminOnly);
			AddIfChanged(list, "hidecommands", before.HideCommands, after.HideCommands);
			AddIfChanged(list, "ignorerain", before.IgnoreRain, after.IgnoreRain);
			AddIfChanged(list, "serverownership", before.ServerOwnership, after.ServerOwnership);
			return list;
		}

		private static void AddIfChanged(List<string> commands, string name, bool before, bool after)
		{
			if (before != after)
			{
				commands.Add(Command(name, OnOff(after)));
			}
		}

		private static string Command(params string[] parts)
		{
			return "!fires " + string.Join(" ", parts);
		}

		private static string OnOff(bool value)
		{
			if (!value)
			{
				return "off";
			}
			return "on";
		}
	}
	public sealed class SettingsMessage
	{
		public const int ProtocolVersion = 1;

		private const string Header = "inferno-settings";

		private const char Field = '\t';

		private const char Line = '\n';

		public bool CanEdit { get; }

		public GeneralSettings General { get; }

		public IReadOnlyDictionary<string, ItemSettings> Items { get; }

		public SettingsMessage(bool canEdit, GeneralSettings general, IReadOnlyDictionary<string, ItemSettings> items)
		{
			CanEdit = canEdit;
			General = general ?? throw new ArgumentNullException("general");
			Items = items ?? throw new ArgumentNullException("items");
		}

		public string Encode()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Append(stringBuilder, "inferno-settings", Number(1));
			Append(stringBuilder, "canedit", Bit(CanEdit));
			Append(stringBuilder, "general", Bit(General.AdminOnly), Bit(General.HideCommands), Bit(General.IgnoreRain), Bit(General.ServerOwnership));
			foreach (KeyValuePair<string, ItemSettings> item in Items)
			{
				if (item.Key.IndexOf('\t') >= 0 || item.Key.IndexOf('\n') >= 0)
				{
					throw new ArgumentException("Prefab name '" + item.Key + "' contains a tab or newline.", "Items");
				}
				ItemSettings value = item.Value;
				Append(stringBuilder, "item", item.Key, Bit(value.AlwaysOn), Number(value.BurnRateLevel), Number(value.Schedule.OnTime.MinutesSinceMidnight), Number(value.Schedule.OffTime.MinutesSinceMidnight), Bit(value.Smoke));
			}
			return stringBuilder.ToString();
		}

		public static bool TryDecode(string? text, out SettingsMessage message)
		{
			message = null;
			if (text == null)
			{
				return false;
			}
			string[] array = text.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length < 3 || !TryFields(array[0], "inferno-settings", 1, out string[] fields) || !TryInt(fields[0], out var value) || value != 1 || !TryFields(array[1], "canedit", 1, out string[] fields2) || !TryBit(fields2[0], out var value2) || !TryFields(array[2], "general", 4, out string[] fields3) || !TryBit(fields3[0], out var value3) || !TryBit(fields3[1], out var value4) || !TryBit(fields3[2], out var value5) || !TryBit(fields3[3], out var value6))
			{
				return false;
			}
			Dictionary<string, ItemSettings> dictionary = new Dictionary<string, ItemSettings>(StringComparer.OrdinalIgnoreCase);
			for (int i = 3; i < array.Length; i++)
			{
				if (!TryItem(array[i], out string name, out ItemSettings settings) || dictionary.ContainsKey(name))
				{
					return false;
				}
				dictionary.Add(name, settings);
			}
			message = new SettingsMessage(value2, new GeneralSettings(value3, value4, value5, value6), dictionary);
			return true;
		}

		private static bool TryItem(string line, out string name, out ItemSettings settings)
		{
			name = string.Empty;
			settings = null;
			string[] fields;
			bool value = default(bool);
			int value2 = default(int);
			bool flag = !TryFields(line, "item", 6, out fields) || fields[0].Length == 0 || !TryBit(fields[1], out value) || !TryInt(fields[2], out value2);
			if (!flag)
			{
				bool flag2 = ((value2 < -10 || value2 > 10) ? true : false);
				flag = flag2;
			}
			if (flag || !TryMinutes(fields[3], out var time) || !TryMinutes(fields[4], out var time2) || !TryBit(fields[5], out var value3))
			{
				return false;
			}
			name = fields[0];
			settings = new ItemSettings(value, value2, new DailySchedule(time, time2), value3);
			return true;
		}

		private static bool TryFields(string line, string tag, int count, out string[] fields)
		{
			string[] array = line.Split(new char[1] { '\t' });
			fields = new string[count];
			if (array.Length != count + 1 || array[0] != tag)
			{
				return false;
			}
			Array.Copy(array, 1, fields, 0, count);
			return true;
		}

		private static bool TryMinutes(string text, out TimeOfDay time)
		{
			time = default(TimeOfDay);
			int value;
			bool flag = !TryInt(text, out value);
			if (!flag)
			{
				bool flag2 = ((value < 0 || value >= 1440) ? true : false);
				flag = flag2;
			}
			if (flag)
			{
				return false;
			}
			time = TimeOfDay.FromHourMinute(value / 60, value % 60);
			return true;
		}

		private static bool TryBit(string text, out bool value)
		{
			value = text == "1";
			if (!value)
			{
				return text == "0";
			}
			return true;
		}

		private static bool TryInt(string text, out int value)
		{
			return int.TryParse(text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out value);
		}

		private static string Bit(bool value)
		{
			if (!value)
			{
				return "0";
			}
			return "1";
		}

		private static string Number(int value)
		{
			return value.ToString(CultureInfo.InvariantCulture);
		}

		private static void Append(StringBuilder sb, params string[] fields)
		{
			sb.Append(string.Join('\t'.ToString(), fields));
			sb.Append('\n');
		}
	}
}
namespace Inferno.Core.Settings
{
	public static class BurnRate
	{
		public const int Min = -10;

		public const int Max = 10;

		public const int Vanilla = 0;

		public static double ToMultiplier(int level)
		{
			if ((level < -10 || level > 10) ? true : false)
			{
				throw new ArgumentOutOfRangeException("level", level, "Burn rate must be -10 to 10.");
			}
			return 1.0 + (double)level / 10.0;
		}
	}
	public sealed class GeneralSettings
	{
		public static GeneralSettings Default { get; } = new GeneralSettings(adminOnly: false, hideCommands: true, ignoreRain: false);

		public bool AdminOnly { get; }

		public bool HideCommands { get; }

		public bool IgnoreRain { get; }

		public bool ServerOwnership { get; }

		public GeneralSettings(bool adminOnly, bool hideCommands, bool ignoreRain, bool serverOwnership = false)
		{
			AdminOnly = adminOnly;
			HideCommands = hideCommands;
			IgnoreRain = ignoreRain;
			ServerOwnership = serverOwnership;
		}

		public GeneralSettings WithAdminOnly(bool value)
		{
			return new GeneralSettings(value, HideCommands, IgnoreRain, ServerOwnership);
		}

		public GeneralSettings WithHideCommands(bool value)
		{
			return new GeneralSettings(AdminOnly, value, IgnoreRain, ServerOwnership);
		}

		public GeneralSettings WithIgnoreRain(bool value)
		{
			return new GeneralSettings(AdminOnly, HideCommands, value, ServerOwnership);
		}

		public GeneralSettings WithServerOwnership(bool value)
		{
			return new GeneralSettings(AdminOnly, HideCommands, IgnoreRain, value);
		}
	}
	public interface ISettingsStore
	{
		GeneralSettings General { get; set; }

		ItemSettings GetItem(string prefabName);

		void SetItem(string prefabName, ItemSettings settings);

		ItemSettings? GetObject(string objectKey);

		void SetObject(string objectKey, ItemSettings? settings);
	}
	public enum ItemKind
	{
		LightSource,
		FuelStation
	}
	public sealed class ItemSettings : IEquatable<ItemSettings>
	{
		public static ItemSettings Vanilla { get; } = new ItemSettings(alwaysOn: false, 0, DailySchedule.AlwaysOn);

		public static ItemSettings AlwaysOnDefault { get; } = new ItemSettings(alwaysOn: true, 0, DailySchedule.AlwaysOn);

		public bool AlwaysOn { get; }

		public int BurnRateLevel { get; }

		public double BurnMultiplier => BurnRate.ToMultiplier(BurnRateLevel);

		public DailySchedule Schedule { get; }

		public bool Smoke { get; }

		public ItemSettings(bool alwaysOn, int burnRate, DailySchedule schedule, bool smoke = true)
		{
			BurnRate.ToMultiplier(burnRate);
			AlwaysOn = alwaysOn;
			BurnRateLevel = burnRate;
			Schedule = schedule;
			Smoke = smoke;
		}

		public static ItemSettings DefaultFor(ItemKind kind)
		{
			if (kind != ItemKind.LightSource)
			{
				return Vanilla;
			}
			return AlwaysOnDefault;
		}

		public ItemSettings WithAlwaysOn(bool value)
		{
			return new ItemSettings(value, BurnRateLevel, Schedule, Smoke);
		}

		public ItemSettings WithBurnRate(int value)
		{
			return new ItemSettings(AlwaysOn, value, Schedule, Smoke);
		}

		public ItemSettings WithSchedule(DailySchedule value)
		{
			return new ItemSettings(AlwaysOn, BurnRateLevel, value, Smoke);
		}

		public ItemSettings WithSmoke(bool value)
		{
			return new ItemSettings(AlwaysOn, BurnRateLevel, Schedule, value);
		}

		public override string ToString()
		{
			return $"alwayson={OnOff(AlwaysOn)} burnrate={BurnRateLevel} schedule={Schedule} smoke={OnOff(Smoke)}";
		}

		public bool Equals(ItemSettings? other)
		{
			if (other != null && AlwaysOn == other.AlwaysOn && BurnRateLevel == other.BurnRateLevel && Schedule == other.Schedule)
			{
				return Smoke == other.Smoke;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as ItemSettings);
		}

		public override int GetHashCode()
		{
			return Schedule.GetHashCode() * 128 + (BurnRateLevel - -10) * 4 + (AlwaysOn ? 2 : 0) + (Smoke ? 1 : 0);
		}

		private static string OnOff(bool value)
		{
			if (!value)
			{
				return "off";
			}
			return "on";
		}
	}
	public sealed class Preset
	{
		private readonly Func<CatalogItem, ItemSettings, ItemSettings?> _apply;

		public string Name { get; }

		public string DefaultTarget { get; }

		public string Description { get; }

		internal Preset(string name, string defaultTarget, string description, Func<CatalogItem, ItemSettings, ItemSettings?> apply)
		{
			Name = name;
			DefaultTarget = defaultTarget;
			Description = description;
			_apply = apply;
		}

		public ItemSettings? Apply(CatalogItem item, ItemSettings current)
		{
			return _apply(item, current);
		}
	}
	public static class Presets
	{
		public static Preset Eternal { get; } = new Preset("eternal", "lights", "always on, never needs fuel (default for lights)", (CatalogItem _, ItemSettings s) => s.WithAlwaysOn(value: true).WithBurnRate(0).WithSchedule(DailySchedule.AlwaysOn));

		public static Preset Night { get; } = new Preset("night", "lights", "lit 18:00-06:00, never uses fuel", (CatalogItem item, ItemSettings s) => (!item.CanSchedule) ? null : s.WithAlwaysOn(value: false).WithBurnRate(-10).WithSchedule(DailySchedule.Night));

		public static Preset Vanilla { get; } = new Preset("vanilla", "all", "plain game behaviour", (CatalogItem _, ItemSettings s) => s.WithAlwaysOn(value: false).WithBurnRate(0).WithSchedule(DailySchedule.AlwaysOn));

		public static IReadOnlyList<Preset> All { get; } = new <>z__ReadOnlyArray<Preset>(new Preset[3] { Eternal, Night, Vanilla });

		public static bool TryGet(string? name, out Preset preset)
		{
			foreach (Preset item in All)
			{
				if (string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase))
				{
					preset = item;
					return true;
				}
			}
			preset = null;
			return false;
		}
	}
	public static class SettingsDiff
	{
		public static IReadOnlyList<SettingChange> Compare(SettingsSnapshot before, SettingsSnapshot after)
		{
			if (before == null)
			{
				throw new ArgumentNullException("before");
			}
			if (after == null)
			{
				throw new ArgumentNullException("after");
			}
			List<SettingChange> list = new List<SettingChange>();
			AddIfChanged(list, "AdminOnly", before.General.AdminOnly, after.General.AdminOnly);
			AddIfChanged(list, "HideCommands", before.General.HideCommands, after.General.HideCommands);
			AddIfChanged(list, "IgnoreRain", before.General.IgnoreRain, after.General.IgnoreRain);
			AddIfChanged(list, "ServerOwnership", before.General.ServerOwnership, after.General.ServerOwnership);
			foreach (KeyValuePair<string, ItemSettings> item in after.Items)
			{
				if (before.Items.TryGetValue(item.Key, out ItemSettings value) && !value.Equals(item.Value))
				{
					list.Add(new SettingChange(item.Key, "Settings", value.ToString(), item.Value.ToString()));
				}
			}
			return list;
		}

		private static void AddIfChanged(List<SettingChange> changes, string name, bool before, bool after)
		{
			if (before != after)
			{
				changes.Add(new SettingChange("General", name, before ? "on" : "off", after ? "on" : "off"));
			}
		}
	}
	public sealed class SettingsSnapshot
	{
		public GeneralSettings General { get; }

		public IReadOnlyDictionary<string, ItemSettings> Items { get; }

		public SettingsSnapshot(GeneralSettings general, IReadOnlyDictionary<string, ItemSettings> items)
		{
			General = general ?? throw new ArgumentNullException("general");
			Items = items ?? throw new ArgumentNullException("items");
		}
	}
}
namespace Inferno.Core.Permissions
{
	public static class PermissionPolicy
	{
		public static bool CanChangeSettings(bool isAdmin, bool adminOnly)
		{
			if (!isAdmin)
			{
				return !adminOnly;
			}
			return true;
		}
	}
}
namespace Inferno.Core.Ownership
{
	public static class FireplaceRpc
	{
		public const int StateOn = 1;

		public const int StateOff = 2;

		public static float AddOne(float fuel, float maxFuel)
		{
			fuel = Sanitize(fuel);
			if (Math.Ceiling(fuel) >= (double)maxFuel)
			{
				return fuel;
			}
			return Clamp(Clamp(fuel, maxFuel) + 1f, maxFuel);
		}

		public static float AddAmount(float fuel, float amount, float maxFuel)
		{
			return Clamp(Sanitize(fuel) + Sanitize(amount, allowNegative: true), maxFuel);
		}

		public static float SetAmount(float fuel, float maxFuel)
		{
			return Clamp(Sanitize(fuel), maxFuel);
		}

		public static int Toggle(int state)
		{
			if (state != 1)
			{
				return 1;
			}
			return 2;
		}

		private static float Clamp(float value, float max)
		{
			return Math.Max(0f, Math.Min(max, value));
		}

		private static float Sanitize(float value, bool allowNegative = false)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value) && (allowNegative || !(value < 0f)))
			{
				return value;
			}
			return 0f;
		}
	}
	public sealed class LoanBook<TKey> where TKey : notnull
	{
		private readonly Dictionary<TKey, double> _until = new Dictionary<TKey, double>();

		public int Count => _until.Count;

		public void Lend(TKey id, double now, double seconds)
		{
			if (!(seconds > 0.0))
			{
				throw new ArgumentOutOfRangeException("seconds", seconds, "Loan time must be > 0.");
			}
			double num = now + seconds;
			_until[id] = (_until.TryGetValue(id, out var value) ? Math.Max(value, num) : num);
		}

		public bool IsOnLoan(TKey id, double now)
		{
			if (_until.TryGetValue(id, out var value))
			{
				return now < value;
			}
			return false;
		}

		public void Prune(double now)
		{
			List<TKey> list = null;
			foreach (KeyValuePair<TKey, double> item in _until)
			{
				if (now >= item.Value)
				{
					(list ?? (list = new List<TKey>())).Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (TKey item2 in list)
			{
				_until.Remove(item2);
			}
		}

		public void Clear()
		{
			_until.Clear();
		}
	}
	public enum OwnershipAction
	{
		None,
		Claim,
		Release
	}
	public static class OwnershipPolicy
	{
		public static OwnershipAction Decide(bool modeEnabled, bool eligible, bool ownedByServer, bool onLoan)
		{
			if (!modeEnabled || !eligible)
			{
				if (!ownedByServer)
				{
					return OwnershipAction.None;
				}
				return OwnershipAction.Release;
			}
			if (!(onLoan || ownedByServer))
			{
				return OwnershipAction.Claim;
			}
			return OwnershipAction.None;
		}
	}
}
namespace Inferno.Core.Fuel
{
	public static class FuelController
	{
		public const double UnattendedStepSeconds = 30.0;

		private const float Epsilon = 0.0001f;

		private const float RefillMargin = 0.5f;

		public static FuelDecision Decide(ItemSettings settings, FuelItemInfo item, FuelState state, float? baseline, TimeOfDay now, bool ignoreRain)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			float num = ((state.Fuel >= 0f) ? state.Fuel : 0f);
			DecideSwitch(settings, item, state, now, ignoreRain, out var setSwitchedOn, out var setMarker);
			bool flag = state.IsUnattended && item.BurnsOverTime && state.SecondsSinceBurnClock >= 30.0;
			bool flag2 = false;
			float? num2;
			if (settings.AlwaysOn)
			{
				flag2 = num <= item.MaxFuel - Math.Min(0.5f, item.MaxFuel / 2f);
				num2 = (flag2 ? new float?(item.MaxFuel) : ((float?)null));
			}
			else
			{
				num2 = ScaleFuelLoss(settings.BurnMultiplier, item.MaxFuel, num, baseline);
				if (flag && state.IsSwitchedOn)
				{
					num2 = BurnUnattended(num2 ?? num, state.SecondsSinceBurnClock, item.SecondsPerFuel, settings.BurnMultiplier) ?? num2;
				}
			}
			bool mustWin = !state.IsUnattended && (flag2 || setSwitchedOn.HasValue);
			return new FuelDecision(num2, setSwitchedOn, setMarker, flag, num2 ?? num, mustWin);
		}

		public static FuelDecision RestoreForShutdown(FuelState state)
		{
			if (!state.SwitchedOffByInferno)
			{
				return new FuelDecision(null, null, null, refreshBurnClock: false, state.Fuel);
			}
			return new FuelDecision(null, state.IsSwitchedOn ? ((bool?)null) : new bool?(true), false, refreshBurnClock: false, state.Fuel);
		}

		private static void DecideSwitch(ItemSettings settings, FuelItemInfo item, FuelState state, TimeOfDay now, bool ignoreRain, out bool? setSwitchedOn, out bool? setMarker)
		{
			setSwitchedOn = null;
			setMarker = null;
			if (!item.HasOnOffSwitch)
			{
				return;
			}
			if (!settings.AlwaysOn && !settings.Schedule.IsOnAt(now))
			{
				if (state.IsSwitchedOn)
				{
					setSwitchedOn = false;
					setMarker = true;
				}
				return;
			}
			if (!state.IsSwitchedOn && (state.SwitchedOffByInferno || ignoreRain))
			{
				setSwitchedOn = true;
			}
			if (state.SwitchedOffByInferno)
			{
				setMarker = false;
			}
		}

		private static float? ScaleFuelLoss(double multiplier, float maxFuel, float fuel, float? baseline)
		{
			if (baseline.HasValue)
			{
				float valueOrDefault = baseline.GetValueOrDefault();
				if (!(fuel >= valueOrDefault))
				{
					float num = valueOrDefault - fuel;
					float num2 = (float)Math.Max(0.0, Math.Min(maxFuel, (double)valueOrDefault - (double)num * multiplier));
					if (!(Math.Abs(num2 - fuel) > 0.0001f))
					{
						return null;
					}
					return num2;
				}
			}
			return null;
		}

		private static float? BurnUnattended(float fuel, double seconds, float secondsPerFuel, double multiplier)
		{
			double num = seconds / (double)secondsPerFuel * multiplier;
			float num2 = (float)Math.Max(0.0, (double)fuel - num);
			if (!(Math.Abs(num2 - fuel) > 0.0001f))
			{
				return null;
			}
			return num2;
		}
	}
	public readonly struct FuelDecision
	{
		public float? SetFuel { get; }

		public bool? SetSwitchedOn { get; }

		public bool? SetSwitchedOffByInferno { get; }

		public bool RefreshBurnClock { get; }

		public float Baseline { get; }

		public bool MustWin { get; }

		public bool HasChanges
		{
			get
			{
				if (!SetFuel.HasValue && !SetSwitchedOn.HasValue && !SetSwitchedOffByInferno.HasValue)
				{
					return RefreshBurnClock;
				}
				return true;
			}
		}

		public FuelDecision(float? setFuel, bool? setSwitchedOn, bool? setSwitchedOffByInferno, bool refreshBurnClock, float baseline, bool mustWin = false)
		{
			SetFuel = setFuel;
			SetSwitchedOn = setSwitchedOn;
			SetSwitchedOffByInferno = setSwitchedOffByInferno;
			RefreshBurnClock = refreshBurnClock;
			Baseline = baseline;
			MustWin = mustWin;
		}
	}
	public sealed class FuelItemInfo
	{
		public float MaxFuel { get; }

		public bool HasOnOffSwitch { get; }

		public float SecondsPerFuel { get; }

		public bool BurnsOverTime
		{
			get
			{
				if (HasOnOffSwitch)
				{
					return SecondsPerFuel > 0f;
				}
				return false;
			}
		}

		public FuelItemInfo(float maxFuel, bool hasOnOffSwitch, float secondsPerFuel = 0f)
		{
			if (!(maxFuel > 0f) || float.IsInfinity(maxFuel))
			{
				throw new ArgumentOutOfRangeException("maxFuel", maxFuel, "Max fuel must be a finite number > 0.");
			}
			if (!(secondsPerFuel >= 0f) || float.IsInfinity(secondsPerFuel))
			{
				throw new ArgumentOutOfRangeException("secondsPerFuel", secondsPerFuel, "Seconds per fuel must be a finite number ≥ 0.");
			}
			MaxFuel = maxFuel;
			HasOnOffSwitch = hasOnOffSwitch;
			SecondsPerFuel = secondsPerFuel;
		}
	}
	public readonly struct FuelState
	{
		public float Fuel { get; }

		public bool IsSwitchedOn { get; }

		public bool SwitchedOffByInferno { get; }

		public bool IsUnattended { get; }

		public double SecondsSinceBurnClock { get; }

		public FuelState(float fuel, bool isSwitchedOn, bool switchedOffByInferno, bool isUnattended, double secondsSinceBurnClock = 0.0)
		{
			Fuel = fuel;
			IsSwitchedOn = isSwitchedOn;
			SwitchedOffByInferno = switchedOffByInferno;
			IsUnattended = isUnattended;
			SecondsSinceBurnClock = secondsSinceBurnClock;
		}
	}
}
namespace Inferno.Core.Diagnostics
{
	public readonly struct ErrorLogDecision
	{
		public bool Log { get; }

		public bool FirstTime { get; }

		public int Suppressed { get; }

		internal ErrorLogDecision(bool log, bool firstTime, int suppressed)
		{
			Log = log;
			FirstTime = firstTime;
			Suppressed = suppressed;
		}
	}
	public sealed class ErrorThrottle
	{
		private sealed class Entry
		{
			public double LastLogged { get; set; }

			public int Suppressed { get; set; }
		}

		private readonly double _intervalSeconds;

		private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal);

		public int Total { get; private set; }

		public int Distinct => _entries.Count;

		public ErrorThrottle(double intervalSeconds)
		{
			if (!(intervalSeconds > 0.0) || double.IsInfinity(intervalSeconds))
			{
				throw new ArgumentOutOfRangeException("intervalSeconds", intervalSeconds, "Interval must be a finite number > 0.");
			}
			_intervalSeconds = intervalSeconds;
		}

		public ErrorLogDecision Record(string key, double nowSeconds)
		{
			if (key == null)
			{
				throw new ArgumentNullException("key");
			}
			Total++;
			if (!_entries.TryGetValue(key, out Entry value))
			{
				_entries[key] = new Entry
				{
					LastLogged = nowSeconds
				};
				return new ErrorLogDecision(log: true, firstTime: true, 0);
			}
			if (nowSeconds - value.LastLogged < _intervalSeconds)
			{
				value.Suppressed++;
				return new ErrorLogDecision(log: false, firstTime: false, value.Suppressed);
			}
			int suppressed = value.Suppressed;
			value.Suppressed = 0;
			value.LastLogged = nowSeconds;
			return new ErrorLogDecision(log: true, firstTime: false, suppressed);
		}
	}
	public sealed class RunningStats
	{
		private double _total;

		public int Count { get; private set; }

		public double Max { get; private set; }

		public double Average
		{
			get
			{
				if (Count != 0)
				{
					return _total / (double)Count;
				}
				return 0.0;
			}
		}

		public void Add(double milliseconds)
		{
			if (milliseconds >= 0.0 && !double.IsInfinity(milliseconds))
			{
				Count++;
				_total += milliseconds;
				Max = Math.Max(Max, milliseconds);
			}
		}

		public void Reset()
		{
			Count = 0;
			_total = 0.0;
			Max = 0.0;
		}

		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture, "{0} × avg {1:0.00} ms, max {2:0.00} ms", Count, Average, Max);
		}
	}
}
namespace Inferno.Core.Commands
{
	public sealed class CommandExecutor
	{
		private readonly struct NearbyScope
		{
			public string? Filter { get; }

			public float? Radius { get; }

			public NearbyScope(string? filter, float? radius)
			{
				Filter = filter;
				Radius = radius;
			}

			public override string ToString()
			{
				string text = ((Filter == null) ? "nearby" : (Filter + " nearby"));
				float? radius = Radius;
				if (!radius.HasValue)
				{
					return text;
				}
				return text + " " + radius.GetValueOrDefault().ToString(CultureInfo.InvariantCulture);
			}
		}

		private sealed class UndoEntry
		{
			public string Description { get; }

			public IReadOnlyList<KeyValuePair<string, ItemSettings>> Items { get; }

			public IReadOnlyList<KeyValuePair<string, ItemSettings?>> Objects { get; }

			public string? GeneralSetting { get; private set; }

			public Func<GeneralSettings, bool, GeneralSettings>? WriteGeneral { get; private set; }

			public bool GeneralValue { get; private set; }

			private UndoEntry(string description, IReadOnlyList<KeyValuePair<string, ItemSettings>> items, IReadOnlyList<KeyValuePair<string, ItemSettings?>> objects)
			{
				Description = description;
				Items = items;
				Objects = objects;
			}

			public static UndoEntry ForItems(string description, IReadOnlyList<KeyValuePair<string, ItemSettings>> before)
			{
				return new UndoEntry(description, before, Array.Empty<KeyValuePair<string, ItemSettings>>());
			}

			public static UndoEntry ForObjects(string description, IReadOnlyList<KeyValuePair<string, ItemSettings?>> before)
			{
				return new UndoEntry(description, Array.Empty<KeyValuePair<string, ItemSettings>>(), before);
			}

			public static UndoEntry ForGeneral(string description, string setting, Func<GeneralSettings, bool, GeneralSettings> write, bool before)
			{
				return new UndoEntry(description, Array.Empty<KeyValuePair<string, ItemSettings>>(), Array.Empty<KeyValuePair<string, ItemSettings>>())
				{
					GeneralSetting = setting,
					WriteGeneral = write,
					GeneralValue = before
				};
			}
		}

		public const string GeneralSection = "General";

		public const int UndoDepth = 10;

		private const int MaxNamesInReply = 4;

		private static readonly string[] HelpLines = new string[6] { "Inferno: write !fires <command> on a sign or in chat.", "alwayson <item> on|off – never needs fuel", "burnrate <item> -10..10 – fuel use, 0 = normal", "schedule <item> night|day|off – when it's lit", "preset night|eternal|vanilla – one-step setups", "More: help items · help presets · help admin · status" };

		private static readonly Dictionary<string, string[]> HelpTopics = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
		{
			["items"] = new string[6] { "<item> can be:", "a name as in game – hot tub, standing wood torch", "a word – torches, braziers, fires, lanterns", "a group – lights, stations, all", "show <item> – its settings · list – everything", "add 'nearby' (ward / 20 m) or 'nearby 5' (metres)" },
			["presets"] = new string[6] { "preset eternal [item] – always on (the default)", "preset night [item] – lit 18:00-06:00, no fuel used", "preset vanilla [item] – normal game behaviour", "schedule <item> 18:00 19:00 – your own times", "undo – take back your last change", "reset <item> – back to defaults" },
			["admin"] = new string[5] { "adminonly on|off – only admins may change settings", "hidecommands on|off – hide !fires lines in chat", "serverownership on|off – experimental, see readme", "smoke <item> on|off – client mod players only", "F5 console: listkeys fires <command>" }
		};

		private readonly ItemCatalog _catalog;

		private readonly ISettingsStore _store;

		private readonly Func<IReadOnlyList<string>> _status;

		private readonly Dictionary<string, List<UndoEntry>> _undo = new Dictionary<string, List<UndoEntry>>(StringComparer.Ordinal);

		private Func<float?, NearbySelection>? _nearby;

		public const string NearbyWord = "nearby";

		public CommandExecutor(ItemCatalog catalog, ISettingsStore store, Func<IReadOnlyList<string>>? status = null)
		{
			_catalog = catalog ?? throw new ArgumentNullException("catalog");
			_store = store ?? throw new ArgumentNullException("store");
			_status = status ?? ((Func<IReadOnlyList<string>>)(() => new <>z__ReadOnlySingleElementList<string>("Inferno is running.")));
		}

		public CommandResult Execute(ParsedCommand command, CommandSender sender, Func<float?, NearbySelection>? nearby = null)
		{
			_nearby = nearby;
			if (command == null)
			{
				throw new ArgumentNullException("command");
			}
			if (sender == null)
			{
				throw new ArgumentNullException("sender");
			}
			switch (command.Kind)
			{
			case CommandKind.Help:
				return Help(command.Name);
			case CommandKind.Status:
				return Ok(_status());
			case CommandKind.List:
			case CommandKind.Show:
			{
				if (!TrySplitNearby(command.Target, out var scope2))
				{
					return Show(command.Target);
				}
				return ShowNearby(scope2);
			}
			default:
			{
				if (!PermissionPolicy.CanChangeSettings(sender.IsAdmin, _store.General.AdminOnly))
				{
					return new CommandResult(new <>z__ReadOnlySingleElementList<string>("Only admins can change Inferno settings while AdminOnly is on."), Array.Empty<SettingChange>(), denied: true);
				}
				NearbyScope scope;
				return command.Kind switch
				{
					CommandKind.AlwaysOn => UpdateItems(sender, command.Target, "AlwaysOn", OnOff(command.Flag), (CatalogItem _, ItemSettings s) => s.WithAlwaysOn(command.Flag)), 
					CommandKind.Smoke => UpdateItems(sender, command.Target, "Smoke", OnOff(command.Flag), (CatalogItem _, ItemSettings s) => s.WithSmoke(command.Flag)), 
					CommandKind.BurnRate => UpdateItems(sender, command.Target, "BurnRate", Number(command.Number), (CatalogItem _, ItemSettings s) => s.WithBurnRate(command.Number).WithAlwaysOn(value: false)), 
					CommandKind.Schedule => UpdateItems(sender, command.Target, "Schedule", command.Schedule.ToString(), (CatalogItem i, ItemSettings s) => i.CanSchedule ? ((!command.Schedule.IsAlwaysOn) ? s.WithSchedule(command.Schedule).WithAlwaysOn(value: false) : s.WithSchedule(command.Schedule)) : null), 
					CommandKind.Reset => TrySplitNearby(command.Target, out scope) ? UpdateObjects(sender, scope, "Settings", "item type's", null) : UpdateItems(sender, command.Target, "Settings", "defaults", (CatalogItem i, ItemSettings _) => ItemSettings.DefaultFor(i.Kind)), 
					CommandKind.Preset => ApplyPreset(sender, command), 
					CommandKind.Undo => Undo(sender), 
					CommandKind.AdminOnly => UpdateGeneral(sender, "AdminOnly", (GeneralSettings g) => g.AdminOnly, (GeneralSettings g, bool v) => g.WithAdminOnly(v), command.Flag), 
					CommandKind.HideCommands => UpdateGeneral(sender, "HideCommands", (GeneralSettings g) => g.HideCommands, (GeneralSettings g, bool v) => g.WithHideCommands(v), command.Flag), 
					CommandKind.IgnoreRain => UpdateGeneral(sender, "IgnoreRain", (GeneralSettings g) => g.IgnoreRain, (GeneralSettings g, bool v) => g.WithIgnoreRain(v), command.Flag), 
					_ => UpdateGeneral(sender, "ServerOwnership", (GeneralSettings g) => g.ServerOwnership, (GeneralSettings g, bool v) => g.WithServerOwnership(v), command.Flag), 
				};
			}
			}
		}

		public string Describe(CatalogItem item)
		{
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			ItemSettings item2 = _store.GetItem(item.PrefabName);
			string text = ((item.Kind == ItemKind.LightSource) ? "light" : "station");
			string text2 = (item.CanSchedule ? item2.Schedule.ToString() : "n/a");
			return item.DisplayName + " (" + item.PrefabName + ", " + text + "): alwayson=" + OnOff(item2.AlwaysOn) + " burnrate=" + Number(item2.BurnRateLevel) + " schedule=" + text2 + " smoke=" + OnOff(item2.Smoke);
		}

		private static CommandResult Help(string? topic)
		{
			if (topic == null)
			{
				return Ok(HelpLines);
			}
			if (!HelpTopics.TryGetValue(topic, out string[] value))
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>("No help topic '" + topic + "'. Topics: items, presets, admin."));
			}
			return Ok(value);
		}

		private CommandResult Show(string target)
		{
			if (!TryResolve(target, out IReadOnlyList<CatalogItem> items, out string error))
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>(error));
			}
			List<string> list = new List<string>(items.Count);
			foreach (CatalogItem item in items)
			{
				list.Add(Describe(item));
			}
			return Ok(list);
		}

		private CommandResult ApplyPreset(CommandSender sender, ParsedCommand command)
		{
			if (!Presets.TryGet(command.Name, out Preset preset))
			{
				string text = string.Join(", ", Presets.All.Select((Preset p) => p.Name + " (" + p.Description + ")"));
				return Ok(new <>z__ReadOnlySingleElementList<string>("Unknown preset '" + command.Name + "'. Presets: " + text + "."));
			}
			string text2 = command.Target ?? preset.DefaultTarget;
			if (TrySplitNearby(text2, out var scope) && scope.Filter == null && preset.DefaultTarget != "all")
			{
				text2 = preset.DefaultTarget + " " + text2;
			}
			return UpdateItems(sender, text2, "Preset", preset.Name, preset.Apply);
		}

		private CommandResult UpdateItems(CommandSender sender, string target, string settingName, string newValueText, Func<CatalogItem, ItemSettings, ItemSettings?> update)
		{
			if (TrySplitNearby(target, out var scope))
			{
				return UpdateObjects(sender, scope, settingName, newValueText, update);
			}
			if (!TryResolve(target, out IReadOnlyList<CatalogItem> items, out string error))
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>(error));
			}
			List<SettingChange> list = new List<SettingChange>();
			List<KeyValuePair<string, ItemSettings>> list2 = new List<KeyValuePair<string, ItemSettings>>();
			List<string> list3 = new List<string>();
			int num = 0;
			int num2 = 0;
			foreach (CatalogItem item2 in items)
			{
				ItemSettings item = _store.GetItem(item2.PrefabName);
				ItemSettings itemSettings = update(item2, item);
				if (itemSettings == null)
				{
					num++;
				}
				else if (!itemSettings.Equals(item))
				{
					if (item.AlwaysOn && !itemSettings.AlwaysOn && settingName != "AlwaysOn" && settingName != "Preset" && settingName != "Settings")
					{
						num2++;
					}
					_store.SetItem(item2.PrefabName, itemSettings);
					list.Add(new SettingChange(item2.PrefabName, settingName, item.ToString(), itemSettings.ToString()));
					list2.Add(new KeyValuePair<string, ItemSettings>(item2.PrefabName, item));
					list3.Add(item2.DisplayName);
				}
			}
			List<string> list4 = new List<string>();
			list4.Add($"{settingName} = {newValueText}: {list.Count} of {items.Count} item(s) changed.");
			List<string> list5 = list4;
			if (list3.Count > 1 && !IsFixedGroup(target))
			{
				list5.Add("Changed: " + NameList(list3));
			}
			if (num2 > 0)
			{
				list5.Add($"AlwaysOn turned off for {num2} item(s) so this takes effect.");
			}
			if (num > 0)
			{
				list5.Add($"{num} item(s) skipped: they have no on/off switch and can't follow a schedule.");
			}
			if (list.Count > 0)
			{
				Remember(sender, UndoEntry.ForItems(settingName + " = " + newValueText + " on '" + target + "'", list2));
			}
			return new CommandResult(list5, list, denied: false);
		}

		private CommandResult UpdateGeneral(CommandSender sender, string settingName, Func<GeneralSettings, bool> read, Func<GeneralSettings, bool, GeneralSettings> write, bool newValue)
		{
			GeneralSettings general = _store.General;
			if (read(general) == newValue)
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>(settingName + " is already " + OnOff(newValue) + "."));
			}
			_store.General = write(general, newValue);
			Remember(sender, UndoEntry.ForGeneral(settingName + " = " + OnOff(newValue), settingName, write, !newValue));
			SettingChange item = new SettingChange("General", settingName, OnOff(!newValue), OnOff(newValue));
			return new CommandResult(new <>z__ReadOnlySingleElementList<string>(settingName + " = " + OnOff(newValue) + "."), new <>z__ReadOnlySingleElementList<SettingChange>(item), denied: false);
		}

		private CommandResult Undo(CommandSender sender)
		{
			if (!_undo.TryGetValue(sender.PlatformId, out List<UndoEntry> value) || value.Count == 0)
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>("Nothing to undo."));
			}
			UndoEntry undoEntry = value[value.Count - 1];
			value.RemoveAt(value.Count - 1);
			List<SettingChange> list = new List<SettingChange>();
			string generalSetting = undoEntry.GeneralSetting;
			if (generalSetting != null)
			{
				GeneralSettings general = _store.General;
				_store.General = undoEntry.WriteGeneral(general, undoEntry.GeneralValue);
				list.Add(new SettingChange("General", generalSetting, OnOff(!undoEntry.GeneralValue), OnOff(undoEntry.GeneralValue)));
			}
			foreach (KeyValuePair<string, ItemSettings> item2 in undoEntry.Items)
			{
				ItemSettings item = _store.GetItem(item2.Key);
				if (!item.Equals(item2.Value))
				{
					_store.SetItem(item2.Key, item2.Value);
					list.Add(new SettingChange(item2.Key, "Undo", item.ToString(), item2.Value.ToString()));
				}
			}
			foreach (KeyValuePair<string, ItemSettings> @object in undoEntry.Objects)
			{
				ItemSettings itemSettings = _store.GetObject(@object.Key);
				if (!object.Equals(itemSettings, @object.Value))
				{
					_store.SetObject(@object.Key, @object.Value);
					list.Add(new SettingChange(@object.Key, "Undo", OwnText(itemSettings), OwnText(@object.Value)));
				}
			}
			string text = ((value.Count == 0) ? string.Empty : $" ({value.Count} more to undo)");
			return new CommandResult(new <>z__ReadOnlySingleElementList<string>("Undone: " + undoEntry.Description + "." + text), list, denied: false);
		}

		private void Remember(CommandSender sender, UndoEntry entry)
		{
			if (!_undo.TryGetValue(sender.PlatformId, out List<UndoEntry> value))
			{
				value = new List<UndoEntry>();
				_undo[sender.PlatformId] = value;
			}
			value.Add(entry);
			if (value.Count > 10)
			{
				value.RemoveAt(0);
			}
		}

		private static bool TrySplitNearby(string target, out NearbyScope scope)
		{
			scope = default(NearbyScope);
			string[] array = target.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			int num = Array.FindLastIndex(array, (string w) => string.Equals(w, "nearby", StringComparison.OrdinalIgnoreCase));
			if (num < 0 || num < array.Length - 2)
			{
				return false;
			}
			float? radius = null;
			if (num == array.Length - 2)
			{
				if (!float.TryParse(array[^1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					return false;
				}
				radius = result;
			}
			scope = new NearbyScope((num == 0) ? null : string.Join(" ", array, 0, num), radius);
			return true;
		}

		private bool TrySelectNearby(NearbyScope scope, out List<(NearbyObject Object, CatalogItem Item)> selected, out string area, out string error)
		{
			selected = new List<(NearbyObject, CatalogItem)>();
			area = string.Empty;
			error = string.Empty;
			string filter = scope.Filter;
			if (_nearby == null)
			{
				error = "'nearby' only works on a sign, in chat or in the F5 console.";
				return false;
			}
			float? radius = scope.Radius;
			if (radius.HasValue)
			{
				float valueOrDefault = radius.GetValueOrDefault();
				if (!(valueOrDefault >= 1f) || !(valueOrDefault <= 100f))
				{
					error = string.Format(CultureInfo.InvariantCulture, "Radius must be {0:0} to {1:0} m.", 1f, 100f);
					return false;
				}
			}
			HashSet<string> hashSet = null;
			if (filter != null)
			{
				if (!TryResolve(filter, out IReadOnlyList<CatalogItem> items, out error))
				{
					return false;
				}
				hashSet = new HashSet<string>(items.Select((CatalogItem i) => i.PrefabName), StringComparer.OrdinalIgnoreCase);
			}
			NearbySelection nearbySelection = _nearby(scope.Radius);
			area = nearbySelection.AreaDescription;
			foreach (NearbyObject @object in nearbySelection.Objects)
			{
				if ((hashSet == null || hashSet.Contains(@object.PrefabName)) && _catalog.TryGet(@object.PrefabName, out CatalogItem item))
				{
					selected.Add((@object, item));
				}
			}
			if (selected.Count == 0)
			{
				error = "No " + ((filter == null) ? "fires" : ("'" + filter + "'")) + " " + area + ".";
				return false;
			}
			return true;
		}

		private CommandResult UpdateObjects(CommandSender sender, NearbyScope scope, string settingName, string newValueText, Func<CatalogItem, ItemSettings, ItemSettings?>? update)
		{
			if (!TrySelectNearby(scope, out List<(NearbyObject, CatalogItem)> selected, out string area, out string error))
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>(error));
			}
			List<SettingChange> list = new List<SettingChange>();
			List<KeyValuePair<string, ItemSettings>> list2 = new List<KeyValuePair<string, ItemSettings>>();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (var (nearbyObject, catalogItem) in selected)
			{
				if (!nearbyObject.Allowed)
				{
					num++;
					continue;
				}
				ItemSettings itemSettings = _store.GetObject(nearbyObject.Key);
				ItemSettings itemSettings2 = itemSettings ?? _store.GetItem(catalogItem.PrefabName);
				ItemSettings itemSettings3;
				if (update == null)
				{
					if (itemSettings == null)
					{
						continue;
					}
					itemSettings3 = null;
				}
				else
				{
					itemSettings3 = update(catalogItem, itemSettings2);
					if (itemSettings3 == null)
					{
						num2++;
						continue;
					}
					if (itemSettings3.Equals(itemSettings2))
					{
						continue;
					}
					if (itemSettings2.AlwaysOn && !itemSettings3.AlwaysOn && settingName != "AlwaysOn" && settingName != "Preset")
					{
						num3++;
					}
				}
				_store.SetObject(nearbyObject.Key, itemSettings3);
				list2.Add(new KeyValuePair<string, ItemSettings>(nearbyObject.Key, itemSettings));
				list.Add(new SettingChange(catalogItem.PrefabName + " " + nearbyObject.Key, settingName, OwnText(itemSettings), OwnText(itemSettings3)));
			}
			List<string> list3 = new List<string>();
			list3.Add((update == null) ? $"{list.Count} of {selected.Count} fire(s) {area} back to their item type's settings." : $"{settingName} = {newValueText}: {list.Count} of {selected.Count} fire(s) {area} changed.");
			List<string> list4 = list3;
			if (num3 > 0)
			{
				list4.Add($"AlwaysOn turned off for {num3} of them so this takes effect.");
			}
			if (num > 0)
			{
				list4.Add($"{num} skipped: they're in a ward you have no access to.");
			}
			if (num2 > 0)
			{
				list4.Add($"{num2} skipped: no on/off switch, so no schedule.");
			}
			if (list.Count > 0)
			{
				Remember(sender, UndoEntry.ForObjects($"{settingName} = {newValueText} on '{scope}'", list2));
			}
			return new CommandResult(list4, list, denied: false);
		}

		private CommandResult ShowNearby(NearbyScope scope)
		{
			if (!TrySelectNearby(scope, out List<(NearbyObject, CatalogItem)> selected, out string area, out string error))
			{
				return Ok(new <>z__ReadOnlySingleElementList<string>(error));
			}
			List<string> list = new List<string> { $"{selected.Count} fire(s) {area}:" };
			foreach (IGrouping<(string, string, bool), (CatalogItem, ItemSettings, bool)> item in (from s in selected
				select (Item: s.Item, Own: _store.GetObject(s.Object.Key)) into s
				select (Item: s.Item, Settings: s.Own ?? _store.GetItem(s.Item.PrefabName), IsOwn: s.Own != null) into s
				group s by (DisplayName: s.Item.DisplayName, Text: s.Settings.ToString(), IsOwn: s.IsOwn)).OrderBy<IGrouping<(string, string, bool), (CatalogItem, ItemSettings, bool)>, string>((IGrouping<(string DisplayName, string Text, bool IsOwn), (CatalogItem Item, ItemSettings Settings, bool IsOwn)> g) => g.Key.DisplayName, StringComparer.OrdinalIgnoreCase))
			{
				string text = (item.Key.Item3 ? " (own settings)" : string.Empty);
				list.Add($"{item.Key.Item1} x{item.Count()}{text}: {item.Key.Item2}");
			}
			return Ok(list);
		}

		private bool TryResolve(string target, out IReadOnlyList<CatalogItem> items, out string error)
		{
			error = string.Empty;
			if (!_catalog.TryResolve(target, out items))
			{
				IReadOnlyList<string> readOnlyList = _catalog.Suggest(target);
				error = ((readOnlyList.Count > 0) ? ("Unknown item '" + target + "'. Did you mean: " + string.Join(", ", readOnlyList) + "?") : ("Unknown item '" + target + "'. Use 'list' to see all items."));
				return false;
			}
			if (items.Count == 0)
			{
				error = "No items in '" + target + "'.";
				return false;
			}
			return true;
		}

		private static bool IsFixedGroup(string target)
		{
			if (!string.Equals(target, "all", StringComparison.OrdinalIgnoreCase) && !string.Equals(target, "lights", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(target, "stations", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static string NameList(List<string> names)
		{
			List<string> list = names.Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
			string text = string.Join(", ", list.Take(4));
			if (list.Count <= 4)
			{
				return text;
			}
			return $"{text}, +{list.Count - 4} more";
		}

		private static string OwnText(ItemSettings? settings)
		{
			if (settings != null)
			{
				return settings.ToString();
			}
			return "follows item type";
		}

		private static CommandResult Ok(IReadOnlyList<string> reply)
		{
			return new CommandResult(reply, Array.Empty<SettingChange>(), denied: false);
		}

		private static string OnOff(bool value)
		{
			if (!value)
			{
				return "off";
			}
			return "on";
		}

		private static string Number(int value)
		{
			return value.ToString(CultureInfo.InvariantCulture);
		}
	}
	public enum CommandKind
	{
		Help,
		Status,
		List,
		Show,
		AlwaysOn,
		BurnRate,
		Schedule,
		Smoke,
		Reset,
		Preset,
		Undo,
		AdminOnly,
		HideCommands,
		IgnoreRain,
		ServerOwnership
	}
	public enum ParseOutcome
	{
		NotACommand,
		Command,
		Invalid
	}
	public static class CommandParser
	{
		public const string Prefix = "!fires";

		public const string ConsolePrefix = "listkeys fires";

		public const int MaxLength = 200;

		private const string ConsoleCarrier = "listkeys";

		private const string ConsoleKeyword = "fires";

		private static readonly char[] Whitespace = new char[2] { ' ', '\t' };

		private static readonly string[] Subcommands = new string[15]
		{
			"HELP", "STATUS", "LIST", "SHOW", "RESET", "ALWAYSON", "SMOKE", "BURNRATE", "SCHEDULE", "PRESET",
			"UNDO", "ADMINONLY", "HIDECOMMANDS", "IGNORERAIN", "SERVEROWNERSHIP"
		};

		public static ParseOutcome Parse(string? text, out ParsedCommand command, out string error)
		{
			command = null;
			error = string.Empty;
			text = RichText.Strip(text);
			string[] array = text.Split(Whitespace, StringSplitOptions.RemoveEmptyEntries);
			string prefix;
			if (array.Length >= 1 && string.Equals(array[0], "!fires", StringComparison.OrdinalIgnoreCase))
			{
				prefix = "!fires";
			}
			else
			{
				if (array.Length < 2 || !string.Equals(array[0], "listkeys", StringComparison.OrdinalIgnoreCase) || !string.Equals(array[1], "fires", StringComparison.OrdinalIgnoreCase))
				{
					return ParseOutcome.NotACommand;
				}
				prefix = "listkeys fires";
				array = array.Skip(1).ToArray();
			}
			if (text.Length > 200)
			{
				error = $"Command too long (max {200} characters).";
				return ParseOutcome.Invalid;
			}
			if (array.Length == 1)
			{
				command = new ParsedCommand(CommandKind.Help);
				return ParseOutcome.Command;
			}
			ParsedCommand parsedCommand = ParseSubcommand(array, prefix, out error);
			if (parsedCommand == null)
			{
				return ParseOutcome.Invalid;
			}
			command = parsedCommand;
			return ParseOutcome.Command;
		}

		private static ParsedCommand? ParseSubcommand(string[] t, string prefix, out string error)
		{
			error = string.Empty;
			switch (t[1].ToUpperInvariant())
			{
			case "HELP":
				if (t.Length == 2)
				{
					return new ParsedCommand(CommandKind.Help);
				}
				if (t.Length == 3)
				{
					string name = t[2];
					return new ParsedCommand(CommandKind.Help, null, flag: false, 0, default(DailySchedule), name);
				}
				break;
			case "STATUS":
				if (t.Length == 2)
				{
					return new ParsedCommand(CommandKind.Status);
				}
				break;
			case "LIST":
				if (t.Length == 2)
				{
					return new ParsedCommand(CommandKind.List, "all");
				}
				if (t.Length >= 3)
				{
					return new ParsedCommand(CommandKind.List, Item(t, 0));
				}
				break;
			case "SHOW":
				if (t.Length >= 3)
				{
					return new ParsedCommand(CommandKind.Show, Item(t, 0));
				}
				break;
			case "RESET":
				if (t.Length >= 3)
				{
					return new ParsedCommand(CommandKind.Reset, Item(t, 0));
				}
				break;
			case "ALWAYSON":
			{
				if (t.Length >= 4 && TryParseOnOff(t[^1], out var value2))
				{
					return new ParsedCommand(CommandKind.AlwaysOn, Item(t, 1), value2);
				}
				break;
			}
			case "SMOKE":
			{
				if (t.Length >= 4 && TryParseOnOff(t[^1], out var value5))
				{
					return new ParsedCommand(CommandKind.Smoke, Item(t, 1), value5);
				}
				break;
			}
			case "BURNRATE":
			{
				if (t.Length >= 4 && TryParseBurnRate(t[^1], out var level))
				{
					return new ParsedCommand(CommandKind.BurnRate, Item(t, 1), flag: false, level);
				}
				break;
			}
			case "SCHEDULE":
			{
				if (t.Length >= 4 && TryParseScheduleWord(t[^1], out var schedule))
				{
					return new ParsedCommand(CommandKind.Schedule, Item(t, 1), flag: false, 0, schedule);
				}
				if (t.Length >= 5 && ClockSetting.TryParse(t[^2], out var result) && ClockSetting.TryParse(t[^1], out var result2))
				{
					return new ParsedCommand(CommandKind.Schedule, Item(t, 2), flag: false, 0, new DailySchedule(result, result2));
				}
				break;
			}
			case "PRESET":
				if (t.Length >= 3)
				{
					string target = ((t.Length > 3) ? Item(t, 0, 3) : null);
					string name = t[2];
					return new ParsedCommand(CommandKind.Preset, target, flag: false, 0, default(DailySchedule), name);
				}
				break;
			case "UNDO":
				if (t.Length == 2)
				{
					return new ParsedCommand(CommandKind.Undo);
				}
				break;
			case "ADMINONLY":
			{
				if (t.Length == 3 && TryParseOnOff(t[2], out var value6))
				{
					return new ParsedCommand(CommandKind.AdminOnly, null, value6);
				}
				break;
			}
			case "HIDECOMMANDS":
			{
				if (t.Length == 3 && TryParseOnOff(t[2], out var value4))
				{
					return new ParsedCommand(CommandKind.HideCommands, null, value4);
				}
				break;
			}
			case "IGNORERAIN":
			{
				if (t.Length == 3 && TryParseOnOff(t[2], out var value3))
				{
					return new ParsedCommand(CommandKind.IgnoreRain, null, value3);
				}
				break;
			}
			case "SERVEROWNERSHIP":
			{
				if (t.Length == 3 && TryParseOnOff(t[2], out var value))
				{
					return new ParsedCommand(CommandKind.ServerOwnership, null, value);
				}
				break;
			}
			}
			error = UsageFor(t[1], prefix);
			if (!IsSubcommand(t[1]))
			{
				string text = SuggestOrder(t, prefix);
				if (text != null)
				{
					error = "Did you mean: " + text;
				}
			}
			return null;
		}

		private static bool IsSubcommand(string word)
		{
			return Array.IndexOf(Subcommands, word.ToUpperInvariant()) >= 0;
		}

		private static string? SuggestOrder(string[] t, string prefix)
		{
			for (int i = 2; i < t.Length; i++)
			{
				if (IsSubcommand(t[i]))
				{
					List<string> second = t.Skip(1).Take(i - 1).ToList();
					List<string> list = t.Skip(i + 1).ToList();
					IEnumerable<string> values = ((string.Equals(t[i], "preset", StringComparison.OrdinalIgnoreCase) && list.Count > 0) ? new string[2]
					{
						t[i],
						list[0]
					}.Concat(second).Concat(list.Skip(1)) : new string[1] { t[i] }.Concat(second).Concat(list));
					string text = prefix + " " + string.Join(" ", values);
					if (Parse(text, out ParsedCommand _, out string _) != ParseOutcome.Command)
					{
						return null;
					}
					return text;
				}
			}
			return null;
		}

		internal static string UsageFor(string subcommand, string prefix)
		{
			return subcommand.ToUpperInvariant() switch
			{
				"HELP" => "Usage: " + prefix + " help [items|presets|admin]", 
				"STATUS" => "Usage: " + prefix + " status", 
				"SHOW" => "Usage: " + prefix + " show <item>   (item = name as in game, e.g. hot tub; or all, lights, stations)", 
				"RESET" => "Usage: " + prefix + " reset <item>", 
				"ALWAYSON" => "Usage: " + prefix + " alwayson <item> on|off", 
				"SMOKE" => "Usage: " + prefix + " smoke <item> on|off  (seen only by players with the Inferno client mod)", 
				"BURNRATE" => $"Usage: {prefix} burnrate <item> <{-10}..{10}>  (0 = vanilla, each step 10 %)", 
				"SCHEDULE" => "Usage: " + prefix + " schedule <item> <on HH:MM> <off HH:MM>   or   " + prefix + " schedule <item> night|day|off", 
				"PRESET" => "Usage: " + prefix + " preset eternal|night|vanilla [item]", 
				"UNDO" => "Usage: " + prefix + " undo", 
				"ADMINONLY" => "Usage: " + prefix + " adminonly on|off", 
				"HIDECOMMANDS" => "Usage: " + prefix + " hidecommands on|off", 
				"IGNORERAIN" => "Usage: " + prefix + " ignorerain on|off", 
				"SERVEROWNERSHIP" => "Usage: " + prefix + " serverownership on|off", 
				_ => "Unknown command '" + subcommand + "'. Type " + prefix + " help", 
			};
		}

		private static string Item(string[] tokens, int trailingArguments, int first = 2)
		{
			return string.Join(" ", tokens, first, tokens.Length - first - trailingArguments);
		}

		private static bool TryParseScheduleWord(string text, out DailySchedule schedule)
		{
			switch (text.ToUpperInvariant())
			{
			case "OFF":
				schedule = DailySchedule.AlwaysOn;
				return true;
			case "NIGHT":
				schedule = DailySchedule.Night;
				return true;
			case "DAY":
				schedule = DailySchedule.Day;
				return true;
			default:
				schedule = default(DailySchedule);
				return false;
			}
		}

		private static bool TryParseOnOff(string text, out bool value)
		{
			value = string.Equals(text, "on", StringComparison.OrdinalIgnoreCase);
			if (!value)
			{
				return string.Equals(text, "off", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool TryParseBurnRate(string text, out int level)
		{
			level = 0;
			bool flag = text.StartsWith("-", StringComparison.Ordinal);
			string text2 = ((flag || text.StartsWith("+", StringComparison.Ordinal)) ? text.Substring(1) : text);
			int length = text2.Length;
			bool flag2 = ((length < 1 || length > 2) ? true : false);
			if (flag2 || !TimeOfDay.TryParseDigits(text2, out var value) || value > 10)
			{
				return false;
			}
			level = (flag ? (-value) : value);
			return true;
		}
	}
	public sealed class CommandResult
	{
		public IReadOnlyList<string> Reply { get; }

		public IReadOnlyList<SettingChange> Changes { get; }

		public bool Denied { get; }

		internal CommandResult(IReadOnlyList<string> reply, IReadOnlyList<SettingChange> changes, bool denied)
		{
			Reply = reply;
			Changes = changes;
			Denied = denied;
		}
	}
	public sealed class CommandSender
	{
		public string Name { get; }

		public string PlatformId { get; }

		public bool IsAdmin { get; }

		public CommandSender(string name, string platformId, bool isAdmin)
		{
			Name = name ?? throw new ArgumentNullException("name");
			PlatformId = platformId ?? throw new ArgumentNullException("platformId");
			IsAdmin = isAdmin;
		}

		public override string ToString()
		{
			return Name + " (" + PlatformId + (IsAdmin ? ", admin" : string.Empty) + ")";
		}
	}
	public sealed class DuplicateFilter
	{
		private readonly double _windowSeconds;

		private readonly Dictionary<string, double> _lastSeen = new Dictionary<string, double>();

		internal int Count => _lastSeen.Count;

		public DuplicateFilter(double windowSeconds)
		{
			if (!(windowSeconds > 0.0))
			{
				throw new ArgumentOutOfRangeException("windowSeconds", windowSeconds, "Window must be > 0.");
			}
			_windowSeconds = windowSeconds;
		}

		public bool IsFirst(long senderId, string text, double nowSeconds)
		{
			Prune(nowSeconds);
			string key = senderId.ToString(CultureInfo.InvariantCulture) + "\n" + text;
			if (_lastSeen.ContainsKey(key))
			{
				return false;
			}
			_lastSeen[key] = nowSeconds;
			return true;
		}

		private void Prune(double nowSeconds)
		{
			List<string> list = null;
			foreach (KeyValuePair<string, double> item in _lastSeen)
			{
				if (nowSeconds - item.Value >= _windowSeconds)
				{
					(list ?? (list = new List<string>())).Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item2 in list)
			{
				_lastSeen.Remove(item2);
			}
		}
	}
	public sealed class ParsedCommand
	{
		public string? Name { get; }

		public CommandKind Kind { get; }

		public string? Target { get; }

		public bool Flag { get; }

		public int Number { get; }

		public DailySchedule Schedule { get; }

		internal ParsedCommand(CommandKind kind, string? target = null, bool flag = false, int number = 0, DailySchedule schedule = default(DailySchedule), string? name = null)
		{
			Kind = kind;
			Target = target;
			Flag = flag;
			Number = number;
			Schedule = schedule;
			Name = name;
		}
	}
	public enum RateDecision
	{
		Allow,
		DenyAndWarn,
		DenySilently
	}
	public sealed class RateLimiter
	{
		private sealed class Bucket
		{
			public double Tokens { get; set; }

			public double Updated { get; set; }

			public bool Warned { get; set; }
		}

		private readonly double _capacity;

		private readonly double _refillPerSecond;

		private readonly Dictionary<string, Bucket> _buckets = new Dictionary<string, Bucket>(StringComparer.Ordinal);

		public int Count => _buckets.Count;

		public RateLimiter(int burst, double refillPerSecond)
		{
			if (burst < 1)
			{
				throw new ArgumentOutOfRangeException("burst", burst, "Burst must be ≥ 1.");
			}
			if (!(refillPerSecond > 0.0) || double.IsInfinity(refillPerSecond))
			{
				throw new ArgumentOutOfRangeException("refillPerSecond", refillPerSecond, "Refill rate must be a finite number > 0.");
			}
			_capacity = burst;
			_refillPerSecond = refillPerSecond;
		}

		public RateDecision TryTake(string playerKey, double nowSeconds)
		{
			if (playerKey == null)
			{
				throw new ArgumentNullException("playerKey");
			}
			Prune(nowSeconds);
			if (!_buckets.TryGetValue(playerKey, out Bucket value))
			{
				value = new Bucket
				{
					Tokens = _capacity,
					Updated = nowSeconds
				};
				_buckets[playerKey] = value;
			}
			Refill(value, nowSeconds);
			if (value.Tokens >= 1.0)
			{
				value.Tokens -= 1.0;
				value.Warned = false;
				return RateDecision.Allow;
			}
			if (value.Warned)
			{
				return RateDecision.DenySilently;
			}
			value.Warned = true;
			return RateDecision.DenyAndWarn;
		}

		private void Refill(Bucket bucket, double now)
		{
			double num = Math.Max(0.0, now - bucket.Updated);
			bucket.Tokens = Math.Min(_capacity, bucket.Tokens + num * _refillPerSecond);
			bucket.Updated = now;
		}

		private void Prune(double now)
		{
			List<string> list = null;
			foreach (KeyValuePair<string, Bucket> bucket in _buckets)
			{
				Bucket value = bucket.Value;
				if (value.Tokens + (now - value.Updated) * _refillPerSecond >= _capacity)
				{
					(list ?? (list = new List<string>())).Add(bucket.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item in list)
			{
				_buckets.Remove(item);
			}
		}
	}
	public static class ReplyLimiter
	{
		public static IReadOnlyList<string> Limit(IReadOnlyList<string> lines, int maxLines)
		{
			if (lines == null)
			{
				throw new ArgumentNullException("lines");
			}
			if (maxLines < 2)
			{
				throw new ArgumentOutOfRangeException("maxLines", maxLines, "Need room for at least one line plus the note.");
			}
			if (lines.Count <= maxLines)
			{
				return lines;
			}
			int num = maxLines - 1;
			List<string> list = new List<string>(maxLines);
			for (int i = 0; i < num; i++)
			{
				list.Add(lines[i]);
			}
			list.Add(string.Format("… {0} more line(s). Use the F5 console ('{1} …') to see everything.", lines.Count - num, "listkeys fires"));
			return list;
		}
	}
	public static class RichText
	{
		private static readonly Regex Tag = new Regex("<[^<>]*>", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		private static readonly Regex LeadingTags = new Regex("^\\s*((?:<[^<>/][^<>]*>\\s*)+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		public static string Strip(string? text)
		{
			if (text != null)
			{
				return Tag.Replace(text, string.Empty);
			}
			return string.Empty;
		}

		public static string Leading(string? text)
		{
			if (text == null)
			{
				return string.Empty;
			}
			Match match = LeadingTags.Match(text);
			if (!match.Success)
			{
				return string.Empty;
			}
			return match.Groups[1].Value.Trim();
		}
	}
	public sealed class SettingChange
	{
		public string Section { get; }

		public string Setting { get; }

		public string OldValue { get; }

		public string NewValue { get; }

		public SettingChange(string section, string setting, string oldValue, string newValue)
		{
			Section = section;
			Setting = setting;
			OldValue = oldValue;
			NewValue = newValue;
		}

		public override string ToString()
		{
			return "[" + Section + "] " + Setting + ": " + OldValue + " -> " + NewValue;
		}
	}
	public static class SignReply
	{
		public const int MaxLength = 50;

		public static string Summarize(IReadOnlyList<string> reply)
		{
			if (reply == null)
			{
				throw new ArgumentNullException("reply");
			}
			if (reply.Count == 0)
			{
				return string.Empty;
			}
			string text = reply[0].Trim();
			if (text.Length > 50)
			{
				return text.Substring(0, 49) + "…";
			}
			return text;
		}
	}
}
namespace Inferno.Core.Catalog
{
	public sealed class CatalogItem
	{
		public string PrefabName { get; }

		public string DisplayName { get; }

		public ItemKind Kind { get; }

		public FuelItemInfo Fuel { get; }

		public bool CanSchedule => Fuel.HasOnOffSwitch;

		public CatalogItem(string prefabName, string displayName, ItemKind kind, FuelItemInfo fuel)
		{
			if (string.IsNullOrWhiteSpace(prefabName))
			{
				throw new ArgumentException("Prefab name is required.", "prefabName");
			}
			if (string.IsNullOrWhiteSpace(displayName))
			{
				throw new ArgumentException("Display name is required.", "displayName");
			}
			PrefabName = prefabName;
			DisplayName = displayName;
			Kind = kind;
			Fuel = fuel ?? throw new ArgumentNullException("fuel");
		}
	}
	public sealed class ItemCatalog
	{
		public const string AllGroup = "all";

		public const string LightsGroup = "lights";

		public const string StationsGroup = "stations";

		public const int MinKeywordLength = 3;

		private readonly Dictionary<string, CatalogItem> _byName = new Dictionary<string, CatalogItem>(StringComparer.OrdinalIgnoreCase);

		private readonly List<CatalogItem> _items = new List<CatalogItem>();

		public IReadOnlyList<CatalogItem> Items => _items;

		public ItemCatalog(IEnumerable<CatalogItem> items)
		{
			if (items == null)
			{
				throw new ArgumentNullException("items");
			}
			foreach (CatalogItem item in items)
			{
				if (item == null)
				{
					throw new ArgumentNullException("items", "Catalog items must not be null.");
				}
				if (IsGroupName(item.PrefabName))
				{
					throw new ArgumentException("Prefab name '" + item.PrefabName + "' is reserved for a group.", "items");
				}
				if (_byName.ContainsKey(item.PrefabName))
				{
					throw new ArgumentException("Duplicate prefab '" + item.PrefabName + "'.", "items");
				}
				_byName.Add(item.PrefabName, item);
				_items.Add(item);
			}
			_items.Sort((CatalogItem a, CatalogItem b) => StringComparer.OrdinalIgnoreCase.Compare(a.PrefabName, b.PrefabName));
		}

		public bool TryGet(string prefabName, out CatalogItem item)
		{
			if (prefabName != null && _byName.TryGetValue(prefabName, out CatalogItem value))
			{
				item = value;
				return true;
			}
			item = null;
			return false;
		}

		public bool TryResolve(string target, out IReadOnlyList<CatalogItem> items)
		{
			switch (target?.ToLowerInvariant())
			{
			case "all":
				items = _items;
				return true;
			case "lights":
				items = _items.Where((CatalogItem i) => i.Kind == ItemKind.LightSource).ToList();
				return true;
			case "stations":
				items = _items.Where((CatalogItem i) => i.Kind == ItemKind.FuelStation).ToList();
				return true;
			default:
			{
				if (TryGet(target, out CatalogItem item))
				{
					items = new <>z__ReadOnlySingleElementList<CatalogItem>(item);
					return true;
				}
				string key = Normalize(target);
				List<CatalogItem> list = ((key.Length == 0) ? new List<CatalogItem>() : _items.Where((CatalogItem i) => Normalize(i.DisplayName) == key).ToList());
				if (list.Count > 0)
				{
					items = list;
					return true;
				}
				items = MatchKeyword(key);
				return items.Count > 0;
			}
			}
		}

		private List<CatalogItem> MatchKeyword(string key)
		{
			foreach (string stem in Stems(key))
			{
				if (stem.Length >= 3)
				{
					List<CatalogItem> list = _items.Where((CatalogItem i) => Normalize(i.DisplayName).Contains(stem)).ToList();
					if (list.Count > 0)
					{
						return list;
					}
				}
			}
			return new List<CatalogItem>();
		}

		private static IEnumerable<string> Stems(string key)
		{
			yield return key;
			if (key.EndsWith("s", StringComparison.Ordinal))
			{
				yield return key.Substring(0, key.Length - 1);
			}
			if (key.EndsWith("es", StringComparison.Ordinal))
			{
				yield return key.Substring(0, key.Length - 2);
			}
		}

		public IReadOnlyList<string> Suggest(string target, int max = 3)
		{
			string key = Normalize(target);
			if (key.Length == 0)
			{
				return Array.Empty<string>();
			}
			return _items.Select((CatalogItem i) => i.DisplayName).Distinct<string>(StringComparer.OrdinalIgnoreCase).Where(delegate(string name)
			{
				string text = Normalize(name);
				return text.Contains(key) || key.Contains(text);
			})
				.OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase)
				.Take(max)
				.ToList();
		}

		internal static string Normalize(string? text)
		{
			if (text == null)
			{
				return string.Empty;
			}
			char[] array = new char[text.Length];
			int length = 0;
			foreach (char c in text)
			{
				if (char.IsLetterOrDigit(c))
				{
					array[length++] = char.ToLowerInvariant(c);
				}
			}
			return new string(array, 0, length);
		}

		private static bool IsGroupName(string name)
		{
			if (!string.Equals(name, "all", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "lights", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(name, "stations", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}
	}
}
namespace Inferno.Core.Areas
{
	public sealed class NearbyArea
	{
		public const float DefaultRadius = 20f;

		public const float MinRadius = 1f;

		public const float MaxRadius = 100f;

		private readonly IReadOnlyList<Ward> _wards;

		private readonly float _x;

		private readonly float _z;

		private readonly float _radius;

		public bool IsWardArea => _wards.Count > 0;

		public string Description
		{
			get
			{
				if (!IsWardArea)
				{
					return string.Format(CultureInfo.InvariantCulture, "within {0:0} m", _radius);
				}
				if (_wards.Count != 1)
				{
					return $"in the area of {_wards.Count} wards";
				}
				return "in this ward's area";
			}
		}

		private NearbyArea(IReadOnlyList<Ward> wards, float x, float z, float radius)
		{
			_wards = wards;
			_x = x;
			_z = z;
			_radius = radius;
		}

		public static NearbyArea Circle(float x, float z, float radius)
		{
			if (!(radius > 0f) || float.IsInfinity(radius))
			{
				throw new ArgumentOutOfRangeException("radius", radius, "Radius must be a finite number > 0.");
			}
			return new NearbyArea(Array.Empty<Ward>(), x, z, radius);
		}

		public static NearbyArea Around(IEnumerable<Ward> wards, float x, float z, float fallbackRadius = 20f)
		{
			if (wards == null)
			{
				throw new ArgumentNullException("wards");
			}
			if (!(fallbackRadius > 0f) || float.IsInfinity(fallbackRadius))
			{
				throw new ArgumentOutOfRangeException("fallbackRadius", fallbackRadius, "Radius must be a finite number > 0.");
			}
			return new NearbyArea(wards.Where((Ward w) => w.Enabled && w.Covers(x, z)).ToList(), x, z, fallbackRadius);
		}

		public bool Contains(float x, float z)
		{
			if (IsWardArea)
			{
				return _wards.Any((Ward w) => w.Covers(x, z));
			}
			float num = _x - x;
			float num2 = _z - z;
			return Math.Sqrt(num * num + num2 * num2) <= (double)_radius;
		}

		public static bool CanAccess(IEnumerable<Ward> wards, float x, float z, long playerId)
		{
			if (wards == null)
			{
				throw new ArgumentNullException("wards");
			}
			bool flag = false;
			foreach (Ward ward in wards)
			{
				if (ward.Enabled && ward.Covers(x, z))
				{
					if (ward.Grants(playerId))
					{
						return true;
					}
					flag = true;
				}
			}
			return !flag;
		}
	}
	public sealed class NearbyObject
	{
		public string Key { get; }

		public string PrefabName { get; }

		public bool Allowed { get; }

		public NearbyObject(string key, string prefabName, bool allowed)
		{
			Key = key ?? throw new ArgumentNullException("key");
			PrefabName = prefabName ?? throw new ArgumentNullException("prefabName");
			Allowed = allowed;
		}
	}
	public sealed class NearbySelection
	{
		public IReadOnlyList<NearbyObject> Objects { get; }

		public string AreaDescription { get; }

		public NearbySelection(IReadOnlyList<NearbyObject> objects, string areaDescription)
		{
			Objects = objects ?? throw new ArgumentNullException("objects");
			AreaDescription = areaDescription ?? throw new ArgumentNullException("areaDescription");
		}
	}
	public sealed class Ward
	{
		public float X { get; }

		public float Z { get; }

		public float Radius { get; }

		public bool Enabled { get; }

		public long Creator { get; }

		public IReadOnlyCollection<long> Permitted { get; }

		public Ward(float x, float z, float radius, bool enabled, long creator, IReadOnlyCollection<long> permitted)
		{
			if (!(radius >= 0f) || float.IsInfinity(radius))
			{
				throw new ArgumentOutOfRangeException("radius", radius, "Radius must be a finite number ≥ 0.");
			}
			X = x;
			Z = z;
			Radius = radius;
			Enabled = enabled;
			Creator = creator;
			Permitted = permitted ?? throw new ArgumentNullException("permitted");
		}

		public bool Covers(float x, float z)
		{
			float num = X - x;
			float num2 = Z - z;
			return Math.Sqrt(num * num + num2 * num2) < (double)Radius;
		}

		public bool Grants(long playerId)
		{
			if (Creator == playerId)
			{
				return true;
			}
			foreach (long item in Permitted)
			{
				if (item == playerId)
				{
					return true;
				}
			}
			return false;
		}
	}
}

plugins/Inferno.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
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 System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Inferno.Core.Areas;
using Inferno.Core.Catalog;
using Inferno.Core.Commands;
using Inferno.Core.Diagnostics;
using Inferno.Core.Fuel;
using Inferno.Core.Ownership;
using Inferno.Core.Permissions;
using Inferno.Core.Settings;
using Inferno.Core.Sync;
using Inferno.Core.Time;
using Inferno.Game;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("GrundleLord")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 GrundleLord. MIT License.")]
[assembly: AssemblyFileVersion("0.3.2.0")]
[assembly: AssemblyInformationalVersion("0.3.2+4c3e115387ee3266bdea5a26431267084f7f433c")]
[assembly: AssemblyProduct("Inferno")]
[assembly: AssemblyTitle("Inferno")]
[assembly: AssemblyVersion("0.3.2.0")]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlyArray<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	int ICollection.Count => _items.Length;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			return _items[index];
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => _items.Length;

	T IReadOnlyList<T>.this[int index] => _items[index];

	int ICollection<T>.Count => _items.Length;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			return _items[index];
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlyArray(T[] items)
	{
		_items = items;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return ((IEnumerable)_items).GetEnumerator();
	}

	void ICollection.CopyTo(Array array, int index)
	{
		((ICollection)_items).CopyTo(array, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return ((IList)_items).Contains(value);
	}

	int IList.IndexOf(object value)
	{
		return ((IList)_items).IndexOf(value);
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return ((IEnumerable<T>)_items).GetEnumerator();
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return ((ICollection<T>)_items).Contains(item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		((ICollection<T>)_items).CopyTo(array, arrayIndex);
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		return ((IList<T>)_items).IndexOf(item);
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
	{
		object IEnumerator.Current => _item;

		T IEnumerator<T>.Current => _item;

		public Enumerator(T item)
		{
			_item = item;
		}

		bool IEnumerator.MoveNext()
		{
			if (!_moveNextCalled)
			{
				return _moveNextCalled = true;
			}
			return false;
		}

		void IEnumerator.Reset()
		{
			_moveNextCalled = false;
		}

		void IDisposable.Dispose()
		{
		}
	}

	int ICollection.Count => 1;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => 1;

	T IReadOnlyList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
	}

	int ICollection<T>.Count => 1;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlySingleElementList(T item)
	{
		_item = item;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection.CopyTo(Array array, int index)
	{
		array.SetValue(_item, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return EqualityComparer<T>.Default.Equals(_item, (T)value);
	}

	int IList.IndexOf(object value)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
		{
			return -1;
		}
		return 0;
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return EqualityComparer<T>.Default.Equals(_item, item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		array[arrayIndex] = _item;
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, item))
		{
			return -1;
		}
		return 0;
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
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 Inferno
{
	[BepInPlugin("GrundleLord.Inferno", "Inferno", "0.3.2")]
	public sealed class InfernoPlugin : BaseUnityPlugin
	{
		private const string TestedGameVersion = "1.0.17";

		private const float GiveUpAfterSeconds = 30f;

		private Harmony? _harmony;

		private float _failingSince = -1f;

		private ConfigSettingsStore? _store;

		private ClientSync? _client;

		private bool _clientFailed;

		private bool _failed;

		private void Awake()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			Diagnostics.Init(((BaseUnityPlugin)this).Logger);
			_store = new ConfigSettingsStore(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger);
			_client = new ClientSync(_store, ((BaseUnityPlugin)this).Logger);
			ClientFeatures.Store = _store;
			_harmony = new Harmony("GrundleLord.Inferno");
			ApplyPatches(_harmony);
			string text = ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Inferno 0.3.2 loaded (Valheim " + text + ")."));
			if (text != "1.0.17")
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("This Inferno build was tested on Valheim 1.0.17; this server runs " + text + ". It will probably work, but please report problems."));
			}
		}

		private void Update()
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			TickClient();
			if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer() || !((Object)(object)ZNetScene.instance != (Object)null) || ZDOMan.instance == null)
			{
				if (InfernoRuntime.Current != null)
				{
					InfernoRuntime.Stop();
					((BaseUnityPlugin)this).Logger.LogInfo((object)"World unloaded; Inferno stopped.");
				}
				_failed = false;
			}
			else
			{
				if (_failed)
				{
					return;
				}
				if (InfernoRuntime.Current == null)
				{
					try
					{
						InfernoRuntime.Start(_store, ((BaseUnityPlugin)this).Logger, _harmony);
					}
					catch (Exception arg)
					{
						_failed = true;
						InfernoRuntime.Stop();
						((BaseUnityPlugin)this).Logger.LogError((object)string.Format("Inferno could not start and is disabled until the world is reloaded; the game runs as vanilla. Please report this with the log (Inferno {0}, Valheim {1}):\n{2}", "0.3.2", Version.CurrentVersion, arg));
						return;
					}
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				try
				{
					InfernoRuntime.Current.Tick(realtimeSinceStartup);
					_failingSince = -1f;
				}
				catch (Exception e)
				{
					Diagnostics.Error("running Inferno's regular update", e);
					if (_failingSince < 0f)
					{
						_failingSince = realtimeSinceStartup;
					}
					else if (realtimeSinceStartup - _failingSince > 30f)
					{
						_failed = true;
						InfernoRuntime.Stop();
						((BaseUnityPlugin)this).Logger.LogError((object)$"Inferno kept failing for {30f:0} s and is disabled until the world is reloaded; the game runs as vanilla. See the first error above.");
					}
				}
			}
		}

		private void ApplyPatches(Harmony harmony)
		{
			List<Type> list = new List<Type>(typeof(GameHooks).GetNestedTypes(BindingFlags.NonPublic));
			list.AddRange(typeof(ClientFeatures).GetNestedTypes(BindingFlags.NonPublic));
			foreach (Type item in list)
			{
				try
				{
					harmony.CreateClassProcessor(item).Patch();
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Could not install hook " + item.Name + "; that feature is disabled. Likely cause: a Valheim update. " + ex.Message));
				}
			}
		}

		private void TickClient()
		{
			if (_clientFailed)
			{
				return;
			}
			try
			{
				_client.Tick(Time.realtimeSinceStartup);
			}
			catch (Exception e)
			{
				_clientFailed = true;
				Diagnostics.Error("running the client mod features (now disabled until the game restarts)", e);
			}
		}

		private void OnDestroy()
		{
			InfernoRuntime.Stop();
			_store?.Dispose();
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "GrundleLord.Inferno";

		public const string PLUGIN_NAME = "Inferno";

		public const string PLUGIN_VERSION = "0.3.2";
	}
}
namespace Inferno.Game
{
	internal static class ClientFeatures
	{
		[HarmonyPatch(typeof(SmokeSpawner), "Spawn")]
		private static class SmokePatch
		{
			private static readonly FieldRef<SmokeSpawner, float> LastSpawnTime = AccessTools.FieldRefAccess<SmokeSpawner, float>("m_lastSpawnTime");

			private static readonly Func<SmokeSpawner, bool> TestBlocked = AccessTools.MethodDelegate<Func<SmokeSpawner, bool>>(AccessTools.Method(typeof(SmokeSpawner), "TestBlocked", (Type[])null, (Type[])null), (object)null, true);

			private static bool Prefix(SmokeSpawner __instance, float time)
			{
				if (!Active || SmokeWanted(__instance))
				{
					return true;
				}
				if (!TestBlocked(__instance))
				{
					LastSpawnTime.Invoke(__instance) = time;
				}
				return false;
			}

			private static bool SmokeWanted(SmokeSpawner spawner)
			{
				int instanceID = ((Object)spawner).GetInstanceID();
				if (!ViewBySpawner.TryGetValue(instanceID, out ZNetView value))
				{
					value = ((Component)spawner).GetComponentInParent<ZNetView>();
					ViewBySpawner[instanceID] = value;
				}
				if ((Object)(object)value == (Object)null || !value.IsValid())
				{
					return true;
				}
				ItemSettings? obj = ObjectSettings.Read(value.GetZDO());
				if (obj == null)
				{
					return Store.GetItem(Utils.GetPrefabName(((Component)value).gameObject)).Smoke;
				}
				return obj.Smoke;
			}
		}

		[HarmonyPatch(typeof(Fireplace), "CheckWet")]
		private static class RainPatch
		{
			private static readonly FieldRef<Fireplace, bool> Wet = AccessTools.FieldRefAccess<Fireplace, bool>("m_wet");

			private static void Postfix(Fireplace __instance)
			{
				if (Active && Store.General.IgnoreRain)
				{
					Wet.Invoke(__instance) = false;
				}
			}
		}

		private static readonly Dictionary<int, ZNetView?> ViewBySpawner = new Dictionary<int, ZNetView>();

		public static ConfigSettingsStore? Store { get; set; }

		private static bool Active
		{
			get
			{
				if (Store != null)
				{
					if (!Store.IsMirroring)
					{
						return InfernoRuntime.Current != null;
					}
					return true;
				}
				return false;
			}
		}
	}
	internal sealed class ClientSync(ConfigSettingsStore store, ManualLogSource log)
	{
		private const int MaxHellos = 3;

		private const float HelloIntervalSeconds = 5f;

		private const float EditQuietSeconds = 0.75f;

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

		private float _sendEditsAt;

		private ZRoutedRpc? _session;

		private int _hellos;

		private float _nextHello;

		public bool IsSynced => store.IsMirroring;

		public void Tick(float now)
		{
			if (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer() || ZRoutedRpc.instance == null || !((Object)(object)ZNetScene.instance != (Object)null) || !((Object)(object)Player.m_localPlayer != (Object)null))
			{
				if (_session != null)
				{
					EndSession();
				}
				return;
			}
			if (_session != ZRoutedRpc.instance)
			{
				BeginSession();
			}
			if (_editedSections.Count > 0 && now >= _sendEditsAt)
			{
				SendEdits();
			}
			if (!IsSynced && _hellos < 3 && now >= _nextHello)
			{
				_hellos++;
				_nextHello = now + 5f;
				_session.InvokeRoutedRPC("Inferno_Hello", new object[1] { 1 });
			}
		}

		private void BeginSession()
		{
			if (_session != null)
			{
				EndSession();
			}
			PrefabDiscovery.Result result = PrefabDiscovery.Run(ZNetScene.instance, log, verbose: false);
			store.BindItems(result.Catalog);
			ZRoutedRpc.instance.Register<string>("Inferno_Settings", (Action<long, string>)OnSettings);
			store.MenuEdited += OnMenuEdited;
			_session = ZRoutedRpc.instance;
			_hellos = 0;
			_nextHello = 0f;
		}

		private void EndSession()
		{
			store.MenuEdited -= OnMenuEdited;
			_editedSections.Clear();
			store.StopMirroring();
			_session = null;
		}

		private void OnSettings(long sender, string text)
		{
			try
			{
				ApplySettings(text);
			}
			catch (Exception e)
			{
				Diagnostics.Error("applying settings received from the server", e);
			}
		}

		private void ApplySettings(string text)
		{
			SettingsMessage val = default(SettingsMessage);
			if (!SettingsMessage.TryDecode(text, ref val))
			{
				log.LogWarning((object)"Received unreadable settings from the server (different Inferno version?); ignoring.");
				return;
			}
			bool num = !IsSynced;
			store.ApplyRemote(val);
			if (num)
			{
				log.LogInfo((object)string.Format("Synced Inferno settings from the server ({0} items, {1}).", val.Items.Count, val.CanEdit ? "editable" : "read-only"));
			}
		}

		private void OnMenuEdited(string section)
		{
			_editedSections.Add(section);
			_sendEditsAt = Time.realtimeSinceStartup + 0.75f;
		}

		private void SendEdits()
		{
			List<string> list = new List<string>(_editedSections);
			_editedSections.Clear();
			if (_session == null)
			{
				return;
			}
			foreach (string item in list)
			{
				foreach (string item2 in store.CommandsFor(item))
				{
					log.LogInfo((object)("Sending to server: " + item2));
					_session.InvokeRoutedRPC("Inferno_Command", new object[1] { item2 });
				}
			}
		}
	}
	internal sealed class CommandService(CommandExecutor executor, ConfigSettingsStore store, ManualLogSource log)
	{
		private readonly DuplicateFilter _chatCopies = new DuplicateFilter(3.0);

		private readonly RateLimiter _limiter = new RateLimiter(10, 1.0);

		private SettingsSnapshot _snapshot = store.Snapshot();

		public Func<Vector3, long, float?, NearbySelection>? FindNearby { get; set; }

		public bool HideCommands => store.General.HideCommands;

		public event Action? SettingsChanged;

		public void HandleMenu(long senderUid, string text)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Invalid comparison between Unknown and I4
			ZNetPeer val = Players.FindPeer(senderUid);
			if (val == null)
			{
				return;
			}
			ParsedCommand command = default(ParsedCommand);
			string error = default(string);
			ParseOutcome val2 = CommandParser.Parse(text, ref command, ref error);
			if ((int)val2 == 0)
			{
				log.LogDebug((object)$"Ignoring malformed menu message from {Players.FromPeer(val)}.");
				return;
			}
			bool denied;
			IReadOnlyList<string> lines = Run("menu", Players.FromPeer(val), text, val2, command, error, null, out denied);
			Players.ShowMessage(val, lines);
			if (denied || (int)val2 == 2)
			{
				this.SettingsChanged?.Invoke();
			}
		}

		public bool HandleChat(long senderUid, string text)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			ParsedCommand command = default(ParsedCommand);
			string error = default(string);
			ParseOutcome val = CommandParser.Parse(text, ref command, ref error);
			if ((int)val == 0)
			{
				return false;
			}
			if (!_chatCopies.IsFirst(senderUid, text.Trim(), (double)Time.realtimeSinceStartup))
			{
				return true;
			}
			ZNetPeer val2 = Players.FindPeer(senderUid);
			if (val2 == null)
			{
				log.LogDebug((object)$"Ignoring chat command from unknown peer {senderUid}.");
				return true;
			}
			bool denied;
			IReadOnlyList<string> lines = Run("chat", Players.FromPeer(val2), text, val, command, error, NearbyOf(val2.m_refPos, val2.m_playerID), out denied);
			Players.ShowMessage(val2, lines);
			return true;
		}

		public bool HandleConsole(ZRpc rpc, string text)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			ParsedCommand command = default(ParsedCommand);
			string error = default(string);
			ParseOutcome val = CommandParser.Parse(text, ref command, ref error);
			if ((int)val == 0)
			{
				return false;
			}
			ZNetPeer val2 = Players.FindPeer(rpc);
			if (val2 == null)
			{
				log.LogDebug((object)"Ignoring console command from unknown connection.");
				return true;
			}
			bool denied;
			IReadOnlyList<string> lines = Run("console", Players.FromPeer(val2), text, val, command, error, NearbyOf(val2.m_refPos, val2.m_playerID), out denied);
			Players.ConsolePrint(rpc, lines);
			return true;
		}

		public bool HandleSign(ZDO sign, string text, out string answer)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: 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_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			answer = string.Empty;
			ParsedCommand command = default(ParsedCommand);
			string error = default(string);
			ParseOutcome val = CommandParser.Parse(text, ref command, ref error);
			if ((int)val == 0)
			{
				return false;
			}
			ZNetPeer val2 = Players.FindPeer(sign.GetOwner());
			CommandSender sender;
			if (val2 != null)
			{
				sender = Players.FromPeer(val2);
			}
			else
			{
				string text2 = sign.GetString(ZDOVars.s_author, "unknown");
				sender = new CommandSender(sign.GetString(ZDOVars.s_authorDisplayName, text2), text2, ZNet.instance.IsAdmin(text2));
			}
			bool denied;
			IReadOnlyList<string> readOnlyList = Run("sign", sender, text, val, command, error, NearbyOf(sign.GetPosition(), val2?.m_playerID ?? 0), out denied);
			if (val2 != null)
			{
				Players.ShowMessage(val2, readOnlyList);
			}
			string text3 = SignReply.Summarize(readOnlyList);
			answer = ((text3.Length == 0) ? text3 : (RichText.Leading(text) + text3));
			return true;
		}

		public void OnConfigReloaded()
		{
			SettingsSnapshot val = store.Snapshot();
			IReadOnlyList<SettingChange> readOnlyList = SettingsDiff.Compare(_snapshot, val);
			foreach (SettingChange item in readOnlyList)
			{
				log.LogInfo((object)$"Config file edited: {item}");
			}
			_snapshot = val;
			if (readOnlyList.Count > 0)
			{
				this.SettingsChanged?.Invoke();
			}
		}

		private Func<float?, NearbySelection>? NearbyOf(Vector3 spot, long playerId)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			Func<Vector3, long, float?, NearbySelection> find = FindNearby;
			if (find == null)
			{
				return null;
			}
			return (float? radius) => find(spot, playerId, radius);
		}

		private IReadOnlyList<string> Run(string source, CommandSender sender, string text, ParseOutcome outcome, ParsedCommand command, string error, Func<float?, NearbySelection>? nearby, out bool denied)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			denied = false;
			RateDecision val = _limiter.TryTake(sender.PlatformId, (double)Time.realtimeSinceStartup);
			if ((int)val != 1)
			{
				if ((int)val == 2)
				{
					return Array.Empty<string>();
				}
				log.LogInfo((object)$"{source} command from {sender}: {Shorten(text)}");
				if ((int)outcome == 2)
				{
					log.LogInfo((object)("  rejected: " + error));
					return new <>z__ReadOnlySingleElementList<string>(error);
				}
				CommandResult val2;
				try
				{
					val2 = executor.Execute(command, sender, nearby);
				}
				catch (Exception e)
				{
					Diagnostics.Error("running the " + source + " command '" + Shorten(text) + "'", e);
					return new <>z__ReadOnlySingleElementList<string>("Something went wrong; see the server log.");
				}
				denied = val2.Denied;
				if (val2.Denied)
				{
					log.LogWarning((object)$"  denied: {sender} is not an admin and AdminOnly is on.");
				}
				foreach (SettingChange change in val2.Changes)
				{
					log.LogInfo((object)$"  changed by {sender.Name}: {change}");
				}
				if (val2.Changes.Count > 0)
				{
					_snapshot = store.Snapshot();
					this.SettingsChanged?.Invoke();
				}
				foreach (string item in val2.Reply)
				{
					log.LogInfo((object)("  reply: " + item));
				}
				return val2.Reply;
			}
			denied = true;
			log.LogWarning((object)$"{source} commands from {sender} are coming too fast; ignoring them for a few seconds.");
			return new <>z__ReadOnlySingleElementList<string>("Slow down: too many Inferno commands. Try again in a few seconds.");
		}

		private static string Shorten(string text)
		{
			string text2 = text.Trim();
			if (text2.Length > 200)
			{
				return text2.Substring(0, 200) + "…";
			}
			return text2;
		}
	}
	internal sealed class ConfigSettingsStore : ISettingsStore, IDisposable
	{
		private sealed class ItemEntries
		{
			private ConfigEntry<bool> _alwaysOn;

			private ConfigEntry<int> _burnRate;

			private ConfigEntry<bool> _smoke;

			private ConfigEntry<int>? _onHour;

			private ConfigEntry<int>? _onMinute;

			private ConfigEntry<int>? _offHour;

			private ConfigEntry<int>? _offMinute;

			public static ItemEntries Bind(ConfigSettingsStore store, CatalogItem item)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				ItemSettings val = ItemSettings.DefaultFor(item.Kind);
				string prefabName = item.PrefabName;
				string text = (((int)item.Kind == 0) ? "light source" : "fuel station (no on/off switch, so no schedule)");
				ItemEntries itemEntries = new ItemEntries
				{
					_alwaysOn = store.Bind(prefabName, "AlwaysOn", val.AlwaysOn, item.DisplayName + " — " + text + ". Keep fuel full at all times. While on, burn rate and schedule are kept but have no effect (setting either by command switches this off)."),
					_burnRate = store.Bind(prefabName, "BurnRate", val.BurnRateLevel, "Fuel burn rate: 0 = vanilla, each step is 10 %. -10 = uses no fuel, 10 = burns twice as fast.", (AcceptableValueBase?)(object)new AcceptableValueRange<int>(-10, 10)),
					_smoke = store.Bind(prefabName, "Smoke", val.Smoke, "Whether this item makes smoke. Only players with the Inferno client mod see the smoke removed; others see vanilla smoke.")
				};
				if (item.CanSchedule)
				{
					AcceptableValueRange<int> range = new AcceptableValueRange<int>(0, 24);
					AcceptableValueRange<int> range2 = new AcceptableValueRange<int>(0, 60);
					itemEntries._onHour = store.Bind(prefabName, "OnTimeHour", 0, "Hour the light turns on. In-game clock (06:00 = sunrise, 18:00 = sunset). On time = off time means no schedule. Only used when AlwaysOn is false.", (AcceptableValueBase?)(object)range);
					itemEntries._onMinute = store.Bind(prefabName, "OnTimeMinute", 0, "Minute the light turns on.", (AcceptableValueBase?)(object)range2);
					itemEntries._offHour = store.Bind(prefabName, "OffTimeHour", 0, "Hour the light turns off.", (AcceptableValueBase?)(object)range);
					itemEntries._offMinute = store.Bind(prefabName, "OffTimeMinute", 0, "Minute the light turns off.", (AcceptableValueBase?)(object)range2);
				}
				return itemEntries;
			}

			public ItemSettings Read(ManualLogSource log)
			{
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_006d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0073: Expected O, but got Unknown
				try
				{
					DailySchedule val = (DailySchedule)((_onHour == null) ? DailySchedule.AlwaysOn : new DailySchedule(ClockSetting.ToTimeOfDay(_onHour.Value, _onMinute.Value), ClockSetting.ToTimeOfDay(_offHour.Value, _offMinute.Value)));
					return new ItemSettings(_alwaysOn.Value, _burnRate.Value, val, _smoke.Value);
				}
				catch (ArgumentOutOfRangeException ex)
				{
					log.LogWarning((object)("Invalid value in config section [" + ((ConfigEntryBase)_alwaysOn).Definition.Section + "]: " + ex.Message + " Using vanilla until fixed."));
					return ItemSettings.Vanilla;
				}
			}

			public void Write(ItemSettings settings)
			{
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: 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_0087: Unknown result type (might be due to invalid IL or missing references)
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				//IL_008f: 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_00a8: 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_00b0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
				_alwaysOn.Value = settings.AlwaysOn;
				_burnRate.Value = settings.BurnRateLevel;
				_smoke.Value = settings.Smoke;
				if (_onHour != null)
				{
					ConfigEntry<int>? onHour = _onHour;
					DailySchedule schedule = settings.Schedule;
					TimeOfDay val = ((DailySchedule)(ref schedule)).OnTime;
					onHour.Value = ((TimeOfDay)(ref val)).Hour;
					ConfigEntry<int>? onMinute = _onMinute;
					schedule = settings.Schedule;
					val = ((DailySchedule)(ref schedule)).OnTime;
					onMinute.Value = ((TimeOfDay)(ref val)).Minute;
					ConfigEntry<int>? offHour = _offHour;
					schedule = settings.Schedule;
					val = ((DailySchedule)(ref schedule)).OffTime;
					offHour.Value = ((TimeOfDay)(ref val)).Hour;
					ConfigEntry<int>? offMinute = _offMinute;
					schedule = settings.Schedule;
					val = ((DailySchedule)(ref schedule)).OffTime;
					offMinute.Value = ((TimeOfDay)(ref val)).Minute;
				}
			}
		}

		private const string GeneralSection = "General";

		private readonly ConfigFile _config;

		private readonly ManualLogSource _log;

		private readonly ConfigEntry<bool> _adminOnly;

		private readonly ConfigEntry<bool> _hideCommands;

		private readonly ConfigEntry<bool> _ignoreRain;

		private readonly ConfigEntry<bool> _serverOwnership;

		private readonly Dictionary<string, ItemEntries> _items = new Dictionary<string, ItemEntries>(StringComparer.OrdinalIgnoreCase);

		private readonly ConfigurationManagerAttributes _menu = new ConfigurationManagerAttributes();

		private FileSystemWatcher? _watcher;

		private int _fileChanged;

		private DateTime _ignoreChangesUntil;

		private SettingsMessage? _remote;

		private bool _suppressEvents;

		private bool _saveOnSetBeforeMirror;

		public string ConfigPath => _config.ConfigFilePath;

		public bool IsMirroring => _remote != null;

		public GeneralSettings General
		{
			get
			{
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0032: Expected O, but got Unknown
				return new GeneralSettings(_adminOnly.Value, _hideCommands.Value, _ignoreRain.Value, _serverOwnership.Value);
			}
			set
			{
				Saving();
				_adminOnly.Value = value.AdminOnly;
				_hideCommands.Value = value.HideCommands;
				_ignoreRain.Value = value.IgnoreRain;
				_serverOwnership.Value = value.ServerOwnership;
			}
		}

		public event Action<string>? MenuEdited;

		public ConfigSettingsStore(ConfigFile config, ManualLogSource log)
		{
			_config = config;
			_log = log;
			_adminOnly = Bind("General", "AdminOnly", GeneralSettings.Default.AdminOnly, "Only admins (the server's adminlist.txt) may change Inferno settings. Anyone may turn this on; only admins may turn it off.");
			_hideCommands = Bind("General", "HideCommands", GeneralSettings.Default.HideCommands, "Hide '!fires' chat commands from other players.");
			_ignoreRain = Bind("General", "IgnoreRain", GeneralSettings.Default.IgnoreRain, "PC client mod only: players with the client mod see fires burn normally in rain. Everyone else sees vanilla rain. Side effect: the server also switches lights back on that a player switched off by hand.");
			_serverOwnership = Bind("General", "ServerOwnership", GeneralSettings.Default.ServerOwnership, "EXPERIMENTAL. The server keeps ownership of fires, so no player's game burns their fuel or switches them off in rain. Burn rate and schedule become exact. Side effects: no sound when adding fuel or switching these fires; they don't go out in rain. Fires that melt snow or spread fire are excluded. Turning it off gives the fires back immediately.");
			_config.SettingChanged += OnSettingChanged;
		}

		public void BindItems(ItemCatalog catalog)
		{
			bool saveOnConfigSet = _config.SaveOnConfigSet;
			_config.SaveOnConfigSet = false;
			try
			{
				foreach (CatalogItem item in catalog.Items)
				{
					if (!_items.ContainsKey(item.PrefabName))
					{
						_items.Add(item.PrefabName, ItemEntries.Bind(this, item));
					}
				}
			}
			finally
			{
				_config.SaveOnConfigSet = saveOnConfigSet;
			}
			if (!IsMirroring)
			{
				Saving();
				_config.Save();
			}
		}

		public ItemSettings GetItem(string prefabName)
		{
			if (!_items.TryGetValue(prefabName, out ItemEntries value))
			{
				return ItemSettings.Vanilla;
			}
			return value.Read(_log);
		}

		public void SetItem(string prefabName, ItemSettings settings)
		{
			if (_items.TryGetValue(prefabName, out ItemEntries value))
			{
				Saving();
				value.Write(settings);
			}
		}

		public ItemSettings? GetObject(string objectKey)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (ObjectSettings.TryParseKey(objectKey, out var id))
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val = ((instance != null) ? instance.GetZDO(id) : null);
				if (val != null)
				{
					return ObjectSettings.Read(val);
				}
			}
			return null;
		}

		public void SetObject(string objectKey, ItemSettings? settings)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (ObjectSettings.TryParseKey(objectKey, out var id))
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val = ((instance != null) ? instance.GetZDO(id) : null);
				if (val != null)
				{
					ObjectSettings.Write(val, settings);
				}
			}
		}

		public SettingsSnapshot Snapshot()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			Dictionary<string, ItemSettings> dictionary = new Dictionary<string, ItemSettings>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, ItemEntries> item in _items)
			{
				dictionary[item.Key] = item.Value.Read(_log);
			}
			return new SettingsSnapshot(General, (IReadOnlyDictionary<string, ItemSettings>)dictionary);
		}

		public void ApplyRemote(SettingsMessage message)
		{
			if (_remote == null)
			{
				_saveOnSetBeforeMirror = _config.SaveOnConfigSet;
				_config.SaveOnConfigSet = false;
			}
			_suppressEvents = true;
			try
			{
				General = message.General;
				foreach (KeyValuePair<string, ItemSettings> item in message.Items)
				{
					if (_items.TryGetValue(item.Key, out ItemEntries value))
					{
						value.Write(item.Value);
					}
				}
				_menu.ReadOnly = !message.CanEdit;
				_remote = message;
			}
			finally
			{
				_suppressEvents = false;
			}
		}

		public void StopMirroring()
		{
			if (_remote == null)
			{
				return;
			}
			_suppressEvents = true;
			try
			{
				_config.Reload();
				_config.SaveOnConfigSet = _saveOnSetBeforeMirror;
				_menu.ReadOnly = false;
				_remote = null;
			}
			finally
			{
				_suppressEvents = false;
			}
		}

		public void WatchFile()
		{
			string configFilePath = _config.ConfigFilePath;
			_watcher?.Dispose();
			_watcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath))
			{
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite)
			};
			_watcher.Changed += delegate
			{
				Interlocked.Exchange(ref _fileChanged, 1);
			};
			_watcher.Created += delegate
			{
				Interlocked.Exchange(ref _fileChanged, 1);
			};
			_watcher.Renamed += delegate
			{
				Interlocked.Exchange(ref _fileChanged, 1);
			};
			_watcher.EnableRaisingEvents = true;
		}

		public bool ReloadIfChanged()
		{
			if (Interlocked.Exchange(ref _fileChanged, 0) == 0 || DateTime.UtcNow < _ignoreChangesUntil)
			{
				return false;
			}
			try
			{
				_config.Reload();
				return true;
			}
			catch (IOException ex)
			{
				_log.LogWarning((object)("Could not reload config yet (" + ex.Message + "); will retry."));
				Interlocked.Exchange(ref _fileChanged, 1);
				return false;
			}
		}

		public void Dispose()
		{
			_watcher?.Dispose();
			_watcher = null;
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, AcceptableValueBase? range = null)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			return _config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, range, new object[1] { _menu }));
		}

		public IReadOnlyList<string> CommandsFor(string section)
		{
			SettingsMessage remote = _remote;
			if (remote == null)
			{
				return Array.Empty<string>();
			}
			if (section == "General")
			{
				return MenuCommands.ForGeneral(remote.General, General);
			}
			if (!remote.Items.TryGetValue(section, out var value))
			{
				return Array.Empty<string>();
			}
			return MenuCommands.ForItem(section, value, GetItem(section));
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs e)
		{
			if (!_suppressEvents && _remote != null)
			{
				this.MenuEdited?.Invoke(e.ChangedSetting.Definition.Section);
			}
		}

		private void Saving()
		{
			_ignoreChangesUntil = DateTime.UtcNow.AddSeconds(2.0);
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? ReadOnly;
	}
	internal static class Diagnostics
	{
		private const double RepeatSummarySeconds = 300.0;

		private static readonly ErrorThrottle Throttle = new ErrorThrottle(300.0);

		private static ManualLogSource? _log;

		public static int ErrorCount => Throttle.Total;

		public static void Init(ManualLogSource log)
		{
			_log = log;
		}

		public static void Error(string where, Exception e)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			ErrorLogDecision val = Throttle.Record(where + ":" + e.GetType().FullName, (double)Time.realtimeSinceStartup);
			if (((ErrorLogDecision)(ref val)).Log && _log != null)
			{
				if (((ErrorLogDecision)(ref val)).FirstTime)
				{
					_log.LogError((object)("Error while " + where + ". Inferno skipped this and keeps running. Please report it with this log " + string.Format("(Inferno {0}, Valheim {1}):\n{2}", "0.3.2", Version.CurrentVersion, e)));
				}
				else
				{
					_log.LogError((object)($"Error while {where} happened again ({((ErrorLogDecision)(ref val)).Suppressed} more time(s) in the last " + $"{5.0:0} min): {e.GetType().Name}: {e.Message}"));
				}
			}
		}

		public static void LogStartup(Harmony? harmony, string configPath)
		{
			if (_log == null)
			{
				return;
			}
			try
			{
				WriteStartup(harmony, configPath);
			}
			catch (Exception e)
			{
				Error("writing the startup diagnostics", e);
			}
		}

		private static void WriteStartup(Harmony? harmony, string configPath)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Invalid comparison between Unknown and I4
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("--- Inferno startup diagnostics (include this when reporting a problem) ---");
			stringBuilder.AppendLine(string.Format("Inferno {0} | Valheim {1} (network {2}) | BepInEx {3}", "0.3.2", Version.CurrentVersion, 40u, typeof(Chainloader).Assembly.GetName().Version));
			stringBuilder.AppendLine(string.Format("Server: {0} | crossplay {1} | world '{2}' | OS {3} ({4})", instance.IsDedicated() ? "dedicated" : "hosted from game", ((int)ZNet.m_onlineBackend == 1) ? "on" : "off", instance.GetWorldName(), Environment.OSVersion, Application.platform));
			stringBuilder.AppendLine("Config: " + configPath);
			List<string> list = (from p in Chainloader.PluginInfos.Values
				where p.Metadata.GUID != "GrundleLord.Inferno"
				select $"{p.Metadata.Name} {p.Metadata.Version}").OrderBy<string, string>((string n) => n, StringComparer.OrdinalIgnoreCase).ToList();
			stringBuilder.AppendLine((list.Count == 0) ? "Other mods: none" : string.Format("Other mods ({0}): {1}", list.Count, string.Join(", ", list)));
			stringBuilder.Append(SharedHooks(harmony));
			_log.LogInfo((object)stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
		}

		private static string SharedHooks(Harmony? harmony)
		{
			if (harmony == null)
			{
				return "Shared hooks: unknown";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (MethodBase patchedMethod in harmony.GetPatchedMethods())
			{
				Patches patchInfo = Harmony.GetPatchInfo(patchedMethod);
				List<string> list = ((patchInfo != null) ? patchInfo.Owners.Where((string o) => o != harmony.Id).ToList() : null);
				if (list != null && list.Count > 0)
				{
					stringBuilder.Append(patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name + " (also patched by " + string.Join(", ", list) + "); ");
				}
			}
			if (stringBuilder.Length != 0)
			{
				return "Shared hooks: " + stringBuilder.ToString().TrimEnd(' ', ';');
			}
			return "Shared hooks: none (no other mod patches the game methods Inferno uses)";
		}
	}
	internal sealed class FuelScanner(PrefabDiscovery.Result discovery, ISettingsStore store, CommandService commands, ServerOwnership ownership, ManualLogSource log)
	{
		private const float ApplyIntervalSeconds = 5f;

		private const float SoonSeconds = 0.5f;

		private const float SweepIntervalSeconds = 30f;

		private const int SweepChunkSize = 20000;

		private const long FallbackDayLengthSeconds = 1800L;

		private const uint RevisionLead = 8u;

		private static readonly int SwitchedOffByInfernoKey = StringExtensionMethods.GetStableHashCode("Inferno_ScheduledOff");

		private static readonly FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID");

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

		private readonly List<ZDOID> _sweepFuel = new List<ZDOID>();

		private readonly List<ZDOID> _sweepSigns = new List<ZDOID>();

		private readonly Dictionary<ZDOID, float> _baselines = new Dictionary<ZDOID, float>();

		private readonly Dictionary<string, ItemSettings> _settingsCache = new Dictionary<string, ItemSettings>();

		private readonly HashSet<ZDOID> _fuelSet = new HashSet<ZDOID>();

		private readonly HashSet<ZDOID> _signSet = new HashSet<ZDOID>();

		private readonly HashSet<ZDOID> _wardSet = new HashSet<ZDOID>();

		private readonly List<ZDOID> _sweepWards = new List<ZDOID>();

		private readonly List<ZDOID> _arrivedSinceSweep = new List<ZDOID>();

		private const float StatsIntervalSeconds = 600f;

		private readonly RunningStats _passStats = new RunningStats();

		private readonly RunningStats _sweepStats = new RunningStats();

		private readonly Stopwatch _sweepWatch = new Stopwatch();

		private int _writesSinceStats;

		private float _nextStats = 600f;

		private List<ZDOID> _fuel = new List<ZDOID>();

		private List<ZDOID> _signs = new List<ZDOID>();

		private int _sweepIndex = -1;

		private float _nextSweep;

		private float _nextApply;

		private bool _warnedDayLength;

		public string StatusText
		{
			get
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				int num = 0;
				int num2 = 0;
				int num3 = 0;
				foreach (ZDOID item in _fuel)
				{
					ZDOMan instance = ZDOMan.instance;
					ZDO val = ((instance != null) ? instance.GetZDO(item) : null);
					if (val != null)
					{
						if (ObjectSettings.Read(val) != null)
						{
							num3++;
						}
						if (ServerOwnership.IsServerSimulated(val))
						{
							num++;
						}
						else if (!val.HasOwner())
						{
							num2++;
						}
					}
				}
				return $"Tracking {_fuel.Count} fuel object(s): {num2} with nobody nearby, {num} handled by the server, {num3} with own settings; {_signs.Count} sign(s), {_wardSet.Count} ward(s).";
			}
		}

		public string CurrentTimeText => ((object)CurrentTime()/*cast due to .constrained prefix*/).ToString();

		public void Notice(ZDO zdo)
		{
			//IL_0021: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			int prefab = zdo.GetPrefab();
			if (discovery.ByHash.ContainsKey(prefab))
			{
				if (_fuelSet.Add(zdo.m_uid))
				{
					_fuel.Add(zdo.m_uid);
					_arrivedSinceSweep.Add(zdo.m_uid);
					ApplySoon();
				}
			}
			else if (discovery.SignHashes.Contains(prefab))
			{
				if (_signSet.Add(zdo.m_uid))
				{
					_signs.Add(zdo.m_uid);
					_arrivedSinceSweep.Add(zdo.m_uid);
				}
				ApplySoon();
			}
			else if (discovery.WardRadii.ContainsKey(prefab))
			{
				_wardSet.Add(zdo.m_uid);
			}
		}

		public List<Ward> Wards()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			List<Ward> list = new List<Ward>(_wardSet.Count);
			foreach (ZDOID item in _wardSet)
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val = ((instance != null) ? instance.GetZDO(item) : null);
				if (val != null && discovery.WardRadii.TryGetValue(val.GetPrefab(), out var value))
				{
					List<long> list2 = new List<long>();
					int num = val.GetInt(ZDOVars.s_permitted, 0);
					for (int i = 0; i < num; i++)
					{
						list2.Add(val.GetLong("pu_id" + i.ToString(CultureInfo.InvariantCulture), 0L));
					}
					Vector3 position = val.GetPosition();
					list.Add(new Ward(position.x, position.z, value, val.GetBool(ZDOVars.s_enabled, false), val.GetLong(ZDOVars.s_creator, 0L), (IReadOnlyCollection<long>)list2));
				}
			}
			return list;
		}

		public NearbySelection Nearby(Vector3 spot, long playerId, float? radius)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Expected O, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Expected O, but got Unknown
			List<Ward> list = Wards();
			NearbyArea obj;
			if (radius.HasValue)
			{
				float valueOrDefault = radius.GetValueOrDefault();
				obj = NearbyArea.Circle(spot.x, spot.z, valueOrDefault);
			}
			else
			{
				obj = NearbyArea.Around((IEnumerable<Ward>)list, spot.x, spot.z, 20f);
			}
			NearbyArea val = obj;
			List<NearbyObject> list2 = new List<NearbyObject>();
			foreach (ZDOID item in _fuel)
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val2 = ((instance != null) ? instance.GetZDO(item) : null);
				if (val2 != null && discovery.ByHash.TryGetValue(val2.GetPrefab(), out CatalogItem value))
				{
					Vector3 position = val2.GetPosition();
					if (val.Contains(position.x, position.z))
					{
						list2.Add(new NearbyObject(ObjectSettings.KeyOf(item), value.PrefabName, NearbyArea.CanAccess((IEnumerable<Ward>)list, position.x, position.z, playerId)));
					}
				}
			}
			return new NearbySelection((IReadOnlyList<NearbyObject>)list2, val.Description);
		}

		public void Tick(float now)
		{
			if (ZDOMan.instance != null && ZNet.instance != null)
			{
				if (_sweepIndex < 0 && now >= _nextSweep)
				{
					BeginSweep();
				}
				if (_sweepIndex >= 0)
				{
					ContinueSweep(now);
				}
				ownership.Tick(now);
				if (now >= _nextApply)
				{
					_nextApply = now + 5f;
					Stopwatch stopwatch = Stopwatch.StartNew();
					ApplySettings(now);
					HandleSigns();
					_passStats.Add(stopwatch.Elapsed.TotalMilliseconds);
				}
				if (now >= _nextStats)
				{
					_nextStats = now + 600f;
					log.LogInfo((object)$"Performance (last {10f:0} min): {StatusText} Passes: {_passStats}. Sweeps: {_sweepStats}. Writes: {_writesSinceStats}. Errors since start: {Diagnostics.ErrorCount}.");
					_passStats.Reset();
					_sweepStats.Reset();
					_writesSinceStats = 0;
				}
			}
		}

		public void ReleaseOwnership()
		{
			if (ZDOMan.instance != null)
			{
				int num = ownership.ReleaseAll(ObjectsById.Invoke(ZDOMan.instance).Values);
				if (num > 0)
				{
					log.LogInfo((object)$"Gave {num} server-owned fire(s) back to the game.");
				}
			}
		}

		public void RestoreForShutdown()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			if (ZDOMan.instance == null)
			{
				return;
			}
			int num = 0;
			foreach (ZDO value2 in ObjectsById.Invoke(ZDOMan.instance).Values)
			{
				if (value2 == null || !discovery.ByHash.TryGetValue(value2.GetPrefab(), out CatalogItem value) || !value.CanSchedule)
				{
					continue;
				}
				try
				{
					FuelDecision decision = FuelController.RestoreForShutdown(ReadState(value2, value));
					if (((FuelDecision)(ref decision)).HasChanges)
					{
						Apply(value2, decision);
						num++;
					}
				}
				catch (Exception e)
				{
					Diagnostics.Error("switching " + value.PrefabName + " back on at shutdown", e);
				}
			}
			log.LogInfo((object)$"Shutdown: switched {num} scheduled-off light(s) back on before saving.");
		}

		private void BeginSweep()
		{
			_sweepBuffer.Clear();
			_sweepBuffer.AddRange(ObjectsById.Invoke(ZDOMan.instance).Values);
			_sweepFuel.Clear();
			_sweepSigns.Clear();
			_sweepWards.Clear();
			_arrivedSinceSweep.Clear();
			_sweepIndex = 0;
		}

		private void ContinueSweep(float now)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			_sweepWatch.Start();
			int num = Math.Min(_sweepIndex + 20000, _sweepBuffer.Count);
			while (_sweepIndex < num)
			{
				try
				{
					ZDO val = _sweepBuffer[_sweepIndex];
					if (val != null && val.IsValid())
					{
						int prefab = val.GetPrefab();
						if (discovery.ByHash.ContainsKey(prefab))
						{
							_sweepFuel.Add(val.m_uid);
						}
						else if (discovery.SignHashes.Contains(prefab))
						{
							_sweepSigns.Add(val.m_uid);
						}
						else if (discovery.WardRadii.ContainsKey(prefab))
						{
							_sweepWards.Add(val.m_uid);
						}
					}
				}
				catch (Exception e)
				{
					Diagnostics.Error("looking for fires and signs in the world", e);
				}
				_sweepIndex++;
			}
			_sweepWatch.Stop();
			if (_sweepIndex < _sweepBuffer.Count)
			{
				return;
			}
			_sweepStats.Add(_sweepWatch.Elapsed.TotalMilliseconds);
			_sweepWatch.Reset();
			_fuelSet.Clear();
			_signSet.Clear();
			_fuelSet.UnionWith(_sweepFuel);
			_signSet.UnionWith(_sweepSigns);
			_wardSet.RemoveWhere(delegate(ZDOID id)
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				ZDOMan instance2 = ZDOMan.instance;
				return ((instance2 != null) ? instance2.GetZDO(id) : null) == null;
			});
			_wardSet.UnionWith(_sweepWards);
			foreach (ZDOID item in _arrivedSinceSweep)
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val2 = ((instance != null) ? instance.GetZDO(item) : null);
				if (val2 != null)
				{
					if (discovery.ByHash.ContainsKey(val2.GetPrefab()))
					{
						_fuelSet.Add(item);
					}
					else
					{
						_signSet.Add(item);
					}
				}
			}
			List<ZDOID> fuel = new List<ZDOID>(_fuelSet);
			List<ZDOID> signs = new List<ZDOID>(_signSet);
			_fuel = fuel;
			_signs = signs;
			_arrivedSinceSweep.Clear();
			HashSet<ZDOID> hashSet = new HashSet<ZDOID>(_fuel);
			List<ZDOID> list = new List<ZDOID>();
			foreach (ZDOID key in _baselines.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (ZDOID item2 in list)
			{
				_baselines.Remove(item2);
			}
			_sweepBuffer.Clear();
			_sweepIndex = -1;
			_nextSweep = now + 30f;
			log.LogDebug((object)$"Sweep done: tracking {_fuel.Count} fuel object(s) and {_signs.Count} sign(s).");
		}

		private void ApplySettings(float realNow)
		{
			//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_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			TimeOfDay val = CurrentTime();
			bool ignoreRain = store.General.IgnoreRain;
			int num = 0;
			_settingsCache.Clear();
			foreach (ZDOID item in _fuel)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(item);
				if (zDO == null || !discovery.ByHash.TryGetValue(zDO.GetPrefab(), out CatalogItem value))
				{
					continue;
				}
				try
				{
					ownership.Manage(zDO, realNow);
					float value2;
					float? num2 = (_baselines.TryGetValue(item, out value2) ? new float?(value2) : ((float?)null));
					ItemSettings value3 = ObjectSettings.Read(zDO);
					if (value3 == null && !_settingsCache.TryGetValue(value.PrefabName, out value3))
					{
						value3 = store.GetItem(value.PrefabName);
						_settingsCache[value.PrefabName] = value3;
					}
					FuelDecision decision = FuelController.Decide(value3, value.Fuel, ReadState(zDO, value), num2, val, ignoreRain);
					_baselines[item] = ((FuelDecision)(ref decision)).Baseline;
					if (((FuelDecision)(ref decision)).HasChanges)
					{
						Apply(zDO, decision);
						num++;
						_writesSinceStats++;
					}
				}
				catch (Exception e)
				{
					Diagnostics.Error("applying settings to " + value.PrefabName, e);
				}
			}
			if (num > 0)
			{
				log.LogDebug((object)$"Applied settings to {num} of {_fuel.Count} object(s) at {val}.");
			}
		}

		private void HandleSigns()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			foreach (ZDOID sign in _signs)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(sign);
				string text = ((zDO != null) ? zDO.GetString(ZDOVars.s_text, "") : null);
				if (zDO == null || string.IsNullOrEmpty(text))
				{
					continue;
				}
				string answer;
				try
				{
					if (!commands.HandleSign(zDO, text, out answer))
					{
						continue;
					}
				}
				catch (Exception e)
				{
					Diagnostics.Error("running a sign command", e);
					answer = "Inferno error; see server log.";
				}
				zDO.Set(ZDOVars.s_text, answer);
			}
		}

		private void ApplySoon()
		{
			_nextApply = Math.Min(_nextApply, Time.realtimeSinceStartup + 0.5f);
		}

		private static FuelState ReadState(ZDO zdo, CatalogItem item)
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			long num = zdo.GetLong(ZDOVars.s_lastTime, 0L);
			double num2 = ((num > 0) ? Math.Max(0.0, (double)(ZNet.instance.GetTime().Ticks - num) / 10000000.0) : 0.0);
			return new FuelState(zdo.GetFloat(ZDOVars.s_fuel, 0f), !item.CanSchedule || zdo.GetInt(ZDOVars.s_state, 1) == 1, zdo.GetInt(SwitchedOffByInfernoKey, 0) == 1, !zdo.HasOwner() || ServerOwnership.IsServerSimulated(zdo), num2);
		}

		private static void Apply(ZDO zdo, FuelDecision decision)
		{
			float? setFuel = ((FuelDecision)(ref decision)).SetFuel;
			if (setFuel.HasValue)
			{
				float valueOrDefault = setFuel.GetValueOrDefault();
				zdo.Set(ZDOVars.s_fuel, valueOrDefault);
			}
			bool? setSwitchedOn = ((FuelDecision)(ref decision)).SetSwitchedOn;
			if (setSwitchedOn.HasValue)
			{
				bool valueOrDefault2 = setSwitchedOn == true;
				zdo.Set(ZDOVars.s_state, valueOrDefault2 ? 1 : 2, false);
			}
			setSwitchedOn = ((FuelDecision)(ref decision)).SetSwitchedOffByInferno;
			if (setSwitchedOn.HasValue)
			{
				bool valueOrDefault3 = setSwitchedOn == true;
				zdo.Set(SwitchedOffByInfernoKey, valueOrDefault3 ? 1 : 0, false);
			}
			if (((FuelDecision)(ref decision)).RefreshBurnClock)
			{
				zdo.Set(ZDOVars.s_lastTime, ZNet.instance.GetTime().Ticks);
			}
			if (((FuelDecision)(ref decision)).MustWin)
			{
				zdo.DataRevision += 8;
			}
		}

		private TimeOfDay CurrentTime()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			long num = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.m_dayLengthSec : 0);
			if (num <= 0)
			{
				if (!_warnedDayLength)
				{
					log.LogWarning((object)$"Day length not available on this server; assuming {1800L} s for schedules.");
					_warnedDayLength = true;
				}
				num = 1800L;
			}
			return GameClock.TimeAt(Math.Max(0.0, ZNet.instance.GetTimeSeconds()), num);
		}
	}
	internal static class GameHooks
	{
		[HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")]
		private static class ChatRelayPatch
		{
			private static bool Prefix(RoutedRPCData rpcData)
			{
				InfernoRuntime current = InfernoRuntime.Current;
				if (current == null || (rpcData.m_methodHash != SayHash && rpcData.m_methodHash != ChatMessageHash))
				{
					return true;
				}
				try
				{
					string text = ReadChatText(rpcData);
					if (text == null || !current.Commands.HandleChat(rpcData.m_senderPeerID, text))
					{
						return true;
					}
					return !current.Commands.HideCommands;
				}
				catch (Exception e)
				{
					Diagnostics.Error("checking a chat message for commands (message passed through unchanged)", e);
					return true;
				}
			}

			private static string? ReadChatText(RoutedRPCData rpcData)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Expected O, but got Unknown
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				ZPackage val = new ZPackage(rpcData.m_parameters.GetArray());
				if (rpcData.m_methodHash == ChatMessageHash)
				{
					val.ReadVector3();
				}
				val.ReadInt();
				val.ReadString();
				val.ReadString();
				return val.ReadString();
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_RemoteCommand")]
		private static class RemoteCommandPatch
		{
			private static bool Prefix(ZRpc rpc, string command)
			{
				InfernoRuntime current = InfernoRuntime.Current;
				if (current == null)
				{
					return true;
				}
				try
				{
					return !current.Commands.HandleConsole(rpc, command);
				}
				catch (Exception e)
				{
					Diagnostics.Error("handling an F5 console command (passed on to the game)", e);
					return true;
				}
			}
		}

		[HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")]
		private static class OwnedFireRpcPatch
		{
			private static bool Prefix(RoutedRPCData data)
			{
				InfernoRuntime current = InfernoRuntime.Current;
				if (current == null)
				{
					return true;
				}
				try
				{
					return !current.Ownership.TryHandleRpc(data);
				}
				catch (Exception e)
				{
					Diagnostics.Error("handling a player action on a server-owned fire (passed on to the game)", e);
					return true;
				}
			}
		}

		[HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")]
		private static class KeepOwnershipPatch
		{
			[ThreadStatic]
			private static bool _active;

			public static bool Active => _active;

			private static void Prefix()
			{
				_active = true;
			}

			private static Exception? Finalizer(Exception? __exception)
			{
				_active = false;
				return __exception;
			}
		}

		[HarmonyPatch(typeof(ZDO), "SetOwner")]
		private static class SetOwnerPatch
		{
			private static bool Prefix(ZDO __instance)
			{
				if (!KeepOwnershipPatch.Active)
				{
					return true;
				}
				InfernoRuntime current = InfernoRuntime.Current;
				try
				{
					return current == null || !current.Ownership.IsProtected(__instance);
				}
				catch (Exception e)
				{
					Diagnostics.Error("keeping a fire server-owned (vanilla owner change allowed)", e);
					return true;
				}
			}
		}

		[HarmonyPatch(typeof(ZDO), "Deserialize")]
		private static class ObjectArrivedPatch
		{
			private static void Postfix(ZDO __instance)
			{
				InfernoRuntime current = InfernoRuntime.Current;
				if (current == null)
				{
					return;
				}
				try
				{
					current.Scanner.Notice(__instance);
				}
				catch (Exception e)
				{
					Diagnostics.Error("noticing a new or changed object", e);
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private static class ShutdownPatch
		{
			private static void Prefix()
			{
				InfernoRuntime current = InfernoRuntime.Current;
				if (current == null)
				{
					return;
				}
				try
				{
					current.Scanner.RestoreForShutdown();
					current.Scanner.ReleaseOwnership();
				}
				catch (Exception e)
				{
					Diagnostics.Error("restoring lights at shutdown", e);
				}
			}
		}

		private static readonly int SayHash = StringExtensionMethods.GetStableHashCode("Say");

		private static readonly int ChatMessageHash = StringExtensionMethods.GetStableHashCode("ChatMessage");
	}
	internal sealed class InfernoRuntime : IDisposable
	{
		private readonly SyncServer _sync;

		private readonly PrefabDiscovery.Result _discovery;

		public static InfernoRuntime? Current { get; private set; }

		public ManualLogSource Log { get; }

		public ConfigSettingsStore Store { get; }

		public CommandService Commands { get; }

		public FuelScanner Scanner { get; }

		public ServerOwnership Ownership { get; }

		private InfernoRuntime(ManualLogSource log, ConfigSettingsStore store, CommandService commands, FuelScanner scanner, ServerOwnership ownership, SyncServer sync, PrefabDiscovery.Result discovery)
		{
			_sync = sync;
			_discovery = discovery;
			Log = log;
			Store = store;
			Commands = commands;
			Scanner = scanner;
			Ownership = ownership;
		}

		public static void Start(ConfigSettingsStore store, ManualLogSource log, Harmony? harmony)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			Diagnostics.LogStartup(harmony, store.ConfigPath);
			PrefabDiscovery.Result result = PrefabDiscovery.Run(ZNetScene.instance, log);
			store.BindItems(result.Catalog);
			store.WatchFile();
			CommandService commandService = new CommandService(new CommandExecutor(result.Catalog, (ISettingsStore)(object)store, (Func<IReadOnlyList<string>>)(() => Current?.StatusLines() ?? Array.Empty<string>())), store, log);
			ServerOwnership ownership = new ServerOwnership(result, (ISettingsStore)(object)store, log);
			FuelScanner fuelScanner = new FuelScanner(result, (ISettingsStore)(object)store, commandService, ownership, log);
			commandService.FindNearby = fuelScanner.Nearby;
			SyncServer syncServer = new SyncServer(store, commandService, log);
			syncServer.Register(ZRoutedRpc.instance);
			Current = new InfernoRuntime(log, store, commandService, fuelScanner, ownership, syncServer, result);
			GeneralSettings general = store.General;
			log.LogInfo((object)$"Running on server. AdminOnly={general.AdminOnly}, HideCommands={general.HideCommands}, IgnoreRain={general.IgnoreRain}, ServerOwnership={general.ServerOwnership}.");
		}

		public static void Stop()
		{
			if (Current == null)
			{
				return;
			}
			try
			{
				Current.Scanner.ReleaseOwnership();
			}
			finally
			{
				Current.Dispose();
				Current = null;
			}
		}

		public void Tick(float now)
		{
			if (Store.ReloadIfChanged())
			{
				Commands.OnConfigReloaded();
			}
			Scanner.Tick(now);
			_sync.Tick(now);
		}

		public IReadOnlyList<string> StatusLines()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			GeneralSettings general = Store.General;
			int num = 0;
			foreach (CatalogItem item in _discovery.Catalog.Items)
			{
				if ((int)item.Kind == 0)
				{
					num++;
				}
			}
			string text = ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString();
			return new <>z__ReadOnlyArray<string>(new string[4]
			{
				"Inferno 0.3.2 running on Valheim " + text + ".",
				$"Item types: {num} light(s), {_discovery.Catalog.Items.Count - num} station(s). {Scanner.StatusText}",
				"In-game time " + Scanner.CurrentTimeText + ". AdminOnly " + OnOff(general.AdminOnly) + ", ServerOwnership " + OnOff(general.ServerOwnership) + ".",
				(Diagnostics.ErrorCount == 0) ? "Errors since start: 0." : $"Errors since start: {Diagnostics.ErrorCount} (see the server log)."
			});
		}

		private static string OnOff(bool value)
		{
			if (!value)
			{
				return "off";
			}
			return "on";
		}

		public void Dispose()
		{
			_sync.Unregister();
			Store.Dispose();
		}
	}
	internal static class ObjectSettings
	{
		private static readonly int OwnKey = StringExtensionMethods.GetStableHashCode("Inferno_Own");

		private static readonly int AlwaysOnKey = StringExtensionMethods.GetStableHashCode("Inferno_AlwaysOn");

		private static readonly int BurnRateKey = StringExtensionMethods.GetStableHashCode("Inferno_BurnRate");

		private static readonly int OnMinutesKey = StringExtensionMethods.GetStableHashCode("Inferno_OnMinutes");

		private static readonly int OffMinutesKey = StringExtensionMethods.GetStableHashCode("Inferno_OffMinutes");

		private static readonly int SmokeKey = StringExtensionMethods.GetStableHashCode("Inferno_Smoke");

		private const uint RevisionLead = 8u;

		public static string KeyOf(ZDOID id)
		{
			return ((ZDOID)(ref id)).UserID.ToString(CultureInfo.InvariantCulture) + ":" + ((ZDOID)(ref id)).ID.ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryParseKey(string key, out ZDOID id)
		{
			//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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			id = ZDOID.None;
			string[] array = key.Split(new char[1] { ':' });
			if (array.Length != 2 || !long.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !uint.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
			{
				return false;
			}
			id = new ZDOID(result, result2);
			return true;
		}

		public static ItemSettings? Read(ZDO zdo)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			if (zdo.GetInt(OwnKey, 0) != 1)
			{
				return null;
			}
			int num = zdo.GetInt(BurnRateKey, 0);
			if ((num < -10 || num > 10) ? true : false)
			{
				num = 0;
			}
			return new ItemSettings(zdo.GetInt(AlwaysOnKey, 0) == 1, num, new DailySchedule(Minutes(zdo.GetInt(OnMinutesKey, 0)), Minutes(zdo.GetInt(OffMinutesKey, 0))), zdo.GetInt(SmokeKey, 1) == 1);
		}

		public static void Write(ZDO zdo, ItemSettings? settings)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			if (settings == null)
			{
				zdo.Set(OwnKey, 0, false);
			}
			else
			{
				zdo.Set(AlwaysOnKey, settings.AlwaysOn ? 1 : 0, false);
				zdo.Set(BurnRateKey, settings.BurnRateLevel, false);
				int onMinutesKey = OnMinutesKey;
				DailySchedule schedule = settings.Schedule;
				TimeOfDay val = ((DailySchedule)(ref schedule)).OnTime;
				zdo.Set(onMinutesKey, ((TimeOfDay)(ref val)).MinutesSinceMidnight, false);
				int offMinutesKey = OffMinutesKey;
				schedule = settings.Schedule;
				val = ((DailySchedule)(ref schedule)).OffTime;
				zdo.Set(offMinutesKey, ((TimeOfDay)(ref val)).MinutesSinceMidnight, false);
				zdo.Set(SmokeKey, settings.Smoke ? 1 : 0, false);
				zdo.Set(OwnKey, 1, false);
			}
			zdo.DataRevision += 8;
		}

		private static TimeOfDay Minutes(int minutes)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (minutes < 0 || minutes >= 1440)
			{
				return default(TimeOfDay);
			}
			return TimeOfDay.FromHourMinute(minutes / 60, minutes % 60);
		}
	}
	internal static class Players
	{
		private const int MaxOnScreenLines = 6;

		public static ZNetPeer? FindPeer(long uid)
		{
			if (uid != 0L)
			{
				ZNet instance = ZNet.instance;
				if (instance == null)
				{
					return null;
				}
				return instance.GetPeer(uid);
			}
			return null;
		}

		public static ZNetPeer? FindPeer(ZRpc rpc)
		{
			ZNet instance = ZNet.instance;
			if (instance == null)
			{
				return null;
			}
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer.m_rpc == rpc)
				{
					return peer;
				}
			}
			return null;
		}

		public static CommandSender FromPeer(ZNetPeer peer)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			ISocket socket = peer.m_socket;
			string text = ((socket != null) ? socket.GetHostName() : null) ?? "unknown";
			return new CommandSender(string.IsNullOrEmpty(peer.m_playerName) ? text : peer.m_playerName, text, ZNet.instance.IsAdmin(text));
		}

		public static void ShowMessage(ZNetPeer peer, IReadOnlyList<string> lines)
		{
			if (lines.Count != 0)
			{
				string text = "[Inferno] " + string.Join("\n", ReplyLimiter.Limit(lines, 6));
				ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 1, text });
			}
		}

		public static void ConsolePrint(ZRpc rpc, IReadOnlyList<string> lines)
		{
			foreach (string line in lines)
			{
				ZNet.instance.RemotePrint(rpc, "[Inferno] " + line);
			}
		}
	}
	internal static class PrefabDiscovery
	{
		[Flags]
		public enum FireExtras
		{
			None = 0,
			MeltsSnow = 1,
			SpreadsFire = 2
		}

		public sealed class Result
		{
			public Dictionary<int, FireExtras> OwnershipCandidates { get; }

			public ItemCatalog Catalog { get; }

			public Dictionary<int, CatalogItem> ByHash { get; }

			public HashSet<int> SignHashes { get; }

			public Dictionary<int, float> WardRadii { get; }

			public Result(ItemCatalog catalog, Dictionary<int, CatalogItem> byHash, HashSet<int> signHashes, Dictionary<int, FireExtras> ownershipCandidates)
			{
				OwnershipCandidates = ownershipCandidates;
				Catalog = catalog;
				ByHash = byHash;
				SignHashes = signHashes;
				WardRadii = new Dictionary<int, float>();
				base..ctor();
			}
		}

		public static Result Run(ZNetScene scene, ManualLogSource log, bool verbose = true)
		{
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Expected O, but got Unknown
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			List<CatalogItem> list = new List<CatalogItem>();
			Dictionary<int, CatalogItem> dictionary = new Dictionary<int, CatalogItem>();
			HashSet<int> hashSet = new HashSet<int>();
			List<string> list2 = new List<string>();
			Dictionary<int, FireExtras> dictionary2 = new Dictionary<int, FireExtras>();
			List<string> list3 = new List<string>();
			List<string> list4 = new List<string>();
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				if ((Object)(object)prefab.GetComponent<Sign>() != (Object)null)
				{
					hashSet.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name));
				}
				if (IsRainSensitive(prefab))
				{
					list2.Add(((Object)prefab).name);
				}
				CatalogItem val = TryCreateItem(prefab, log);
				if (val == null || dictionary.ContainsKey(StringExtensionMethods.GetStableHashCode(((Object)prefab).name)))
				{
					continue;
				}
				list.Add(val);
				dictionary.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name), val);
				if (!val.Fuel.BurnsOverTime)
				{
					continue;
				}
				if (HasOtherOwnerParts(prefab))
				{
					list3.Add(((Object)prefab).name);
					continue;
				}
				FireExtras fireExtras = ExtrasOf(prefab.GetComponent<Fireplace>());
				dictionary2.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name), fireExtras);
				if (fireExtras != FireExtras.None)
				{
					list4.Add($"{((Object)prefab).name} ({fireExtras})");
				}
			}
			ItemCatalog val2 = new ItemCatalog((IEnumerable<CatalogItem>)list);
			Result result = new Result(val2, dictionary, hashSet, dictionary2);
			foreach (GameObject prefab2 in scene.m_prefabs)
			{
				PrivateArea val3 = (((Object)(object)prefab2 == (Object)null) ? null : prefab2.GetComponent<PrivateArea>());
				if ((Object)(object)val3 != (Object)null)
				{
					result.WardRadii[StringExtensionMethods.GetStableHashCode(((Object)prefab2).name)] = val3.m_radius;
				}
			}
			if (!verbose)
			{
				return result;
			}
			foreach (CatalogItem item in val2.Items)
			{
				log.LogInfo((object)string.Format("Found {0}: {1} ({2}), max fuel {3}", ((int)item.Kind == 0) ? "light source" : "fuel station", item.PrefabName, item.DisplayName, item.Fuel.MaxFuel));
			}
			log.LogInfo((object)$"Discovered {val2.Items.Count} fuel-burning item types and {hashSet.Count} sign types.");
			log.LogInfo((object)((list2.Count == 0) ? "No lights switch themselves off in rain." : ("Lights that switch themselves off in rain or strong wind when uncovered: " + string.Join(", ", list2))));
			log.LogInfo((object)($"Server ownership mode can manage {dictionary2.Count} fire type(s)." + ((list4.Count == 0) ? string.Empty : (" Except where their extras apply (MeltsSnow: Deep North; SpreadsFire: Ashlands or fire modifier): " + string.Join(", ", list4) + ".")) + ((list3.Count == 0) ? string.Empty : (" Never (other owner-run parts): " + string.Join(", ", list3) + "."))));
			log.LogInfo((object)$"Ward types for 'nearby': {result.WardRadii.Count}.");
			return result;
		}

		private static CatalogItem? TryCreateItem(GameObject prefab, ManualLogSource log)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			try
			{
				Fireplace component = prefab.GetComponent<Fireplace>();
				if ((Object)(object)component != (Object)null)
				{
					return (component.m_infiniteFuel || component.m_secPerFuel <= 0f || component.m_maxFuel <= 0f) ? null : Create(prefab, component.m_name, (ItemKind)0, new FuelItemInfo(component.m_maxFuel, true, component.m_secPerFuel));
				}
				Smelter component2 = prefab.GetComponent<Smelter>();
				if ((Object)(object)component2 != (Object)null)
				{
					if (component2.m_maxFuel <= 0 || (Object)(object)component2.m_fuelItem == (Object)null)
					{
						return null;
					}
					bool flag = component2.m_conversion != null && component2.m_conversion.Exists((ItemConversion c) => (Object)(object)c?.m_to != (Object)null);
					ItemKind kind = (ItemKind)((component2.m_maxOre > 0 || flag) ? 1 : 0);
					return Create(prefab, component2.m_name, kind, new FuelItemInfo((float)component2.m_maxFuel, false, 0f));
				}
				CookingStation component3 = prefab.GetComponent<CookingStation>();
				if ((Object)(object)component3 != (Object)null)
				{
					return (!component3.m_useFuel || component3.m_maxFuel <= 0) ? null : Create(prefab, component3.m_name, (ItemKind)1, new FuelItemInfo((float)component3.m_maxFuel, false, 0f));
				}
			}
			catch (Exception ex) when (((ex is ArgumentException || ex is NullReferenceException) ? 1 : 0) != 0)
			{
				log.LogWarning((object)("Skipping prefab '" + ((Object)prefab).name + "': " + ex.Message));
			}
			return null;
		}

		private static bool HasOtherOwnerParts(GameObject prefab)
		{
			if (!((Object)(object)prefab.GetComponentInChildren<Smelter>() != (Object)null) && !((Object)(object)prefab.GetComponentInChildren<CookingStation>() != (Object)null) && !((Object)(object)prefab.GetComponentInChildren<Container>() != (Object)null) && !((Object)(object)prefab.GetComponentInChildren<Fermenter>() != (Object)null) && !((Object)(object)prefab.GetComponentInChildren<Beehive>() != (Object)null))
			{
				return (Object)(object)prefab.GetComponentInChildren<Ship>() != (Object)null;
			}
			return true;
		}

		private static FireExtras ExtrasOf(Fireplace fireplace)
		{
			return (FireExtras)((((Object)(object)fireplace.m_snowMelter != (Object)null) ? 1 : 0) | ((fireplace.m_igniteInterval > 0f && fireplace.m_igniteCapsuleRadius > 0f && (Object)(object)fireplace.m_firePrefab != (Object)null) ? 2 : 0));
		}

		private static bool IsRainSensitive(GameObject prefab)
		{
			Fireplace component = prefab.GetComponent<Fireplace>();
			if ((Object)(object)component != (Object)null && component.m_canTurnOff && (Object)(object)component.m_enabledObjectLow != (Object)null)
			{
				return (Object)(object)component.m_enabledObjectHigh != (Object)null;
			}
			return false;
		}

		private static CatalogItem Create(GameObject prefab, string componentName, ItemKind kind, FuelItemInfo fuel)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			Piece component = prefab.GetComponent<Piece>();
			string token = (((Object)(object)component != (Object)null && !string.IsNullOrWhiteSpace(component.m_name)) ? component.m_name : componentName);
			return new CatalogItem(((Object)prefab).name, Localize(token, ((Object)prefab).name), kind, fuel);
		}

		private static string Localize(string token, string fallback)
		{
			if (string.IsNullOrWhiteSpace(token))
			{
				return fallback;
			}
			try
			{
				Localization instance = Localization.instance;
				string text = ((instance != null) ? instance.Localize(token) : null);
				return string.IsNullOrWhiteSpace(text) ? token : text;
			}
			catch (Exception ex) when (((ex is NullReferenceException || ex is InvalidOperationException) ? 1 : 0) != 0)
			{
				return token;
			}
		}
	}
	internal sealed class ServerOwnership(PrefabDiscovery.Result discovery, ISettingsStore store, ManualLogSource log)
	{
		private const double LoanSeconds = 30.0;

		private const double ForwardDelaySeconds = 1.0;

		private static readonly int AddFuelHash = StringExtensionMethods.GetStableHashCode("RPC_AddFuel");

		private static readonly int AddFuelAmountHash = StringExtensionMethods.GetStableHashCode("RPC_AddFuelAmount");

		private static readonly int SetFuelAmountHash = StringExtensionMethods.GetStableHashCode("RPC_SetFuelAmount");

		private static readonly int ToggleOnHash = StringExtensionMethods.GetStableHashCode("RPC_ToggleOn");

		private static readonly Action<ZRoutedRpc, RoutedRPCData> RouteRpc = AccessTools.MethodDelegate<Action<ZRoutedRpc, RoutedRPCData>>(AccessTools.Method(typeof(ZRoutedRpc), "RouteRPC", (Type[])null, (Type[])null), (object)null, true);

		private readonly LoanBook<ZDOID> _loans = new LoanBook<ZDOID>();

		private readonly Dictionary<ZDOID, Biome> _biomes = new Dictionary<ZDOID, Biome>();

		private readonly List<(double At, RoutedRPCData Data)> _forwards = new List<(double, RoutedRPCData)>();

		public static long ServerId => ZDOMan.GetSessionID();

		public bool Enabled => store.General.ServerOwnership;

		public static bool IsServerSimulated(ZDO zdo)
		{
			if (zdo.GetOwner() == ServerId)
			{
				return (Object)(object)ZNetScene.instance.FindInstance(zdo) == (Object)null;
			}
			return false;
		}

		public void Manage(ZDO zdo, double now)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Invalid comparison between Unknown and I4
			OwnershipAction val = OwnershipPolicy.Decide(Enabled, IsEligible(zdo), zdo.GetOwner() == ServerId, _loans.IsOnLoan(zdo.m_uid, now));
			if ((int)val == 1)
			{
				zdo.SetOwner(ServerId);
			}
			else if ((int)val == 2)
			{
				zdo.SetOwner(0L);
			}
		}

		public bool IsProtected(ZDO zdo)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled && zdo.GetOwner() == ServerId && IsEligible(zdo))
			{
				return !_loans.IsOnLoan(zdo.m_uid, (double)Time.realtimeSinceStartup);
			}
			return false;
		}

		public bool IsEligible(ZDO zdo)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			if (!discovery.OwnershipCandidates.TryGetValue(zdo.GetPrefab(), out var value))
			{
				return false;
			}
			if (value == PrefabDiscovery.FireExtras.None)
			{
				return true;
			}
			Biome val = BiomeOf(zdo);
			if ((value & PrefabDiscovery.FireExtras.MeltsSnow) != PrefabDiscovery.FireExtras.None && (int)val == 64)
			{
				return false;
			}
			if ((value & PrefabDiscovery.FireExtras.SpreadsFire) != PrefabDiscovery.FireExtras.None)
			{
				if ((int)val != 32)
				{
					return !ZoneSystem.instance.GetGlobalKey((GlobalKeys)18);
				}
				return false;
			}
			return true;
		}

		private Biome BiomeOf(ZDO zdo)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (!_biomes.TryGetValue(zdo.m_uid, out var value))
			{
				WorldGenerator instance = WorldGenerator.instance;
				value = (Biome)((instance != null) ? ((int)instance.GetBiome(zdo.GetPosition())) : 0);
				_biomes[zdo.m_uid] = value;
			}
			return value;
		}

		public bool TryHandleRpc(RoutedRPCData data)
		{
			//IL_0022: 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_0079: Expected O, but got Unknown
			if (((ZDOID)(ref data.m_targetZDO)).IsNone() || data.m_targetPeerID != ServerId)
			{
				return false;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(data.m_targetZDO);
			if (zDO == null || !IsServerSimulated(zDO) || !discovery.ByHash.TryGetValue(zDO.GetPrefab(), out CatalogItem value) || !value.Fuel.BurnsOverTime)
			{
				return false;
			}
			int methodHash = data.m_methodHash;
			ZPackage val = new ZPackage(data.m_parameters.GetArray());
			float num = zDO.GetFloat(ZDOVars.s_fuel, 0f);
			float maxFuel = value.Fuel.MaxFuel;
			if (methodHash == AddFuelHash)
			{
				zDO.Set(ZDOVars.s_fuel, FireplaceRpc.AddOne(num, maxFuel));
			}
			else if (methodHash == AddFuelAmountHash)
			{
				zDO.Set(ZDOVars.s_fuel, FireplaceRpc.AddAmount(num, val.ReadSingle(), maxFuel));
			}
			else if (methodHash == SetFuelAmountHash)
			{
				zDO.Set(ZDOVars.s_fuel, FireplaceRpc.SetAmount(val.ReadSingle(), maxFuel));
			}
			else
			{
				if (methodHash != ToggleOnHash)
				{
					Lend(zDO, data, value.PrefabName);
					return true;
				}
				zDO.Set(ZDOVars.s_state, FireplaceRpc.Toggle(zDO.GetInt(ZDOVars.s_state, 1)), false);
			}
			log.LogDebug((object)$"Handled {MethodName(methodHash)} on server-owned {value.PrefabName} from peer {data.m_senderPeerID}.");
			return true;
		}

		public void Tick(double now)
		{
			for (int num = _forwards.Count - 1; num >= 0; num--)
			{
				if (!(now < _forwards[num].At))
				{
					RoutedRPCData item = _forwards[num].Data;
					_forwards.RemoveAt(num);
					try
					{
						RouteRpc(ZRoutedRpc.instance, item);
					}
					catch (Exception e)
					{
						Diagnostics.Error("passing a player's action on to them", e);
					}
				}
			}
			_loans.Prune(now);
		}

		public int ReleaseAll(IEnumerable<ZDO> zdos)
		{
			int num = 0;
			foreach (ZDO zdo in zdos)
			{
				try
				{
					if (zdo != null && zdo.GetOwner() == ServerId && discovery.OwnershipCandidates.ContainsKey(zdo.GetPrefab()))
					{
						zdo.SetOwner(0L);
						num++;
					}
				}
				catch (Exception e)
				{
					Diagnostics.Error("giving a server-owned fire back", e);
				}
			}
			_loans.Clear();
			_forwards.Clear();
			return num;
		}

		private void Lend(ZDO zdo, RoutedRPCData data, string prefabName)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_0092: Expected O, but got Unknown
			long senderPeerID = data.m_senderPeerID;
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			_loans.Lend(zdo.m_uid, (double)realtimeSinceStartup, 30.0);
			if (senderPeerID == 0L || senderPeerID == ServerId)
			{
				zdo.SetOwner(0L);
				return;
			}
			zdo.SetOwner(senderPeerID);
			RoutedRPCData item = new RoutedRPCData
			{
				m_msgID = data.m_msgID,
				m_senderPeerID = senderPeerID,
				m_targetPeerID = senderPeerID,
				m_targetZDO = data.m_targetZDO,
				m_methodHash = data.m_methodHash,
				m_parameters = new ZPackage(data.m_parameters.GetArray())
			};
			_forwards.Add(((double)realtimeSinceStartup + 1.0, item));
			log.LogDebug((object)$"Lent server-owned {prefabName} to peer {senderPeerID} for an action ({data.m_methodHash}).");
		}

		private static string MethodName(int hash)
		{
			if (hash != AddFuelHash)
			{
				if (hash != AddFuelAmountHash)
				{
					if (hash != SetFuelAmountHash)
					{
						return "switch on/off";
					}
					return "set fuel";
				}
				return "add fuel amount";
			}
			return "add fuel";
		}
	}
	internal sealed class SyncServer(ConfigSettingsStore store, CommandService commands, ManualLogSource log)
	{
		public const string HelloRpc = "Inferno_Hello";

		public const string CommandRpc = "Inferno_Command";

		public const string SettingsRpc = "Inferno_Settings";

		private const float BroadcastIntervalSeconds = 0.5f;

		private const float HelloCooldownSeconds = 5f;

		private readonly HashSet<long> _subscribers = new HashSet<long>();

		private readonly Dictionary<long, float> _lastHello = new Dictionary<long, float>();

		private bool _dirty;

		private float _nextBroadcast;

		public void Register(ZRoutedRpc rpc)
		{
			rpc.Register<int>("Inferno_Hello", (Action<long, int>)OnHello);
			rpc.Register<string>("Inferno_Command", (Action<long, string>)OnCommand);
			commands.SettingsChanged += MarkDirty;
		}

		public void Unregister()
		{
			commands.SettingsChanged -= MarkDirty;
		}

		public void Tick(float now)
		{
			if (_dirty && !(now < _nextBroadcast))
			{
				_dirty = false;
				_nextBroadcast = now + 0.5f;
				Broadcast();
			}
		}

		private void MarkDirty()
		{
			_dirty = true;
		}

		private void OnHello(long sender, int protocolVersion)
		{
			try
			{
				Hello(sender, protocolVersion);
			}
			catch (Exception e)
			{
				Diagnostics.Error("answering a client mod's hello", e);
			}
		}

		private void Hello(long sender, int protocolVersion)
		{
			ZNetPeer val = Players.FindPeer(sender);
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (val != null && (!_lastHello.TryGetValue(sender, out var value) || !(realtimeSinceStartup - value < 5f)))
			{
				_lastHello[sender] = realtimeSinceStartup;
				if (protocolVersion != 1)
				{
					log.LogWarning((object)$"{Players.FromPeer(val)} has an incompatible Inferno client (protocol {protocolVersion}, server {1}); menu disabled for them.");
					Players.ShowMessage(val, new <>z__ReadOnlySingleElementList<string>("Your Inferno version doesn't match the server's; the settings menu is disabled. Please update."));
				}
				else
				{
					_subscribers.Add(sender);
					log.LogInfo((object)$"{Players.FromPeer(val)} connected with the Inferno client mod.");
					Send(val);
				}
			}
		}

		private void OnCommand(long sender, string text)
		{
			try
			{
				commands.HandleMenu(sender, text);
			}
			catch (Exception e)
			{
				Diagnostics.Error("running a settings-menu command", e);
			}
		}

		private void Broadcast()
		{
			foreach (long item in new List<long>(_subscribers))
			{
				ZNetPeer val = Players.FindPeer(item);
				if (val == null)
				{
					_subscribers.Remove(item);
					_lastHello.Remove(item);
					continue;
				}
				try
				{
					Send(val);
				}
				catch (Exception e)
				{
					Diagnostics.Error("sending settings to a client mod", e);
				}
			}
		}

		private void Send(ZNetPeer peer)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			SettingsMessage val = new SettingsMessage(PermissionPolicy.CanChangeSettings(Players.FromPeer(peer).IsAdmin, store.General.AdminOnly), store.General, store.Snapshot().Items);
			ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "Inferno_Settings", new object[1] { val.Encode() });
		}
	}
}