Decompiled source of AggroKit v0.1.0

AggroKit/AggroKit.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ForgeKit;
using HarmonyLib;
using Photon;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("AggroKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+45183af4120fbaf88628c4bbbd3436ac738782fc")]
[assembly: AssemblyProduct("AggroKit")]
[assembly: AssemblyTitle("AggroKit")]
[assembly: AssemblyMetadata("BuildStamp", "45183af 2026-07-29")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
namespace AggroKit;

public static class AggroEvents
{
	private sealed class Scope : IDisposable
	{
		public void Dispose()
		{
			PopCause();
		}
	}

	[HarmonyPatch(typeof(TargetingSystem), "SetLockingPoint")]
	internal static class P_SetLockingPoint
	{
		private static bool Prepare()
		{
			return Plugin.EnableObservation.Value;
		}

		private static void Prefix(TargetingSystem __instance, out Character __state)
		{
			__state = __instance.LockedCharacter;
		}

		private static void Postfix(TargetingSystem __instance, Character __state)
		{
			try
			{
				Character now = __instance.LockedCharacter;
				if ((Object)(object)now == (Object)(object)__state)
				{
					return;
				}
				Character owner = s_tsOwner.Invoke(__instance);
				if (AggroLog.Enabled)
				{
					AggroLog.Record("TARGET", Name(owner) + ": " + Name(__state) + " -> " + Name(now) + " cause=" + Cause);
				}
				if (AggroEvents.TargetChanged != null)
				{
					Fire(delegate
					{
						AggroEvents.TargetChanged(owner, __state, now);
					});
				}
			}
			catch
			{
			}
		}
	}

	[HarmonyPatch(typeof(CharacterAI), "SwitchAiState")]
	internal static class P_SwitchAiState
	{
		private static bool Prepare()
		{
			return Plugin.EnableObservation.Value;
		}

		private static void Prefix(CharacterAI __instance, out AIState __state)
		{
			__state = SafeCurrent(__instance);
		}

		private static void Postfix(CharacterAI __instance, AIState __state)
		{
			try
			{
				AIState now = SafeCurrent(__instance);
				if ((Object)(object)now == (Object)(object)__state)
				{
					return;
				}
				if (AggroLog.Enabled)
				{
					AggroLog.Record("STATE", Name(((CharacterControl)__instance).Character) + ": " + (((object)__state)?.GetType().Name ?? "?") + " -> " + (((object)now)?.GetType().Name ?? "?") + " cause=" + Cause);
				}
				if (AggroEvents.StateChanged != null)
				{
					Fire(delegate
					{
						AggroEvents.StateChanged(__instance, __state, now);
					});
				}
			}
			catch
			{
			}
		}

		private static AIState SafeCurrent(CharacterAI ai)
		{
			try
			{
				return ai.CurrentAiState;
			}
			catch
			{
				return null;
			}
		}
	}

	[HarmonyPatch(typeof(AICEnemyDetection), "Detected")]
	internal static class P_Detected
	{
		private static bool Prefix(AICEnemyDetection __instance, ref LockingPoint _point, out bool __state)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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)
			__state = Plugin.EnableObservation.Value;
			if (__state)
			{
				PushCause("detected");
			}
			try
			{
				CharacterAI ai = s_condAi.Invoke((AICondition)(object)__instance);
				Character val = (Object.op_Implicit((Object)(object)_point) ? _point.OwnerChar : null);
				if ((Object)(object)val != (Object)null)
				{
					if (s_detectVeto.Contains(UID.op_Implicit(val.UID)))
					{
						if (AggroLog.Enabled)
						{
							AggroLog.Record("DETECT", Name(Object.op_Implicit((Object)(object)ai) ? ((CharacterControl)ai).Character : null) + " -> " + Name(val) + " VETOED");
						}
						return false;
					}
					if (s_detectRedirect.TryGetValue(UID.op_Implicit(val.UID), out var value))
					{
						if (Object.op_Implicit((Object)(object)value) && value.Alive && Object.op_Implicit((Object)(object)value.LockingPoint))
						{
							if (AggroLog.Enabled)
							{
								AggroLog.Record("DETECT", Name(Object.op_Implicit((Object)(object)ai) ? ((CharacterControl)ai).Character : null) + " -> " + Name(val) + " REDIRECTED -> " + Name(value));
							}
							_point = value.LockingPoint;
							val = value;
						}
						else if (!Object.op_Implicit((Object)(object)value) || !value.Alive)
						{
							PruneRedirect(UID.op_Implicit(val.UID));
							if (AggroLog.Enabled)
							{
								AggroLog.Record("DETECT", Name(Object.op_Implicit((Object)(object)ai) ? ((CharacterControl)ai).Character : null) + " -> " + Name(val) + " (stale redirect -> " + Name(value) + " pruned)");
							}
						}
						else if (AggroLog.Enabled)
						{
							AggroLog.Record("DETECT", Name(Object.op_Implicit((Object)(object)ai) ? ((CharacterControl)ai).Character : null) + " -> " + Name(val));
						}
					}
					else if (AggroLog.Enabled)
					{
						AggroLog.Record("DETECT", Name(Object.op_Implicit((Object)(object)ai) ? ((CharacterControl)ai).Character : null) + " -> " + Name(val));
					}
				}
				if (AggroEvents.Detected != null)
				{
					Character t = val;
					Fire(delegate
					{
						AggroEvents.Detected.Invoke(ai, t);
					});
				}
			}
			catch
			{
			}
			return true;
		}

		private static void Finalizer(bool __state)
		{
			if (__state)
			{
				PopCause();
			}
		}
	}

	[HarmonyPatch(typeof(AICEnemyDetection), "CharHurt")]
	internal static class P_CharHurt
	{
		private static bool Prefix(AICEnemyDetection __instance, Character _dealerChar, out bool __state)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			__state = Plugin.EnableObservation.Value;
			if (__state)
			{
				PushCause("charhurt");
			}
			try
			{
				CharacterAI ai = s_condAi.Invoke((AICondition)(object)__instance);
				bool flag = Object.op_Implicit((Object)(object)_dealerChar) && s_noAggroDealers.Contains(UID.op_Implicit(_dealerChar.UID));
				Character c = ((Object.op_Implicit((Object)(object)ai) && Object.op_Implicit((Object)(object)ai.TargetingSystem)) ? ai.TargetingSystem.LockedCharacter : null);
				if (AggroLog.Enabled)
				{
					AggroLog.Record("HURT", Name(Object.op_Implicit((Object)(object)ai) ? ((CharacterControl)ai).Character : null) + " dealer=" + Name(_dealerChar) + " cur=" + Name(c) + string.Format(" switchChance={0:F0}{1}", __instance.ChanceToSwitchTargetOnHurt, flag ? " VETOED" : ""));
				}
				if (AggroEvents.Hurt != null)
				{
					Fire(delegate
					{
						AggroEvents.Hurt.Invoke(ai, _dealerChar);
					});
				}
				if (flag)
				{
					return false;
				}
			}
			catch
			{
			}
			return true;
		}

		private static void Finalizer(bool __state)
		{
			if (__state)
			{
				PopCause();
			}
		}
	}

	[HarmonyPatch(typeof(AISCombat), "SetPreferredTarget")]
	internal static class P_SetPreferredTarget
	{
		private static bool Prefix(AISCombat __instance, ref LockingPoint _lockingPoint, int _flankerID, out bool __state)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			__state = Plugin.EnableObservation.Value;
			if (__state)
			{
				PushCause("squad-pref");
			}
			try
			{
				CharacterAI val = s_stateAi.Invoke((AIState)(object)__instance);
				Character val2 = (Object.op_Implicit((Object)(object)val) ? ((CharacterControl)val).Character : null);
				Character val3 = (Object.op_Implicit((Object)(object)_lockingPoint) ? _lockingPoint.OwnerChar : null);
				if ((Object)(object)val3 != (Object)null && (Object)(object)val3 == (Object)(object)val2 && Plugin.BlockSquadSelfTarget.Value)
				{
					if (AggroLog.Enabled)
					{
						AggroLog.Record("SQUAD-PREF", Name(val2) + " -> " + Name(val3) + " SELF-TARGET BLOCKED");
					}
					return false;
				}
				if ((Object)(object)val3 != (Object)null && s_detectVeto.Contains(UID.op_Implicit(val3.UID)))
				{
					if (AggroLog.Enabled)
					{
						AggroLog.Record("SQUAD-PREF", Name(val2) + " -> " + Name(val3) + " VETOED");
					}
					return false;
				}
				if ((Object)(object)val3 != (Object)null && s_detectRedirect.TryGetValue(UID.op_Implicit(val3.UID), out var value))
				{
					if (Object.op_Implicit((Object)(object)value) && value.Alive && Object.op_Implicit((Object)(object)value.LockingPoint) && (Object)(object)value != (Object)(object)val2)
					{
						if (AggroLog.Enabled)
						{
							AggroLog.Record("SQUAD-PREF", $"{Name(val2)} -> {Name(val3)} REDIRECTED -> {Name(value)} flanker={_flankerID}");
						}
						_lockingPoint = value.LockingPoint;
						return true;
					}
					if (!Object.op_Implicit((Object)(object)value) || !value.Alive)
					{
						PruneRedirect(UID.op_Implicit(val3.UID));
						if (AggroLog.Enabled)
						{
							AggroLog.Record("SQUAD-PREF", $"{Name(val2)} -> {Name(val3)} (stale redirect -> {Name(value)} pruned) flanker={_flankerID}");
						}
						return true;
					}
				}
				if (AggroLog.Enabled)
				{
					AggroLog.Record("SQUAD-PREF", $"{Name(val2)} -> {Name(val3)} flanker={_flankerID}");
				}
			}
			catch
			{
			}
			return true;
		}

		private static void Finalizer(bool __state)
		{
			if (__state)
			{
				PopCause();
			}
		}
	}

	[HarmonyPatch(typeof(Character), "SendChangeFaction")]
	internal static class P_SendChangeFaction
	{
		private static bool Prepare()
		{
			return Plugin.EnableObservation.Value;
		}

		private static void Prefix(Character __instance, int _factionID)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: 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)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Factions val = (Factions)_factionID;
				if (__instance.Faction != val)
				{
					string[] array = new StackTrace(2, fNeedFileInfo: false).ToString().Split(new char[1] { '\n' });
					StringBuilder stringBuilder = new StringBuilder();
					for (int i = 0; i < array.Length && i < 6; i++)
					{
						stringBuilder.Append(array[i].Trim().Replace("at ", "")).Append(" <- ");
					}
					if (AggroLog.Enabled)
					{
						AggroLog.Record("FACTION", $"{Name(__instance)}: {__instance.Faction} -> {val} via {stringBuilder}");
					}
				}
			}
			catch
			{
			}
		}
	}

	[HarmonyPatch(typeof(AISCombat), "Defense")]
	internal static class P_Defense
	{
		private static void Prefix(AISCombat __instance, ref Character _dealer, out bool __state)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			__state = Plugin.EnableObservation.Value;
			if (__state)
			{
				PushCause("defense");
			}
			try
			{
				if (Object.op_Implicit((Object)(object)_dealer) && s_noAggroDealers.Contains(UID.op_Implicit(_dealer.UID)))
				{
					CharacterAI val = s_stateAi.Invoke((AIState)(object)__instance);
					if (AggroLog.Enabled)
					{
						AggroLog.Record("DEFENSE", Name(Object.op_Implicit((Object)(object)val) ? ((CharacterControl)val).Character : null) + " dealer=" + Name(_dealer) + " VETOED (block/dodge kept, retarget skipped)");
					}
					_dealer = null;
				}
			}
			catch
			{
			}
		}

		private static void Finalizer(bool __state)
		{
			if (__state)
			{
				PopCause();
			}
		}
	}

	[HarmonyPatch(typeof(AISquad), "AddTarget")]
	internal static class P_SquadAddTarget
	{
		private static bool Prepare()
		{
			return Plugin.EnableObservation.Value;
		}

		private static void Postfix(AISquad __instance, LockingPoint _point)
		{
			try
			{
				if (AggroLog.Enabled)
				{
					AggroLog.Record("SQUAD-ADD", "squad '" + ((Object)__instance).name + "' target=" + Name(Object.op_Implicit((Object)(object)_point) ? _point.OwnerChar : null));
				}
			}
			catch
			{
			}
		}
	}

	[HarmonyPatch(typeof(AISCombat), "CheckContagionOnAI")]
	internal static class P_Contagion
	{
		private static bool Prepare()
		{
			return Plugin.EnableObservation.Value;
		}

		private static void Prefix()
		{
			PushCause("contagion");
		}

		private static void Finalizer()
		{
			PopCause();
		}

		private static void Postfix(AISCombat __instance, CharacterAI _charAi, AIState __result)
		{
			try
			{
				if (!((Object)(object)__result == (Object)null))
				{
					CharacterAI val = s_stateAi.Invoke((AIState)(object)__instance);
					if (AggroLog.Enabled)
					{
						AggroLog.Record("CONTAGION", Name(Object.op_Implicit((Object)(object)val) ? ((CharacterControl)val).Character : null) + " spread combat to " + Name(Object.op_Implicit((Object)(object)_charAi) ? ((CharacterControl)_charAi).Character : null));
					}
				}
			}
			catch
			{
			}
		}
	}

	[CompilerGenerated]
	private static Action<CharacterAI, Character> m_Detected;

	[CompilerGenerated]
	private static Action<CharacterAI, Character> m_Hurt;

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

	private static readonly Dictionary<string, Character> s_detectRedirect = new Dictionary<string, Character>();

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

	public const string DevOwner = "ak_cmd";

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

	private static readonly List<string> s_causes = new List<string>();

	private static readonly Scope s_scope = new Scope();

	private static readonly FieldRef<TargetingSystem, Character> s_tsOwner = AccessTools.FieldRefAccess<TargetingSystem, Character>("m_character");

	private static readonly FieldRef<AICondition, CharacterAI> s_condAi = AccessTools.FieldRefAccess<AICondition, CharacterAI>("m_characterAI");

	private static readonly FieldRef<AIState, CharacterAI> s_stateAi = AccessTools.FieldRefAccess<AIState, CharacterAI>("m_characterAI");

	public static int ControlCount => s_detectVeto.Count + s_detectRedirect.Count + s_noAggroDealers.Count;

	public static bool AnyControls
	{
		get
		{
			if (s_detectVeto.Count <= 0 && s_detectRedirect.Count <= 0)
			{
				return s_noAggroDealers.Count > 0;
			}
			return true;
		}
	}

	private static string Cause
	{
		get
		{
			if (s_causes.Count <= 0)
			{
				return "direct";
			}
			return s_causes[s_causes.Count - 1];
		}
	}

	public static event Action<Character, Character, Character> TargetChanged;

	public static event Action<CharacterAI, Character> Detected
	{
		[CompilerGenerated]
		add
		{
			Action<CharacterAI, Character> val = AggroEvents.m_Detected;
			Action<CharacterAI, Character> val2;
			do
			{
				val2 = val;
				Action<CharacterAI, Character> value2 = (Action<CharacterAI, Character>)(object)Delegate.Combine((Delegate?)(object)val2, (Delegate?)(object)value);
				val = Interlocked.CompareExchange(ref AggroEvents.m_Detected, value2, val2);
			}
			while (val != val2);
		}
		[CompilerGenerated]
		remove
		{
			Action<CharacterAI, Character> val = AggroEvents.m_Detected;
			Action<CharacterAI, Character> val2;
			do
			{
				val2 = val;
				Action<CharacterAI, Character> value2 = (Action<CharacterAI, Character>)(object)Delegate.Remove((Delegate?)(object)val2, (Delegate?)(object)value);
				val = Interlocked.CompareExchange(ref AggroEvents.m_Detected, value2, val2);
			}
			while (val != val2);
		}
	}

	public static event Action<CharacterAI, Character> Hurt
	{
		[CompilerGenerated]
		add
		{
			Action<CharacterAI, Character> val = AggroEvents.m_Hurt;
			Action<CharacterAI, Character> val2;
			do
			{
				val2 = val;
				Action<CharacterAI, Character> value2 = (Action<CharacterAI, Character>)(object)Delegate.Combine((Delegate?)(object)val2, (Delegate?)(object)value);
				val = Interlocked.CompareExchange(ref AggroEvents.m_Hurt, value2, val2);
			}
			while (val != val2);
		}
		[CompilerGenerated]
		remove
		{
			Action<CharacterAI, Character> val = AggroEvents.m_Hurt;
			Action<CharacterAI, Character> val2;
			do
			{
				val2 = val;
				Action<CharacterAI, Character> value2 = (Action<CharacterAI, Character>)(object)Delegate.Remove((Delegate?)(object)val2, (Delegate?)(object)value);
				val = Interlocked.CompareExchange(ref AggroEvents.m_Hurt, value2, val2);
			}
			while (val != val2);
		}
	}

	public static event Action<CharacterAI, AIState, AIState> StateChanged;

	private static void Own(string store, string uid, string owner)
	{
		s_controlOwners[store + "|" + uid] = (string.IsNullOrEmpty(owner) ? "ak_cmd" : owner);
	}

	private static void Disown(string store, string uid)
	{
		s_controlOwners.Remove(store + "|" + uid);
	}

	private static string OwnerOf(string store, string uid)
	{
		if (!s_controlOwners.TryGetValue(store + "|" + uid, out var value))
		{
			return "ak_cmd";
		}
		return value;
	}

	public static void SetDetectionVeto(Character target, bool on, string owner = "ak_cmd")
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)target))
		{
			string text = UID.op_Implicit(target.UID);
			if (on)
			{
				s_detectVeto.Add(text);
				Own("veto", text, owner);
			}
			else
			{
				s_detectVeto.Remove(text);
				Disown("veto", text);
			}
		}
	}

	public static void SetDetectionRedirect(Character from, Character to, string owner = "ak_cmd")
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)from) && Object.op_Implicit((Object)(object)to))
		{
			string text = UID.op_Implicit(from.UID);
			s_detectRedirect[text] = to;
			Own("redirect", text, owner);
		}
	}

	private static void PruneRedirect(string uid)
	{
		s_detectRedirect.Remove(uid);
		Disown("redirect", uid);
	}

	public static void ClearDetectionRedirect(Character from)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)from))
		{
			PruneRedirect(UID.op_Implicit(from.UID));
		}
	}

	public static void SetNoAggroDealer(Character dealer, bool on, string owner = "ak_cmd")
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)dealer))
		{
			string text = UID.op_Implicit(dealer.UID);
			if (on)
			{
				s_noAggroDealers.Add(text);
				Own("noaggro", text, owner);
			}
			else
			{
				s_noAggroDealers.Remove(text);
				Disown("noaggro", text);
			}
		}
	}

	public static void ClearAllControls()
	{
		s_detectVeto.Clear();
		s_detectRedirect.Clear();
		s_noAggroDealers.Clear();
		s_controlOwners.Clear();
	}

	public static int ClearControlsOwnedBy(string owner)
	{
		if (string.IsNullOrEmpty(owner))
		{
			owner = "ak_cmd";
		}
		int num = 0;
		num += RemoveOwned(s_detectVeto, "veto", owner);
		num += RemoveOwned(s_noAggroDealers, "noaggro", owner);
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, Character> item in s_detectRedirect)
		{
			if (OwnerOf("redirect", item.Key) == owner)
			{
				list.Add(item.Key);
			}
		}
		foreach (string item2 in list)
		{
			PruneRedirect(item2);
			num++;
		}
		return num;
	}

	private static int RemoveOwned(HashSet<string> store, string storeName, string owner)
	{
		List<string> list = new List<string>();
		foreach (string item in store)
		{
			if (OwnerOf(storeName, item) == owner)
			{
				list.Add(item);
			}
		}
		foreach (string item2 in list)
		{
			store.Remove(item2);
			Disown(storeName, item2);
		}
		return list.Count;
	}

	public static int CountControlsOwnedBy(string owner)
	{
		if (string.IsNullOrEmpty(owner))
		{
			owner = "ak_cmd";
		}
		int num = 0;
		foreach (string item in s_detectVeto)
		{
			if (OwnerOf("veto", item) == owner)
			{
				num++;
			}
		}
		foreach (string s_noAggroDealer in s_noAggroDealers)
		{
			if (OwnerOf("noaggro", s_noAggroDealer) == owner)
			{
				num++;
			}
		}
		foreach (string key in s_detectRedirect.Keys)
		{
			if (OwnerOf("redirect", key) == owner)
			{
				num++;
			}
		}
		return num;
	}

	public static string DescribeControls()
	{
		return $"detectVeto={s_detectVeto.Count} redirect={s_detectRedirect.Count} noAggroDealers={s_noAggroDealers.Count}";
	}

	internal static void PushCause(string cause)
	{
		s_causes.Add(cause);
	}

	internal static void PopCause()
	{
		if (s_causes.Count > 0)
		{
			s_causes.RemoveAt(s_causes.Count - 1);
		}
	}

	public static IDisposable CauseScope(string cause)
	{
		PushCause(cause);
		return s_scope;
	}

	internal static string Name(Character c)
	{
		if (!Object.op_Implicit((Object)(object)c))
		{
			return "(none)";
		}
		return c.Name.Trim() + "#" + Uid4(c);
	}

	internal static string Uid4(Character c)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		string text = UID.op_Implicit(c.UID);
		if (text.Length <= 4)
		{
			return text;
		}
		return text.Substring(text.Length - 4);
	}

	private static void Fire(Action a)
	{
		try
		{
			a();
		}
		catch (Exception ex)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("[AggroKit] event subscriber threw: " + ex.Message));
			}
		}
	}
}
public static class AggroLog
{
	private struct Entry
	{
		public float T;

		public string Kind;

		public string Text;
	}

	private const int Capacity = 1024;

	private static readonly Entry[] s_buf = new Entry[1024];

	private static int s_next;

	private static int s_count;

	public static bool Mirror;

	public static bool Enabled
	{
		get
		{
			if (!Mirror)
			{
				if (Plugin.EnableObservation != null)
				{
					return Plugin.EnableObservation.Value;
				}
				return false;
			}
			return true;
		}
	}

	public static int Count => s_count;

	public static void Record(string kind, string text)
	{
		s_buf[s_next] = new Entry
		{
			T = Time.time,
			Kind = kind,
			Text = text
		};
		s_next = (s_next + 1) % 1024;
		if (s_count < 1024)
		{
			s_count++;
		}
		if (Mirror && Plugin.Log != null)
		{
			Plugin.Log.LogMessage((object)$"[AGGRO] t={s_buf[(s_next + 1024 - 1) % 1024].T:F1} {kind} {text}");
		}
	}

	public static string Dump(int last = 0)
	{
		int num = ((last <= 0 || last > s_count) ? s_count : last);
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine($"[AggroKit] event buffer: showing {num} of {s_count} (capacity {1024}):");
		for (int i = 0; i < num; i++)
		{
			int num2 = (s_next - num + i + 1024) % 1024;
			stringBuilder.AppendLine($"  t={s_buf[num2].T,8:F1} {s_buf[num2].Kind,-11} {s_buf[num2].Text}");
		}
		return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
	}

	public static void Clear()
	{
		s_next = 0;
		s_count = 0;
	}
}
public static class AggroTools
{
	public static bool ForceTarget(CharacterAI enemy, Character target)
	{
		if (!Object.op_Implicit((Object)(object)enemy) || (Object)(object)enemy.TargetingSystem == (Object)null || !Object.op_Implicit((Object)(object)target) || !target.Alive || !Object.op_Implicit((Object)(object)target.LockingPoint))
		{
			return false;
		}
		using (AggroEvents.CauseScope("forcetarget"))
		{
			enemy.TargetingSystem.SetLockingPoint(target.LockingPoint);
			if (!(enemy.CurrentAiState is AISCombat))
			{
				int num = FindCombatStateIndex(enemy);
				if (num < 0)
				{
					return false;
				}
				enemy.SwitchAiState(num);
			}
		}
		if (enemy.CurrentAiState is AISCombat)
		{
			return (Object)(object)enemy.TargetingSystem.LockedCharacter == (Object)(object)target;
		}
		return false;
	}

	public static bool Calm(CharacterAI enemy)
	{
		if (!Object.op_Implicit((Object)(object)enemy) || (Object)(object)enemy.TargetingSystem == (Object)null)
		{
			return false;
		}
		using (AggroEvents.CauseScope("calm"))
		{
			enemy.TargetingSystem.SetLockingPoint((LockingPoint)null);
			if (enemy.CurrentAiState is AISCombat)
			{
				enemy.SwitchAiState(0);
			}
		}
		return true;
	}

	public static int Taunt(Vector3 center, float radius, Character to, Character from = null)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		int num = 0;
		foreach (CharacterAI item in AisInRange(center, radius))
		{
			Character val = (Object.op_Implicit((Object)(object)item.TargetingSystem) ? item.TargetingSystem.LockedCharacter : null);
			if ((!((Object)(object)from != (Object)null) || !((Object)(object)val != (Object)(object)from)) && (!((Object)(object)from == (Object)null) || !((Object)(object)val == (Object)null)) && !((Object)(object)val == (Object)(object)to) && ForceTarget(item, to))
			{
				num++;
			}
		}
		return num;
	}

	public static void SetUntargetable(Character c, bool untargetable)
	{
		if (Object.op_Implicit((Object)(object)c))
		{
			c.QuestNonTargetable = untargetable;
		}
	}

	public static void SetUndetectable(Character c, bool undetectable)
	{
		if (Object.op_Implicit((Object)(object)c))
		{
			c.DetectabilityMult = (undetectable ? 0f : 1f);
		}
	}

	public static List<CharacterAI> EnemiesTargeting(Character target, Vector3 center, float radius)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		List<CharacterAI> list = new List<CharacterAI>();
		foreach (CharacterAI item in AisInRange(center, radius))
		{
			if (Object.op_Implicit((Object)(object)item.TargetingSystem) && (Object)(object)item.TargetingSystem.LockedCharacter == (Object)(object)target)
			{
				list.Add(item);
			}
		}
		return list;
	}

	public static string DumpAggro(Vector3 center, float radius)
	{
		//IL_0008: 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_014d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		StringBuilder stringBuilder = new StringBuilder();
		int num = 0;
		foreach (CharacterAI item in AisInRange(center, radius))
		{
			Character character = ((CharacterControl)item).Character;
			Character c = (Object.op_Implicit((Object)(object)item.TargetingSystem) ? item.TargetingSystem.LockedCharacter : null);
			string arg = (StateIdsMatchIndices(item) ? "" : " STATEID-MISMATCH!");
			PhotonView photonView = ((MonoBehaviour)character).photonView;
			stringBuilder.AppendLine($"  {AggroEvents.Name(character)} [{character.Faction}] hp={(Object.op_Implicit((Object)(object)character.Stats) ? character.Stats.CurrentHealth : (-1f)):F0}" + " state=" + (((object)item.CurrentAiState)?.GetType().Name ?? "?") + " target=" + AggroEvents.Name(c) + " squad=" + (Object.op_Implicit((Object)(object)item.AISquad) ? ((Object)item.AISquad).name : "-") + " pv=" + (Object.op_Implicit((Object)(object)photonView) ? photonView.viewID.ToString() : "-") + $" dist={Vector3.Distance(center, ((Component)character).transform.position):F1}{arg}");
			num++;
		}
		stringBuilder.Insert(0, $"[AggroKit] {num} AI character(s) within {radius:F0}m:\n");
		return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
	}

	public static string DumpAiStates(CharacterAI ai)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)ai))
		{
			return "[AggroKit] no AI.";
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine($"[AggroKit] AI tree of {AggroEvents.Name(((CharacterControl)ai).Character)} [{((CharacterControl)ai).Character.Faction}]" + " current=" + (((object)ai.CurrentAiState)?.GetType().Name ?? "?") + ":");
		AIState[] aiStates = ai.AiStates;
		if (aiStates == null)
		{
			return stringBuilder.Append("  (no AiStates array)").ToString();
		}
		for (int i = 0; i < aiStates.Length; i++)
		{
			string text = (((Object)(object)aiStates[i] != (Object)null && aiStates[i].StateID != i) ? "  <-- MISMATCH" : "");
			string[] array = new string[7];
			object arg = i;
			AIState obj = aiStates[i];
			array[0] = string.Format("  [{0}] StateID={1}", arg, ((obj != null) ? obj.StateID.ToString() : null) ?? "?");
			array[1] = " ";
			array[2] = ((object)aiStates[i])?.GetType().Name ?? "null";
			array[3] = " '";
			AIState obj2 = aiStates[i];
			array[4] = ((obj2 != null) ? ((Object)obj2).name : null) ?? "-";
			array[5] = "'";
			array[6] = text;
			stringBuilder.AppendLine(string.Concat(array));
		}
		return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
	}

	public static bool StateIdsMatchIndices(CharacterAI ai)
	{
		AIState[] array = (Object.op_Implicit((Object)(object)ai) ? ai.AiStates : null);
		if (array == null)
		{
			return true;
		}
		for (int i = 0; i < array.Length; i++)
		{
			if ((Object)(object)array[i] != (Object)null && array[i].StateID != i)
			{
				return false;
			}
		}
		return true;
	}

	public static CharacterAI FindAi(string namePart, Vector3 center, float radius)
	{
		//IL_0023: 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_0077: 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)
		CharacterAI result = null;
		float num = float.MaxValue;
		bool flag = namePart.StartsWith("uid:", StringComparison.OrdinalIgnoreCase);
		string value = (flag ? namePart.Substring(4) : namePart);
		foreach (CharacterAI item in AisInRange(center, radius))
		{
			Character character = ((CharacterControl)item).Character;
			if (flag ? UID.op_Implicit(character.UID).EndsWith(value, StringComparison.OrdinalIgnoreCase) : (character.Name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0))
			{
				float num2 = Vector3.Distance(center, ((Component)character).transform.position);
				if (num2 < num)
				{
					num = num2;
					result = item;
				}
			}
		}
		return result;
	}

	public static int FindCombatStateIndex(CharacterAI ai)
	{
		AIState[] array = (Object.op_Implicit((Object)(object)ai) ? ai.AiStates : null);
		if (array == null)
		{
			return -1;
		}
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i] is AISCombat)
			{
				return i;
			}
		}
		return -1;
	}

	public static IEnumerable<CharacterAI> AisInRange(Vector3 center, float radius)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		return AisInRange(center, radius, excludePlayerFaction: false, null);
	}

	public static IEnumerable<CharacterAI> AisInRange(Vector3 center, float radius, bool excludePlayerFaction, Character exclude)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		CharacterManager instance = CharacterManager.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			yield break;
		}
		List<Character> list = new List<Character>(instance.Characters.Values);
		foreach (Character item in list)
		{
			if (Object.op_Implicit((Object)(object)item) && item.IsAI && item.Alive && (!excludePlayerFaction || (int)item.Faction != 1) && item != exclude && !(Vector3.Distance(center, ((Component)item).transform.position) > radius))
			{
				CharacterAI component = ((Component)item).GetComponent<CharacterAI>();
				if (Object.op_Implicit((Object)(object)component))
				{
					yield return component;
				}
			}
		}
	}
}
[BepInPlugin("cobalt.aggrokit", "AggroKit", "0.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	private sealed class Watch
	{
		public CharacterAI Ai;

		public string Label;

		public string LastState;

		public string LastTarget;

		public float SinceTime;
	}

	private sealed class Args
	{
		public string Arg1;

		public string Arg2;

		public string Arg3;

		public float Radius;

		public Character Player;

		public Vector3 Pos;
	}

	public const string GUID = "cobalt.aggrokit";

	public const string NAME = "AggroKit";

	public const string VERSION = "0.1.0";

	internal static ManualLogSource Log;

	public static ConfigEntry<bool> EnableCommandFile;

	public static ConfigEntry<bool> EnableDumpKey;

	public static ConfigEntry<KeyboardShortcut> DumpKey;

	public static ConfigEntry<bool> EnableObservation;

	public static ConfigEntry<bool> BlockSquadSelfTarget;

	private CommandRegistry _commands;

	private CommandChannel _channel;

	private readonly List<Watch> _watches = new List<Watch>();

	private float _nextWatchTick;

	private VerbHost _verbs;

	private static Character LocalPlayer
	{
		get
		{
			CharacterManager instance = CharacterManager.Instance;
			if (instance == null)
			{
				return null;
			}
			return instance.GetFirstLocalCharacter();
		}
	}

	internal void Awake()
	{
		//IL_0065: 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
		//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		EnableCommandFile = ((BaseUnityPlugin)this).Config.Bind<bool>("Dev", "EnableCommandFile", true, "Poll BepInEx/config/ak_cmd.txt for dev commands (aggrodump/forcetarget/taunt/calm/...).");
		EnableDumpKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Dev", "EnableDumpKey", true, "Whether DumpKey logs an aggro dump of all AI characters near the player.");
		DumpKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Dev", "DumpKey", new KeyboardShortcut((KeyCode)284, Array.Empty<KeyCode>()), "Key that logs an aggro dump of all AI characters near the player (needs EnableDumpKey=true).");
		Keybinds.Claim("AggroKit", "dump nearby AI aggro", DumpKey);
		EnableObservation = ((BaseUnityPlugin)this).Config.Bind<bool>("Research", "EnableObservation", false, "Re-arm AggroEvents' purely-observational Harmony taps (SetLockingPoint, SwitchAiState, AISquad.AddTarget, CheckContagionOnAI, and the SendChangeFaction faction tracer with its per-flip StackTrace capture) that record the [AGGRO] event buffer. OFF by default so these hot AI-tick paths stay un-patched. The detection-side control patches (Detected/CharHurt/Defense veto/redirect/no-aggro) and the IsTargetable override gate stay applied regardless -- they're inert until a verb activates them. Patch application is decided in Awake, so a change needs a relaunch.");
		BlockSquadSelfTarget = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "BlockSquadSelfTarget", true, "Kill-switch for the always-on engine bugfix in AISCombat.SetPreferredTarget: block a squad-contagion self-target assignment (source's target IS the infected AI in a feud -> ~50s of no-op fake combat). Unreachable in vanilla, so default ON is safe. This is a behavior FIX, not instrumentation -- OFF only to A/B the raw engine bug.");
		RegisterVerbs();
		_channel = new CommandChannel("ak_cmd.txt", Log, _commands, 0.3f, false, false);
		new Harmony("cobalt.aggrokit").PatchAll();
		SceneManager.sceneLoaded += OnSceneLoaded;
		Log.LogMessage((object)("AggroKit 0.1.0 loaded (cmd file: " + _channel.Path + ")."));
		Log.LogMessage((object)("[AggroKit] build " + BuildStamp.Read(((object)this).GetType().Assembly) + " @ " + ((object)this).GetType().Assembly.Location));
	}

	private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if ((int)mode != 0)
		{
			return;
		}
		int num = TauntController.ReleaseOwnedBy("ak_cmd", "scene load");
		if (TargetableOverrides.Count != 0 || AggroEvents.AnyControls || num != 0)
		{
			string arg = AggroEvents.DescribeControls();
			int num2 = TargetableOverrides.ClearOwnedBy("ak_cmd");
			int num3 = AggroEvents.ClearControlsOwnedBy("ak_cmd");
			int num4 = TargetableOverrides.Count + AggroEvents.ControlCount;
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogMessage((object)("[AggroKit] scene '" + ((Scene)(ref scene)).name + "' loaded (Single) -- cleared " + $"{num2 + num3 + num} dev-staged (overrides={num2} " + $"controls={num3} of ({arg}) taunts={num}), " + $"retained {num4 + TauntController.Count} consumer-owned."));
			}
		}
	}

	internal void Update()
	{
		//IL_0011: 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)
		if (EnableDumpKey.Value)
		{
			KeyboardShortcut value = DumpKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				_channel.Run("aggrodump");
			}
		}
		TickWatches();
		TauntController.Tick();
		if (EnableCommandFile.Value)
		{
			_channel.Tick();
		}
	}

	private void AddWatch(CharacterAI ai)
	{
		if (Object.op_Implicit((Object)(object)ai) && !_watches.Any((Watch w) => (Object)(object)w.Ai == (Object)(object)ai))
		{
			if (_watches.Count >= 8)
			{
				Log.LogMessage((object)"[AggroKit] watch list full (8).");
				return;
			}
			Watch watch = new Watch
			{
				Ai = ai,
				Label = AggroEvents.Name(((CharacterControl)ai).Character),
				SinceTime = Time.time
			};
			Sample(ai, out watch.LastState, out watch.LastTarget);
			_watches.Add(watch);
			Log.LogMessage((object)("[WATCH] + " + watch.Label + " state=" + watch.LastState + " target=" + watch.LastTarget));
		}
	}

	private static void Sample(CharacterAI ai, out string state, out string target)
	{
		state = "?";
		target = "(none)";
		try
		{
			state = ((object)ai.CurrentAiState)?.GetType().Name ?? "?";
			target = AggroEvents.Name(Object.op_Implicit((Object)(object)ai.TargetingSystem) ? ai.TargetingSystem.LockedCharacter : null);
		}
		catch
		{
		}
	}

	private void TickWatches()
	{
		if (_watches.Count == 0 || Time.time < _nextWatchTick)
		{
			return;
		}
		_nextWatchTick = Time.time + 0.5f;
		for (int num = _watches.Count - 1; num >= 0; num--)
		{
			Watch watch = _watches[num];
			if (!Object.op_Implicit((Object)(object)watch.Ai) || !Object.op_Implicit((Object)(object)((CharacterControl)watch.Ai).Character))
			{
				Log.LogMessage((object)$"[WATCH] t={Time.time:F1} {watch.Label} DESTROYED (held {Time.time - watch.SinceTime:F1}s)");
				_watches.RemoveAt(num);
			}
			else
			{
				Sample(watch.Ai, out var state, out var target);
				if (!(state == watch.LastState) || !(target == watch.LastTarget))
				{
					float num2 = (Object.op_Implicit((Object)(object)((CharacterControl)watch.Ai).Character.Stats) ? ((CharacterControl)watch.Ai).Character.Stats.CurrentHealth : (-1f));
					Log.LogMessage((object)($"[WATCH] t={Time.time:F1} {watch.Label} state={state} target={target} hp={num2:F0}" + $" (prev {watch.LastState}/{watch.LastTarget} held {Time.time - watch.SinceTime:F1}s)"));
					watch.LastState = state;
					watch.LastTarget = target;
					watch.SinceTime = Time.time;
				}
			}
		}
	}

	private CharacterAI Find(Args a, string namePart, float range = 40f)
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		CharacterAI val = (CharacterAI)((namePart == null || namePart == "nearest") ? ((object)(from ai in AggroTools.AisInRange(a.Pos, range)
			where (int)((CharacterControl)ai).Character.Faction != 1
			orderby Vector3.Distance(a.Pos, ((Component)ai).transform.position)
			select ai).FirstOrDefault()) : ((object)AggroTools.FindAi(namePart, a.Pos, range)));
		if (!Object.op_Implicit((Object)(object)val))
		{
			Log.LogMessage((object)string.Format("[AggroKit] no AI matching '{0}' within {1:F0}m.", namePart ?? "nearest", range));
		}
		return val;
	}

	private void Register(string verb, string help, Action<Args> body, bool needsPlayer = true)
	{
		_verbs.Register(verb, help, (Action<VerbContext>)delegate(VerbContext ctx)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			Args args = new Args
			{
				Arg1 = ctx.Arg(1),
				Arg2 = ctx.Arg(2),
				Arg3 = ctx.Arg(3),
				Player = ctx.Player,
				Pos = (Object.op_Implicit((Object)(object)ctx.Player) ? ((Component)ctx.Player).transform.position : Vector3.zero)
			};
			args.Radius = ((args.Arg1 != null && float.TryParse(args.Arg1, out var result)) ? result : 20f);
			body(args);
		}, "[AggroKit]", needsPlayer, false, false, "[AggroKit] no local player yet.");
	}

	private void RegisterVerbs()
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected O, but got Unknown
		_commands = new CommandRegistry(Log);
		_verbs = new VerbHost(_commands, Log, (Func<Character>)(() => LocalPlayer));
		Register("aggrodump", "Aggro dump of all AI near the player ('aggrodump [radius]') + the player's targetability state.", delegate(Args a)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Log.LogMessage((object)AggroTools.DumpAggro(a.Pos, a.Radius));
			Log.LogMessage((object)($"[AggroKit] player: QuestNonTargetable={a.Player.QuestNonTargetable}" + $" DetectabilityMult={a.Player.DetectabilityMult:F2}" + $" overrides={TargetableOverrides.Count} {AggroEvents.DescribeControls()}"));
		});
		Register("aistates", "Dump an AI's state machine ('aistates [name|nearest]').", delegate(Args a)
		{
			CharacterAI val = Find(a, a.Arg1);
			if (Object.op_Implicit((Object)(object)val))
			{
				Log.LogMessage((object)AggroTools.DumpAiStates(val));
			}
		});
		Register("aggrolog", "The [AGGRO] event buffer: 'aggrolog [n|on|off|clear]'.", delegate(Args a)
		{
			if (a.Arg1 == "on")
			{
				AggroLog.Mirror = true;
				Log.LogMessage((object)"[AggroKit] aggrolog mirroring ON.");
			}
			else if (a.Arg1 == "off")
			{
				AggroLog.Mirror = false;
				Log.LogMessage((object)"[AggroKit] aggrolog mirroring OFF.");
			}
			else if (a.Arg1 == "clear")
			{
				AggroLog.Clear();
				Log.LogMessage((object)"[AggroKit] aggrolog buffer cleared.");
			}
			else if (!AggroLog.Enabled)
			{
				Log.LogMessage((object)"[AggroKit] event recording is disabled (buffer isn't being filled) -- enable with 'aggrolog on' or [Research] EnableObservation, then retry.");
			}
			else
			{
				Log.LogMessage((object)AggroLog.Dump((a.Arg1 != null && int.TryParse(a.Arg1, out var result)) ? result : 0));
			}
		});
		Register("watch", "Watch an AI's (state,target) changes ('watch <name|nearest|off>').", delegate(Args a)
		{
			if (a.Arg1 == "off")
			{
				_watches.Clear();
				Log.LogMessage((object)"[WATCH] all watches cleared.");
			}
			else
			{
				CharacterAI val = Find(a, a.Arg1);
				if (Object.op_Implicit((Object)(object)val))
				{
					AddWatch(val);
				}
			}
		});
		Register("forcetarget", "Force an AI onto the player ('forcetarget [name|nearest]') + auto-watch it.", delegate(Args a)
		{
			CharacterAI val = Find(a, a.Arg1);
			if (Object.op_Implicit((Object)(object)val))
			{
				AddWatch(val);
				Log.LogMessage((object)("[AggroKit] ForceTarget(" + AggroEvents.Name(((CharacterControl)val).Character) + " -> player)" + $" = {AggroTools.ForceTarget(val, a.Player)}"));
			}
		});
		Register("feud", "Make any two AIs fight: 'feud <nameA> <nameB> [nooverride|both]' (the R2 headline).", delegate(Args a)
		{
			if (a.Arg1 == null || a.Arg2 == null)
			{
				Log.LogMessage((object)"[AggroKit] usage: feud <nameA> <nameB> [nooverride|both]");
			}
			else
			{
				CharacterAI val = Find(a, a.Arg1);
				CharacterAI val2 = Find(a, a.Arg2);
				if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2) || (Object)(object)val == (Object)(object)val2)
				{
					Log.LogMessage((object)"[AggroKit] feud needs two distinct AIs.");
				}
				else
				{
					bool flag = val.TargetingSystem.IsTargetable(((CharacterControl)val2).Character);
					bool flag2 = val2.TargetingSystem.IsTargetable(((CharacterControl)val).Character);
					Log.LogMessage((object)("[AggroKit] feud: native targetability " + AggroEvents.Name(((CharacterControl)val).Character) + "->" + $"{AggroEvents.Name(((CharacterControl)val2).Character)}={flag}, reverse={flag2}"));
					if (a.Arg3 != "nooverride" && (!flag || !flag2))
					{
						TargetableOverrides.Set(((CharacterControl)val).Character, ((CharacterControl)val2).Character, targetable: true);
						TargetableOverrides.Set(((CharacterControl)val2).Character, ((CharacterControl)val).Character, targetable: true);
						Log.LogMessage((object)"[AggroKit] feud: pair overrides installed both ways.");
					}
					AddWatch(val);
					AddWatch(val2);
					bool flag3 = AggroTools.ForceTarget(val, ((CharacterControl)val2).Character);
					bool flag4 = a.Arg3 == "both" && AggroTools.ForceTarget(val2, ((CharacterControl)val).Character);
					Log.LogMessage((object)($"[AggroKit] feud: forced A->B={flag3}" + ((a.Arg3 == "both") ? $" B->A={flag4}" : " (B left to retaliate naturally)")));
				}
			}
		});
		Register("taunt", "Taunt every AI in radius onto the player ('taunt [radius]') + auto-watch them.", delegate(Args a)
		{
			//IL_000b: 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)
			Log.LogMessage((object)$"[AggroKit] taunted {AggroTools.Taunt(a.Pos, a.Radius, a.Player)} AI(s) onto player.");
			foreach (CharacterAI item in AggroTools.EnemiesTargeting(a.Player, a.Pos, a.Radius))
			{
				AddWatch(item);
			}
		});
		Register("calm", "Calm every AI in radius ('calm [radius]').", delegate(Args a)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			foreach (CharacterAI item2 in AggroTools.AisInRange(a.Pos, a.Radius).ToList())
			{
				if (AggroTools.Calm(item2))
				{
					num++;
				}
			}
			Log.LogMessage((object)$"[AggroKit] calmed {num} AI(s).");
		});
		Register("untargetable", "Player QuestNonTargetable=true.", delegate(Args a)
		{
			AggroTools.SetUntargetable(a.Player, untargetable: true);
			Log.LogMessage((object)"[AggroKit] player QuestNonTargetable=true");
		});
		Register("targetable", "Player QuestNonTargetable=false.", delegate(Args a)
		{
			AggroTools.SetUntargetable(a.Player, untargetable: false);
			Log.LogMessage((object)"[AggroKit] player QuestNonTargetable=false");
		});
		Register("undetectable", "Player DetectabilityMult=0.", delegate(Args a)
		{
			AggroTools.SetUndetectable(a.Player, undetectable: true);
			Log.LogMessage((object)"[AggroKit] player DetectabilityMult=0");
		});
		Register("detectable", "Player DetectabilityMult=1.", delegate(Args a)
		{
			AggroTools.SetUndetectable(a.Player, undetectable: false);
			Log.LogMessage((object)"[AggroKit] player DetectabilityMult=1");
		});
		Register("shieldme", "Override: nobody may target the player.", delegate(Args a)
		{
			TargetableOverrides.SetForAll(a.Player, targetable: false);
			Log.LogMessage((object)$"[AggroKit] override: nobody may target player ({TargetableOverrides.Count} overrides live).");
		});
		Register("unshieldme", "Clear the shieldme override.", delegate(Args a)
		{
			TargetableOverrides.ClearForAll(a.Player);
			Log.LogMessage((object)$"[AggroKit] override cleared ({TargetableOverrides.Count} live).");
		});
		Register("clearoverrides", "Clear ALL targetability overrides.", delegate
		{
			TargetableOverrides.ClearAll();
			Log.LogMessage((object)"[AggroKit] all overrides cleared.");
		});
		Register("stealthme", "Veto AI detection of the player ('stealthme [off]').", delegate(Args a)
		{
			AggroEvents.SetDetectionVeto(a.Player, a.Arg1 != "off");
			Log.LogMessage((object)("[AggroKit] detection veto of player: " + ((a.Arg1 != "off") ? "ON" : "off") + "."));
		});
		Register("decoy", "Redirect detections of the player onto an AI ('decoy <name>|off').", delegate(Args a)
		{
			if (a.Arg1 == null || a.Arg1 == "off")
			{
				AggroEvents.ClearDetectionRedirect(a.Player);
				Log.LogMessage((object)"[AggroKit] decoy redirect cleared.");
			}
			else
			{
				CharacterAI val = Find(a, a.Arg1);
				if (Object.op_Implicit((Object)(object)val))
				{
					AggroEvents.SetDetectionRedirect(a.Player, ((CharacterControl)val).Character);
					Log.LogMessage((object)("[AggroKit] detections of player now redirect to " + AggroEvents.Name(((CharacterControl)val).Character) + "."));
				}
			}
		});
		Register("noaggro", "The player's hits generate no aggro ('noaggro [off]').", delegate(Args a)
		{
			AggroEvents.SetNoAggroDealer(a.Player, a.Arg1 != "off");
			Log.LogMessage((object)("[AggroKit] no-aggro dealer (player): " + ((a.Arg1 != "off") ? "ON" : "off") + "."));
		});
		Register("status", "One-glance status: player flags, overrides, controls, watches, aggrolog.", delegate(Args a)
		{
			Log.LogMessage((object)("[AggroKit] status:\n" + $"  player: QuestNonTargetable={a.Player.QuestNonTargetable} DetectabilityMult={a.Player.DetectabilityMult:F2}\n" + $"  overrides: {TargetableOverrides.Count} live\n" + "  controls: " + AggroEvents.DescribeControls() + "\n  watches: " + ((_watches.Count == 0) ? "-" : string.Join(", ", _watches.Select((Watch w) => w.Label).ToArray())) + "\n" + $"  aggrolog: enabled={AggroLog.Enabled} mirror={AggroLog.Mirror} buffered={AggroLog.Count}"));
		});
		Register("restore", "RESTORE everything: player targetable+detectable, all overrides/controls/watches cleared.", delegate(Args a)
		{
			AggroTools.SetUntargetable(a.Player, untargetable: false);
			AggroTools.SetUndetectable(a.Player, undetectable: false);
			TargetableOverrides.ClearAll();
			AggroEvents.ClearAllControls();
			int num = TauntController.ReleaseAll("restore");
			_watches.Clear();
			Log.LogMessage((object)("[AggroKit] RESTORED: player targetable+detectable, all overrides/controls/watches cleared" + ((num > 0) ? $" ({num} taunt hold(s) released)." : ".")));
		});
		Register("selftest", "Zero-interaction environment checks ([SELFTEST] PASS/FAIL ... DONE).", delegate
		{
			SelfTest();
		}, needsPlayer: false);
	}

	private void SelfTest()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		SelfTestHarness t = new SelfTestHarness(Log);
		t.Begin("AggroKit environment in-game");
		MethodInfo methodInfo = AccessTools.Method(typeof(TargetingSystem), "IsTargetable", new Type[1] { typeof(Character) }, (Type[])null);
		Check("IsTargetable(Character) method resolves", methodInfo != null);
		Check("IsTargetable(Character) postfix installed", PatchedBy(methodInfo, postfix: true));
		CheckObs("SetLockingPoint postfix", PatchedBy(AccessTools.Method(typeof(TargetingSystem), "SetLockingPoint", (Type[])null, (Type[])null), postfix: true));
		CheckObs("SwitchAiState postfix", PatchedBy(AccessTools.Method(typeof(CharacterAI), "SwitchAiState", (Type[])null, (Type[])null), postfix: true));
		Check("AICEnemyDetection.Detected resolves + prefixed", PatchedBy(AccessTools.Method(typeof(AICEnemyDetection), "Detected", (Type[])null, (Type[])null), postfix: false));
		Check("AICEnemyDetection.CharHurt resolves + prefixed", PatchedBy(AccessTools.Method(typeof(AICEnemyDetection), "CharHurt", (Type[])null, (Type[])null), postfix: false));
		Check("AISCombat.SetPreferredTarget prefixed", PatchedBy(AccessTools.Method(typeof(AISCombat), "SetPreferredTarget", (Type[])null, (Type[])null), postfix: false));
		Check("AISCombat.Defense resolves + prefixed", PatchedBy(AccessTools.Method(typeof(AISCombat), "Defense", (Type[])null, (Type[])null), postfix: false));
		CheckObs("Character.SendChangeFaction prefix", PatchedBy(AccessTools.Method(typeof(Character), "SendChangeFaction", (Type[])null, (Type[])null), postfix: false));
		CheckObs("AISquad.AddTarget postfix", PatchedBy(AccessTools.Method(typeof(AISquad), "AddTarget", (Type[])null, (Type[])null), postfix: true));
		Check("TargetingSystem.m_character field reachable", AccessTools.Field(typeof(TargetingSystem), "m_character") != null);
		Check("AICondition.m_characterAI field reachable", AccessTools.Field(typeof(AICEnemyDetection), "m_characterAI") != null);
		Check("AIState.m_characterAI field reachable", AccessTools.Field(typeof(AISCombat), "m_characterAI") != null);
		Check("layer 'DetectabilityEmitters' exists", LayerMask.NameToLayer("DetectabilityEmitters") >= 0);
		Check("layer 'LockingPoints' exists", LayerMask.NameToLayer("LockingPoints") >= 0);
		Check("layer 'Hitbox' exists", LayerMask.NameToLayer("Hitbox") >= 0);
		Check("Global.DetectabilityEmittersMask nonzero", Global.DetectabilityEmittersMask != 0);
		Check("Global.WeaponHittingMask nonzero", Global.WeaponHittingMask != 0);
		Check("no two mods claim the same key (bug 26)", !Keybinds.HasConflicts());
		t.Done();
		void Check(string name, bool ok)
		{
			t.Check(name, ok);
		}
		void CheckObs(string name, bool installed)
		{
			t.Check(string.Format("{0} (observation gate {1}, installed={2})", name, EnableObservation.Value ? "ON" : "OFF", installed), installed == EnableObservation.Value);
		}
		static bool PatchedBy(MethodBase m, bool postfix)
		{
			if (m == null)
			{
				return false;
			}
			Patches patchInfo = Harmony.GetPatchInfo(m);
			if (patchInfo == null)
			{
				return false;
			}
			ReadOnlyCollection<Patch> source = (postfix ? patchInfo.Postfixes : patchInfo.Prefixes);
			return source.Any((Patch p) => p.owner == "cobalt.aggrokit");
		}
	}
}
public static class TargetableOverrides
{
	[HarmonyPatch(typeof(TargetingSystem), "IsTargetable", new Type[] { typeof(Character) })]
	internal static class TargetingSystem_IsTargetable_Character
	{
		private static void Postfix(TargetingSystem __instance, Character _char, ref bool __result)
		{
			if ((s_pair.Count != 0 || s_all.Count != 0) && TryResolve(s_owner.Invoke(__instance), _char, out var forced))
			{
				__result = forced;
			}
		}
	}

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

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

	private static readonly FieldRef<TargetingSystem, Character> s_owner = AccessTools.FieldRefAccess<TargetingSystem, Character>("m_character");

	public const string DevOwner = "ak_cmd";

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

	public static int Count => s_pair.Count + s_all.Count;

	private static void Own(string store, string key, string owner)
	{
		s_owners[store + "|" + key] = (string.IsNullOrEmpty(owner) ? "ak_cmd" : owner);
	}

	private static void Disown(string store, string key)
	{
		s_owners.Remove(store + "|" + key);
	}

	private static string OwnerOf(string store, string key)
	{
		if (!s_owners.TryGetValue(store + "|" + key, out var value))
		{
			return "ak_cmd";
		}
		return value;
	}

	public static void Set(Character attacker, Character target, bool targetable, string owner = "ak_cmd")
	{
		if (Object.op_Implicit((Object)(object)attacker) && Object.op_Implicit((Object)(object)target))
		{
			string key = PairKey(attacker, target);
			s_pair[key] = targetable;
			Own("pair", key, owner);
		}
	}

	public static void Clear(Character attacker, Character target)
	{
		if (Object.op_Implicit((Object)(object)attacker) && Object.op_Implicit((Object)(object)target))
		{
			string key = PairKey(attacker, target);
			s_pair.Remove(key);
			Disown("pair", key);
		}
	}

	public static void SetForAll(Character target, bool targetable, string owner = "ak_cmd")
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)target))
		{
			string key = UID.op_Implicit(target.UID);
			s_all[key] = targetable;
			Own("all", key, owner);
		}
	}

	public static void ClearForAll(Character target)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)target))
		{
			string key = UID.op_Implicit(target.UID);
			s_all.Remove(key);
			Disown("all", key);
		}
	}

	public static void ClearAll()
	{
		s_pair.Clear();
		s_all.Clear();
		s_owners.Clear();
	}

	public static int ClearOwnedBy(string owner)
	{
		if (string.IsNullOrEmpty(owner))
		{
			owner = "ak_cmd";
		}
		return RemoveOwned(s_pair, "pair", owner) + RemoveOwned(s_all, "all", owner);
	}

	private static int RemoveOwned(Dictionary<string, bool> store, string storeName, string owner)
	{
		List<string> list = new List<string>();
		foreach (string key in store.Keys)
		{
			if (OwnerOf(storeName, key) == owner)
			{
				list.Add(key);
			}
		}
		foreach (string item in list)
		{
			store.Remove(item);
			Disown(storeName, item);
		}
		return list.Count;
	}

	public static int CountOwnedBy(string owner)
	{
		if (string.IsNullOrEmpty(owner))
		{
			owner = "ak_cmd";
		}
		int num = 0;
		foreach (string key in s_pair.Keys)
		{
			if (OwnerOf("pair", key) == owner)
			{
				num++;
			}
		}
		foreach (string key2 in s_all.Keys)
		{
			if (OwnerOf("all", key2) == owner)
			{
				num++;
			}
		}
		return num;
	}

	private static string PairKey(Character attacker, Character target)
	{
		//IL_0001: 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)
		return UID.op_Implicit(attacker.UID) + "|" + UID.op_Implicit(target.UID);
	}

	internal static bool TryResolve(Character attacker, Character target, out bool forced)
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		forced = false;
		if ((Object)(object)target == (Object)null)
		{
			return false;
		}
		if ((Object)(object)attacker != (Object)null && s_pair.Count > 0 && s_pair.TryGetValue(PairKey(attacker, target), out forced))
		{
			return true;
		}
		return s_all.TryGetValue(UID.op_Implicit(target.UID), out forced);
	}
}
public static class TauntController
{
	private sealed class HoldRecord
	{
		public CharacterAI Enemy;

		public Character Protect;

		public float Until;

		public float NextAssertAt;

		public float LeashMeters;

		public string Owner;

		public Action<string> Log;

		public int Asserts;

		public int Corrections;
	}

	private const float ReassertSeconds = 0.3f;

	public const string DevOwner = "ak_cmd";

	private static readonly List<HoldRecord> s_holds = new List<HoldRecord>();

	private static string s_last = "never taunted";

	public static bool Active => s_holds.Count > 0;

	public static int Count => s_holds.Count;

	public static string Forensics => ForensicsFor(null);

	public static string ForensicsFor(string owner)
	{
		StringBuilder stringBuilder = new StringBuilder();
		int num = 0;
		for (int i = 0; i < s_holds.Count; i++)
		{
			HoldRecord holdRecord = s_holds[i];
			if (owner == null || !(holdRecord.Owner != owner))
			{
				if (num > 0)
				{
					stringBuilder.Append(" · ");
				}
				string[] obj = new string[6] { "ACTIVE on '", null, null, null, null, null };
				CharacterAI enemy = holdRecord.Enemy;
				object obj2;
				if (enemy == null)
				{
					obj2 = null;
				}
				else
				{
					Character character = ((CharacterControl)enemy).Character;
					obj2 = ((character != null) ? character.Name : null);
				}
				obj[1] = (string)obj2;
				obj[2] = "' (anchor '";
				Character protect = holdRecord.Protect;
				obj[3] = ((protect != null) ? protect.Name : null);
				obj[4] = "'), ";
				obj[5] = $"{Mathf.Max(0f, holdRecord.Until - Time.time):F1}s left, {holdRecord.Asserts} asserts ({holdRecord.Corrections} steal-corrections)";
				stringBuilder.Append(string.Concat(obj));
				num++;
			}
		}
		if (num != 0)
		{
			return stringBuilder.ToString();
		}
		return s_last;
	}

	public static bool Hold(Character protect, Character enemy, float seconds, float leashMeters, string owner = "ak_cmd", Action<string> log = null)
	{
		Action<string> action = log ?? new Action<string>(DefaultLog);
		if ((Object)(object)enemy == (Object)null || seconds <= 0f)
		{
			return false;
		}
		if ((Object)(object)protect == (Object)null || !protect.Alive)
		{
			action("[TAUNT] no live anchor to pin onto — taunt skipped.");
			return false;
		}
		CharacterAI component = ((Component)enemy).GetComponent<CharacterAI>();
		if ((Object)(object)component == (Object)null)
		{
			action("[TAUNT] '" + enemy.Name + "' has no CharacterAI — nothing to taunt.");
			return false;
		}
		for (int num = s_holds.Count - 1; num >= 0; num--)
		{
			if ((Object)(object)s_holds[num].Protect == (Object)(object)protect)
			{
				s_holds.RemoveAt(num);
			}
		}
		s_holds.Add(new HoldRecord
		{
			Enemy = component,
			Protect = protect,
			Until = Time.time + seconds,
			NextAssertAt = 0f,
			LeashMeters = leashMeters,
			Owner = (string.IsNullOrEmpty(owner) ? "ak_cmd" : owner),
			Log = action
		});
		action($"[TAUNT] '{enemy.Name}' locked onto the companion for {seconds:F1}s.");
		return true;
	}

	public static void Tick()
	{
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		if (s_holds.Count == 0)
		{
			return;
		}
		for (int num = s_holds.Count - 1; num >= 0; num--)
		{
			HoldRecord holdRecord = s_holds[num];
			if (Time.time >= holdRecord.Until)
			{
				ReleaseAt(num, "expiry");
			}
			else
			{
				Character val = (((Object)(object)holdRecord.Enemy != (Object)null) ? ((CharacterControl)holdRecord.Enemy).Character : null);
				if ((Object)(object)val == (Object)null || !val.Alive)
				{
					ReleaseAt(num, "enemy dead");
				}
				else
				{
					Character protect = holdRecord.Protect;
					if ((Object)(object)protect == (Object)null || !protect.Alive)
					{
						ReleaseAt(num, "anchor down");
					}
					else if (holdRecord.LeashMeters > 0f && Vector3.Distance(((Component)protect).transform.position, ((Component)val).transform.position) > holdRecord.LeashMeters)
					{
						ReleaseAt(num, "beyond leash");
					}
					else if (!(Time.time < holdRecord.NextAssertAt))
					{
						holdRecord.NextAssertAt = Time.time + 0.3f;
						bool flag = (Object)(object)holdRecord.Enemy.TargetingSystem != (Object)null && (Object)(object)holdRecord.Enemy.TargetingSystem.LockedCharacter != (Object)(object)protect;
						if (AggroTools.ForceTarget(holdRecord.Enemy, protect))
						{
							holdRecord.Asserts++;
							if (flag && holdRecord.Asserts > 1)
							{
								holdRecord.Corrections++;
								(holdRecord.Log ?? new Action<string>(DefaultLog))("[TAUNT] '" + val.Name + "' had switched targets — lock re-asserted onto the companion " + $"(correction #{holdRecord.Corrections}).");
							}
						}
					}
				}
			}
		}
	}

	public static int ReleaseAll(string reason)
	{
		int count = s_holds.Count;
		for (int num = s_holds.Count - 1; num >= 0; num--)
		{
			ReleaseAt(num, reason);
		}
		return count;
	}

	public static int ReleaseOwnedBy(string owner, string reason)
	{
		if (string.IsNullOrEmpty(owner))
		{
			owner = "ak_cmd";
		}
		int num = 0;
		for (int num2 = s_holds.Count - 1; num2 >= 0; num2--)
		{
			if (s_holds[num2].Owner == owner)
			{
				ReleaseAt(num2, reason);
				num++;
			}
		}
		return num;
	}

	public static int CountOwnedBy(string owner)
	{
		if (string.IsNullOrEmpty(owner))
		{
			owner = "ak_cmd";
		}
		int num = 0;
		for (int i = 0; i < s_holds.Count; i++)
		{
			if (s_holds[i].Owner == owner)
			{
				num++;
			}
		}
		return num;
	}

	private static void ReleaseAt(int index, string reason)
	{
		HoldRecord holdRecord = s_holds[index];
		s_holds.RemoveAt(index);
		object[] obj = new object[4] { reason, null, null, null };
		CharacterAI enemy = holdRecord.Enemy;
		object obj2;
		if (enemy == null)
		{
			obj2 = null;
		}
		else
		{
			Character character = ((CharacterControl)enemy).Character;
			obj2 = ((character != null) ? character.Name : null);
		}
		obj[1] = obj2;
		obj[2] = holdRecord.Asserts;
		obj[3] = holdRecord.Corrections;
		s_last = string.Format("released ({0}): held '{1}' with {2} asserts, {3} steal-corrections", obj);
		(holdRecord.Log ?? new Action<string>(DefaultLog))("[TAUNT] " + s_last + ".");
	}

	private static void DefaultLog(string line)
	{
		ManualLogSource log = Plugin.Log;
		if (log != null)
		{
			log.LogMessage((object)line);
		}
	}
}