Decompiled source of Immaculate Covenant v0.4.0

ImmaculateCovenant.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using ImmaculateCovenant.Camp;
using ImmaculateCovenant.Companion;
using ImmaculateCovenant.Debug;
using ImmaculateCovenant.Dialogue;
using ImmaculateCovenant.Quests;
using ImmaculateCovenant.Rally;
using ImmaculateCovenant.World;
using NodeCanvas.BehaviourTrees;
using NodeCanvas.DialogueTrees;
using NodeCanvas.Framework;
using NodeCanvas.Framework.Internal;
using NodeCanvas.Tasks.Actions;
using NodeCanvas.Tasks.Conditions;
using Photon;
using SideLoader;
using SideLoader.Managers;
using SideLoader.Model;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace ImmaculateCovenant
{
	internal static class Camps
	{
		public const string Enmerkar = "Enmerkar";

		public const string Chersonese = "Chersonese";

		public const string HallowedMarsh = "HallowedMarsh";

		public const string Abrassar = "Abrassar";

		public const string AntiqueField = "AntiqueField";

		public const string Caldera = "Caldera";

		public static readonly string[] All = new string[6] { "Enmerkar", "Chersonese", "HallowedMarsh", "Abrassar", "AntiqueField", "Caldera" };

		public static string GraphOf(string camp)
		{
			if (camp == "Enmerkar")
			{
				return "Dialogue_Emercar_Immaculate_Real";
			}
			return "Dialogue_" + camp + "_Immaculate_Real";
		}

		public static string RegionName(string camp)
		{
			return camp switch
			{
				"Enmerkar" => "Enmerkar Forest", 
				"HallowedMarsh" => "Hallowed Marsh", 
				"AntiqueField" => "Antique Plateau", 
				_ => camp, 
			};
		}

		public static string CampOfScene(string scene)
		{
			return Array.Find(All, (string c) => SceneOf(c) == scene);
		}

		public static string SceneOf(string camp)
		{
			return camp switch
			{
				"Enmerkar" => "EmercarDungeonsSmall", 
				"Chersonese" => "ChersoneseDungeonsSmall", 
				"HallowedMarsh" => "HallowedDungeonsSmall", 
				"Abrassar" => "AbrassarDungeonsSmall", 
				"AntiqueField" => "AntiqueFieldDungeonsSmall", 
				"Caldera" => "CalderaDungeonsSmall", 
				_ => null, 
			};
		}
	}
	internal static class CovenantEvents
	{
		public const string FactionImmaculate = "tchernobill.immcov.faction";

		public const string Q0Stage = "tchernobill.immcov.q0.stage";

		public const string Q0TaskCartilage = "tchernobill.immcov.q0.task.cartilage";

		public const string Q0TaskMark = "tchernobill.immcov.q0.task.mark";

		public const string Q0TaskBook = "tchernobill.immcov.q0.task.book";

		public const string LightMenderGiven = "tchernobill.immcov.lexicon.lightmender";

		public const string ForbiddenGiven = "tchernobill.immcov.lexicon.forbidden";

		public const string Q1Stage = "tchernobill.immcov.q1.stage";

		public const string Q1Companion = "tchernobill.immcov.q1.companion";

		public const string Q1Failed = "tchernobill.immcov.q1.failed";

		public const string Q1VitalHealth = "tchernobill.immcov.q1.vital.health";

		public const string Q1VitalStamina = "tchernobill.immcov.q1.vital.stamina";

		public const string Q1VitalMana = "tchernobill.immcov.q1.vital.mana";

		public const string Q1ZephyrienDone = "tchernobill.immcov.q1.zephyrien";

		public const string CorruptedLexiconGiven = "tchernobill.immcov.lexicon.corrupted";

		public const string Q2Stage = "tchernobill.immcov.q2.stage";

		public const string Q2Tracked = "tchernobill.immcov.q2.tracked";

		public const string Q2KevinMet = "tchernobill.immcov.q2.kevin";

		public const string Q2FactBlood = "tchernobill.immcov.q2.fact.blood";

		public const string Q2FactLevant = "tchernobill.immcov.q2.fact.levant";

		public const string Q2Failed = "tchernobill.immcov.q2.failed";

		public const string Rallied = "tchernobill.immcov.rallied";

		public const string FactionChosen = "bjVloYMQxk6KXx0gph2A1Q";

		public const string FactionBlueChamber = "nllwi4FnR0qN7968EsJ07A";

		public const string FactionHeroicKingdom = "SV3XUncnQU60ozhj9xTRpg";

		public const string FactionHolyMission = "1tkTEWMPbEGKmWItwxy4kQ";

		public const string FactionSorobore = "TSqqSaAA-kekbG6ml8yQ6Q";

		public const string DoneQuest0 = "P2rqNERqN0O1RhkD1ff7_w";

		public const string DoneQuest1 = "HbTd6ustoU-VhQeidJcAEw";

		public const string DoneQuest2 = "Wl08NWMJokemPVfTEyT3UA";

		public const string ReadyQ3 = "M9jv0FFhbEmAEham4pMO3Q";

		public const string VendavelQuestTimer = "bm3rB3abI0KFok2x5P0lrg";

		public const string CallToAdventureCompleted = "ZYzrMi1skUiJ4BgXXQ3sfw";

		public const string ImmaculateDead = "MTRMDD0o0UGhfrQ4-pRNYw";

		public static readonly string[] OtherCampsVisited = new string[6] { "esrjYN5-c0m8keOg90Y3_w", "5WhWAil3tEmon4-3uBqZiQ", "3NcmHOsj6USG4jwTm2_W0A", "D7mq-YiVdESQPjm1g8CX5A", "IHPdYtJA-UOACoeqm-cPTQ", "nWcZgFRgwkilRj8koIaTRw" };

		public static void Register()
		{
			Create("tchernobill.immcov.faction", "IC_FactionCommitment", "Faction_Immaculate", "Player chose the Immaculate Covenant");
			Create("tchernobill.immcov.q0.stage", "IC_Q0_Recruitment", "Q0_Stage", "1 offered, 2 task done, 3 sworn", stackable: true);
			Create("tchernobill.immcov.q0.task.cartilage", "IC_Q0_Recruitment", "Q0_Task_Cartilage", "Gave 3 Thorny Cartilage");
			Create("tchernobill.immcov.q0.task.mark", "IC_Q0_Recruitment", "Q0_Task_Mark", "Showed the Scourge's mark (Corruption)");
			Create("tchernobill.immcov.q0.task.book", "IC_Q0_Recruitment", "Q0_Task_Book", "Gave a lexicon");
			Create("tchernobill.immcov.lexicon.lightmender", "IC_Books", "LightMenderGiven", "The Immaculate keeps the Light Mender's Lexicon");
			Create("tchernobill.immcov.lexicon.forbidden", "IC_Books", "ForbiddenGiven", "The Immaculate keeps a Forbidden Knowledge lexicon");
			Create("tchernobill.immcov.q1.stage", "IC_Q1_TwoHearts", "Q1_Stage", "1 offered, 2 together, 3 sailed, 4 done", stackable: true);
			Create("tchernobill.immcov.q1.companion", "IC_Q1_TwoHearts", "Q1_Companion", "The Immaculate travels with the player");
			Create("tchernobill.immcov.q1.failed", "IC_Q1_TwoHearts", "Q1_Failed", "The Immaculate was lost during the companionship");
			Create("tchernobill.immcov.q1.vital.health", "IC_Q1_TwoHearts", "Q1_Vital_Health", "Talked about health");
			Create("tchernobill.immcov.q1.vital.stamina", "IC_Q1_TwoHearts", "Q1_Vital_Stamina", "Talked about stamina");
			Create("tchernobill.immcov.q1.vital.mana", "IC_Q1_TwoHearts", "Q1_Vital_Mana", "Talked about mana at the Ley Line");
			Create("tchernobill.immcov.q1.zephyrien", "IC_Q1_TwoHearts", "Q1_Zephyrien", "Zephyrien beaten in the Conflux Chambers");
			Create("tchernobill.immcov.lexicon.corrupted", "IC_Books", "CorruptedLexiconGiven", "The Corrupted Lexicon was handed back");
			TwoHeartsPlaces.Register();
			Create("tchernobill.immcov.q2.stage", "IC_Q2_AnotherLeash", "Q2_Stage", "1 offered, 2 done", stackable: true);
			Create("tchernobill.immcov.q2.tracked", "IC_Q2_AnotherLeash", "Q2_Tracked", "Went after the jar-carriers (Dead Roots)");
			Create("tchernobill.immcov.q2.kevin", "IC_Q2_AnotherLeash", "Q2_KevinMet", "Met Kevin leaving Dead Roots");
			Create("tchernobill.immcov.q2.fact.blood", "IC_Q2_AnotherLeash", "Q2_Fact_Blood", "Kevin let slip: Immaculate blood, by the jar");
			Create("tchernobill.immcov.q2.fact.levant", "IC_Q2_AnotherLeash", "Q2_Fact_Levant", "Kevin let slip: he goes back to Levant");
			Create("tchernobill.immcov.q2.failed", "IC_Q2_AnotherLeash", "Q2_Failed", "Purifier failed before Q2 ended");
			Create("tchernobill.immcov.rallied", "IC_General", "Rallied", "Immaculates rallied to the Covenant (finale)", stackable: true);
			RallyRegistry.RegisterEvents();
			CampQuests.RegisterEvents();
		}

		private static void Create(string uid, string family, string name, string desc, bool stackable = false, bool timed = false)
		{
			CustomQuests.CreateQuestEvent(uid, timed, stackable, true, family, name, desc);
		}

		public static bool Has(string uid)
		{
			if ((Object)(object)QuestEventManager.Instance != (Object)null)
			{
				return QuestEventManager.Instance.HasQuestEvent(uid);
			}
			return false;
		}

		public static int Stack(string uid)
		{
			if (!((Object)(object)QuestEventManager.Instance != (Object)null))
			{
				return 0;
			}
			return Math.Max(0, QuestEventManager.Instance.GetEventCurrentStack(uid));
		}

		public static void Add(string uid, int stack = 1)
		{
			if (stack > 0)
			{
				QuestEventManager.Instance.AddEvent(uid, stack, true);
			}
		}

		public static void Remove(string uid)
		{
			QuestEventManager instance = QuestEventManager.Instance;
			if (instance != null)
			{
				instance.RemoveEvent(uid, true);
			}
		}

		public static void RaiseTo(string uid, int value)
		{
			Add(uid, value - Stack(uid));
		}

		public static bool Elapsed(string uid, int hours)
		{
			if (Has(uid))
			{
				return QuestEventManager.Instance.CheckEventExpire(uid, hours);
			}
			return false;
		}
	}
	internal static class Ids
	{
		public const int Q0_Recruitment = -71100;

		public const int Q1_TwoHearts = -71101;

		public const int Q2_AnotherLeash = -71102;

		public const int Q3_TwoBirdsOneBullet = -71103;

		public const int Q4_Finale = -71104;

		public const int CorruptedLexicon = -71105;

		public const int TwoHearts_Health = -71106;

		public const int TwoHearts_Stamina = -71107;

		public const int TwoHearts_Mana = -71108;

		public const int Q5_Dreamer = -71109;

		public const int Q2_InfuseDecay = -71110;

		public const int Q2_DecayImbue = -71111;

		public const int Q2_ImmaculateBalance = -71112;

		public const int Rally_TheGathering = -71113;

		public const int Rally_QuietingRune = -71114;

		public const int Rally_Quieted = -71115;

		public const int Camp_KinErrands = -71116;
	}
	internal static class Membership
	{
		[HarmonyPatch(typeof(QuestEventManager), "AddEvent", new Type[]
		{
			typeof(string),
			typeof(int),
			typeof(bool)
		})]
		private static class BlockLockedFlags
		{
			private static bool Prefix(string _eventUID, ref bool __result)
			{
				if (_eventUID == null || !Locked.Contains(_eventUID) || !IsMember)
				{
					return true;
				}
				__result = false;
				return false;
			}
		}

		public const int Possessed = 8200190;

		public const string EnmerkarVisited = "Cm_v5-h2zUexTtxQUJNNFg";

		public const string ImmaculateItems = "vyd636V_RE-RqTzBdB3vYg";

		public static readonly HashSet<string> Locked = new HashSet<string>(CovenantEvents.OtherCampsVisited.Concat(new string[2] { "Cm_v5-h2zUexTtxQUJNNFg", "vyd636V_RE-RqTzBdB3vYg" }));

		public const int LookingToTheFuture = 7011002;

		public static bool IsMember => CovenantEvents.Has("tchernobill.immcov.faction");

		public static void Init()
		{
			Plugin.OnWorldLoaded("Looking to the Future", CompleteLookingToTheFuture);
		}

		public static void CompleteLookingToTheFuture()
		{
			if (!IsMember)
			{
				return;
			}
			CharacterManager instance = CharacterManager.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				Character worldHostCharacter = instance.GetWorldHostCharacter();
				if (worldHostCharacter == null)
				{
					obj = null;
				}
				else
				{
					CharacterInventory inventory = worldHostCharacter.Inventory;
					obj = ((inventory != null) ? inventory.QuestKnowledge : null);
				}
			}
			CharacterQuestKnowledge val = (CharacterQuestKnowledge)obj;
			if (!((Object)(object)val == (Object)null) && ((CharacterKnowledge)val).IsItemLearned(7011002))
			{
				Item itemFromItemID = ((CharacterKnowledge)val).GetItemFromItemID(7011002);
				Item obj2 = ((itemFromItemID is Quest) ? itemFromItemID : null);
				QuestProgress val2 = ((obj2 != null) ? ((Component)obj2).GetComponent<QuestProgress>() : null);
				if (!((Object)(object)val2 == (Object)null) && !val2.IsCompleted)
				{
					val2.SetIsCompleted(true);
					Plugin.Log.LogMessage((object)"[Covenant] 'Looking to the Future' completed (faction commitment)");
				}
			}
		}

		public static void Join(Character player)
		{
			foreach (string item in Locked)
			{
				CovenantEvents.Remove(item);
			}
			CompleteLookingToTheFuture();
			if ((Object)(object)player != (Object)null && !((CharacterKnowledge)player.Inventory.SkillKnowledge).IsItemLearned(8200190))
			{
				player.Inventory.ReceiveSkillReward(8200190);
				Plugin.Log.LogMessage((object)"[Covenant] Possessed taught with the oath");
			}
		}
	}
	[BepInPlugin("Tchernobill.ImmaculateCovenant", "Immaculate Covenant", "0.4.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "Tchernobill.ImmaculateCovenant";

		public const string NAME = "Immaculate Covenant";

		public const string VERSION = "0.4.0";

		internal static ManualLogSource Log;

		internal static Plugin Instance;

		private static readonly JournalQuest[] Quests = new JournalQuest[5]
		{
			RecruitmentQuest.Journal,
			TwoHeartsQuest.Journal,
			AnotherLeashQuest.Journal,
			GatheringQuest.Journal,
			CampQuests.ErrandsQuest.Journal
		};

		private static bool toldCoop;

		internal static bool ForceCoop;

		internal static bool Solo
		{
			get
			{
				if (!ForceCoop && !PhotonNetwork.isNonMasterClientInRoom && (PhotonNetwork.room == null || PhotonNetwork.room.PlayerCount <= 1))
				{
					if (!((Object)(object)CharacterManager.Instance == (Object)null))
					{
						return CharacterManager.Instance.PlayerCharacters.Count <= 1;
					}
					return true;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			CovenantEvents.Register();
			SL.OnPacksLoaded += delegate
			{
				Guard("build quests", delegate
				{
					JournalQuest[] quests = Quests;
					for (int i = 0; i < quests.Length; i++)
					{
						quests[i].Build();
					}
				});
			};
			SL.OnPacksLoaded += delegate
			{
				Guard("build items", delegate
				{
					TwoHeartsBonuses.Build();
					Lexicons.BuildCorruptedLexicon();
					AnotherLeashSkills.Build();
					QuietingRune.Build();
				});
			};
			SL.OnGameplayResumedAfterLoading += delegate
			{
				Guard("refresh quests", delegate
				{
					JournalQuest[] quests = Quests;
					for (int i = 0; i < quests.Length; i++)
					{
						quests[i].Refresh();
					}
				});
			};
			SL.OnGameplayResumedAfterLoading += delegate
			{
				Guard("co-op notice", TellCoop);
			};
			CampDialogues.Register();
			SelfTest.Init();
			CampTest.Init();
			CompanionKeeper.HoldDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Companion", "HoldDistance", 3f, "The Immaculate stays put while his place beside you is closer than this (metres).");
			CompanionKeeper.PushGap = ((BaseUnityPlugin)this).Config.Bind<float>("Companion", "PushGap", 0.1f, "Walking into the Immaculate pushes him aside when the gap between your bodies is below this (metres).");
			ButcherSpar.MaxLessons = ((BaseUnityPlugin)this).Config.Bind<int>("Rally", "ButcherMaxLessons", -1, "The most lessons the rallied Butcher can learn from the matches he loses (+4% health, +3 impact resistance, +3% damage each). -1: no limit.");
			Membership.Init();
			FiCompanion.Init();
			TwoHeartsWorld.Register();
			AnotherLeashWorld.Register();
			RallyWorld.Register();
			RallyCamp.Register();
			CampWalk.Register();
			CampNets.Register();
			CompanionTest.Init();
			SceneDump.Init();
			TemplateDump.Init();
			ItemDump.Init();
			Q1Test.Init();
			Q2Test.Init();
			RallyTest.Init();
			Rally5Test.Init();
			RoamTest.Init();
			CampQuestTest.Init();
			PerfCheck.Init();
			PerfScenario.Init();
			new Harmony("Tchernobill.ImmaculateCovenant").PatchAll();
			Log.LogMessage((object)"Immaculate Covenant 0.4.0 loaded.");
		}

		internal static void OnWorldLoaded(string what, Action action)
		{
			SL.OnGameplayResumedAfterLoading += delegate
			{
				if (Solo)
				{
					Guard(what, action);
				}
			};
		}

		private static void TellCoop()
		{
			if (Solo || toldCoop)
			{
				return;
			}
			toldCoop = true;
			Log.LogMessage((object)"co-op detected: the Covenant's world content stays off (solo mod)");
			CharacterManager instance = CharacterManager.Instance;
			if (instance == null)
			{
				return;
			}
			Character firstLocalCharacter = instance.GetFirstLocalCharacter();
			if (firstLocalCharacter != null)
			{
				CharacterUI characterUI = firstLocalCharacter.CharacterUI;
				if (characterUI != null)
				{
					characterUI.ShowInfoNotification("Immaculate Covenant is a solo mod: its quests and characters stay away in co-op.");
				}
			}
		}

		internal static void Guard(string what, Action action)
		{
			try
			{
				action();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"{what} failed: {arg}");
			}
		}
	}
}
namespace ImmaculateCovenant.World
{
	internal static class Doors
	{
		[HarmonyPatch(typeof(InteractionSwitchArea), "OnActivate")]
		private static class Patch
		{
			private static bool Prefix(InteractionSwitchArea __instance)
			{
				//IL_009d: Unknown result type (might be due to invalid IL or missing references)
				foreach (var rule in rules)
				{
					string item = rule.name;
					Func<InteractionSwitchArea, bool> refuses = rule.refuses;
					bool refused = false;
					Plugin.Guard("door rule '" + item + "'", delegate
					{
						refused = refuses(__instance);
					});
					if (refused)
					{
						((InteractionBase)__instance).InterruptActivation();
						Plugin.Log.LogMessage((object)$"[Doors] door to {__instance.Area} refused by '{item}'");
						return false;
					}
				}
				foreach (var observer in observers)
				{
					var (text, passes) = observer;
					Plugin.Guard("door observer '" + text + "'", delegate
					{
						passes(__instance);
					});
				}
				return true;
			}
		}

		private static readonly List<(int order, string name, Func<InteractionSwitchArea, bool> refuses)> rules = new List<(int, string, Func<InteractionSwitchArea, bool>)>();

		private static readonly List<(string name, Action<InteractionSwitchArea> passes)> observers = new List<(string, Action<InteractionSwitchArea>)>();

		public static void Refuse(int order, string name, Func<InteractionSwitchArea, bool> refuses)
		{
			rules.Add((order, name, refuses));
			rules.Sort(((int order, string name, Func<InteractionSwitchArea, bool> refuses) a, (int order, string name, Func<InteractionSwitchArea, bool> refuses) b) => a.order.CompareTo(b.order));
		}

		public static void OnPass(string name, Action<InteractionSwitchArea> passes)
		{
			observers.Add((name, passes));
		}
	}
	internal static class Deaths
	{
		[HarmonyPatch(typeof(Character), "Die")]
		private static class Patch
		{
			private static bool Prefix(Character __instance, out bool __state)
			{
				__state = false;
				foreach (var rule in rules)
				{
					string item = rule.name;
					Func<Character, bool> spares = rule.spares;
					bool spared = false;
					Plugin.Guard("death rule '" + item + "'", delegate
					{
						spared = spares(__instance);
					});
					if (spared)
					{
						__state = true;
						return false;
					}
				}
				return true;
			}

			private static void Postfix(Character __instance, bool __state)
			{
				if (__state || (Object)(object)__instance == (Object)null || !__instance.IsDead)
				{
					return;
				}
				foreach (var observer in observers)
				{
					var (text, died) = observer;
					Plugin.Guard("death observer '" + text + "'", delegate
					{
						died(__instance);
					});
				}
			}
		}

		private static readonly List<(int order, string name, Func<Character, bool> spares)> rules = new List<(int, string, Func<Character, bool>)>();

		private static readonly List<(string name, Action<Character> died)> observers = new List<(string, Action<Character>)>();

		public static void Spare(int order, string name, Func<Character, bool> spares)
		{
			rules.Add((order, name, spares));
			rules.Sort(((int order, string name, Func<Character, bool> spares) a, (int order, string name, Func<Character, bool> spares) b) => a.order.CompareTo(b.order));
		}

		public static void OnDeath(string name, Action<Character> died)
		{
			observers.Add((name, died));
		}
	}
}
namespace ImmaculateCovenant.Rally
{
	internal static class ButcherSpar
	{
		private class Watch : MonoBehaviour
		{
			private void Update()
			{
				Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
				if (Active && !((Object)(object)firstLocalCharacter == (Object)null))
				{
					if (firstLocalCharacter.Stats.CurrentHealth <= firstLocalCharacter.Stats.ActiveMaxHealth * 0.1f)
					{
						End(heWon: true);
					}
					else if (Fighter.Stats.CurrentHealth <= Fighter.Stats.ActiveMaxHealth * 0.1f)
					{
						End(heWon: false);
					}
				}
			}
		}

		private class Snapshot
		{
			private float health;

			private float stamina;

			private float mana;

			private float burntHealth;

			private float burntStamina;

			private float burntMana;

			private HashSet<string> statuses;

			public static Snapshot Take(Character p)
			{
				CharacterStats stats = p.Stats;
				return new Snapshot
				{
					health = stats.CurrentHealth,
					stamina = stats.CurrentStamina,
					mana = stats.CurrentMana,
					burntHealth = stats.m_burntHealth,
					burntStamina = stats.m_burntStamina,
					burntMana = stats.m_burntMana,
					statuses = new HashSet<string>(from s in p.StatusEffectMngr.Statuses
						where (Object)(object)s != (Object)null
						select s.IdentifierName)
				};
			}

			public void Restore(Character p)
			{
				CharacterStats stats = p.Stats;
				stats.m_burntHealth = burntHealth;
				stats.m_burntStamina = burntStamina;
				stats.m_burntMana = burntMana;
				stats.SetHealth(health);
				stats.m_stamina = stamina;
				stats.SetMana(mana);
				foreach (StatusEffect item in p.StatusEffectMngr.Statuses.Where((StatusEffect s) => (Object)(object)s != (Object)null && !statuses.Contains(s.IdentifierName)).ToList())
				{
					p.StatusEffectMngr.RemoveStatusWithIdentifierName(item.IdentifierName);
				}
			}
		}

		public const string Challenge = "Are you ready to fight, little one?";

		public const string Ready = "I have never been more ready.";

		public const string NotNow = "Not now.";

		public const string Afraid = "Tsss... Don't be afraid to take a lesson.";

		public const string HeWon = "I got you this time!";

		public const string HeLost = "At least I learned something...";

		internal static ConfigEntry<int> MaxLessons;

		private const float EndsBelow = 0.1f;

		private const string Source = "ImmCov_ButcherLessons";

		public static readonly Vector3 ButcherSpot = new Vector3(1199f, 0f, 51.7f);

		public static readonly Vector3 PlayerSpot = new Vector3(1199f, 0f, 43f);

		private static Character campButcher;

		private static Snapshot before;

		private static bool ending;

		public const float LairRadius = 80f;

		private static int Cap
		{
			get
			{
				if (MaxLessons != null)
				{
					return MaxLessons.Value;
				}
				return -1;
			}
		}

		public static Character Fighter { get; private set; }

		public static bool Active => (Object)(object)Fighter != (Object)null;

		public static int Lessons
		{
			get
			{
				if (Cap >= 0)
				{
					return Math.Min(Cap, CovenantEvents.Stack("tchernobill.immcov.rally.butcher.lessons"));
				}
				return CovenantEvents.Stack("tchernobill.immcov.rally.butcher.lessons");
			}
		}

		private static void Log(string s)
		{
			RallyWorld.Log("[spar] " + s);
		}

		public static bool IsFighter(Character c)
		{
			if ((Object)(object)c != (Object)null)
			{
				return (Object)(object)c == (Object)(object)Fighter;
			}
			return false;
		}

		public static void ClearLair(Character butcher)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_007a: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			foreach (Character item in CharacterManager.Instance.Characters.Values.ToList())
			{
				if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)butcher) && !((Object)(object)item == (Object)(object)Fighter) && item.IsAI && !(((Component)item).gameObject.scene != activeScene) && !RallyCamp.IsCampNpc(item) && !(Vector3.Distance(((Component)item).transform.position, ButcherSpot) > 80f))
				{
					((Component)item).gameObject.SetActive(false);
					Log("his lair keeps no guard: '" + ((Object)item).name + "' switched off");
				}
			}
		}

		public static DialogueTreeExt CampDialogue(RallyCandidate c)
		{
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Expected O, but got Unknown
			DialogueKit dialogueKit = DialogueKit.NewTree("ImmCov_Rallied_" + c.Id, c.Speaker);
			FinishNode val = dialogueKit.End();
			MultipleChoiceNodeExt val2 = dialogueKit.Choices("I have never been more ready.", "Not now.");
			((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val2, dialogueKit.Chain((Node)dialogueKit.Act("spar with the Butcher", delegate
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(Begin());
			}), (Node)val), 0, -1);
			((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val2, dialogueKit.Lines((Node)(object)val, "Tsss... Don't be afraid to take a lesson."), 1, -1);
			dialogueKit.Start(dialogueKit.Lines((Node)(object)val2, "Are you ready to fight, little one?"));
			return (DialogueTreeExt)dialogueKit.Graph;
		}

		private static IEnumerator Begin()
		{
			yield return (object)new WaitForSeconds(0.5f);
			Plugin.Guard("spar start", Start);
		}

		public static void Start()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			if (!Active)
			{
				Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
				RallyCandidate c = RallyRegistry.Get("butcher");
				campButcher = RallyCamp.Find(c);
				Character val = RallyWorld.Find(c);
				if ((Object)(object)firstLocalCharacter == (Object)null || (Object)(object)val == (Object)null)
				{
					Log($"cannot start: player {(Object)(object)firstLocalCharacter != (Object)null}, his body {(Object)(object)val != (Object)null}");
					return;
				}
				before = Snapshot.Take(firstLocalCharacter);
				ending = false;
				Vector3 val2 = PlayerSpot - ButcherSpot;
				val2.y = 0f;
				Fighter = CharacterCloner.Clone(val, "ImmCov_ButcherSparring", ButcherSpot, Quaternion.LookRotation(((Vector3)(ref val2)).normalized), withAi: true);
				ApplyLessons(Fighter, Lessons);
				firstLocalCharacter.Teleport(FiCompanion.FootLevel(firstLocalCharacter, PlayerSpot), Quaternion.LookRotation(-((Vector3)(ref val2)).normalized));
				((Component)Fighter).gameObject.AddComponent<Watch>();
				((MonoBehaviour)Plugin.Instance).StartCoroutine(Engage());
				Log($"match begins (lessons {Lessons}, his max health {Fighter.Stats.ActiveMaxHealth:0})");
			}
		}

		internal static void ApplyLessons(Character c, int n)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			if (n <= 0)
			{
				return;
			}
			CharacterStats stats = c.Stats;
			stats.m_maxHealthStat.AddStack(new StatStack("ImmCov_ButcherLessons", 0.04f * (float)n, (Tag[])null), true);
			stats.m_maxHealthStat.Update();
			stats.m_impactResistance.AddStack(new StatStack("ImmCov_ButcherLessons", 3f * (float)n, (Tag[])null), false);
			stats.m_impactResistance.Update();
			foreach (Stat item in stats.m_damageTypesModifier.Where((Stat d) => d != null))
			{
				item.AddStack(new StatStack("ImmCov_ButcherLessons", 0.03f * (float)n, (Tag[])null), false);
				item.Update();
			}
			stats.SetHealth(stats.ActiveMaxHealth);
		}

		private static IEnumerator Engage()
		{
			yield return (object)new WaitForSeconds(1f);
			Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
			if ((Object)(object)Fighter == (Object)null || (Object)(object)firstLocalCharacter == (Object)null)
			{
				yield break;
			}
			Fighter.ChangeFaction((Factions)2, false);
			Fighter.TargetingSystem.TargetableFactions = (Factions[])(object)new Factions[1] { (Factions)1 };
			Fighter.Stats.SetHealth(Fighter.Stats.ActiveMaxHealth);
			CharacterControl characterControl = Fighter.CharacterControl;
			CharacterAI val = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
			if (val == null)
			{
				yield break;
			}
			Fighter.TargetingSystem.SetLockingPoint(firstLocalCharacter.LockingPoint);
			AIState[] aiStates = val.AiStates;
			int num = 0;
			while (aiStates != null && num < aiStates.Length)
			{
				if (aiStates[num] is AISCombat)
				{
					val.SwitchAiState(num);
					break;
				}
				num++;
			}
		}

		public static void End(bool heWon)
		{
			if (Active && !ending)
			{
				ending = true;
				((MonoBehaviour)Plugin.Instance).StartCoroutine(Finish(heWon));
			}
		}

		private static IEnumerator Finish(bool heWon)
		{
			Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
			Log(heWon ? "he won" : "he lost: one more lesson");
			if (!heWon && (Cap < 0 || CovenantEvents.Stack("tchernobill.immcov.rally.butcher.lessons") < Cap))
			{
				CovenantEvents.Add("tchernobill.immcov.rally.butcher.lessons");
			}
			if ((Object)(object)Fighter != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)Fighter).gameObject);
			}
			Fighter = null;
			if ((Object)(object)firstLocalCharacter != (Object)null)
			{
				TargetingSystem targetingSystem = firstLocalCharacter.TargetingSystem;
				if (targetingSystem != null)
				{
					targetingSystem.ReleaseTarget();
				}
				before?.Restore(firstLocalCharacter);
				if ((Object)(object)campButcher != (Object)null)
				{
					Vector3 position = ((Component)campButcher).transform.position + ((Component)campButcher).transform.forward * 1.8f;
					firstLocalCharacter.Teleport(FiCompanion.FootLevel(firstLocalCharacter, position), Quaternion.LookRotation(-((Component)campButcher).transform.forward));
				}
			}
			yield return (object)new WaitForSeconds(1.5f);
			if ((Object)(object)campButcher != (Object)null)
			{
				DialogueKit dialogueKit = DialogueKit.NewTree("ImmCov_ButcherAfterMatch", RallyRegistry.Get("butcher").Speaker);
				dialogueKit.Start(dialogueKit.Lines((Node)(object)dialogueKit.End(), heWon ? "I got you this time!" : "At least I learned something..."));
				CompanionTalk.PlayFrom(campButcher, RallyRegistry.Get("butcher").Speaker, (DialogueTreeExt)dialogueKit.Graph);
			}
			ending = false;
		}

		private static bool MatchEnds(Character c)
		{
			if (!Active)
			{
				return false;
			}
			if ((Object)(object)c == (Object)(object)Fighter)
			{
				c.Stats.SetHealth(1f);
				End(heWon: false);
				return true;
			}
			if (c.IsLocalPlayer)
			{
				c.Stats.SetHealth(1f);
				End(heWon: true);
				return true;
			}
			return false;
		}

		public static void Register()
		{
			Deaths.Spare(10, "the Butcher's match ends instead", MatchEnds);
		}
	}
	internal class ButcherTrial : MonoBehaviour
	{
		[HarmonyPatch(typeof(Character), "OnReceiveHit")]
		private static class Hurt
		{
			private static void Prefix(Character __instance, Character _dealerChar)
			{
				ButcherTrial component = ((Component)__instance).GetComponent<ButcherTrial>();
				if ((Object)(object)component != (Object)null && (Object)(object)_dealerChar != (Object)null && (Object)(object)_dealerChar.OwnerPlayerSys != (Object)null)
				{
					component.lastPlayerHit = Time.time;
				}
			}
		}

		public const float Needed = 60f;

		public const int CastsNeeded = 3;

		public const string TooThick = "The leash is too thick on this one.";

		private const float Range = 25f;

		public RallyCandidate Candidate;

		public Character Npc;

		public float CombatTime;

		public int Casts;

		private float lastHealth;

		private float lastPlayerHit = -99f;

		private Factions[] targets;

		private float talkStarted;

		public bool Talking { get; private set; }

		internal DialogueTreeExt Tree { get; private set; }

		private static void Log(string s)
		{
			RallyWorld.Log("[butcher] " + s);
		}

		public void Init(RallyCandidate candidate, Character npc)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			Candidate = candidate;
			Npc = npc;
			lastHealth = npc.Stats.CurrentHealth;
			npc.TargetingSystem.TargetableFactions = (Factions[])(object)new Factions[1] { (Factions)1 };
			Log($"trial ready at {((Component)npc).transform.position}");
		}

		private void Update()
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Npc == (Object)null)
			{
				return;
			}
			Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
			if (Talking)
			{
				HoldStill();
				if ((!((Object)(object)firstLocalCharacter != (Object)null) || !((Object)(object)firstLocalCharacter.CharacterUI != (Object)null) || !firstLocalCharacter.CharacterUI.IsDialogueInProgress) && Time.time > talkStarted + 2f)
				{
					Finish();
				}
				return;
			}
			float currentHealth = Npc.Stats.CurrentHealth;
			if (currentHealth < lastHealth - 0.01f && Time.time - lastPlayerHit < 1f)
			{
				if (CombatTime > 0f)
				{
					Log($"he bled after {CombatTime:0.0} s: the count starts again");
				}
				CombatTime = 0f;
			}
			lastHealth = currentHealth;
			if ((Object)(object)firstLocalCharacter != (Object)null && Vector3.Distance(((Component)firstLocalCharacter).transform.position, ((Component)Npc).transform.position) < 25f && (firstLocalCharacter.InCombat || (Object)(object)Npc.TargetingSystem.LockedCharacter == (Object)(object)firstLocalCharacter))
			{
				CombatTime += Time.deltaTime;
			}
		}

		public string OnRune()
		{
			if (Talking)
			{
				return null;
			}
			Casts++;
			Log($"rune {Casts}, {CombatTime:0.0} s without blood");
			if (Casts >= 3 && CombatTime >= 60f)
			{
				((MonoBehaviour)this).StartCoroutine(Talk());
				return null;
			}
			return "The leash is too thick on this one.";
		}

		private IEnumerator Talk()
		{
			Talking = true;
			talkStarted = Time.time;
			Npc.Invincible = true;
			targets = Npc.TargetingSystem.TargetableFactions;
			Npc.TargetingSystem.TargetableFactions = (Factions[])(object)new Factions[0];
			Npc.TargetingSystem.ReleaseTarget();
			CharacterControl characterControl = Npc.CharacterControl;
			CharacterAI val = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
			if (val != null)
			{
				val.SetNPCActive(false);
				val.StopMovement();
				((Behaviour)val).enabled = false;
			}
			Npc.ForceCancel(true, true);
			Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
			if ((Object)(object)firstLocalCharacter != (Object)null)
			{
				Vector3 val2 = ((Component)firstLocalCharacter).transform.position - ((Component)Npc).transform.position;
				val2.y = 0f;
				if (((Vector3)(ref val2)).sqrMagnitude > 0.01f)
				{
					((Component)Npc).transform.rotation = Quaternion.LookRotation(((Vector3)(ref val2)).normalized);
				}
			}
			yield return (object)new WaitForSeconds(1f);
			Tree = RallyDialogue.BuildButcher(Candidate);
			bool flag = CompanionTalk.PlayFrom(Npc, Candidate.Speaker, Tree);
			Log("what sort of game is this: conversation " + (flag ? "started" : "NOT started"));
			talkStarted = Time.time;
		}

		private void HoldStill()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			NavMeshAgent component = ((Component)Npc).GetComponent<NavMeshAgent>();
			if ((Object)(object)component != (Object)null && ((Behaviour)component).enabled && component.isOnNavMesh)
			{
				component.isStopped = true;
				component.velocity = Vector3.zero;
			}
			if ((Object)(object)Npc.Animator != (Object)null)
			{
				Npc.Animator.SetFloat("moveForward", 0f);
				Npc.Animator.SetFloat("moveSide", 0f);
			}
		}

		private void Finish()
		{
			Talking = false;
			if (Candidate.Rallied)
			{
				Log("he joins the Covenant: off to the Antique Plateau camp");
				((Component)Npc).gameObject.SetActive(false);
				return;
			}
			Npc.Invincible = false;
			Npc.TargetingSystem.TargetableFactions = targets;
			NavMeshAgent component = ((Component)Npc).GetComponent<NavMeshAgent>();
			if ((Object)(object)component != (Object)null && ((Behaviour)component).enabled && component.isOnNavMesh)
			{
				component.isStopped = false;
			}
			CharacterControl characterControl = Npc.CharacterControl;
			CharacterAI val = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
			if (val != null)
			{
				((Behaviour)val).enabled = true;
				val.SetNPCActive(true);
			}
			Log("no rally: the fight goes on");
			Object.Destroy((Object)(object)this);
		}
	}
	internal static class CharacterCloner
	{
		private static GameObject holder;

		private static Transform Holder()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if ((Object)(object)holder == (Object)null)
			{
				holder = new GameObject("ImmCov_CloneHolder");
				holder.SetActive(false);
				Object.DontDestroyOnLoad((Object)(object)holder);
			}
			return holder.transform;
		}

		public static Character Clone(Character source, string name, Vector3 position, Quaternion rotation, bool withAi)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)source).gameObject, Holder());
			val.SetActive(false);
			((Object)val).name = name;
			FiCompanion.Strip(val);
			ButcherTrial[] componentsInChildren = val.GetComponentsInChildren<ButcherTrial>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren[i]);
			}
			RallyKeeper[] componentsInChildren2 = val.GetComponentsInChildren<RallyKeeper>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren2[i]);
			}
			Transform val2 = val.transform.Find("ImmCov_TalkRig");
			if ((Object)(object)val2 != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)((Component)val2).gameObject);
			}
			Character component = val.GetComponent<Character>();
			string text = ((object)UID.Generate()/*cast due to .constrained prefix*/).ToString();
			FiCompanion.BindViews(val);
			component.SetUID(new UID(text));
			int num = 0;
			Item[] componentsInChildren3 = val.GetComponentsInChildren<Item>(true);
			for (int i = 0; i < componentsInChildren3.Length; i++)
			{
				componentsInChildren3[i].UID = text + "_e" + num++;
			}
			val.transform.SetParent((Transform)null, true);
			SceneManager.MoveGameObjectToScene(val, SceneManager.GetActiveScene());
			val.transform.position = position;
			val.transform.rotation = rotation;
			if (withAi)
			{
				FiCompanion.WakeAi(val);
				CharacterAI component2 = val.GetComponent<CharacterAI>();
				if ((Object)(object)component2 != (Object)null && (Object)(object)component2.AIStatesPrefab == (Object)null)
				{
					component2.AIStatesPrefab = val.GetComponentInChildren<AIRoot>(true);
				}
			}
			val.SetActive(true);
			component.EquipmentInitDone();
			if (!withAi)
			{
				CharacterControl characterControl = component.CharacterControl;
				CharacterAI val3 = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
				if (val3 != null)
				{
					val3.SetNPCActive(false);
					((Behaviour)val3).enabled = false;
				}
				NavMeshAgent component3 = val.GetComponent<NavMeshAgent>();
				if ((Object)(object)component3 != (Object)null)
				{
					((Behaviour)component3).enabled = false;
				}
			}
			return component;
		}
	}
	internal static class GatheringQuest
	{
		private const string LogIntro = "tchernobill.immcov.rally.log.intro";

		public static readonly JournalQuest Journal = Build();

		public static int Total => RallyRegistry.All.Count((RallyCandidate c) => c.Rallied);

		private static string LogCamp(string camp)
		{
			return "tchernobill.immcov.rally.log." + camp;
		}

		private static JournalQuest Build()
		{
			JournalQuest journalQuest = new JournalQuest(-71113, "The Gathering", "Immaculates who chose the Covenant are gathering in the camps of the one who reads.", Update).Entry("tchernobill.immcov.rally.log.intro", "Some Immaculates have chosen the Covenant. They wait in the camps where the Friendly Immaculate stops on his journey.");
			string[] all = Camps.All;
			foreach (string camp in all)
			{
				journalQuest.Entry(LogCamp(camp), Camps.RegionName(camp) + " camp: {1}");
			}
			return journalQuest;
		}

		private static void Update(JournalQuest q)
		{
			q.Write("tchernobill.immcov.rally.log.intro");
			string[] all = Camps.All;
			foreach (string camp in all)
			{
				int num = RallyRegistry.RalliedIn(camp);
				if (num > 0)
				{
					q.WriteParams(LogCamp(camp), (num == 1) ? "1 Immaculate" : $"{num} Immaculates");
				}
			}
		}

		public static void Refresh()
		{
			if (Total >= 1)
			{
				if (!Journal.IsKnown)
				{
					Journal.Give();
				}
				else
				{
					Journal.Refresh();
				}
			}
		}
	}
	internal static class QuietingRune
	{
		[HarmonyPatch(typeof(StatusEffectManager), "AddStatusEffect", new Type[]
		{
			typeof(StatusEffect),
			typeof(Character),
			typeof(string[])
		})]
		private static class Lands
		{
			private static bool Prefix(StatusEffectManager __instance, StatusEffect _statusEffect, Character _dealer, string[] _loadData, ref StatusEffect __result)
			{
				if ((Object)(object)_statusEffect == (Object)null || _statusEffect.IdentifierName != "Quieted" || _loadData != null)
				{
					return true;
				}
				Character target = __instance.m_character;
				bool quieted = false;
				Plugin.Guard("Quieted", delegate
				{
					quieted = Cast(_dealer ?? CharacterManager.Instance.GetFirstLocalCharacter(), target) == null && (((Component)target).GetComponent<RallyKeeper>()?.Quieted ?? false);
				});
				if (quieted)
				{
					return true;
				}
				__result = null;
				return false;
			}

			private static void Postfix(StatusEffectManager __instance, StatusEffect _statusEffect, StatusEffect __result)
			{
				if ((Object)(object)_statusEffect == (Object)null || _statusEffect.IdentifierName != "Quieted" || (Object)(object)__result != (Object)null)
				{
					return;
				}
				Character character = __instance.m_character;
				if (character != null && ((Component)character).GetComponent<RallyKeeper>()?.Quieted == true)
				{
					Log($"the game refused Quieted on '{((Object)character).name}': immune {character.Stats.HasStatusImmunity(_statusEffect.InheritedTags)}, " + $"build-up modifier {character.Stats.GetStatusBuildUpModifier(_statusEffect.IdentifierName)}, quest immunity {character.QuestStatusImmunity}, " + "tags [" + string.Join(",", _statusEffect.InheritedTags.Select((Tag t) => t.TagName)) + "]");
				}
			}
		}

		private const int CurseHex = 8201023;

		public const string Status = "Quieted";

		public const string SkillName = "Quiet";

		public const float WeakenedBelow = 0.4f;

		public const float QuietSeconds = 90f;

		public const float ManaCost = 40f;

		public const string NoTarget = "No Immaculate is close enough to hear it.";

		public const string Grotesque = "There is no one inside to hear it.";

		public const string Unknown = "This one is too deep in the Scourge to hear you.";

		public const string TooStrong = "The leash pulls too hard. Weaken it first.";

		public const string NeverAgain = "It will not listen to you again.";

		public const string AlreadyHears = "This one already hears you.";

		internal static string LastSaid;

		public const string NotInCoop = "The rune stays silent: the Covenant walks alone (solo mod).";

		private static void Log(string s)
		{
			RallyWorld.Log("[quiet] " + s);
		}

		public static bool Known(Character c)
		{
			if ((Object)(object)c != (Object)null && (Object)(object)c.Inventory != (Object)null)
			{
				return ((CharacterKnowledge)c.Inventory.SkillKnowledge).IsItemLearned(-71114);
			}
			return false;
		}

		public static void Teach(Character c)
		{
			if ((Object)(object)c != (Object)null && !Known(c))
			{
				c.Inventory.ReceiveSkillReward(-71114);
				Log("taught to " + c.Name);
			}
		}

		public static void Build()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Expected O, but got Unknown
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Expected O, but got Unknown
			((ContentTemplate)new SL_StatusEffect
			{
				TargetStatusIdentifier = "Curse",
				NewStatusID = -71115,
				StatusIdentifier = "Quieted",
				Name = "Quieted",
				Description = "The Scourge's leash is still, for a while. What this Immaculate does with the silence is its own.",
				Lifespan = 90f,
				IsMalusEffect = true,
				Tags = new string[0],
				FamilyMode = (FamilyModes)0,
				BindFamily = new SL_StatusEffectFamily
				{
					UID = "tchernobill.immcov.quieted",
					Name = "Quieted",
					StackBehaviour = (StackBehaviors)4,
					MaxStackCount = -1,
					LengthType = (LengthTypes)0
				},
				SLPackName = "ImmaculateCovenant",
				SubfolderName = "Quieted",
				EffectBehaviour = (EditBehaviours)2,
				Effects = (SL_EffectTransform[])(object)new SL_EffectTransform[0]
			}).ApplyTemplate();
			SL_Item obj = SL_Item.ParseItemToTemplate(ResourcesPrefabManager.Instance.GetItemPrefab(8201023));
			SL_AttackSkill val = (SL_AttackSkill)(object)((obj is SL_AttackSkill) ? obj : null);
			if (val == null)
			{
				Plugin.Log.LogError((object)$"[Rally] Curse Hex ({8201023}) not found: no Quiet skill.");
				return;
			}
			((SL_Item)val).Target_ItemID = 8201023;
			((SL_Item)val).New_ItemID = -71114;
			((SL_Item)val).Name = "Quiet";
			((SL_Item)val).Description = "A touch the Runed Immaculate drew from the Cabal of Wind's teachings. It does not command: it asks the Scourge's leash to be still for a while, and puts nothing in its place.\n\nInflicts Quieted on a weakened Immaculate (below 40% health): for 90 seconds, it stands still and can be spoken to.";
			((SL_Skill)val).ManaCost = 40f;
			((SL_Item)val).SLPackName = "ImmaculateCovenant";
			((SL_Item)val).SubfolderName = "Quieting";
			((SL_Item)val).EffectBehaviour = (EditBehaviours)1;
			foreach (SL_ShootBlast item in ((IEnumerable<SL_EffectTransform>)(((object)((SL_Item)val).EffectTransforms) ?? ((object)new SL_EffectTransform[0]))).SelectMany((SL_EffectTransform t) => (IEnumerable<SL_Effect>)(((object)t.Effects) ?? ((object)new SL_Effect[0]))).OfType<SL_ShootBlast>())
			{
				item.EffectBehaviour = (EditBehaviours)2;
				SL_EffectTransform[] array = new SL_EffectTransform[1];
				SL_EffectTransform val2 = new SL_EffectTransform();
				val2.TransformName = "Effects";
				val2.Effects = (SL_Effect[])(object)new SL_Effect[1] { (SL_Effect)new SL_AddStatusEffect
				{
					StatusEffect = "Quieted",
					ChanceToContract = 100
				} };
				array[0] = val2;
				item.BlastEffects = (SL_EffectTransform[])(object)array;
			}
			((ContentTemplate)val).ApplyTemplate();
			Item itemPrefab = ResourcesPrefabManager.Instance.GetItemPrefab(-71114);
			Skill val3 = (Skill)(object)((itemPrefab is Skill) ? itemPrefab : null);
			StatusEffect statusEffectPrefab = ResourcesPrefabManager.Instance.GetStatusEffectPrefab("Quieted");
			Plugin.Log.LogMessage((object)(string.Format("[Rally] skill {0} '{1}' built: {2}; ", -71114, (val3 != null) ? ((Item)val3).Name : null, ((Object)(object)val3 != (Object)null) ? $"ok (mana {val3.ManaCost}, cooldown {val3.Cooldown} s)" : "MISSING") + "status '" + ((statusEffectPrefab != null) ? statusEffectPrefab.IdentifierName : null) + "' " + (((Object)(object)statusEffectPrefab != (Object)null) ? $"ok (icon {(Object)(object)statusEffectPrefab.StatusIcon != (Object)null}, malus {statusEffectPrefab.IsMalusEffect})" : "MISSING")));
		}

		public static bool IsImmaculate(Character c)
		{
			if ((Object)(object)c != (Object)null && c.m_nameLocKey != null)
			{
				return c.m_nameLocKey.Contains("Immaculate");
			}
			return false;
		}

		public static bool IsGrotesque(Character c)
		{
			if ((Object)(object)c != (Object)null && c.m_nameLocKey != null)
			{
				return c.m_nameLocKey.Contains("Grotesque");
			}
			return false;
		}

		public static string Cast(Character caster, Character target)
		{
			string text = (LastSaid = (Plugin.Solo ? Resolve(caster, target) : "The rune stays silent: the Covenant walks alone (solo mod)."));
			Log("by " + ((caster != null) ? caster.Name : null) + " on '" + ((target != null) ? ((Object)target).name : null) + "': " + (text ?? "(quieted)"));
			if (text != null && caster != null)
			{
				CharacterUI characterUI = caster.CharacterUI;
				if (characterUI != null)
				{
					characterUI.ShowInfoNotification(text);
				}
			}
			return text;
		}

		private static string Resolve(Character caster, Character target)
		{
			if ((Object)(object)target == (Object)null)
			{
				return "No Immaculate is close enough to hear it.";
			}
			if (IsGrotesque(target))
			{
				return "There is no one inside to hear it.";
			}
			RallyCandidate rallyCandidate = RallyRegistry.OfCharacter(target);
			if (rallyCandidate == null)
			{
				return "This one is too deep in the Scourge to hear you.";
			}
			if (rallyCandidate.Kind == RallyKind.Butcher)
			{
				ButcherTrial component = ((Component)target).GetComponent<ButcherTrial>();
				if (!((Object)(object)component != (Object)null))
				{
					return "This one is too deep in the Scourge to hear you.";
				}
				return component.OnRune();
			}
			if ((!rallyCandidate.Roaming || !rallyCandidate.Closed) && (rallyCandidate.Refused || (rallyCandidate.Kind != RallyKind.Wild && rallyCandidate.Hostile)))
			{
				return "It will not listen to you again.";
			}
			RallyKeeper component2 = ((Component)target).GetComponent<RallyKeeper>();
			if ((Object)(object)component2 != (Object)null && component2.Peaceful)
			{
				return "This one already hears you.";
			}
			if (rallyCandidate.Kind != RallyKind.Wild)
			{
				return "This one is too deep in the Scourge to hear you.";
			}
			if (target.Stats.CurrentHealth > target.Stats.ActiveMaxHealth * 0.4f)
			{
				return "The leash pulls too hard. Weaken it first.";
			}
			(component2 ?? ((Component)target).gameObject.AddComponent<RallyKeeper>()).InitQuieted(rallyCandidate, target, 90f);
			return null;
		}
	}
	internal static class RallyCamp
	{
		private class StayPut : MonoBehaviour
		{
			private Vector3 spot;

			private Quaternion facing;

			private void Start()
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: 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_001d: Unknown result type (might be due to invalid IL or missing references)
				spot = ((Component)this).transform.position;
				facing = ((Component)this).transform.rotation;
			}

			private void LateUpdate()
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = ((Component)this).transform.position - spot;
				if (((Vector3)(ref val)).sqrMagnitude > 0.04f)
				{
					((Component)this).transform.position = spot;
				}
				((Component)this).transform.rotation = facing;
			}
		}

		public const string ObjectPrefix = "ImmCov_Rallied_";

		internal static readonly Dictionary<string, Vector3[]> Anchors = new Dictionary<string, Vector3[]>
		{
			["HallowedMarsh"] = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(2397f, 0f, 9.5f),
				new Vector3(2407.5f, 0f, 10f),
				new Vector3(2401.5f, 0f, 6.5f),
				new Vector3(2397.5f, 0f, 6.5f)
			},
			["Chersonese"] = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(2396f, 0f, 10f),
				new Vector3(2400.5f, 0f, 15.5f),
				new Vector3(2402.5f, 0f, 7f),
				new Vector3(2397.5f, 0f, 6.5f)
			},
			["Enmerkar"] = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(1801f, 0f, -3.5f),
				new Vector3(1797.5f, 0f, -5f),
				new Vector3(1803.5f, 0f, -12.5f),
				new Vector3(1796f, 0f, -1f)
			},
			["Abrassar"] = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(1502f, 5f, 20f),
				new Vector3(1497.5f, 5f, 18.5f),
				new Vector3(1499.5f, 5f, 17f),
				new Vector3(1496.5f, 5f, 21f)
			},
			["AntiqueField"] = (Vector3[])(object)new Vector3[5]
			{
				new Vector3(274f, -4f, 30f),
				new Vector3(279f, -4f, 29f),
				new Vector3(277f, -4f, 38.5f),
				new Vector3(283f, -4f, 35.5f),
				new Vector3(272.5f, -4f, 36.5f)
			},
			["Caldera"] = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(906f, 0.3f, 20.5f),
				new Vector3(900.5f, 0.3f, 21f),
				new Vector3(906.5f, 0.3f, 27f),
				new Vector3(903f, 0.3f, 18.5f)
			}
		};

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

		private static string cachedScene;

		private static List<Character> sceneCharacters;

		private static GameObject posedFi;

		private static void Log(string s)
		{
			RallyWorld.Log("[camp] " + s);
		}

		public static void Register()
		{
			Plugin.OnWorldLoaded("rally camp", OnLoaded);
		}

		private static void OnLoaded()
		{
			cachedScene = null;
			sceneCharacters = null;
			posedFi = null;
			GatheringQuest.Refresh();
			string camp = Camps.CampOfScene(SceneManagerHelper.ActiveSceneName);
			if (camp != null)
			{
				List<RallyCandidate> list = RallyRegistry.All.Where((RallyCandidate c) => c.HomeCamp == camp && c.Rallied).ToList();
				if (list.Count > 0)
				{
					((MonoBehaviour)Plugin.Instance).StartCoroutine(SpawnAll(camp, list));
				}
			}
		}

		private static IEnumerator SpawnAll(string camp, List<RallyCandidate> rallied)
		{
			yield return (object)new WaitForSeconds(1f);
			for (int i = 0; i < rallied.Count; i++)
			{
				RallyCandidate c = rallied[i];
				int index = i;
				if (CampQuests.Active(c).Kind != ErrandKind.Walk)
				{
					Plugin.Guard("rallied " + c.Id, delegate
					{
						Spawn(c, camp, index);
					});
					yield return null;
				}
			}
		}

		internal static Character BodySource(RallyCandidate c)
		{
			if (c.Scene == SceneManagerHelper.ActiveSceneName)
			{
				Character val = RallyWorld.Find(c);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			List<Character> source = (from ch in SceneCharacters()
				where (Object)(object)ch != (Object)null && !IsCampNpc(ch) && !ButcherSpar.IsFighter(ch)
				select ch).ToList();
			if (c.Build == "Sword")
			{
				Character val2 = ((IEnumerable<Character>)source).FirstOrDefault((Func<Character, bool>)((Character ch) => ch.m_nameLocKey == "Horror_OnehandedImmaculate"));
				if ((Object)(object)val2 != (Object)null)
				{
					return val2;
				}
			}
			return ((IEnumerable<Character>)source).FirstOrDefault((Func<Character, bool>)((Character ch) => ((Component)ch).GetComponentsInChildren<QuestEventOnDeath>(true).Any(delegate(QuestEventOnDeath q)
			{
				QuestEventReference questEventRef = q.m_questEventRef;
				return ((questEventRef != null) ? questEventRef.EventUID : null) == "MTRMDD0o0UGhfrQ4-pRNYw";
			}))) ?? ((IEnumerable<Character>)source).FirstOrDefault((Func<Character, bool>)((Character ch) => ch.m_nameLocKey == "Horror_Immaculate"));
		}

		public static Character Find(RallyCandidate c)
		{
			if (!spawned.TryGetValue(c.Id, out var value) || !((Object)(object)value != (Object)null))
			{
				return null;
			}
			return value;
		}

		public static bool IsCampNpc(Character ch)
		{
			if ((Object)(object)ch != (Object)null)
			{
				return spawned.Values.Contains(ch);
			}
			return false;
		}

		public static void Remove(RallyCandidate c)
		{
			Character val = Find(c);
			spawned.Remove(c.Id);
			if ((Object)(object)val != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)val).gameObject);
			}
		}

		public static void Respawn(RallyCandidate c)
		{
			string camp = c.HomeCamp;
			int index = RallyRegistry.All.Where((RallyCandidate x) => x.HomeCamp == camp && x.Rallied).ToList().IndexOf(c);
			if (index >= 0)
			{
				Plugin.Guard("rallied " + c.Id, delegate
				{
					Spawn(c, camp, index);
				});
			}
		}

		private static void Spawn(RallyCandidate candidate, string camp, int index)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Find(candidate) != (Object)null)
			{
				return;
			}
			Character val = BodySource(candidate);
			if ((Object)(object)val == (Object)null)
			{
				Log(camp + ": no Immaculate body in the scene, " + candidate.Id + " cannot appear");
				return;
			}
			(Vector3, Quaternion) tuple = Place(camp, index);
			Vector3 item = tuple.Item1;
			Quaternion item2 = tuple.Item2;
			Character val2 = CharacterCloner.Clone(val, "ImmCov_Rallied_" + candidate.Id, item, item2, withAi: false);
			spawned[candidate.Id] = val2;
			((Component)val2).gameObject.AddComponent<StayPut>();
			val2.Invincible = true;
			val2.ChangeFaction((Factions)1, false);
			val2.TargetingSystem.TargetableFactions = (Factions[])(object)new Factions[0];
			if (candidate.Kind == RallyKind.Willing && candidate.Build == "Sword")
			{
				RunedLook.Apply(val2);
			}
			else
			{
				RunedLook.Remove(val2);
			}
			DialogueTreeController val3 = CompanionTalk.AttachTo(val2, Dialogue(candidate), candidate.Speaker);
			CampMerchants.Attach(val2, candidate);
			Log($"{candidate.Id} in the {camp} camp at {((Component)val2).transform.position} (body from '{((Object)val).name}', talkable {(Object)(object)val3 != (Object)null})");
		}

		private static void Cache()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			Scene scene = SceneManager.GetActiveScene();
			if (!(cachedScene == ((Scene)(ref scene)).name) || sceneCharacters == null)
			{
				cachedScene = ((Scene)(ref scene)).name;
				sceneCharacters = (from ch in Resources.FindObjectsOfTypeAll<Character>()
					where (Object)(object)ch != (Object)null && ((Component)ch).gameObject.scene == scene
					select ch).ToList();
				Transform? obj = ((IEnumerable<Transform>)Resources.FindObjectsOfTypeAll<Transform>()).FirstOrDefault((Func<Transform, bool>)((Transform t) => (Object)(object)t != (Object)null && ((Component)t).gameObject.scene == scene && ((Object)t).name.StartsWith("mdl_env_ImmaculatePoseA_c")));
				posedFi = ((obj != null) ? ((Component)obj).gameObject : null);
			}
		}

		private static List<Character> SceneCharacters()
		{
			Cache();
			return sceneCharacters;
		}

		internal static GameObject PosedFriendlyImmaculate()
		{
			Cache();
			return posedFi;
		}

		internal static (Vector3, Quaternion) Place(string camp, int index)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_007a: 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_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: 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_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: 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_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = PosedFriendlyImmaculate();
			Vector3 val2;
			if (Anchors.TryGetValue(camp, out var value) && index < value.Length)
			{
				val2 = value[index];
			}
			else
			{
				val2 = (((Object)(object)val != (Object)null) ? val.transform.position : ((Component)CharacterManager.Instance.GetFirstLocalCharacter()).transform.position) + Quaternion.Euler(0f, 72f * (float)index, 0f) * new Vector3(0f, 0f, -4f);
				Log($"{camp}: no anchor {index}, placed near the Friendly Immaculate");
			}
			NavMeshHit val3 = default(NavMeshHit);
			if (NavMesh.SamplePosition(val2, ref val3, 2f, -1))
			{
				val2 = ((NavMeshHit)(ref val3)).position;
			}
			Vector3 val4 = (((Object)(object)val != (Object)null) ? val.transform.position : (val2 + Vector3.forward)) - val2;
			val4.y = 0f;
			return (val2, (((Vector3)(ref val4)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(((Vector3)(ref val4)).normalized) : Quaternion.identity);
		}

		public static DialogueTreeExt Dialogue(RallyCandidate c)
		{
			if (c.Kind != RallyKind.Butcher)
			{
				return CampTalk.Build(c);
			}
			return ButcherSpar.CampDialogue(c);
		}
	}
	internal enum RallyArgument
	{
		Kin,
		Training,
		Protection,
		Cure,
		Elatt
	}
	internal static class RallyDialogue
	{
		public const string Opening = "Some of your kind are gathering. Immaculates who want to be free of the leash — together.";

		public const string Wary = "Together. Humans say that word, and then they bring nets.";

		public const string DoneTalking = "We are done talking, human.";

		public const string Willing = "I have carried runes so that my kin might choose. If others choose with me, I will not stand apart. Tell the reader in the caves to expect me.";

		public static readonly (RallyArgument arg, string choice, string fails)[] Arguments = new(RallyArgument, string, string)[5]
		{
			(RallyArgument.Kin, "Come with those who want what you want. That is all we are: kin, bound by a shared aim.", "Kin? My kin are the ones who kill beside me. You offer me strangers."),
			(RallyArgument.Training, "Come when you find the time and we will fight together, learning from each other, becoming stronger.", "Fight beside a human, for sport? I have no use for games."),
			(RallyArgument.Protection, "Join us and you'll be safe. No more hunters, no more Wolfgang contracts.", "There is no such thing as safety. I will keep my own claws."),
			(RallyArgument.Cure, "We can free you. A mage's runes could quiet the Scourge in you for good.", "Free me? You mean change me, so I fit better in your hands. A leash with runes carved on it is still a leash."),
			(RallyArgument.Elatt, "Elatt's faithful hunt your kind. Stand with us against them.", null)
		};

		public static readonly Dictionary<RallyArgument, string> Success = new Dictionary<RallyArgument, string>
		{
			[RallyArgument.Kin] = "Kin... yes. A pack that hunts for something other than his light. I did not know such a pack could exist. Show me where it sleeps.",
			[RallyArgument.Training] = "You held me without breaking me. Yes. Teach me that, and I will teach you what I know of claws."
		};

		public const string ButcherSuccess = "Hah. You held me off without drawing blood. Fine. Come and teach me that trick, little one.";

		public const string Rage = "That name... it burns behind my eyes. Run, human. RUN.";

		public const string QuietPreamble = "...It is quiet. Why is it quiet?";

		public const string ButcherOpening = "What sort of game is this, human?";

		public const string TeachAsk = "Teach me how to quiet the leash.";

		public const string Teaching = "Give me your hand. ...There. This does not command. It only asks the leash to be still, for a while. What they do with the silence is theirs.";

		public const string LeaveMe = "Leave me, human...";

		public static RallyArgument Winner(RallyCandidate c)
		{
			switch (c.Kind)
			{
			case RallyKind.Butcher:
				return RallyArgument.Training;
			case RallyKind.Wild:
				if (CovenantEvents.Stack(c.KeyEvent) == 0)
				{
					CovenantEvents.RaiseTo(c.KeyEvent, (Random.value < 0.5f) ? 1 : 2);
				}
				if (CovenantEvents.Stack(c.KeyEvent) != 2)
				{
					return RallyArgument.Kin;
				}
				return RallyArgument.Training;
			default:
				return RallyArgument.Kin;
			}
		}

		public static DialogueTreeExt Build(RallyCandidate c)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			//IL_0304: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Expected O, but got Unknown
			DialogueKit dialogueKit = DialogueKit.NewTree("ImmCov_Rally_" + c.Id, c.ActorKey ?? "Horror_Immaculate");
			FinishNode val = dialogueKit.End();
			Node val2 = dialogueKit.Lines((Node)(object)val, "We are done talking, human.");
			if (c.Roaming && c.Closed)
			{
				dialogueKit.Start(dialogueKit.Lines((Node)(object)val, "...It is quiet. Why is it quiet?", "Leave me, human..."));
				return (DialogueTreeExt)dialogueKit.Graph;
			}
			Node val3;
			if (c.Kind == RallyKind.Willing)
			{
				val3 = dialogueKit.Lines(dialogueKit.Chain((Node)dialogueKit.Act("rally " + c.Id, delegate
				{
					RallyActions.Rally(c);
				}), (Node)val), "I have carried runes so that my kin might choose. If others choose with me, I will not stand apart. Tell the reader in the caves to expect me.");
			}
			else
			{
				MultipleChoiceNodeExt args = dialogueKit.Choices(Arguments.Select(((RallyArgument arg, string choice, string fails) a) => a.choice).ToArray());
				for (int num = 0; num < Arguments.Length; num++)
				{
					((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)args, Answer(dialogueKit, c, Arguments[num].arg, Arguments[num].fails, val2, (Node)(object)val), num, -1);
				}
				val3 = dialogueKit.Lines(dialogueKit.Chain((Node)dialogueKit.Act("shuffle the arguments", delegate
				{
					Shuffle(args);
				}), (Node)args), "Together. Humans say that word, and then they bring nets.");
			}
			MultipleChoiceNodeExt val4;
			if (c.Kind == RallyKind.Willing)
			{
				val4 = dialogueKit.Choices("Some of your kind are gathering. Immaculates who want to be free of the leash — together.", "Teach me how to quiet the leash.", "Farewell.");
				((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val4, val3, 0, -1);
				((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val4, Teach(dialogueKit, (Node)(object)val4), 1, -1);
				((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val4, (Node)(object)val, 2, -1);
				TeachCondition(val4, 1);
			}
			else
			{
				val4 = dialogueKit.Choices("Some of your kind are gathering. Immaculates who want to be free of the leash — together.", "Farewell.");
				((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val4, val3, 0, -1);
				((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)val4, (Node)(object)val, 1, -1);
			}
			Node no = (Node)((c.Kind == RallyKind.Wild) ? ((object)dialogueKit.Lines((Node)(object)val4, "...It is quiet. Why is it quiet?")) : ((object)val4));
			dialogueKit.Start(dialogueKit.Branch(c.Id + " refused", () => c.Refused, val2, no));
			return (DialogueTreeExt)dialogueKit.Graph;
		}

		public static Node Teach(DialogueKit kit, Node back)
		{
			return kit.Chain((Node)kit.Act("teach the Quieting Rune", delegate
			{
				QuietingRune.Teach(CharacterManager.Instance.GetFirstLocalCharacter());
			}), kit.Lines(back, "Give me your hand. ...There. This does not command. It only asks the leash to be still, for a while. What they do with the silence is theirs."));
		}

		public static void TeachCondition(MultipleChoiceNodeExt menu, int index)
		{
			menu.availableChoices[index].condition = (ConditionTask)(object)new DelegateCondition
			{
				Label = "Quieting Rune not known",
				Check = () => !QuietingRune.Known(CharacterManager.Instance.GetFirstLocalCharacter())
			};
		}

		public static DialogueTreeExt BuildButcher(RallyCandidate c)
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Expected O, but got Unknown
			DialogueKit dialogueKit = DialogueKit.NewTree("ImmCov_Rally_" + c.Id + "_trial", c.Speaker);
			FinishNode val = dialogueKit.End();
			Node refusedForGood = dialogueKit.Lines((Node)(object)val, "We are done talking, human.");
			MultipleChoiceNodeExt args = dialogueKit.Choices(Arguments.Select(((RallyArgument arg, string choice, string fails) a) => a.choice).ToArray());
			for (int num = 0; num < Arguments.Length; num++)
			{
				((Graph)dialogueKit.Graph).ConnectNodes((Node)(object)args, Answer(dialogueKit, c, Arguments[num].arg, Arguments[num].fails, refusedForGood, (Node)(object)val), num, -1);
			}
			dialogueKit.Start(dialogueKit.Lines(dialogueKit.Chain((Node)dialogueKit.Act("shuffle the arguments", delegate
			{
				Shuffle(args);
			}), (Node)args), "What sort of game is this, human?"));
			return (DialogueTreeExt)dialogueKit.Graph;
		}

		private static Node Answer(DialogueKit kit, RallyCandidate c, RallyArgument arg, string fails, Node refusedForGood, Node end)
		{
			if (arg == RallyArgument.Elatt)
			{
				if (c.Kind != RallyKind.Butcher)
				{
					return kit.Lines(kit.Chain((Node)kit.Act(c.Id + " enraged", delegate
					{
						RallyActions.Enrage(c);
					}), end), "That name... it burns behind my eyes. Run, human. RUN.");
				}
				return kit.Lines(kit.Chain((Node)kit.Act(c.Id + " refuses", delegate
				{
					RallyActions.Refuse(c);
				}), end), "That name... it burns behind my eyes. Run, human. RUN.");
			}
			Node val = kit.Lines(kit.Chain((Node)kit.Act(c.Id + " refuses", delegate
			{
				RallyActions.Refuse(c);
			}), refusedForGood), fails);
			if (!Success.TryGetValue(arg, out var value))
			{
				return val;
			}
			if (arg == RallyArgument.Training && c.Kind == RallyKind.Butcher)
			{
				value = "Hah. You held me off without drawing blood. Fine. Come and teach me that trick, little one.";
			}
			Node yes = kit.Lines(kit.Chain((Node)kit.Act("rally " + c.Id, delegate
			{
				RallyActions.Rally(c);
			}), end), value);
			return kit.Branch($"{c.Id}: {arg} wins", () => Winner(c) == arg, yes, val);
		}

		internal static void Shuffle(MultipleChoiceNodeExt menu)
		{
			List<Choice> availableChoices = menu.availableChoices;
			List<Connection> outConnections = ((Node)menu).outConnections;
			for (int num = availableChoices.Count - 1; num > 0; num--)
			{
				int num2 = Random.Range(0, num + 1);
				List<Choice> list = availableChoices;
				int index = num;
				List<Choice> list2 = availableChoices;
				int index2 = num2;
				Choice value = availableChoices[num2];
				Choice value2 = availableChoices[num];
				list[index] = value;
				list2[index2] = value2;
				List<Connection> list3 = outConnections;
				index2 = num;
				List<Connection> list4 = outConnections;
				index = num2;
				Connection value3 = outConnections[num2];
				Connection value4 = outConnections[num];
				list3[index2] = value3;
				list4[index] = value4;
			}
		}
	}
	internal enum RallyKind
	{
		Willing,
		Controlled,
		Wild,
		Butcher
	}
	internal sealed class RallyCandidate
	{
		public string Id;

		public string Scene;

		public string Uid;

		public RallyKind Kind;

		public string HomeCamp;

		public string Epithet;

		public string DeathEvent;

		public string ActorKey;

		public bool ForceActive;

		public bool Roaming;

		public string Build = "Blade";

		public string RalliedEvent => "tchernobill.immcov.rally." + Id;

		public string HostileEvent => "tchernobill.immcov.rally." + Id + ".hostile";

		public string RefusedEvent => "tchernobill.immcov.rally." + Id + ".refused";

		public string KeyEvent => "tchernobill.immcov.rally." + Id + ".key";

		public string KilledEvent => "tchernobill.immcov.rally." + Id + ".killed";

		public string Speaker => ActorKey ?? Epithet;

		public bool Rallied => CovenantEvents.Has(RalliedEvent);

		public bool Hostile => CovenantEvents.Has(HostileEvent);

		public bool Refused => CovenantEvents.Has(RefusedEvent);

		public bool Dead
		{
			get
			{
				if (DeathEvent == null || !CovenantEvents.Has(DeathEvent))
				{
					return CovenantEvents.Has(KilledEvent);
				}
				return true;
			}
		}

		public bool Closed
		{
			get
			{
				if (!Rallied && !Refused)
				{
					return Dead;
				}
				return true;
			}
		}

		public bool Peaceful
		{
			get
			{
				if ((Kind == RallyKind.Controlled || Kind == RallyKind.Willing) && Membership.IsMember && CovenantEvents.Has("HbTd6ustoU-VhQeidJcAEw") && !Hostile && !Rallied)
				{
					return !Dead;
				}
				return false;
			}
		}

		public bool Wanted
		{
			get
			{
				if (Kind != RallyKind.Butcher)
				{
					return Peaceful;
				}
				if (Membership.IsMember)
				{
					CharacterManager instance = CharacterManager.Instance;
					if (QuietingRune.Known((instance != null) ? instance.GetFirstLocalCharacter() : null) && !Rallied && !Refused)
					{
						return !Dead;
					}
				}
				return false;
			}
		}
	}
	internal static class RallyRegistry
	{
		public static readonly RallyCandidate[] All = new RallyCandidate[10]
		{
			new RallyCandidate
			{
				Id = "jager.marsh",
				Scene = "HallowedDungeonsSmall",
				Uid = "hauSskFJIE20K4cgOt24ng",
				Kind = RallyKind.Controlled,
				HomeCamp = "HallowedMarsh",
				Epithet = "Immaculate of the Marsh Pack",
				DeathEvent = "tt1zE5uBKkKqGuX3dq3Y-A"
			},
			new RallyCandidate
			{
				Id = "jager.deadroots",
				Scene = "Hallowed_Dungeon7",
				Uid = "qvZ55MM-h02bNaaWS2EczA",
				Kind = RallyKind.Controlled,
				HomeCamp = "HallowedMarsh",
				Epithet = "Immaculate of the Dead Roots",
				DeathEvent = "7pfmwlCTw0um8aMX_GBJSQ"
			},
			new RallyCandidate
			{
				Id = "runed",
				Scene = "AntiqueFieldDungeonsSmall",
				Uid = "Wys5vscAcEqR4Xwta_E0vQ",
				Kind = RallyKind.Willing,
				HomeCamp = "AntiqueField",
				Epithet = "Runed Immaculate",
				ActorKey = "name_unpc_runedimmaculate_01",
				ForceActive = true,
				Build = "Sword"
			},
			new RallyCandidate
			{
				Id = "butcher",
				Scene = "AntiqueFieldDungeonsSmall",
				Uid = "mqWvHBbvxkWUhdJNOHp1yw",
				Kind = RallyKind.Butcher,
				HomeCamp = "AntiqueField",
				Epithet = "The Butcher of Men",
				ActorKey = "name_unpc_butcherofmen_01",
				ForceActive = true
			},
			new RallyCandidate
			{
				Id = "wild.cabal",
				Scene = "Emercar",
				Uid = "aFWh8-z5GkOACRBL9njk6w",
				Kind = RallyKind.Wild,
				HomeCamp = "Enmerkar",
				Epithet = "Immaculate of the Wind Temple"
			},
			new RallyCandidate
			{
				Id = "wild.enmerkar",
				Scene = "Emercar",
				Uid = "KWx6GkbL1Uib6lCpxuoYog",
				Kind = RallyKind.Wild,
				HomeCamp = "Enmerkar",
				Epithet = "Immaculate of the Forest Roads",
				Roaming = true
			},
			new RallyCandidate
			{
				Id = "wild.chersonese",
				Scene = "ChersoneseNewTerrain",
				Uid = "UNA7OILbj0umhDN1GNHVig",
				Kind = RallyKind.Wild,
				HomeCamp = "Chersonese",
				Epithet = "Immaculate of the Salt Wind",
				Roaming = true
			},
			new RallyCandidate
			{
				Id = "wild.abrassar",
				Scene = "Abrassar",
				Uid = "KWx6GkbL1Uib6lCpxuoYog",
				Kind = RallyKind.Wild,
				HomeCamp = "Abrassar",
				Epithet = "Immaculate of the Dunes",
				Roaming = true
			},
			new RallyCandidate
			{
				Id = "wild.marsh",
				Scene = "HallowedMarshNewTerrain",
				Uid = "KWx6GkbL1Uib6lCpxuoYog",
				Kind = RallyKind.Wild,
				HomeCamp = "HallowedMarsh",
				Epithet = "Immaculate of the Black Water",
				Roaming = true
			},
			new RallyCandidate
			{
				Id = "wild.plateau",
				Scene = "AntiqueField_Dungeon2",
				Uid = "Fo6BpM5bXUiWesVztLo2pw",
				Kind = RallyKind.Wild,
				HomeCamp = "AntiqueField",
				Epithet = "Immaculate of the Sunken Halls",
				Build = "Sword"
			}
		};

		public const string ButcherLessons = "tchernobill.immcov.rally.butcher.lessons";

		public static IEnumerable<RallyCandidate> InScene(string scene)
		{
			return All.Where((RallyCandidate c) => c.Scene == scene);
		}

		public static RallyCandidate Get(string id)
		{
			return All.FirstOrDefault((RallyCandidate c) => c.Id == id);
		}

		public static RallyCandidate OfCharacter(Character ch)
		{
			if (!((Object)(object)ch == (Object)null))
			{
				return All.FirstOrDefault((RallyCandidate c) => UID.op_Implicit(c.Uid) == ch.UID && c.Scene == SceneManagerHelper.ActiveSceneName);
			}
			return null;
		}

		public static int RalliedIn(string camp)
		{
			return All.Count((RallyCandidate c) => c.HomeCamp == camp && c.Rallied);
		}

		public static void RegisterEvents()
		{
			RallyCandidate[] all = All;
			foreach (RallyCandidate rallyCandidate in all)
			{
				Create(rallyCandidate.RalliedEvent, "Rally_" + rallyCandidate.Id, rallyCandidate.Epithet + " joined the Covenant");
				Create(rallyCandidate.HostileEvent, "Rally_" + rallyCandidate.Id + "_Hostile", rallyCandidate.Epithet + ": struck by the player, never peaceful again");
				Create(rallyCandidate.RefusedEvent, "Rally_" + rallyCandidate.Id + "_Refused", rallyCandidate.Epithet + ": refused for good (wrong argument)");
				Create(rallyCandidate.KeyEvent, "Rally_" + rallyCandidate.Id + "_Key", rallyCandidate.Epithet + ": winning argument drawn (1 Kin, 2 Training)", stackable: true);
				Create(rallyCandidate.KilledEvent, "Rally_" + rallyCandidate.Id + "_Killed", rallyCandidate.Epithet + ": killed");
			}
			Create("tchernobill.immcov.rally.butcher.lessons", "Rally_Butcher_Lessons", "Sparring matches the Butcher lost (he grows stronger)", stackable: true);
		}

		private static void Create(string uid, string name, string desc, bool stackable = false)
		{
			CustomQuests.CreateQuestEvent(uid, false, stackable, true, "IC_Rally", name, desc);
		}
	}
	internal static class RallyWorld
	{
		private class Director : MonoBehaviour
		{
			private float next;

			private void Update()
			{
				if (Time.time < next)
				{
					return;
				}
				next = Time.time + 2f;
				foreach (RallyCandidate c in RallyRegistry.InScene(SceneManagerHelper.ActiveSceneName))
				{
					Plugin.Guard("rally director " + c.Id, delegate
					{
						Tend(c);
					});
				}
			}

			private static void Tend(RallyCandidate c)
			{
				//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
				Character val = Find(c);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				if (c.Kind == RallyKind.Butcher && Membership.IsMember && (c.Wanted || c.Rallied))
				{
					ButcherSpar.ClearLair(val);
				}
				if (c.Rallied && !c.Roaming)
				{
					ForcedOn.Remove(val);
					if (((Component)val).gameObject.activeSelf)
					{
						((Component)val).gameObject.SetActive(false);
						Log(c.Id + " rallied: no longer here");
					}
					return;
				}
				if (c.ForceActive && c.Wanted && !((Component)val).gameObject.activeInHierarchy && !val.IsDead)
				{
					ForcedOn.Add(val);
					Transform val2 = ((Component)val).transform;
					while ((Object)(object)val2 != (Object)null)
					{
						if (!((Component)val2).gameObject.activeSelf)
						{
							((Component)val2).gameObject.SetActive(true);
						}
						val2 = val2.parent;
					}
					Log($"{c.Id} switched on at {((Component)val).transform.position}");
				}
				else if (!c.Wanted)
				{
					ForcedOn.Remove(val);
				}
				if (((Component)val).gameObject.activeInHierarchy && !val.IsDead)
				{
					if (c.Build == "Sword" && c.Kind == RallyKind.Willing)
					{
						RunedLook.Apply(val);
					}
					if (c.Kind == RallyKind.Butcher && c.Wanted && (Object)(object)((Component)val).GetComponent<ButcherTrial>() == (Object)null)
					{
						((Component)val).gameObject.AddComponent<ButcherTrial>().Init(c, val);
					}
					if (!((Object)(object)((Component)val).GetComponent<RallyKeeper>() != (Object)null) && c.Peaceful)
					{
						((Component)val).gameObject.AddComponent<RallyKeeper>().Init(c, val);
					}
				}
			}
		}

		[HarmonyPatch(typeof(SetActiveGameObject), "OnExecute")]
		private static class KeepForcedOn
		{
			private static bool Prefix(SetActiveGameObject __instance)
			{
				GameObject go = __instance.Target?.value;
				if ((Object)(object)go == (Object)null || __instance.IsActive == null || __instance.IsActive.value || ForcedOn.Count == 0)
				{
					return true;
				}
				ForcedOn.RemoveWhere((Character ch) => (Object)(object)ch == (Object)null);
				if (!ForcedOn.Any((Character ch) => ((Component)ch).transform.IsChildOf(go.transform)))
				{
					return true;
				}
				((ActionTask)__instance).EndAction();
				return false;
			}
		}

		[HarmonyPatch(typeof(Character), "OnReceiveHit")]
		private static class StruckByThePlayer
		{
			private static void Prefix(Character __instance, Character _dealerChar)
			{
				if ((Object)(object)_dealerChar == (Object)null || (Object)(object)_dealerChar.OwnerPlayerSys == (Object)null)
				{
					return;
				}
				RallyKeeper component = ((Component)__instance).GetComponent<RallyKeeper>();
				if (!((Object)(object)component == (Object)null) && component.Peaceful)
				{
					if (component.Quieted)
					{
						component.EndQuiet("struck by the player");
					}
					else
					{
						component.TurnHostile(_dealerChar);
					}
				}
			}
		}

		private static readonly HashSet<Character> ForcedOn = new HashSet<Character>();

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

		private static string scanned;

		internal static void Log(string s)
		{
			Plugin.Log.LogMessage((object)("[Rally] " + s));
		}

		public static void Register()
		{
			Deaths.OnDeath("rally: a candidate killed is gone for good", Killed);
			ButcherSpar.Register();
			Plugin.OnWorldLoaded("rally world", OnLoaded);
		}

		private static void OnLoaded()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			string activeSceneName = SceneManagerHelper.ActiveSceneName;
			found.Clear();
			scanned = null;
			if (Membership.IsMember && RallyRegistry.InScene(activeSceneName).Any())
			{
				new GameObject("ImmCov_RallyDirector").AddComponent<Director>();
			}
		}

		public static void Leave(RallyCandidate c, Character npc)
		{
			if (c.Roaming)
			{
				AISquad val = ((Component)npc).GetComponent<AISquadMember>()?.AISquad;
				AISquadManager instance = AISquadManager.Instance;
				RallyKeeper component = ((Component)npc).GetComponent<RallyKeeper>();
				if ((Object)(object)component != (Object)null)
				{
					component.EndQuiet("rallied");
				}
				if ((Object)(object)val != (Object)null && (Object)(object)instance != (Object)null)
				{
					instance.m_specialSquadInPlay.Remove(val);
					val.SetSquadActive(false, true);
					instance.SyncSquadInfo(val);
					Log(c.Id + ": squad '" + ((Object)val).name + "' back to the reserve");
					return;
				}
			}
			((Component)npc).gameObject.SetActive(false);
		}

		public static Character Find(RallyCandidate c)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			if (found.TryGetValue(c.Id, out var value) && (Object)(object)value != (Object)null && value.UID == UID.op_Implicit(c.Uid))
			{
				return value;
			}
			Character value2 = CharacterManager.Instance.GetCharacter(c.Uid);
			if ((Object)(object)value2 == (Object)null || RallyCamp.IsCampNpc(value2) || ButcherSpar.IsFighter(value2))
			{
				value2 = null;
			}
			string activeSceneName = SceneManagerHelper.ActiveSceneName;
			if ((Object)(object)value2 == (Object)null && scanned != activeSceneName)
			{
				scanned = activeSceneName;
				Scene activeScene = SceneManager.GetActiveScene();
				HashSet<string> hashSet = new HashSet<string>(from x in RallyRegistry.InScene(activeSceneName)
					select x.Uid);
				Character[] array = Resources.FindObjectsOfTypeAll<Character>();
				foreach (Character ch in array)
				{
					if (!((Object)(object)ch != (Object)null) || !(((Component)ch).gameObject.scene == activeScene) || !hashSet.Contains(UID.op_Implicit(ch.UID)) || RallyCamp.IsCampNpc(ch) || ButcherSpar.IsFighter(ch))
					{
						continue;
					}
					foreach (RallyCandidate item in from x in RallyRegistry.InScene(activeSceneName)
						where UID.op_Implicit(x.Uid) == ch.UID
						select x)
					{
						found[item.Id] = ch;
					}
				}
				found.TryGetValue(c.Id, out value2);
			}
			if ((Object)(object)value2 != (Object)null)
			{
				found[c.Id] = value2;
			}
			return value2;
		}

		private static void Killed(Character ch)
		{
			if (!RallyCamp.IsCampNpc(ch) && !ButcherSpar.IsFighter(ch))
			{
				RallyCandidate rallyCandidate = RallyRegistry.OfCharacter(ch);
				if (rallyCandidate != null && !rallyCandidate.Rallied && !CovenantEvents.Has(rallyCandidate.KilledEvent))
				{
					CovenantEvents.Add(rallyCandidate.KilledEvent);
					Log(rallyCandidate.Id + " killed: gone for good");
				}
			}
		}
	}
	internal static class RallyActions
	{
		public static void Rally(RallyCandidate c)
		{
			if (!c.Rallied)
			{
				CovenantEvents.Add(c.RalliedEvent);
			}
			CovenantEvents.RaiseTo("tchernobill.immcov.rallied", RallyRegistry.All.Count((RallyCandidate x) => x.Rallied));
			GatheringQuest.Refresh();
			RallyWorld.Log(string.Format("{0} joins the Covenant ({1} rallied)", c.Id, CovenantEvents.Stack("tchernobill.immcov.rallied")));
		}

		public static void Refuse(RallyCandidate c)
		{
			if (!c.Refused)
			{
				CovenantEvents.Add(c.RefusedEvent);
			}
			RallyWorld.Log(c.Id + " refuses, for good");
		}

		public static void Enrage(RallyCandidate c)
		{
			if (!c.Hostile)
			{
				CovenantEvents.Add(c.HostileEvent);
			}
			RallyWorld.Log(c.Id + " hears Elatt's name");
		}
	}
	internal class RallyKeeper : MonoBehaviour
	{
		public RallyCandidate Candidate;

		public Character Npc;

		internal const float ApproachRange = 4f;

		private Factions[] originalTargets;

		private DialogueTreeController talk;

		private float next;

		private bool left;

		private bool ralliedBefore;

		private float quietUntil;

		public bool Peaceful { get; private set; }

		internal bool Frozen { get; private set; }

		public bool Quieted { get; private set; }

		public void InitQuieted(RallyCandidate candidate, Character npc, float seconds)
		{
			Quieted = true;
			quietUntil = Time.time + seconds;
			Init(candidate, npc);
			npc.Invincible = true;
			Freeze(CharacterManager.Instance.GetFirstLocalCharacter());
			RallyWorld.Log($"{candidate.Id} quieted for {seconds} s");
		}

		public void EndQuiet(string why)
		{
			if (Quieted)
			{
				Quieted = false;
				Peaceful = false;
				Npc.Invincible = false;
				if (Frozen)
				{
					Unfreeze();
				}
				Transform val = ((Component)Npc).transform.Find("ImmCov_TalkRig");
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
				Npc.TargetingSystem.TargetableFactions = originalTargets;
				if ((Object)(object)Npc.StatusEffectMngr != (Object)null && Npc.StatusEffectMngr.HasStatusEffect("Quieted"))
				{
					Npc.StatusEffectMngr.RemoveStatusWithIdentifierName("Quieted");
				}
				RallyWorld.Log(Candidate.Id + " no longer quieted (" + why + ")");
				Object.Destroy((Object)(object)this);
			}
		}

		public void Init(RallyCandidate candidate, Character npc)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			ralliedBefore = candidate.Rallied;
			Candidate = candidate;
			Npc = npc;
			originalTargets = (Factions[])(((object)npc.TargetingSystem.TargetableFactions?.ToArray()) ?? ((object)new Factions[0]));
			Peaceful = true;
			Disarm();
			talk = CompanionTalk.AttachTo(npc, RallyDialogue.Build(candidate), candidate.ActorKey ?? npc.m_nameLocKey);
			RallyWorld.Log($"{candidate.Id} ({npc.Faction}) leaves the player alone; talkable: {(Object)(object)talk != (Object)null}");
		}

		private void Disarm()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Invalid comparison between Unknown and I4
			TargetingSystem targetingSystem = Npc.TargetingSystem;
			if (targetingSystem.TargetableFactions != null && UnityEngineExtensions.Contains<Factions>(targetingSystem.TargetableFactions, (Factions)1))
			{
				targetingSystem.TargetableFactions = targetingSystem.TargetableFactions.Where((Factions f) => (int)f != 1).ToArray();
			}
			Character lockedCharacter = targetingSystem.LockedCharacter;
			if ((Object)(object)lockedCharacter != (Object)null && (int)lockedCharacter.Faction == 1)
			{
				targetingSystem.ReleaseTarget();
				CharacterControl characterControl = Npc.CharacterControl;
				CharacterAI val = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
				if (val != null && val.CurrentAiState is AISCombat)
				{
					SwitchTo<AISWander>(val);
				}
			}
		}

		private static void SwitchTo<T>(CharacterAI ai) where T : AIState
		{
			AIState[] aiStates = ai.AiStates;
			int num = 0;
			while (aiStates != null && num < aiStates.Length)
			{
				if (aiStates[num] is T)
				{
					ai.SwitchAiState(num);
					break;
				}
				num++;
			}
		}

		private void Update()
		{
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			if (!Peaceful || (Object)(object)Npc == (Object)null || Time.time < next)
			{
				return;
			}
			next = Time.time + 0.25f;
			Disarm();
			Character firstLocalCharacter = CharacterManager.Instance.GetFirstLocalCharacter();
			bool flag = (Object)(object)talk != (Object)null && (Object)(object)((GraphOwner)talk).graph != (Object)null && ((GraphOwner)talk).graph.isRunning && (Object)(object)firstLocalCharacter != (Object)null && (Object)(object)firstLocalCharacter.CharacterUI != (Object)null && firstLocalCharacter.CharacterUI.IsDialogueInProgress;
			if (Quieted && !flag && !Candidate.Rallied && (Candidate.Refused || Candidate.Hostile || Time.time > quietUntil))
			{
				EndQuiet(Candidate.Refused ? "refused" : (Candidate.Hostile ? "enraged" : "the rune wore off"));
				return;
			}
			bool flag2 = (Object)(object)firstLocalCharacter != (Object)null && !firstLocalCharacter.InCombat && !Npc.InCombat && Vector3.Distance(((Component)firstLocalCharacter).transform.position, ((Component)Npc).transform.position) < 4f;
			bool flag3 = flag || flag2 || Quieted;
			if (!flag && Candidate.Rallied && !ralliedBefore && (Peaceful || Quieted) && !left)
			{
				left = true;
				RallyWorld.Log(Candidate.Id + " leaves for the " + Candidate.HomeCamp + " camp");
				RallyWorld.Leave(Candidate, Npc);
				return;
			}
			if (!flag && Candidate.Hostile && !Quieted)
			{
				TurnHostile(firstLocalCharacter);
				return;
			}
			if (flag3 && !Frozen)
			{
				Freeze(firstLocalCharacter);
			}
			else if (!flag3 && Frozen)
			{
				Unfreeze();
			}
			if (Frozen)
			{
				HoldStill(firstLocalCharacter);
			}
			if (Frozen && !flag && (Object)(object)firstLocalCharacter != (Object)null)
			{
				Face(firstLocalCharacter);
			}
		}

		private void Freeze(Character player)
		{
			Frozen = true;
			CharacterControl characterControl = Npc.CharacterControl;
			CharacterAI val = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
			if (val != null)
			{
				val.SetNPCActive(false);
				val.StopMovement();
				((Behaviour)val).enabled = false;
			}
			Face(player);
		}

		private void Face(Character player)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = ((Component)player).transform.position - ((Component)Npc).transform.position;
			val.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude > 0.01f)
			{
				((Component)Npc).transform.rotation = Quaternion.Slerp(((Component)Npc).transform.rotation, Quaternion.LookRotation(((Vector3)(ref val)).normalized), 0.5f);
			}
		}

		private void HoldStill(Character player)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			NavMeshAgent component = ((Component)Npc).GetComponent<NavMeshAgent>();
			if ((Object)(object)component != (Object)null && ((Behaviour)component).enabled && component.isOnNavMesh)
			{
				component.isStopped = true;
				component.velocity = Vector3.zero;
			}
			if ((Object)(object)Npc.Animator != (Object)null)
			{
				Npc.Animator.SetFloat("moveForward", 0f);
				Npc.Animator.SetFloat("moveSide", 0f);
			}
		}

		private void Unfreeze()
		{
			Frozen = false;
			NavMeshAgent component = ((Component)Npc).GetComponent<NavMeshAgent>();
			if ((Object)(object)component != (Object)null && ((Behaviour)component).enabled && component.isOnNavMesh)
			{
				component.isStopped = false;
			}
			CharacterControl characterControl = Npc.CharacterControl;
			CharacterAI val = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
			if (val != null)
			{
				((Behaviour)val).enabled = true;
				val.SetNPCActive(true);
			}
		}

		public void TurnHostile(Character striker)
		{
			if (Peaceful)
			{
				Peaceful = false;
				if (Frozen)
				{
					Unfreeze();
				}
				Transform val = ((Component)Npc).transform.Find("ImmCov_TalkRig");
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
				talk = null;
				Npc.TargetingSystem.TargetableFactions = originalTargets;
				if (!CovenantEvents.Has(Candidate.HostileEvent))
				{
					CovenantEvents.Add(Candidate.HostileEvent);
				}
				CharacterControl characterControl = Npc.CharacterControl;
				CharacterAI val2 = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null);
				if (val2 != null && (Object)(object)striker != (Object)null)
				{
					Npc.TargetingSystem.SetLockingPoint(striker.LockingPoint);
					SwitchTo<AISCombat>(val2);
				}
				RallyWorld.Log(Candidate.Id + " struck by " + ((striker != null) ? striker.Name : null) + ": hostile for good");
			}
		}
	}
	internal static class RunedLook
	{
		public const string Marker = "ImmCov_RunicVersion";

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

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

		private static readonly (string mesh, Vector3 pos, Quaternion rot, float scale)[] Runes = new(string, Vector3, Quaternion, float)[6]
		{
			("mdl_env_propRuneF", new Vector3(0.002f, 0.225f, -0.087f), new Quaternion(0.081f, -0.015f, 0.042f, 0.996f), 0.5f),
			("mdl_env_propRuneR", new Vector3(0.002f, 0.20