Decompiled source of UndisturbedBossFights v1.0.1

UndisturbedBossFights.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
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: AssemblyVersion("0.0.0.0")]
namespace UndisturbedBossFights;

[HarmonyPatch(typeof(Terminal), "InitTerminal")]
internal static class Commands
{
	private const float RaidSoonSeconds = 10f;

	private static readonly FieldInfo eventTimer = AccessTools.Field(typeof(RandEventSystem), "m_eventTimer");

	private static readonly FieldInfo eventIntervalMin = AccessTools.Field(typeof(RandEventSystem), "m_eventIntervalMin");

	private static void Postfix()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		//IL_007c: 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_006d: Expected O, but got Unknown
		new ConsoleCommand("ubf_status", "- UndisturbedBossFights: show boss fights the mod sees and when the next raid roll is due", (ConsoleEvent)delegate(ConsoleEventArgs args)
		{
			bool flag = false;
			foreach (string item in FightTracker.Describe())
			{
				if (!flag)
				{
					args.Context.AddString("Boss fights:");
				}
				flag = true;
				args.Context.AddString("  " + item);
			}
			if (!flag)
			{
				args.Context.AddString("No boss fight going on.");
			}
			RandEventSystem instance = RandEventSystem.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				args.Context.AddString("Raid timing is only known on the server.");
			}
			else if (Game.m_eventRate <= 0f)
			{
				args.Context.AddString("Raids are turned off in this world.");
			}
			else
			{
				float num = EventDue(instance) - (float)eventTimer.GetValue(instance);
				args.Context.AddString(RaidPatch.Holding ? "A raid roll is due and waiting for the boss fight to end." : ("Next raid roll in " + (num / 60f).ToString("0.0", CultureInfo.InvariantCulture) + " min."));
			}
		}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		new ConsoleCommand("ubf_raidsoon", "- UndisturbedBossFights: make the next raid roll due in " + 10f + " seconds (for testing)", (ConsoleEvent)delegate(ConsoleEventArgs args)
		{
			RandEventSystem instance = RandEventSystem.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				args.Context.AddString("Only works on the server (single player or the host).");
			}
			else
			{
				eventTimer.SetValue(instance, EventDue(instance) - 10f);
				args.Context.AddString("The next raid roll is due in " + 10f + " seconds. Its chance to start a raid is still the usual one.");
			}
		}, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}

	private static float EventDue(RandEventSystem events)
	{
		return (float)eventIntervalMin.GetValue(events) * 60f * Game.m_eventRate;
	}
}
internal interface ISynced
{
	string Key { get; }

	string Serialize();

	void ApplyServer(string text);

	void ClearServer();
}
public class Synced<T> : ISynced
{
	public readonly ConfigEntry<T> Entry;

	private T serverValue;

	private bool hasServerValue;

	public T Value
	{
		get
		{
			if (!hasServerValue)
			{
				return Entry.Value;
			}
			return serverValue;
		}
	}

	public string Key => ((ConfigEntryBase)Entry).Definition.Section + "/" + ((ConfigEntryBase)Entry).Definition.Key;

	public event Action Changed;

	public Synced(ConfigEntry<T> entry)
	{
		Entry = entry;
		EventHandler eventHandler = delegate
		{
			RaiseChanged();
			ConfigSync.OnLocalChanged();
		};
		entry.SettingChanged += eventHandler;
		ConfigSync.Register(this);
	}

	public string Serialize()
	{
		return TomlTypeConverter.ConvertToString((object)Entry.Value, typeof(T));
	}

	public void ApplyServer(string text)
	{
		serverValue = (T)TomlTypeConverter.ConvertToValue(text, typeof(T));
		hasServerValue = true;
		RaiseChanged();
	}

	public void ClearServer()
	{
		if (hasServerValue)
		{
			hasServerValue = false;
			RaiseChanged();
		}
	}

	private void RaiseChanged()
	{
		if (this.Changed != null)
		{
			this.Changed();
		}
	}
}
internal static class ConfigSync
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class NewConnectionPatch
	{
		private static void Postfix(ZNet __instance, ZNetPeer __0)
		{
			if (__instance.IsServer())
			{
				__0.m_rpc.Register<string>("UndisturbedBossFights_Hello", (Action<ZRpc, string>)OnHello);
			}
			else
			{
				__0.m_rpc.Register<ZPackage>("UndisturbedBossFights_Config", (Action<ZRpc, ZPackage>)OnConfig);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private static class PeerInfoPatch
	{
		private static void Postfix(ZNet __instance, ZRpc __0)
		{
			if (__instance.IsServer())
			{
				ZNetPeer val = FindPeer(__0);
				if (val != null)
				{
					Send(val);
					((MonoBehaviour)Plugin.Instance).StartCoroutine(CheckHello(val));
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	private static class LeavePatch
	{
		private static void Postfix()
		{
			foreach (ISynced setting in settings)
			{
				setting.ClearServer();
			}
			peersWithMod.Clear();
		}
	}

	private const string ConfigRpc = "UndisturbedBossFights_Config";

	private const string HelloRpc = "UndisturbedBossFights_Hello";

	private const float HelloTimeout = 20f;

	private static readonly List<ISynced> settings = new List<ISynced>();

	private static readonly HashSet<long> peersWithMod = new HashSet<long>();

	private static bool IsServer
	{
		get
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}
	}

	internal static void Register(ISynced setting)
	{
		settings.Add(setting);
	}

	internal static void OnLocalChanged()
	{
		if (!IsServer)
		{
			return;
		}
		foreach (ZNetPeer peer in ZNet.instance.GetPeers())
		{
			if (peer.m_rpc != null)
			{
				Send(peer);
			}
		}
	}

	private static void Send(ZNetPeer peer)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write("1.0.1");
		val.Write(settings.Count);
		foreach (ISynced setting in settings)
		{
			val.Write(setting.Key);
			val.Write(setting.Serialize());
		}
		peer.m_rpc.Invoke("UndisturbedBossFights_Config", new object[1] { val });
	}

	private static void OnConfig(ZRpc rpc, ZPackage pkg)
	{
		string text = pkg.ReadString();
		int num = pkg.ReadInt();
		Dictionary<string, string> dictionary = new Dictionary<string, string>();
		for (int i = 0; i < num; i++)
		{
			string key = pkg.ReadString();
			dictionary[key] = pkg.ReadString();
		}
		foreach (ISynced setting in settings)
		{
			if (dictionary.TryGetValue(setting.Key, out var value))
			{
				try
				{
					setting.ApplyServer(value);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Could not apply server value for " + setting.Key + ": " + ex.Message));
				}
			}
		}
		Plugin.Log.LogInfo((object)"Using the server's UndisturbedBossFights settings.");
		if (text != "1.0.1")
		{
			Warn("UndisturbedBossFights version mismatch: server has " + text + ", you have 1.0.1.");
		}
		rpc.Invoke("UndisturbedBossFights_Hello", new object[1] { "1.0.1" });
	}

	private static void OnHello(ZRpc rpc, string clientVersion)
	{
		ZNetPeer val = FindPeer(rpc);
		if (val != null)
		{
			peersWithMod.Add(val.m_uid);
			if (clientVersion != "1.0.1")
			{
				Warn(val.m_playerName + " has UndisturbedBossFights " + clientVersion + ", the server has 1.0.1.");
			}
		}
	}

	private static IEnumerator CheckHello(ZNetPeer peer)
	{
		yield return (object)new WaitForSeconds(20f);
		if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.GetPeers().Contains(peer) && !peersWithMod.Contains(peer.m_uid))
		{
			Warn(peer.m_playerName + " doesn't have UndisturbedBossFights installed. Raids and spawns near them may still interrupt boss fights.");
		}
	}

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

	private static void Warn(string message)
	{
		Plugin.Log.LogWarning((object)message);
		if ((Object)(object)MessageHud.instance != (Object)null)
		{
			MessageHud.instance.ShowMessage((MessageType)1, message, 0, (Sprite)null, false, true);
		}
	}
}
internal static class FightTracker
{
	private class Fight
	{
		public string BossName;

		public Vector3 BossPos;

		public readonly List<Vector3> Players = new List<Vector3>();

		public float LastSeen;
	}

	private class RemoteFight
	{
		public string PlayerName;

		public string BossNames;

		public float Until;
	}

	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class NewConnectionPatch
	{
		private static void Postfix(ZNet __instance, ZNetPeer __0)
		{
			if (__instance.IsServer())
			{
				__0.m_rpc.Register<string>("UndisturbedBossFights_Fight", (Action<ZRpc, string>)OnReport);
			}
		}
	}

	private const float ScanInterval = 1f;

	private const float EndDelay = 20f;

	private const float ReportInterval = 2f;

	private const float ReportTimeout = 6f;

	private const string ReportRpc = "UndisturbedBossFights_Fight";

	private static readonly Dictionary<Character, Fight> fights = new Dictionary<Character, Fight>();

	private static readonly Dictionary<long, RemoteFight> remoteFights = new Dictionary<long, RemoteFight>();

	private static readonly List<Character> toRemove = new List<Character>();

	private static readonly List<long> remoteToRemove = new List<long>();

	private static float scanTimer;

	private static float reportTimer;

	private static bool wasActive;

	internal static bool AnyFightActive
	{
		get
		{
			if (fights.Count <= 0)
			{
				return remoteFights.Count > 0;
			}
			return true;
		}
	}

	internal static void Update()
	{
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			fights.Clear();
			remoteFights.Clear();
			wasActive = false;
			return;
		}
		scanTimer += Time.deltaTime;
		if (scanTimer >= 1f)
		{
			scanTimer = 0f;
			Scan();
			ExpireRemote();
		}
		if (!ZNet.instance.IsServer() && fights.Count > 0)
		{
			reportTimer += Time.deltaTime;
			if (reportTimer >= 2f)
			{
				reportTimer = 0f;
				Report();
			}
		}
		else
		{
			reportTimer = 2f;
		}
		bool anyFightActive = AnyFightActive;
		if (anyFightActive != wasActive)
		{
			wasActive = anyFightActive;
			Plugin.Debug(anyFightActive ? "A boss fight started: raids and nearby spawns are held back." : "No boss fight anymore: raids and spawns are back to normal.");
		}
	}

	private static void Scan()
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Unknown result type (might be due to invalid IL or missing references)
		float time = Time.time;
		float value = Plugin.FightDistance.Value;
		foreach (Character allCharacter in Character.GetAllCharacters())
		{
			if ((Object)(object)allCharacter == (Object)null || !allCharacter.IsBoss() || allCharacter.IsDead())
			{
				continue;
			}
			BaseAI baseAI = allCharacter.GetBaseAI();
			if ((Object)(object)baseAI == (Object)null || (!baseAI.IsAlerted() && !baseAI.HaveTarget()))
			{
				continue;
			}
			Vector3 position = ((Component)allCharacter).transform.position;
			Fight value2 = null;
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer == (Object)null || ((Character)allPlayer).IsDead())
				{
					continue;
				}
				Vector3 position2 = ((Component)allPlayer).transform.position;
				if (Vector3.Distance(position2, position) > value)
				{
					continue;
				}
				if (value2 == null)
				{
					if (!fights.TryGetValue(allCharacter, out value2))
					{
						value2 = new Fight();
						value2.BossName = allCharacter.GetHoverName();
						fights[allCharacter] = value2;
						Plugin.Debug("Boss fight with " + value2.BossName + " at " + ((Vector3)(ref position)).ToString("0") + ".");
					}
					value2.Players.Clear();
					value2.BossPos = position;
					value2.LastSeen = time;
				}
				value2.Players.Add(position2);
			}
		}
		toRemove.Clear();
		foreach (KeyValuePair<Character, Fight> fight in fights)
		{
			if (time - fight.Value.LastSeen > 20f)
			{
				toRemove.Add(fight.Key);
			}
		}
		foreach (Character item in toRemove)
		{
			Plugin.Debug("Boss fight with " + fights[item].BossName + " is over.");
			fights.Remove(item);
		}
	}

	internal static bool IsNearFight(Vector3 pos, out string bossName)
	{
		//IL_0033: 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_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_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_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: 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)
		float value = Plugin.SpawnBlockRadius.Value;
		float num = value * value;
		foreach (Fight value2 in fights.Values)
		{
			bossName = value2.BossName;
			Vector3 val = value2.BossPos - pos;
			if (((Vector3)(ref val)).sqrMagnitude <= num)
			{
				return true;
			}
			foreach (Vector3 player in value2.Players)
			{
				Vector3 val2 = player - pos;
				if (((Vector3)(ref val2)).sqrMagnitude <= num)
				{
					return true;
				}
			}
		}
		bossName = null;
		return false;
	}

	private static string LocalBossNames()
	{
		List<string> list = new List<string>();
		foreach (Fight value in fights.Values)
		{
			list.Add(value.BossName);
		}
		return string.Join(", ", list.ToArray());
	}

	private static void Report()
	{
		ZRpc serverRPC = ZNet.instance.GetServerRPC();
		if (serverRPC != null)
		{
			serverRPC.Invoke("UndisturbedBossFights_Fight", new object[1] { LocalBossNames() });
		}
	}

	private static void OnReport(ZRpc rpc, string bossNames)
	{
		ZNetPeer val = null;
		foreach (ZNetPeer peer in ZNet.instance.GetPeers())
		{
			if (peer.m_rpc == rpc)
			{
				val = peer;
			}
		}
		if (val != null)
		{
			if (!remoteFights.TryGetValue(val.m_uid, out var value))
			{
				value = new RemoteFight();
				remoteFights[val.m_uid] = value;
				Plugin.Debug(val.m_playerName + " is in a boss fight with " + bossNames + ".");
			}
			value.PlayerName = val.m_playerName;
			value.BossNames = bossNames;
			value.Until = Time.time + 6f;
		}
	}

	private static void ExpireRemote()
	{
		remoteToRemove.Clear();
		foreach (KeyValuePair<long, RemoteFight> remoteFight in remoteFights)
		{
			if (Time.time > remoteFight.Value.Until)
			{
				remoteToRemove.Add(remoteFight.Key);
			}
		}
		foreach (long item in remoteToRemove)
		{
			Plugin.Debug(remoteFights[item].PlayerName + "'s boss fight is over.");
			remoteFights.Remove(item);
		}
	}

	internal static IEnumerable<string> Describe()
	{
		foreach (Fight fight in fights.Values)
		{
			yield return fight.BossName + " at " + ((Vector3)(ref fight.BossPos)).ToString("0") + ", " + fight.Players.Count + " player(s) near, last seen fighting " + (Time.time - fight.LastSeen).ToString("0") + " s ago";
		}
		foreach (RemoteFight fight2 in remoteFights.Values)
		{
			yield return fight2.PlayerName + " is fighting " + fight2.BossNames + " (reported by their game)";
		}
	}
}
[HarmonyPatch(typeof(RandEventSystem), "UpdateRandomEvent")]
internal static class RaidPatch
{
	private const float Margin = 0.01f;

	private static bool holding;

	internal static bool Holding => holding;

	private static void Prefix(float __0, ref float ___m_eventTimer, float ___m_eventIntervalMin, List<RandomEvent> ___m_events, RandomEvent ___m_activeEvent)
	{
		if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			return;
		}
		if (!Plugin.DelayRaids.Value || !FightTracker.AnyFightActive || Game.m_eventRate <= 0f)
		{
			if (holding)
			{
				holding = false;
				Plugin.Debug("Boss fight over: the held raid roll happens now.");
			}
			return;
		}
		bool flag = false;
		float num = ___m_eventIntervalMin * 60f * Game.m_eventRate;
		if (___m_eventTimer + __0 > num)
		{
			___m_eventTimer = num - __0 - 0.01f;
			flag = true;
		}
		foreach (RandomEvent ___m_event in ___m_events)
		{
			if (___m_event.m_enabled && !(___m_event.m_standaloneInterval <= 0f) && ___m_event != ___m_activeEvent)
			{
				float num2 = ___m_event.m_standaloneInterval * Game.m_eventRate;
				if (___m_event.m_time + __0 > num2)
				{
					___m_event.m_time = num2 - __0 - 0.01f;
					flag = true;
				}
			}
		}
		if (flag && !holding)
		{
			holding = true;
			Plugin.Debug("A raid roll is due, but a boss fight is going on. Holding it until the fight is over.");
		}
	}
}
[HarmonyPatch(typeof(SpawnSystem), "Spawn")]
internal static class SpawnPatch
{
	private static bool Prefix(SpawnData __0, Vector3 __1, bool __2)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		if (__2 || !Plugin.BlockSpawns.Value)
		{
			return true;
		}
		if (!FightTracker.IsNearFight(__1, out var bossName))
		{
			return true;
		}
		Plugin.Debug("Blocked a " + ((Object)__0.m_prefab).name + " from spawning at " + ((Vector3)(ref __1)).ToString("0") + " during the fight with " + bossName + ".");
		return false;
	}
}
[BepInPlugin("UndisturbedBossFights", "UndisturbedBossFights", "1.0.1")]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "UndisturbedBossFights";

	public const string ModName = "UndisturbedBossFights";

	public const string Version = "1.0.1";

	internal static Synced<bool> DelayRaids;

	internal static Synced<bool> BlockSpawns;

	internal static Synced<float> SpawnBlockRadius;

	internal static Synced<float> FightDistance;

	internal static ConfigEntry<bool> DebugLog;

	internal static Plugin Instance;

	internal static ManualLogSource Log;

	private Synced<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Expected O, but got Unknown
		return new Synced<T>(((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, new ConfigDescription(description.Description + " In multiplayer, the server's value is used.", description.AcceptableValues, new object[0])));
	}

	private Synced<T> Bind<T>(string section, string key, T defaultValue, string description)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		return Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, new object[0]));
	}

	private void Awake()
	{
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Expected O, but got Unknown
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Expected O, but got Unknown
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		DelayRaids = Bind("1 - General", "DelayRaids", defaultValue: true, "While a boss fight is going on, no raid (random event) can start. A raid that was due waits until the fight is over, then the game rolls for it as usual. Raids started from the console are not held back.");
		BlockSpawns = Bind("1 - General", "BlockSpawnsNearFight", defaultValue: true, "While a boss fight is going on, no new creatures spawn naturally near the boss or the players fighting it. Creatures already there, the boss's own summons and creatures of a raid that was already running are not affected.");
		SpawnBlockRadius = Bind("1 - General", "SpawnBlockRadius", 80f, new ConfigDescription("How close (in meters) to the boss or a player fighting it a new creature may not spawn. The game spawns creatures 40-80 m from a player, so 80 blocks almost all of them.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 200f), new object[0]));
		FightDistance = Bind("1 - General", "FightDistance", 60f, new ConfigDescription("A boss counts as being fought when it is alerted or has a target and a player is within this many meters of it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 150f), new object[0]));
		DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "DebugLog", false, "Writes boss fights, held-back raids and blocked spawns to the BepInEx log. Useful when reporting a problem. Not synced in multiplayer.");
		new Harmony("UndisturbedBossFights").PatchAll(typeof(Plugin).Assembly);
	}

	private void Update()
	{
		FightTracker.Update();
	}

	internal static void Debug(string message)
	{
		if (DebugLog.Value)
		{
			Log.LogInfo((object)message);
		}
	}
}