Decompiled source of Ashlands Fire Particle Fix v1.1.1

BepInEx/plugins/AshlandsFireParticleFix/AshlandsFireGuard.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: InternalsVisibleTo("NativeGuardProbe")]
[assembly: AssemblyVersion("1.1.1.0")]
[BepInPlugin("local.sadge.ashlandsfireguard", "Ashlands Fire & Particle Fix", "1.1.1")]
public sealed class AshlandsFireGuard : BaseUnityPlugin
{
	private sealed class Entry
	{
		public ZDO Record;

		public ZDOID Id;

		public float Created;

		public long Sequence;

		public bool PendingDelete;
	}

	[HarmonyPatch(typeof(ZDO), "SetPrefab")]
	private static class PrefabPatch
	{
		private static void Postfix(ZDO __instance)
		{
			Track(__instance);
		}
	}

	[HarmonyPatch(typeof(ZDO), "Deserialize")]
	private static class ReceivePatch
	{
		private static void Postfix(ZDO __instance)
		{
			Track(__instance);
		}
	}

	[HarmonyPatch(typeof(ZDOMan), "LoadChunks")]
	private static class LoadPatch
	{
		private static void Postfix(ZDOMan __instance, Dictionary<ZDOID, ZDO> ___m_objectsByID)
		{
			tracked.Clear();
			SweepCount = 0;
			sequence = 0L;
			if (!ScopeAllowsServer())
			{
				return;
			}
			foreach (ZDO value in ___m_objectsByID.Values)
			{
				if (value.GetPrefab() == 1730843282 || value.GetPrefab() == -837447586)
				{
					Track(value);
				}
			}
			log.LogInfo((object)("Saved Ashlands wildfires tracked: " + tracked.Count));
			if (current.startupCleanup.Value)
			{
				Check(__instance);
			}
			current.Schedule();
		}
	}

	public const string PluginGuid = "local.sadge.ashlandsfireguard";

	internal const int HouseFireHash = 1730843282;

	internal const int FireHash = -837447586;

	internal static int SweepCount;

	private static AshlandsFireGuard current;

	private static ManualLogSource log;

	private Harmony patches;

	private ConfigEntry<bool> guardEnabled;

	private ConfigEntry<int> interval;

	private ConfigEntry<int> perSector;

	private ConfigEntry<int> total;

	private ConfigEntry<int> lifetime;

	private ConfigEntry<bool> deduplicate;

	private ConfigEntry<int> cellSize;

	private ConfigEntry<bool> startupCleanup;

	private ConfigEntry<bool> logSummary;

	private HashSet<string> allowedWorlds;

	private static long sequence;

	private static readonly Dictionary<ZDOID, Entry> tracked = new Dictionary<ZDOID, Entry>();

	private void Awake()
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Expected O, but got Unknown
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Expected O, but got Unknown
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Expected O, but got Unknown
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Expected O, but got Unknown
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Expected O, but got Unknown
		//IL_022b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0235: Expected O, but got Unknown
		current = this;
		log = ((BaseUnityPlugin)this).Logger;
		guardEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the server-side wildfire guard. Restart the host after changing settings.");
		interval = ((BaseUnityPlugin)this).Config.Bind<int>("Cleanup", "CheckIntervalSeconds", 60, new ConfigDescription("Seconds between cleanup passes. Counts can exceed limits between passes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 3600), new object[0]));
		perSector = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "MaxFiresPerSector", 8, new ConfigDescription("Maximum retained Fire/HouseFire objects per 64x64m sector at each pass.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000), new object[0]));
		total = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "MaxFiresInAshlands", 128, new ConfigDescription("Maximum retained Fire/HouseFire objects across the entire Ashlands biome at each pass.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100000), new object[0]));
		lifetime = ((BaseUnityPlugin)this).Config.Bind<int>("Cleanup", "MaxLifetimeSeconds", 60, new ConfigDescription("Remove tracked fires at or beyond this age. Zero disables age-based removal. Actual removal may wait one cleanup interval; age resets on world load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 86400), new object[0]));
		deduplicate = ((BaseUnityPlugin)this).Config.Bind<bool>("Limits", "SuppressOverlappingFires", true, "Retain at most one wildfire in each spatial cell.");
		cellSize = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "CellSizeMeters", 2, new ConfigDescription("Width, height and depth of the deduplication cells.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), new object[0]));
		startupCleanup = ((BaseUnityPlugin)this).Config.Bind<bool>("Cleanup", "CleanupOnWorldLoad", true, "Run a quota/overlap pass once after loading the saved world. Saved fires start with age zero; the lifetime rule still waits for the configured age.");
		logSummary = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LogCleanupSummary", true, "Log one aggregate line when a pass removes fires.");
		string value = ((BaseUnityPlugin)this).Config.Bind<string>("General", "AllowedWorldNames", "", "Optional comma-separated world names, case-sensitive. Empty means all worlds. Restart the host after changing this setting.").Value;
		allowedWorlds = new HashSet<string>(StringComparer.Ordinal);
		string[] array = value.Split(new char[1] { ',' });
		foreach (string text in array)
		{
			if (!string.IsNullOrWhiteSpace(text))
			{
				allowedWorlds.Add(text.Trim());
			}
		}
		patches = new Harmony("local.sadge.ashlandsfireguard");
		patches.PatchAll(typeof(AshlandsFireGuard).Assembly);
		Schedule();
		((BaseUnityPlugin)this).Logger.LogInfo((object)(guardEnabled.Value ? ("Wildfire guard ready: Ashlands only; cleanup interval " + interval.Value + "s; limits " + perSector.Value + "/sector and " + total.Value + "/biome; age threshold " + lifetime.Value + "s.") : "Wildfire guard disabled by configuration."));
	}

	private void Schedule()
	{
		((MonoBehaviour)this).CancelInvoke("PeriodicCheck");
		if (guardEnabled.Value)
		{
			((MonoBehaviour)this).InvokeRepeating("PeriodicCheck", (float)interval.Value, (float)interval.Value);
		}
	}

	private void OnDestroy()
	{
		((MonoBehaviour)this).CancelInvoke("PeriodicCheck");
		if (patches != null)
		{
			patches.UnpatchSelf();
		}
		tracked.Clear();
		if (object.ReferenceEquals(current, this))
		{
			current = null;
		}
	}

	private static bool ScopeAllowsServer()
	{
		if ((Object)(object)current == (Object)null || !current.guardEnabled.Value)
		{
			return false;
		}
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null || !instance.IsServer())
		{
			return false;
		}
		if (current.allowedWorlds.Count != 0)
		{
			if (instance.GetWorld() != null)
			{
				return current.allowedWorlds.Contains(instance.GetWorldName());
			}
			return false;
		}
		return true;
	}

	internal static bool IsTarget(int prefab, Vector3 position)
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Invalid comparison between Unknown and I4
		if (prefab != 1730843282 && prefab != -837447586)
		{
			return false;
		}
		if (float.IsNaN(position.x) || float.IsNaN(position.y) || float.IsNaN(position.z) || float.IsInfinity(position.x) || float.IsInfinity(position.y) || float.IsInfinity(position.z))
		{
			return false;
		}
		if (WorldGenerator.instance != null)
		{
			return (int)WorldGenerator.instance.GetBiome(position) == 32;
		}
		return false;
	}

	internal static void Track(ZDO record)
	{
		//IL_0025: 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_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		if (record != null)
		{
			int prefab = record.GetPrefab();
			if ((prefab == 1730843282 || prefab == -837447586) && ScopeAllowsServer() && IsTarget(prefab, record.GetPosition()) && (!tracked.TryGetValue(record.m_uid, out var value) || !object.ReferenceEquals(value.Record, record)))
			{
				tracked[record.m_uid] = new Entry
				{
					Record = record,
					Id = record.m_uid,
					Created = Time.realtimeSinceStartup,
					Sequence = ++sequence
				};
			}
		}
	}

	internal static void Check(ZDOMan manager)
	{
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: 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_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		if (tracked.Count == 0 || !ScopeAllowsServer())
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		SweepCount++;
		List<Entry> list = new List<Entry>();
		List<ZDOID> list2 = new List<ZDOID>();
		foreach (KeyValuePair<ZDOID, Entry> item2 in tracked)
		{
			Entry value = item2.Value;
			if (!object.ReferenceEquals(manager.GetZDO(value.Id), value.Record) || !IsTarget(value.Record.GetPrefab(), value.Record.GetPosition()))
			{
				list2.Add(item2.Key);
			}
			else if (!value.PendingDelete)
			{
				list.Add(value);
			}
		}
		foreach (ZDOID item3 in list2)
		{
			tracked.Remove(item3);
		}
		list.Sort((Entry a, Entry b) => b.Sequence.CompareTo(a.Sequence));
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		HashSet<string> hashSet = new HashSet<string>();
		int num = 0;
		int num2 = 0;
		foreach (Entry item4 in list)
		{
			Vector3 position = item4.Record.GetPosition();
			string key2 = Math.Floor((position.x + 32f) / 64f) + ":" + Math.Floor((position.z + 32f) / 64f);
			string item = Math.Floor(position.x / (float)current.cellSize.Value) + ":" + Math.Floor(position.y / (float)current.cellSize.Value) + ":" + Math.Floor(position.z / (float)current.cellSize.Value);
			dictionary.TryGetValue(key2, out var value2);
			bool flag = current.lifetime.Value > 0 && realtimeSinceStartup - item4.Created >= (float)current.lifetime.Value;
			bool flag2 = current.deduplicate.Value && hashSet.Contains(item);
			if (flag || num >= current.total.Value || value2 >= current.perSector.Value || flag2)
			{
				item4.Record.SetOwner(ZDOMan.GetSessionID());
				manager.DestroyZDO(item4.Record);
				item4.PendingDelete = true;
				num2++;
			}
			else
			{
				num++;
				dictionary[key2] = value2 + 1;
				hashSet.Add(item);
			}
		}
		if (num2 > 0 && current.logSummary.Value)
		{
			log.LogInfo((object)("Ashlands wildfire cleanup: removed " + num2 + "; retained " + num + "."));
		}
	}

	private void PeriodicCheck()
	{
		if (tracked.Count != 0)
		{
			ZDOMan instance = ZDOMan.instance;
			if (instance != null)
			{
				Check(instance);
			}
		}
	}
}