Decompiled source of Guest Role v2.7.4

BepInEx/plugins/Guest-Role/Guest-Role.dll

Decompiled 19 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Guest-Role")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.7.4.0")]
[assembly: AssemblyInformationalVersion("2.7.4")]
[assembly: AssemblyProduct("Guest-Role")]
[assembly: AssemblyTitle("Guest-Role")]
[assembly: AssemblyVersion("2.7.4.0")]
namespace GuestMedic;

[BepInPlugin("Guest-Role", "Guest-Role", "2.7.4")]
[BepInIncompatibility("local.bei.guesthealthpackgrant")]
[BepInDependency("REPOLib", "4.2.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class Plugin : BaseUnityPlugin
{
	private sealed class BirdSaveState
	{
		internal SortedDictionary<string, Dictionary<string, int>> Original;

		internal SortedDictionary<string, Dictionary<string, int>> Copy;
	}

	private sealed class MeleeSpecialistHitState
	{
		internal Plugin Owner;

		internal int Original;
	}

	private sealed class RoleAnnouncement
	{
		internal PlayerAvatar Player;

		internal string Id;

		internal string Text;

		internal int Role;

		internal object Room;

		internal float Deadline;
	}

	private sealed class TankRevival
	{
		internal int Health;

		internal float Deadline;

		internal bool Requested;
	}

	private sealed class TankDamageContext
	{
		internal PlayerAvatar Victim;

		internal int Before;

		internal int Incoming;

		internal bool Handled;

		internal bool Local;

		internal bool Enemy;

		internal bool Ignore;

		internal bool Resisted;

		internal bool InitialHealth;

		internal int NativeMaximum;

		internal int RestoredMaximum;

		internal int Upgrades;
	}

	private string birdSelected;

	private string birdError;

	private object birdRoom;

	private BirdBonus birdJump;

	private BirdBonus birdWings;

	private Harmony birdPatches;

	private ConfigEntry<int> birdWingsLevel;

	private ConfigEntry<float> birdAirTurn;

	private float birdNextTurn;

	private static readonly FieldInfo birdStats = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");

	private static readonly FieldInfo birdTumble = AccessTools.Field(typeof(PlayerAvatar), "tumble");

	private static readonly FieldInfo birdGrounded = AccessTools.Field(typeof(PlayerAvatar), "isGrounded");

	private static readonly FieldInfo birdTumbling = AccessTools.Field(typeof(PlayerAvatar), "isTumbling");

	private static readonly FieldInfo birdInput = AccessTools.Field(typeof(PlayerAvatar), "InputDirectionRaw");

	private static readonly FieldInfo birdVelocity = AccessTools.Field(typeof(PlayerAvatar), "rbVelocityRaw");

	private static readonly FieldInfo birdWingsActive = AccessTools.Field(typeof(PlayerAvatar), "upgradeTumbleWingsVisualsActive");

	private static readonly FieldInfo birdFallTimer = AccessTools.Field(typeof(PlayerAvatar), "fallDamageResetTimer");

	private Harmony roleUpgradePatches;

	private readonly BirdFlightRules birdFlight = new BirdFlightRules();

	private ConfigEntry<float> birdFlightSeconds;

	private ConfigEntry<float> birdFlightSpeed;

	private ConfigEntry<float> birdFlightUpwardAngle;

	private PlayerAvatar birdFlightPlayer;

	private object birdFlightStage;

	private ItemUpgradePlayerTumbleWingsLogic birdFlyingLogic;

	private static readonly FieldInfo wingsFetched = AccessTools.Field(typeof(ItemUpgradePlayerTumbleWingsLogic), "fetchComplete");

	private static readonly FieldInfo wingsReady = AccessTools.Field(typeof(ItemUpgradePlayerTumbleWingsLogic), "lateStartDone");

	private static readonly FieldInfo wingsTimer = AccessTools.Field(typeof(ItemUpgradePlayerTumbleWingsLogic), "tumbleWingTimer");

	private static readonly FieldInfo wingsPinkTimer = AccessTools.Field(typeof(ItemUpgradePlayerTumbleWingsLogic), "tumbleWingPinkTimer");

	private static readonly FieldInfo tumbleTriggered = AccessTools.Field(typeof(PlayerTumble), "isPlayerInputTriggered");

	private static readonly FieldInfo tumbleForced = AccessTools.Field(typeof(PlayerTumble), "tumbleOverride");

	private NativeEngineer engineer;

	private Harmony engineerPatches;

	private string engineerError;

	private readonly RepairRules repairRules = new RepairRules();

	private NativeRepair repair;

	private RoleShuffleBridge roleBridge;

	private string repairError;

	private string mechanicName = "None";

	private int selectedRole;

	internal const int RepairExpressionIndex = 3;

	private string meleeSpecialistSelected;

	private string meleeSpecialistError;

	private object meleeSpecialistRoom;

	private BirdBonus meleeSpecialistLaunch;

	private Harmony meleeSpecialistPatches;

	private readonly HashSet<HurtCollider> meleeSpecialistHits = new HashSet<HurtCollider>();

	private readonly Stack<PlayerAvatar> meleeSpecialistExplosions = new Stack<PlayerAvatar>();

	private static readonly FieldInfo meleeSpecialistLastHolder = AccessTools.Field(typeof(PhysGrabObject), "lastPlayerGrabbing");

	private static readonly FieldInfo meleeSpecialistGrabTimer = AccessTools.Field(typeof(PhysGrabObject), "grabbedTimer");

	private static readonly FieldInfo meleeSpecialistTumbleSetup = AccessTools.Field(typeof(PlayerTumble), "setup");

	public const string PluginId = "Guest-Role";

	internal const int ExpressionIndex = 3;

	internal static Plugin Current;

	private readonly GrantRules rules = new GrantRules();

	private readonly List<InputKey> blockHostInput = new List<InputKey>();

	private ConfigEntry<bool> modEnabled;

	private ConfigEntry<int> reviveHealth;

	private ConfigEntry<int> medicHealingLimit;

	private ConfigEntry<int> medicReviveLimit;

	private ConfigEntry<int> medicHealingHotkey;

	private int medicExpression = 3;

	private ConfigEntry<KeyboardShortcut> menuKey;

	private NativeContext native;

	private bool menuOpen;

	private bool hostReady;

	private bool mapReady;

	private int selectedRow;

	private float nextRosterRefresh;

	private float noticeUntil;

	private string selectedName = "None";

	private string notice = "";

	private List<PlayerAvatar> players = new List<PlayerAvatar>();

	private bool panelGrantRequested;

	private bool panelClearRequested;

	private object session;

	private static readonly string[] PanelRoles = new string[5] { "Medic", "Mechanic", "Tank", "Bird", "Brawler" };

	private GUIStyle panelText;

	private GUIStyle panelTitle;

	private GUIStyle panelSmall;

	private GUIStyle panelButton;

	private GUIStyle panelRow;

	private Vector2 panelScroll;

	private int lastPanelRow = -1;

	private Texture2D panelPointer;

	private readonly List<RoleAnnouncement> roleAnnouncements = new List<RoleAnnouncement>();

	private static readonly FieldInfo roleVoiceChat = AccessTools.Field(typeof(PlayerAvatar), "voiceChat");

	private readonly TankRules tankRules = new TankRules();

	private readonly TankHitScope tankHitScope = new TankHitScope();

	private readonly List<TankDamageContext> tankContexts = new List<TankDamageContext>();

	private readonly Dictionary<PlayerAvatar, TankRevival> tankRevivals = new Dictionary<PlayerAvatar, TankRevival>();

	private readonly Dictionary<PlayerAvatar, int> tankWrites = new Dictionary<PlayerAvatar, int>();

	private Harmony tankPatches;

	private string tankError;

	private string tankName = "None";

	private string tankLastResult = "No damage observed yet.";

	private float nextTankDiagnostic;

	private ConfigEntry<bool> tankLogging;

	private bool tankWriting;

	private object tankStage;

	private object tankRoom;

	private PlayerAvatar maxAvatar;

	private int maxOriginal;

	private int maxUpgrade;

	private int maxApplied;

	private object maxRoom;

	private static readonly MethodInfo tankRpc = typeof(PhotonView).GetMethod("RPC", new Type[3]
	{
		typeof(string),
		typeof(RpcTarget),
		typeof(object[])
	});

	private static readonly FieldInfo headTimer = AccessTools.Field(typeof(PlayerDeathHead), "triggeredTimer");

	private static readonly FieldInfo healthReady = AccessTools.Field(typeof(PlayerHealth), "healthSet");

	private static readonly FieldInfo detectionOnly = AccessTools.Field(typeof(HurtCollider), "detectionOnly");

	private static readonly FieldInfo ignoreLocal = AccessTools.Field(typeof(HurtCollider), "ignoreLocalPlayer");

	private bool EngineerActive
	{
		get
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Invalid comparison between Unknown and I4
			if (((Behaviour)this).enabled && modEnabled != null && modEnabled.Value && Object.op_Implicit((Object)(object)GameManager.instance) && SemiFunc.IsMultiplayer() && SemiFunc.IsMasterClient() && native.Room != null && session == native.Room && Object.op_Implicit((Object)(object)RunManager.instance) && Object.op_Implicit((Object)(object)GameDirector.instance) && Object.op_Implicit((Object)(object)LevelGenerator.Instance) && LevelGenerator.Instance.Generated && SemiFunc.RunIsLevel())
			{
				return (int)GameDirector.instance.currentState == 2;
			}
			return false;
		}
	}

	internal RoleShuffleBridge RepairBridge => roleBridge;

	private bool MeleeSpecialistAuthority
	{
		get
		{
			if (((Behaviour)this).enabled && hostReady && modEnabled.Value && meleeSpecialistError == null && Object.op_Implicit((Object)(object)GameManager.instance) && SemiFunc.IsMasterClient())
			{
				return session == native.Room;
			}
			return false;
		}
	}

	private string MedicKeyLabel
	{
		get
		{
			if (medicHealingHotkey.Value != -1)
			{
				return medicHealingHotkey.Value + " / Expression" + medicExpression;
			}
			return "Disabled";
		}
	}

	private string RoleName
	{
		get
		{
			if (selectedRole != 0)
			{
				if (selectedRole != 1)
				{
					if (selectedRole != 2)
					{
						if (selectedRole != 3)
						{
							return "Brawler";
						}
						return "Bird";
					}
					return "Tank";
				}
				return "Mechanic";
			}
			return "Medic";
		}
	}

	private int ConfiguredReviveHealth => Math.Max(1, Math.Min(1000, reviveHealth.Value));

	private string RoleDescription
	{
		get
		{
			if (selectedRole != 0)
			{
				if (selectedRole != 1)
				{
					if (selectedRole != 2)
					{
						if (selectedRole != 3)
						{
							return "Brawler: +1 Tumble Launch level; tumble attacks deal 30 damage to enemies; all melee weapon hits deal x1.1 damage to enemies (rounded).";
						}
						return "Bird: +1 extra jump, Wings level " + birdWingsLevel.Value + ", no fall damage (pits excluded). Tumble in midair + forward: camera-directed flight, landing recharges.";
					}
					return "Tank: restore allies' enemy damage within 5m; take 100% yourself with 15% resistance, stopping at 1 HP. At 1 HP cover stops. Lethal hits use corrective revival.";
				}
				return "Mechanic: directly hold valuables + hold 7 / Expression3 to repair 2%/s (50%/stage). Engineer effect suppression stays automatic. Carts do not repair their contents.";
			}
			return $"Medic: auto revive nearest fallen ally within 3m at {ConfiguredReviveHealth} HP, {rules.RevivesRemaining}/{rules.ReviveLimit} revives left. Hold {MedicKeyLabel}: 5 HP per ally per 2s within 5m, {rules.HealingRemaining}/{rules.HealingLimit} HP left.";
		}
	}

	private string PanelError
	{
		get
		{
			object obj;
			if (selectedRole != 0)
			{
				if (selectedRole != 1)
				{
					if (selectedRole != 2)
					{
						if (selectedRole != 3)
						{
							return meleeSpecialistError;
						}
						return birdError;
					}
					return tankError;
				}
				obj = repairError;
				if (obj == null)
				{
					return engineerError;
				}
			}
			else
			{
				obj = null;
			}
			return (string)obj;
		}
	}

	private bool TankAuthority
	{
		get
		{
			if (((Behaviour)this).enabled && hostReady && Object.op_Implicit((Object)(object)GameManager.instance) && SemiFunc.IsMasterClient())
			{
				return session == native.Room;
			}
			return false;
		}
	}

	private bool TankActive
	{
		get
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Invalid comparison between Unknown and I4
			if (TankAuthority && modEnabled.Value && tankError == null && mapReady && Object.op_Implicit((Object)(object)RunManager.instance) && SemiFunc.RunIsLevel() && Object.op_Implicit((Object)(object)GameDirector.instance))
			{
				return (int)GameDirector.instance.currentState == 2;
			}
			return false;
		}
	}

	private bool TankResistanceActive
	{
		get
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			if (TankAuthority && modEnabled.Value && tankError == null && Object.op_Implicit((Object)(object)GameDirector.instance))
			{
				return (int)GameDirector.instance.currentState == 2;
			}
			return false;
		}
	}

	private void InitializeBird()
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Expected O, but got Unknown
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Expected O, but got Unknown
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f9: Expected O, but got Unknown
		birdWingsLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Bird", "TumbleWingsLevel", 10, new ConfigDescription("Minimum Wings level when granted. Native code has no fixed maximum; level 10 reaches zero-gravity flight. Later purchases are retained.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
		birdAirTurn = ((BaseUnityPlugin)this).Config.Bind<float>("Bird", "AirTurnDegreesPerSecond", 180f, new ConfigDescription("Additional horizontal air steering. Preserves vertical velocity; 0 disables. Applied to the assigned player only.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 720f), Array.Empty<object>()));
		try
		{
			if (new FieldInfo[8] { birdStats, birdTumble, birdGrounded, birdTumbling, birdInput, birdVelocity, birdWingsActive, birdFallTimer }.Any((FieldInfo f) => f == null))
			{
				throw new MissingMemberException("Bird game API changed.");
			}
			birdPatches = new Harmony("Guest-Role.Bird");
			InitializeBirdFlight();
		}
		catch (Exception ex)
		{
			Harmony obj = birdPatches;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			birdError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("Bird unavailable; other roles retained: " + ex));
		}
	}

	private void TickBirdSafely()
	{
		if (birdError != null)
		{
			return;
		}
		try
		{
			TickBird();
		}
		catch (Exception ex)
		{
			birdError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("Bird stopped: " + ex));
			try
			{
				ReleaseBird();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex2);
			}
		}
	}

	private void TickBird()
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Invalid comparison between Unknown and I4
		if (birdRoom != session)
		{
			ReleaseBird();
			birdSelected = null;
			birdRoom = session;
		}
		if (!hostReady || !modEnabled.Value || birdSelected == null)
		{
			ReleaseBird();
			return;
		}
		if (!Object.op_Implicit((Object)(object)StatsManager.instance) || !Object.op_Implicit((Object)(object)PunManager.instance) || !Object.op_Implicit((Object)(object)GameDirector.instance) || (int)GameDirector.instance.currentState != 2)
		{
			StopBirdFlight();
			return;
		}
		PlayerAvatar val = ((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetList()).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => Object.op_Implicit((Object)(object)p) && native.Id(p) == birdSelected));
		if (!native.CanUse(val) || !mapReady)
		{
			StopBirdFlight();
		}
		if (native.CanUse(val))
		{
			if (birdJump != null && (birdJump.Values != StatsManager.instance.playerUpgradeExtraJump || birdWings == null || birdWings.Values != StatsManager.instance.playerUpgradeTumbleWings))
			{
				ReleaseBird();
			}
			if (birdJump == null)
			{
				Dictionary<string, int> playerUpgradeExtraJump = StatsManager.instance.playerUpgradeExtraJump;
				Dictionary<string, int> playerUpgradeTumbleWings = StatsManager.instance.playerUpgradeTumbleWings;
				playerUpgradeTumbleWings.TryGetValue(birdSelected, out var value);
				birdJump = new BirdBonus(birdSelected, playerUpgradeExtraJump, 1);
				ChangeBirdUpgrade(birdJump, "ExtraJump", 1);
				birdWings = new BirdBonus(birdSelected, playerUpgradeTumbleWings, BirdBonus.WingsBonus(value, birdWingsLevel.Value));
				ChangeBirdUpgrade(birdWings, "TumbleWings", birdWings.Amount);
			}
			val.FallDamageResetSet(Mathf.Max(0.25f, (float)birdFallTimer.GetValue(val)));
			if (!(Time.timeScale <= 0f) && mapReady && !(Time.time < birdNextTurn))
			{
				birdNextTurn = Time.time + 0.1f;
				BirdAirControl(val);
			}
		}
	}

	private void BirdAirControl(PlayerAvatar player)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: 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_0073: 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_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: 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_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: 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_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0148: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_016e: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		if (birdAirTurn.Value <= 0f || (bool)birdGrounded.GetValue(player))
		{
			return;
		}
		Vector3 val = (Vector3)birdInput.GetValue(player);
		if (!Finite(val) || ((Vector3)(ref val)).sqrMagnitude < 0.01f || (bool)birdTumbling.GetValue(player))
		{
			return;
		}
		Vector3 val2 = (Vector3)birdVelocity.GetValue(player);
		Vector3 val3 = default(Vector3);
		((Vector3)(ref val3))..ctor(val2.x, 0f, val2.z);
		if (!Finite(val3) || ((Vector3)(ref val3)).magnitude < 0.2f || ((Vector3)(ref val3)).magnitude > 12f)
		{
			return;
		}
		Vector3 val4 = Vector3.ProjectOnPlane((Object.op_Implicit((Object)(object)player.localCamera) ? player.localCamera.GetOverrideTransform() : ((Component)player).transform).forward, Vector3.up);
		Vector3 val5 = ((Vector3)(ref val4)).normalized;
		if (((Vector3)(ref val5)).sqrMagnitude < 0.1f)
		{
			val5 = ((Component)player).transform.forward;
		}
		Vector3 val6 = val5 * val.z + Vector3.Cross(Vector3.up, val5) * val.x;
		if (!(((Vector3)(ref val6)).sqrMagnitude < 0.01f))
		{
			Vector3 val7 = Vector3.ClampMagnitude(Vector3.RotateTowards(val3, ((Vector3)(ref val6)).normalized * ((Vector3)(ref val3)).magnitude, birdAirTurn.Value * (MathF.PI / 180f) * 0.1f, 0f) - val3, 1.5f);
			val7.y = 0f;
			if (!(((Vector3)(ref val7)).sqrMagnitude < 0.0001f))
			{
				player.ForceImpulse(val7);
			}
		}
	}

	private void ChangeBirdUpgrade(BirdBonus bonus, string upgrade, int delta)
	{
		if (delta != 0)
		{
			if (upgrade == "ExtraJump")
			{
				PunManager.instance.UpgradePlayerExtraJump(bonus.Player, delta);
			}
			else
			{
				PunManager.instance.UpgradePlayerTumbleWings(bonus.Player, delta);
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if (!Object.op_Implicit((Object)(object)component))
			{
				throw new InvalidOperationException("PunManager PhotonView is unavailable.");
			}
			tankRpc.Invoke(component, new object[3]
			{
				"TesterUpgradeCommandRPC",
				(object)(RpcTarget)1,
				new object[3] { bonus.Player, upgrade, delta }
			});
			tankRpc.Invoke(component, new object[3]
			{
				"UpdateStatRPC",
				(object)(RpcTarget)1,
				new object[3]
				{
					"playerUpgrade" + upgrade,
					bonus.Player,
					bonus.Current
				}
			});
		}
	}

	private void SelectBird(string id)
	{
		if (!(birdSelected == id))
		{
			ReleaseBird();
			birdSelected = id;
		}
	}

	private void ReleaseBird()
	{
		StopBirdFlight();
		birdFlight.Reset();
		birdFlightPlayer = null;
		birdFlightStage = null;
		BirdBonus item = birdJump;
		BirdBonus item2 = birdWings;
		birdJump = (birdWings = null);
		Exception ex = null;
		(BirdBonus, string)[] array = new(BirdBonus, string)[2]
		{
			(item, "ExtraJump"),
			(item2, "TumbleWings")
		};
		for (int i = 0; i < array.Length; i++)
		{
			(BirdBonus, string) tuple = array[i];
			var (birdBonus, _) = tuple;
			if (birdBonus == null)
			{
				continue;
			}
			int withoutBonus = birdBonus.WithoutBonus;
			bool flag = Object.op_Implicit((Object)(object)StatsManager.instance) && birdBonus.Values == ((tuple.Item2 == "ExtraJump") ? StatsManager.instance.playerUpgradeExtraJump : StatsManager.instance.playerUpgradeTumbleWings);
			try
			{
				if (flag && Object.op_Implicit((Object)(object)PunManager.instance) && SemiFunc.IsMasterClient() && birdRoom == native.Room)
				{
					ChangeBirdUpgrade(birdBonus, tuple.Item2, withoutBonus - birdBonus.Current);
				}
			}
			catch (Exception ex2)
			{
				ex = ex ?? ex2;
			}
			finally
			{
				if (birdBonus.Values.ContainsKey(birdBonus.Player))
				{
					birdBonus.Values[birdBonus.Player] = withoutBonus;
				}
			}
		}
		if (ex != null)
		{
			throw ex;
		}
	}

	private void InitializeRoleUpgradePersistence()
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Expected O, but got Unknown
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Expected O, but got Unknown
		//IL_0076: Expected O, but got Unknown
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Expected O, but got Unknown
		if (birdStats == null)
		{
			throw new MissingMemberException("Temporary upgrade save API changed.");
		}
		roleUpgradePatches = new Harmony("Guest-Role.TemporaryUpgrades");
		roleUpgradePatches.Patch((MethodBase)AccessTools.Method(typeof(StatsManager), "SaveGame", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BirdSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "BirdSaveFinalizer", (Type[])null), (HarmonyMethod)null);
		string[] array = new string[2] { "LoadGame", "ResetAllStats" };
		foreach (string text in array)
		{
			roleUpgradePatches.Patch((MethodBase)AccessTools.Method(typeof(StatsManager), text, (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BirdResetPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}

	private static void BirdSavePrefix(StatsManager __instance, out BirdSaveState __state)
	{
		__state = null;
		Plugin current = Current;
		if (!Object.op_Implicit((Object)(object)current))
		{
			return;
		}
		bool flag = current.birdJump != null && current.birdJump.Values == __instance.playerUpgradeExtraJump;
		bool flag2 = current.meleeSpecialistLaunch != null && current.meleeSpecialistLaunch.Values == __instance.playerUpgradeLaunch;
		if (!flag && !flag2)
		{
			return;
		}
		SortedDictionary<string, Dictionary<string, int>> sortedDictionary = (SortedDictionary<string, Dictionary<string, int>>)birdStats.GetValue(__instance);
		SortedDictionary<string, Dictionary<string, int>> sortedDictionary2 = new SortedDictionary<string, Dictionary<string, int>>(sortedDictionary, sortedDictionary.Comparer);
		if (flag)
		{
			sortedDictionary2["playerUpgradeExtraJump"] = current.birdJump.ForSave();
			if (current.birdWings != null)
			{
				sortedDictionary2["playerUpgradeTumbleWings"] = current.birdWings.ForSave();
			}
		}
		if (flag2)
		{
			sortedDictionary2["playerUpgradeLaunch"] = current.meleeSpecialistLaunch.ForSave();
		}
		__state = new BirdSaveState
		{
			Original = sortedDictionary,
			Copy = sortedDictionary2
		};
		birdStats.SetValue(__instance, sortedDictionary2);
	}

	private static Exception BirdSaveFinalizer(StatsManager __instance, BirdSaveState __state, Exception __exception)
	{
		if (__state != null && birdStats.GetValue(__instance) == __state.Copy)
		{
			birdStats.SetValue(__instance, __state.Original);
		}
		return __exception;
	}

	private static void BirdResetPrefix()
	{
		Plugin current = Current;
		if (!Object.op_Implicit((Object)(object)current) || !SemiFunc.IsMasterClientOrSingleplayer())
		{
			return;
		}
		try
		{
			current.ReleaseBird();
			current.birdSelected = null;
		}
		finally
		{
			current.ReleaseMeleeSpecialist();
			current.meleeSpecialistSelected = null;
		}
	}

	private void InitializeBirdFlight()
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Expected O, but got Unknown
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Expected O, but got Unknown
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Expected O, but got Unknown
		birdFlightSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Bird", "FlightDurationSeconds", 3.5f, new ConfigDescription("Flight window per takeoff. Starts on forward input while tumbling in midair; only landing refills it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>()));
		birdFlightSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Bird", "FlightSpeed", 4f, new ConfigDescription("Flight speed in metres per second along the camera direction, including pitch.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 15f), Array.Empty<object>()));
		birdFlightUpwardAngle = ((BaseUnityPlugin)this).Config.Bind<float>("Bird", "FlightUpwardAngleDegrees", 30f, new ConfigDescription("Aim flight this many degrees above the camera direction, capped at straight up. 0 restores exact camera-direction flight. Does not increase total speed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
		FieldInfo[] array = new FieldInfo[6] { wingsFetched, wingsReady, wingsTimer, wingsPinkTimer, tumbleTriggered, tumbleForced };
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i] == null)
			{
				throw new MissingMemberException("Bird flight game API changed.");
			}
		}
		birdPatches.Patch((MethodBase)AccessTools.Method(typeof(ItemUpgradePlayerTumbleWingsLogic), "FixedUpdate", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BirdWingsFixedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private static bool BirdWingsFixedPrefix(ItemUpgradePlayerTumbleWingsLogic __instance)
	{
		Plugin current = Current;
		if (!Object.op_Implicit((Object)(object)current) || !((Behaviour)current).enabled || !current.hostReady || !current.modEnabled.Value || current.birdError != null || !SemiFunc.IsMasterClient() || current.session != current.native.Room)
		{
			return true;
		}
		PlayerAvatar playerAvatar = __instance.playerAvatar;
		if (!Object.op_Implicit((Object)(object)playerAvatar) || current.birdSelected == null || current.native.Id(playerAvatar) != current.birdSelected)
		{
			return true;
		}
		try
		{
			return current.BirdFlightFixed(__instance, playerAvatar);
		}
		catch (Exception ex)
		{
			current.birdError = ex.GetBaseException().Message;
			((BaseUnityPlugin)current).Logger.LogError((object)("Bird flight stopped: " + ex));
			try
			{
				current.ReleaseBird();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)current).Logger.LogError((object)ex2);
			}
			return true;
		}
	}

	private bool BirdFlightFixed(ItemUpgradePlayerTumbleWingsLogic logic, PlayerAvatar player)
	{
		//IL_00ee: 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)
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0187: Unknown result type (might be due to invalid IL or missing references)
		//IL_018f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0213: Unknown result type (might be due to invalid IL or missing references)
		//IL_029a: Unknown result type (might be due to invalid IL or missing references)
		//IL_029f: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_030f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0314: Unknown result type (might be due to invalid IL or missing references)
		//IL_031c: Unknown result type (might be due to invalid IL or missing references)
		if (!mapReady || !native.CanUse(player) || birdJump == null || !(bool)wingsFetched.GetValue(logic) || !(bool)wingsReady.GetValue(logic))
		{
			StopBirdFlight();
			return true;
		}
		if (birdFlightPlayer != player || birdFlightStage != LevelGenerator.Instance)
		{
			StopBirdFlight();
			birdFlight.Reset();
			birdFlightPlayer = player;
			birdFlightStage = LevelGenerator.Instance;
		}
		object? value = birdTumble.GetValue(player);
		PlayerTumble val = (PlayerTumble)((value is PlayerTumble) ? value : null);
		PhysGrabObject val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<PhysGrabObject>() : null);
		if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val2.rb))
		{
			StopBirdFlight();
			return true;
		}
		bool num = (bool)birdTumbling.GetValue(player);
		bool grounded = (num ? SemiFunc.OnGroundCheck(((Component)val).transform.position, 1f, val2) : ((bool)birdGrounded.GetValue(player)));
		Vector3 val3 = (Vector3)birdInput.GetValue(player);
		bool eligible = num && val2.playerGrabbing.Count == 0 && !val2.rb.isKinematic && ((bool)tumbleTriggered.GetValue(val) || !(bool)tumbleForced.GetValue(val)) && (float)wingsPinkTimer.GetValue(logic) <= 0f && !GameDirector.instance.DisableInput;
		if (!birdFlight.Step(Time.timeAsDouble, grounded, eligible, Finite(val3) && val3.z > 0.1f, birdFlightSeconds.Value))
		{
			StopBirdFlight();
			if ((bool)birdWingsActive.GetValue(player) && (float)wingsPinkTimer.GetValue(logic) <= 0f)
			{
				player.UpgradeTumbleWingsVisualsActive(false, false);
			}
			return false;
		}
		Transform val4 = (Object.op_Implicit((Object)(object)player.localCamera) ? player.localCamera.GetOverrideTransform() : null);
		if (!Object.op_Implicit((Object)(object)val4) || !Finite(val4.forward))
		{
			StopBirdFlight();
			return false;
		}
		birdFlyingLogic = logic;
		if (!(bool)birdWingsActive.GetValue(player))
		{
			player.UpgradeTumbleWingsVisualsActive(true, false);
		}
		wingsTimer.SetValue(logic, (float)(birdFlight.Remaining(Time.timeAsDouble) / (double)birdFlightSeconds.Value));
		val2.OverrideZeroGravity(0.1f);
		val2.rb.useGravity = false;
		val.DisableCustomGravity(0.1f);
		Vector3 val5 = val4.forward;
		val5 = Vector3.RotateTowards(((Vector3)(ref val5)).normalized, Vector3.up, birdFlightUpwardAngle.Value * (MathF.PI / 180f), 0f);
		Vector3 normalized = ((Vector3)(ref val5)).normalized;
		val2.rb.velocity = normalized * birdFlightSpeed.Value;
		Vector3 val6 = SemiFunc.PhysFollowRotation(((Component)val2).transform, Quaternion.LookRotation(normalized, val4.up), val2.rb, 20f);
		val2.rb.AddTorque(val6, (ForceMode)1);
		return false;
	}

	private void StopBirdFlight()
	{
		ItemUpgradePlayerTumbleWingsLogic val = birdFlyingLogic;
		birdFlyingLogic = null;
		if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.playerAvatar) && SemiFunc.IsMasterClient() && birdRoom == native.Room && (bool)birdWingsActive.GetValue(val.playerAvatar))
		{
			val.playerAvatar.UpgradeTumbleWingsVisualsActive(false, false);
		}
	}

	private bool IsEngineer(PlayerAvatar player)
	{
		if (Object.op_Implicit((Object)(object)player) && repairRules.Selected != null && native.Id(player) == repairRules.Selected)
		{
			return native.CanUse(player);
		}
		return false;
	}

	private void InitializeEngineer()
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Expected O, but got Unknown
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Expected O, but got Unknown
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Expected O, but got Unknown
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0127: Expected O, but got Unknown
		try
		{
			engineer = new NativeEngineer(() => EngineerActive, IsEngineer, (PlayerAvatar p) => roleBridge != null && roleBridge.NativeEngineer(p));
			engineerPatches = new Harmony("Guest-Role.Engineer");
			HarmonyMethod val = new HarmonyMethod(typeof(Plugin), "EngineerEffectPrefix", (Type[])null);
			foreach (MethodBase item in NativeEngineer.EffectMethods())
			{
				engineerPatches.Patch(item, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin), "EngineerGrabStartPrefix", (Type[])null);
			val2.priority = 0;
			val2.after = new string[1] { "REPOJP.RoleShuffle" };
			HarmonyMethod val3 = val2;
			engineerPatches.Patch((MethodBase)AccessTools.Method(typeof(PhysGrabObject), "GrabStartedRPC", (Type[])null, (Type[])null), val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			engineerPatches.Patch((MethodBase)AccessTools.Method(typeof(PhysGrabObject), "GrabEndedRPC", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "EngineerGrabEndPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		catch (Exception ex)
		{
			Harmony obj = engineerPatches;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			engineer = null;
			engineerError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("Engineer addition unavailable; existing Medic and repair remain active: " + ex));
		}
	}

	private static bool EngineerEffectPrefix(Component __instance)
	{
		if (Object.op_Implicit((Object)(object)Current))
		{
			return !Current.RunEngineer((NativeEngineer e) => e.Suppress(__instance));
		}
		return true;
	}

	private static bool EngineerGrabStartPrefix(PhysGrabObject __instance, int playerPhotonID, PhotonMessageInfo _info)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)Current))
		{
			return !Current.RunEngineer((NativeEngineer e) => e.Register(__instance, playerPhotonID, _info));
		}
		return true;
	}

	private static void EngineerGrabEndPrefix(PhysGrabObject __instance, int playerPhotonID, PhotonMessageInfo _info)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)Current))
		{
			Current.RunEngineer(delegate(NativeEngineer e)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				e.Release(__instance, playerPhotonID, _info);
				return true;
			});
		}
	}

	private bool RunEngineer(Func<NativeEngineer, bool> action)
	{
		if (engineer == null || engineerError != null)
		{
			return false;
		}
		try
		{
			return action(engineer);
		}
		catch (Exception ex)
		{
			engineerError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("Engineer addition stopped; Medic and repair retained: " + ex));
			try
			{
				engineer.RestoreAll();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex2);
			}
			return false;
		}
	}

	private void InitializeRepair()
	{
		try
		{
			repair = new NativeRepair();
			roleBridge = new RoleShuffleBridge();
		}
		catch (Exception ex)
		{
			repairError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("Mechanic grant disabled; Medic remains available: " + ex));
		}
	}

	private void TickMechanicSafely()
	{
		try
		{
			TickMechanic();
		}
		catch (Exception ex)
		{
			repairError = ex.GetBaseException().Message;
			repairRules.ResetHold();
			SetNotice("Mechanic stopped after an error; Medic is still available. See LogOutput.log.");
			((BaseUnityPlugin)this).Logger.LogError((object)("Mechanic stopped without retrying an uncertain repair: " + ex));
		}
	}

	private void TickMechanic()
	{
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0159: Unknown result type (might be due to invalid IL or missing references)
		if (repairError != null || !mapReady || !modEnabled.Value || Time.timeScale <= 0f || repairRules.Selected == null)
		{
			repairRules.ResetHold();
			return;
		}
		PlayerAvatar val = ((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetList()).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => Object.op_Implicit((Object)(object)p) && native.Id(p) == repairRules.Selected));
		if (!native.CanUse(val))
		{
			repairRules.ResetHold();
			return;
		}
		mechanicName = PlayerLabel(val);
		if (roleBridge.NativeMechanic(val, out var used))
		{
			repairRules.ObserveExternalUsage(used);
			repairRules.ResetHold();
			return;
		}
		PhysGrabObject val2 = repair.DirectHeld(val);
		PhysGrabObjectImpactDetector detector;
		ValuableObject val3 = repair.Valuable(val2, out detector);
		if (!Object.op_Implicit((Object)(object)val3) || !repair.NetworkReady(detector) || !Finite(val2.centerPoint))
		{
			repairRules.ResetHold();
			return;
		}
		float num = repair.Original(val3);
		float num2 = repair.Current(val3);
		float num3 = repairRules.Tick(native.Id(val), val, val3, num, num2, Time.timeAsDouble, native.ExpressionActive(val));
		if (!(num3 < 1f))
		{
			try
			{
				repair.Apply(detector, num3, val2.centerPoint);
			}
			finally
			{
				repairRules.Complete(num, num2, repair.Current(val3));
			}
			if (repairRules.Remaining <= 1E-06)
			{
				SetNotice("Mechanic repair exhausted for this stage: 50% used.");
			}
		}
	}

	internal bool AllowNativeMechanicRepair(PlayerAvatar player)
	{
		if (!((Behaviour)this).enabled || !hostReady || !modEnabled.Value || !Object.op_Implicit((Object)(object)player) || repairRules.Selected == null || native.Id(player) != repairRules.Selected)
		{
			return true;
		}
		if (repairError == null && mapReady && !menuOpen && Time.timeScale > 0f && native.CanUse(player))
		{
			return native.ExpressionActive(player);
		}
		return false;
	}

	private string GrantedRoles(PlayerAvatar player)
	{
		string text = native.Id(player);
		return ((text == rules.Selected) ? " [Medic]" : "") + ((text == repairRules.Selected) ? " [Mechanic]" : "") + ((text == tankRules.Selected) ? " [Tank]" : "") + ((text == birdSelected) ? " [Bird]" : "") + ((text == meleeSpecialistSelected) ? " [Brawler]" : "");
	}

	private bool IsMeleeSpecialist(PlayerAvatar player)
	{
		if (MeleeSpecialistAuthority && meleeSpecialistSelected != null && Object.op_Implicit((Object)(object)player) && native.Id(player) == meleeSpecialistSelected)
		{
			return native.CanUse(player);
		}
		return false;
	}

	private void InitializeMeleeSpecialist()
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Expected O, but got Unknown
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Expected O, but got Unknown
		//IL_0090: Expected O, but got Unknown
		//IL_00bd: 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_00df: Expected O, but got Unknown
		//IL_00df: Expected O, but got Unknown
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Expected O, but got Unknown
		try
		{
			if (meleeSpecialistLastHolder == null || meleeSpecialistGrabTimer == null || meleeSpecialistTumbleSetup == null)
			{
				throw new MissingMemberException("MeleeSpecialist game API changed.");
			}
			meleeSpecialistPatches = new Harmony("Guest-Role.MeleeSpecialist");
			meleeSpecialistPatches.Patch((MethodBase)AccessTools.Method(typeof(HurtCollider), "EnemyHurt", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "MeleeSpecialistHitPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "MeleeSpecialistHitFinalizer", (Type[])null), (HarmonyMethod)null);
			meleeSpecialistPatches.Patch((MethodBase)AccessTools.Method(typeof(ItemMeleeInflatableHammer), "ExplosionRPC", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "MeleeSpecialistExplosionPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "MeleeSpecialistExplosionFinalizer", (Type[])null), (HarmonyMethod)null);
			meleeSpecialistPatches.Patch((MethodBase)AccessTools.Method(typeof(ParticleScriptExplosion), "Spawn", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "MeleeSpecialistExplosionSpawned", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		catch (Exception ex)
		{
			Harmony obj = meleeSpecialistPatches;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			meleeSpecialistError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("MeleeSpecialist unavailable; other roles retained: " + ex));
		}
	}

	private void TickMeleeSpecialistSafely()
	{
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Invalid comparison between Unknown and I4
		if (meleeSpecialistError != null)
		{
			return;
		}
		try
		{
			if (meleeSpecialistRoom != session)
			{
				ReleaseMeleeSpecialist();
				meleeSpecialistSelected = null;
				meleeSpecialistRoom = session;
			}
			if (!MeleeSpecialistAuthority || meleeSpecialistSelected == null)
			{
				ReleaseMeleeSpecialist();
			}
			else
			{
				if (!Object.op_Implicit((Object)(object)StatsManager.instance) || !Object.op_Implicit((Object)(object)PunManager.instance) || !Object.op_Implicit((Object)(object)GameDirector.instance) || (int)GameDirector.instance.currentState != 2)
				{
					return;
				}
				PlayerAvatar val = ((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetList()).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => Object.op_Implicit((Object)(object)p) && native.Id(p) == meleeSpecialistSelected));
				if (!native.CanUse(val))
				{
					return;
				}
				object? value = birdTumble.GetValue(val);
				PlayerTumble val2 = (PlayerTumble)((value is PlayerTumble) ? value : null);
				if (Object.op_Implicit((Object)(object)val2) && (bool)meleeSpecialistTumbleSetup.GetValue(val2))
				{
					if (meleeSpecialistLaunch != null && meleeSpecialistLaunch.Values != StatsManager.instance.playerUpgradeLaunch)
					{
						ReleaseMeleeSpecialist();
					}
					if (meleeSpecialistLaunch == null)
					{
						meleeSpecialistLaunch = new BirdBonus(meleeSpecialistSelected, StatsManager.instance.playerUpgradeLaunch, 1);
						ChangeMeleeSpecialistLaunch(meleeSpecialistLaunch, 1);
					}
				}
			}
		}
		catch (Exception ex)
		{
			meleeSpecialistError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("MeleeSpecialist stopped: " + ex));
			try
			{
				ReleaseMeleeSpecialist();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex2);
			}
		}
	}

	private void ChangeMeleeSpecialistLaunch(BirdBonus bonus, int delta)
	{
		if (delta != 0)
		{
			PunManager.instance.UpgradePlayerTumbleLaunch(bonus.Player, delta);
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if (!Object.op_Implicit((Object)(object)component))
			{
				throw new InvalidOperationException("PunManager PhotonView is unavailable.");
			}
			tankRpc.Invoke(component, new object[3]
			{
				"TesterUpgradeCommandRPC",
				(object)(RpcTarget)1,
				new object[3] { bonus.Player, "Launch", delta }
			});
			tankRpc.Invoke(component, new object[3]
			{
				"UpdateStatRPC",
				(object)(RpcTarget)1,
				new object[3] { "playerUpgradeLaunch", bonus.Player, bonus.Current }
			});
		}
	}

	private void SelectMeleeSpecialist(string id)
	{
		if (!(meleeSpecialistSelected == id))
		{
			ReleaseMeleeSpecialist();
			meleeSpecialistSelected = id;
		}
	}

	private void ReleaseMeleeSpecialist()
	{
		BirdBonus birdBonus = meleeSpecialistLaunch;
		meleeSpecialistLaunch = null;
		if (birdBonus == null)
		{
			return;
		}
		int withoutBonus = birdBonus.WithoutBonus;
		try
		{
			if (Object.op_Implicit((Object)(object)StatsManager.instance) && birdBonus.Values == StatsManager.instance.playerUpgradeLaunch && Object.op_Implicit((Object)(object)PunManager.instance) && SemiFunc.IsMasterClient() && meleeSpecialistRoom == native.Room)
			{
				ChangeMeleeSpecialistLaunch(birdBonus, withoutBonus - birdBonus.Current);
			}
		}
		finally
		{
			if (birdBonus.Values.ContainsKey(birdBonus.Player))
			{
				birdBonus.Values[birdBonus.Player] = withoutBonus;
			}
		}
	}

	private PlayerAvatar MeleeSpecialistMeleeUser(Component weapon)
	{
		ItemMelee val = (Object.op_Implicit((Object)(object)weapon) ? weapon.GetComponentInParent<ItemMelee>() : null);
		if (!Object.op_Implicit((Object)(object)val) || val.swingLogicOnly)
		{
			return null;
		}
		PhysGrabObject component = ((Component)val).GetComponent<PhysGrabObject>();
		if (!Object.op_Implicit((Object)(object)component))
		{
			return null;
		}
		if (component.playerGrabbing.Count > 0)
		{
			if (!Object.op_Implicit((Object)(object)component.playerGrabbing[0]))
			{
				return null;
			}
			return component.playerGrabbing[0].playerAvatar;
		}
		if (!((float)meleeSpecialistGrabTimer.GetValue(component) > 0f))
		{
			return null;
		}
		object? value = meleeSpecialistLastHolder.GetValue(component);
		return (PlayerAvatar)((value is PlayerAvatar) ? value : null);
	}

	private static void MeleeSpecialistHitPrefix(HurtCollider __instance, out MeleeSpecialistHitState __state)
	{
		__state = null;
		Plugin current = Current;
		if (!Object.op_Implicit((Object)(object)current) || !current.MeleeSpecialistAuthority || !current.mapReady || current.meleeSpecialistHits.Contains(__instance) || __instance.deathPit || __instance.enemyKill || !__instance.enemyLogic)
		{
			return;
		}
		PlayerTumble componentInParent = ((Component)__instance).GetComponentInParent<PlayerTumble>();
		int enemyDamage;
		if (Object.op_Implicit((Object)(object)componentInParent) && (Object)(object)componentInParent.hurtCollider == (Object)(object)__instance && current.IsMeleeSpecialist(componentInParent.playerAvatar))
		{
			enemyDamage = 30;
		}
		else
		{
			MeleeSpecialistWeaponSource component = ((Component)__instance).GetComponent<MeleeSpecialistWeaponSource>();
			PlayerAvatar player = (Object.op_Implicit((Object)(object)component) ? component.Player : current.MeleeSpecialistMeleeUser((Component)(object)__instance));
			if (!current.IsMeleeSpecialist(player) || __instance.enemyDamage <= 0)
			{
				return;
			}
			enemyDamage = MeleeSpecialistRules.MeleeDamage(__instance.enemyDamage);
		}
		__state = new MeleeSpecialistHitState
		{
			Owner = current,
			Original = __instance.enemyDamage
		};
		current.meleeSpecialistHits.Add(__instance);
		__instance.enemyDamage = enemyDamage;
	}

	private static Exception MeleeSpecialistHitFinalizer(HurtCollider __instance, MeleeSpecialistHitState __state, Exception __exception)
	{
		if (__state != null)
		{
			if (Object.op_Implicit((Object)(object)__instance))
			{
				__instance.enemyDamage = __state.Original;
			}
			__state.Owner.meleeSpecialistHits.Remove(__instance);
		}
		return __exception;
	}

	private static void MeleeSpecialistExplosionPrefix(ItemMeleeInflatableHammer __instance, out bool __state)
	{
		__state = false;
		Plugin current = Current;
		if (Object.op_Implicit((Object)(object)current) && current.MeleeSpecialistAuthority && current.mapReady)
		{
			current.meleeSpecialistExplosions.Push(current.MeleeSpecialistMeleeUser((Component)(object)__instance));
			__state = true;
		}
	}

	private static Exception MeleeSpecialistExplosionFinalizer(bool __state, Exception __exception)
	{
		if (__state && Object.op_Implicit((Object)(object)Current) && Current.meleeSpecialistExplosions.Count > 0)
		{
			Current.meleeSpecialistExplosions.Pop();
		}
		return __exception;
	}

	private static void MeleeSpecialistExplosionSpawned(ParticlePrefabExplosion __result)
	{
		Plugin current = Current;
		if (Object.op_Implicit((Object)(object)current) && Object.op_Implicit((Object)(object)__result) && current.meleeSpecialistExplosions.Count != 0 && Object.op_Implicit((Object)(object)__result.HurtCollider))
		{
			PlayerAvatar player = current.meleeSpecialistExplosions.Peek();
			if (current.IsMeleeSpecialist(player))
			{
				(((Component)__result.HurtCollider).GetComponent<MeleeSpecialistWeaponSource>() ?? ((Component)__result.HurtCollider).gameObject.AddComponent<MeleeSpecialistWeaponSource>()).Player = player;
			}
		}
	}

	private void Awake()
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Expected O, but got Unknown
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Expected O, but got Unknown
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Expected O, but got Unknown
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Expected O, but got Unknown
		try
		{
			if (ConfigRename.MoveLegacy(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath)))
			{
				((BaseUnityPlugin)this).Config.Reload();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Settings moved to Guest-Role.cfg; existing values preserved.");
			}
			modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable Medic, Mechanic, Tank, Bird and Brawler grants. Host only.");
			reviveHealth = ((BaseUnityPlugin)this).Config.Bind<int>("Revival", "HealthAfterRevive", 25, new ConfigDescription("Total HP after automatic revival, capped at the target maximum. Replaces ReviveHealth from 1.x.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>()));
			menuKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "HostMenuKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Host-only role panel. Up/Down: player, Left/Right: Medic, Mechanic, Tank, Bird or Brawler, Enter: grant, Backspace: clear the displayed role.");
			medicHealingHotkey = ((BaseUnityPlugin)this).Config.Bind<int>("Medic", "HealingHotkey", 7, new ConfigDescription("Vanilla expression key for healing: 5/6/7/8/9/0, or -1 to disable healing. Unmodded guests use their binding for the corresponding Expression1-6. Hold mode is recommended.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[7] { 5, 6, 7, 8, 9, 0, -1 }), Array.Empty<object>()));
			medicHealingLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Medic", "HealingLimitPerStage", 100, new ConfigDescription("Total ally HP healed per stage, shared across all recipients. 0 disables healing. Changing settings does not reset HP already spent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>()));
			medicReviveLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Medic", "RevivesPerStage", 1, new ConfigDescription("Automatic revivals per stage. 0 disables revival. Changing settings does not reset uses already spent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
			ConfigureMedic();
			native = new NativeContext();
			InitializeRepair();
			Current = this;
			InitializeEngineer();
			InitializeTank();
			InitializeRoleUpgradePersistence();
			InitializeBird();
			InitializeMeleeSpecialist();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Guest-Role 2.7.4 loaded: Tank (200 base max HP, 5m enemy damage cover, 100% transfer, 15% damage resistance, nonlethal transfer cost); F8 Mechanic includes Expression3 repair (top-row 7, 2%/s, 50%/stage) plus Engineer effect suppression; nearest death head within 3m revives at {ConfiguredReviveHealth} HP up to {rules.ReviveLimit} times per stage; {MedicKeyLabel} heals allies within 5m by 5 HP every 2s, {rules.HealingLimit} HP shared cap. No health pack spawning.");
		}
		catch (Exception ex)
		{
			((Behaviour)this).enabled = false;
			((BaseUnityPlugin)this).Logger.LogError((object)("Guest-Role disabled: " + ex));
		}
	}

	private bool SyncContext()
	{
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Invalid comparison between Unknown and I4
		hostReady = ((Behaviour)this).enabled && Object.op_Implicit((Object)(object)GameManager.instance) && SemiFunc.IsMultiplayer() && SemiFunc.IsMasterClient() && native.Room != null;
		if (!hostReady)
		{
			rules.SetSession(null);
			repairRules.SetSession(null);
			session = null;
			menuOpen = false;
			mapReady = false;
			selectedName = "None";
			return false;
		}
		object obj = session;
		session = native.Room;
		rules.SetSession(session);
		repairRules.SetSession(session);
		if (obj != session)
		{
			selectedName = (mechanicName = "None");
			players.Clear();
		}
		mapReady = Object.op_Implicit((Object)(object)RunManager.instance) && Object.op_Implicit((Object)(object)GameDirector.instance) && LevelGenerator.Instance != null && LevelGenerator.Instance.Generated && SemiFunc.RunIsLevel() && (int)GameDirector.instance.currentState == 2;
		if (mapReady)
		{
			repairRules.SetMap(LevelGenerator.Instance);
			if (rules.SetMap(LevelGenerator.Instance))
			{
				SetNotice($"New stage: Medic {rules.ReviveLimit} revives + {rules.HealingLimit} HP healing; Mechanic 50% repair available.");
			}
		}
		return mapReady;
	}

	private void Update()
	{
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			ConfigureMedic();
			SyncContext();
			RunEngineer(delegate(NativeEngineer nativeEngineer)
			{
				nativeEngineer.Reconcile();
				return true;
			});
			TankSafely(TickTank);
			TickBirdSafely();
			TickMeleeSpecialistSafely();
			TickRoleSpeech();
			if (!hostReady)
			{
				return;
			}
			KeyboardShortcut value = menuKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				menuOpen = !menuOpen;
				if (menuOpen)
				{
					RefreshPlayers();
					selectedRow = 0;
				}
			}
			if (!menuOpen)
			{
				panelGrantRequested = (panelClearRequested = false);
				TickMedic();
				TickMechanicSafely();
				return;
			}
			if (Input.GetKeyDown((KeyCode)27))
			{
				menuOpen = false;
				panelGrantRequested = (panelClearRequested = false);
				return;
			}
			if (Object.op_Implicit((Object)(object)CursorManager.instance))
			{
				SemiFunc.CursorUnlock(0.1f);
			}
			if (Object.op_Implicit((Object)(object)CameraAim.Instance))
			{
				CameraAim.Instance.OverrideAimStop();
			}
			rules.ResetChannel();
			repairRules.ResetHold();
			if (Object.op_Implicit((Object)(object)InputManager.instance))
			{
				InputManager.instance.DisableControlsExcept(0.1f, blockHostInput);
			}
			if (Time.unscaledTime >= nextRosterRefresh)
			{
				RefreshPlayers();
			}
			if (Input.GetKeyDown((KeyCode)273))
			{
				selectedRow = Mathf.Max(0, selectedRow - 1);
			}
			if (Input.GetKeyDown((KeyCode)274))
			{
				selectedRow = Mathf.Min(Mathf.Max(0, players.Count - 1), selectedRow + 1);
			}
			if (Input.GetKeyDown((KeyCode)276))
			{
				selectedRole = (selectedRole + 4) % 5;
			}
			if (Input.GetKeyDown((KeyCode)275))
			{
				selectedRole = (selectedRole + 1) % 5;
			}
			if (ConsumePanelRequest(ref panelClearRequested) || Input.GetKeyDown((KeyCode)8))
			{
				if (selectedRole == 0)
				{
					rules.Select(null);
					selectedName = "None";
				}
				else if (selectedRole == 1)
				{
					repairRules.Select(null);
					mechanicName = "None";
				}
				else if (selectedRole == 2)
				{
					tankRules.Select(null);
					tankName = "None";
				}
				else if (selectedRole == 3)
				{
					SelectBird(null);
				}
				else
				{
					SelectMeleeSpecialist(null);
				}
				SetNotice(RoleName + " cleared. Stage budgets were kept.");
			}
			if ((!ConsumePanelRequest(ref panelGrantRequested) && !Input.GetKeyDown((KeyCode)13)) || players.Count <= 0)
			{
				return;
			}
			PlayerAvatar val = players[Mathf.Clamp(selectedRow, 0, players.Count - 1)];
			if (!Object.op_Implicit((Object)(object)val))
			{
				return;
			}
			string text = native.Id(val);
			if (string.IsNullOrEmpty(text))
			{
				SetNotice("Player information is still loading.");
				return;
			}
			if (selectedRole == 0)
			{
				ClearConflictingRoles(text, selectedRole);
				rules.Select(text);
				selectedName = PlayerLabel(val);
				SetNotice("Medic: " + selectedName + ". Approach death heads to revive; hold " + MedicKeyLabel + " to heal allies.");
			}
			else if (selectedRole == 1)
			{
				if (repairError != null)
				{
					SetNotice("Mechanic unavailable: " + repairError);
					return;
				}
				ClearConflictingRoles(text, selectedRole);
				repairRules.Select(text);
				mechanicName = PlayerLabel(val);
				SetNotice("Mechanic: " + mechanicName + ". Hold 7 / Expression3 while holding valuables: repair 2%/s, 50%/stage.");
			}
			else if (selectedRole == 2)
			{
				if (tankError != null)
				{
					SetNotice("Tank unavailable: " + tankError);
					return;
				}
				ClearConflictingRoles(text, selectedRole);
				tankRules.Select(text);
				tankName = PlayerLabel(val);
				SetNotice("Tank: " + tankName + ". Cover enemy damage within 5m; absorb all with 15% resistance, retain at least 1 HP.");
			}
			else if (selectedRole == 3)
			{
				if (birdError != null)
				{
					SetNotice("Bird unavailable: " + birdError);
					return;
				}
				ClearConflictingRoles(text, selectedRole);
				SelectBird(text);
				TickBirdSafely();
				if (birdError != null)
				{
					birdSelected = null;
					SetNotice("Bird unavailable: " + birdError);
					return;
				}
				SetNotice("Bird: " + PlayerLabel(val) + ". Extra jump, Wings, fall protection and air steering.");
			}
			else
			{
				if (meleeSpecialistError != null)
				{
					SetNotice("Brawler unavailable: " + meleeSpecialistError);
					return;
				}
				ClearConflictingRoles(text, selectedRole);
				SelectMeleeSpecialist(text);
				TickMeleeSpecialistSafely();
				if (meleeSpecialistError != null)
				{
					meleeSpecialistSelected = null;
					SetNotice("Brawler unavailable: " + meleeSpecialistError);
					return;
				}
				SetNotice("Brawler: " + PlayerLabel(val) + ". Tumble Launch +1, tumble damage 30, melee damage x1.1.");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Granted " + RoleName + " to " + PlayerLabel(val)));
			AnnounceRole(val, text, selectedRole, RoleName);
			menuOpen = false;
		}
		catch (Exception e)
		{
			DisableAfterError(e);
		}
	}

	private void RefreshPlayers()
	{
		string highlighted = ((players.Count > 0 && selectedRow < players.Count && Object.op_Implicit((Object)(object)players[selectedRow])) ? native.Id(players[selectedRow]) : null);
		nextRosterRefresh = Time.unscaledTime + 0.5f;
		players = (Object.op_Implicit((Object)(object)GameDirector.instance) ? (from p in SemiFunc.PlayerGetList()
			where Object.op_Implicit((Object)(object)p) && !string.IsNullOrEmpty(native.Id(p))
			orderby (p != PlayerAvatar.instance) ? 1 : 0
			select p).ThenBy<PlayerAvatar, string>((PlayerAvatar p) => native.Id(p), StringComparer.Ordinal).ToList() : new List<PlayerAvatar>());
		int num = ((highlighted == null) ? (-1) : players.FindIndex((PlayerAvatar p) => native.Id(p) == highlighted));
		if (num >= 0)
		{
			selectedRow = num;
		}
		selectedRow = Mathf.Clamp(selectedRow, 0, Mathf.Max(0, players.Count - 1));
	}

	private void ClearConflictingRoles(string id, int role)
	{
		string[] array = RoleSelection.Assign(new string[5] { rules.Selected, repairRules.Selected, tankRules.Selected, birdSelected, meleeSpecialistSelected }, role, id);
		if (role != 0 && rules.Selected != array[0])
		{
			rules.Select(null);
			selectedName = "None";
		}
		if (role != 1 && repairRules.Selected != array[1])
		{
			repairRules.Select(null);
			mechanicName = "None";
		}
		if (role != 2 && tankRules.Selected != array[2])
		{
			tankRules.Select(null);
			tankName = "None";
			TankSafely(RestoreTankMaximum);
		}
		if (role != 3 && birdSelected != array[3])
		{
			SelectBird(null);
		}
		if (role != 4 && meleeSpecialistSelected != array[4])
		{
			SelectMeleeSpecialist(null);
		}
		RunEngineer(delegate(NativeEngineer e)
		{
			e.Reconcile();
			return true;
		});
	}

	private string PlayerLabel(PlayerAvatar player)
	{
		return native.Name(player) + ((player == PlayerAvatar.instance) ? " [Host / You]" : " [Guest]");
	}

	private void TickMedic()
	{
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		if (!mapReady || !modEnabled.Value || Time.timeScale <= 0f || rules.Selected == null)
		{
			rules.ResetChannel();
			return;
		}
		PlayerAvatar[] array = (from p in SemiFunc.PlayerGetList()
			where Object.op_Implicit((Object)(object)p)
			select p).ToArray();
		PlayerAvatar medic = ((IEnumerable<PlayerAvatar>)array).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => native.Id(p) == rules.Selected));
		if (!native.CanUse(medic) || !Finite(((Component)medic).transform.position))
		{
			rules.ResetChannel();
			return;
		}
		selectedName = PlayerLabel(medic);
		PlayerAvatar val = null;
		if (rules.RevivesRemaining > 0)
		{
			PlayerDeathHead nearest = (from p in array
				select native.Head(p) into h
				where !TankRevivalPending(Object.op_Implicit((Object)(object)h) ? h.playerAvatar : null) && native.CanRevive(medic, h) && Within(((Component)medic).transform.position, HeadPosition(h), 3f)
				select h).OrderBy(delegate(PlayerDeathHead h)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//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)
				Vector3 val2 = HeadPosition(h) - ((Component)medic).transform.position;
				return ((Vector3)(ref val2)).sqrMagnitude;
			}).ThenBy<PlayerDeathHead, string>((PlayerDeathHead h) => native.Id(h.playerAvatar), StringComparer.Ordinal).FirstOrDefault();
			if (Object.op_Implicit((Object)(object)nearest) && rules.TryRevive(rules.Selected, () => ReviveTeammate(medic, nearest)))
			{
				val = nearest.playerAvatar;
				SetNotice(native.Name(medic) + " revived " + native.Name(val) + $". Revives remaining: {rules.RevivesRemaining}/{rules.ReviveLimit}.");
			}
		}
		bool eligible = medicExpression >= 1 && native.ExpressionActive(medic, medicExpression) && !HoldingBoombox(medic);
		if (rules.HealingTick(native.Id(medic), medic, eligible, Time.timeAsDouble))
		{
			HealNearby(medic, array, val);
		}
	}

	private static bool HoldingBoombox(PlayerAvatar avatar)
	{
		Transform val = (Object.op_Implicit((Object)(object)avatar.physGrabber) ? avatar.physGrabber.grabbedObjectTransform : null);
		if (Object.op_Implicit((Object)(object)val))
		{
			if (!Object.op_Implicit((Object)(object)((Component)val).GetComponentInParent<ValuableBoombox>()))
			{
				return Object.op_Implicit((Object)(object)((Component)val).GetComponentInChildren<ValuableBoombox>());
			}
			return true;
		}
		return false;
	}

	private static Vector3 HeadPosition(PlayerDeathHead head)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		PhysGrabObject component = ((Component)head).GetComponent<PhysGrabObject>();
		if (!Object.op_Implicit((Object)(object)component))
		{
			return ((Component)head).transform.position;
		}
		return component.centerPoint;
	}

	private static bool Within(Vector3 a, Vector3 b, float radius)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		if (Finite(a) && Finite(b))
		{
			Vector3 val = a - b;
			return GrantRules.WithinRadius(((Vector3)(ref val)).sqrMagnitude, radius);
		}
		return false;
	}

	private bool ReviveTeammate(PlayerAvatar medic, PlayerDeathHead head)
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		if (!modEnabled.Value || rules.Selected != native.Id(medic) || !native.CanRevive(medic, head) || !Within(((Component)medic).transform.position, HeadPosition(head), 3f))
		{
			return false;
		}
		PlayerAvatar playerAvatar = head.playerAvatar;
		int num = GrantRules.RevivalHealth(ConfiguredReviveHealth, native.MaxHealth(playerAvatar));
		playerAvatar.Revive(false);
		if (native.IsDead(playerAvatar))
		{
			return false;
		}
		int num2 = num - 1;
		if (num2 > 0)
		{
			try
			{
				playerAvatar.playerHealth.HealOther(num2, false);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Revive succeeded but its HP top-up failed; use consumed, no resend: " + ex));
			}
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Native revive issued: " + native.Name(medic) + " -> " + native.Name(playerAvatar) + "; requestedHP=" + num + "; additionalHealing=" + num2));
		return true;
	}

	private void ConfigureMedic()
	{
		rules.Configure(medicHealingLimit.Value, medicReviveLimit.Value);
		int num = ((medicHealingHotkey.Value == -1) ? (-1) : GrantRules.ExpressionForDigit(medicHealingHotkey.Value));
		if (num != medicExpression)
		{
			rules.ResetChannel();
			medicExpression = num;
		}
	}

	private void HealNearby(PlayerAvatar medic, PlayerAvatar[] roster, PlayerAvatar justRevived)
	{
		int num = 0;
		foreach (PlayerAvatar item in roster.Where((PlayerAvatar p) => (Object)(object)p != (Object)(object)medic && (Object)(object)p != (Object)(object)justRevived && native.CanUse(p) && Within(((Component)medic).transform.position, ((Component)p).transform.position, 5f)).OrderBy(delegate(PlayerAvatar p)
		{
			//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)
			Vector3 val = ((Component)p).transform.position - ((Component)medic).transform.position;
			return ((Vector3)(ref val)).sqrMagnitude;
		}).ThenBy<PlayerAvatar, string>((PlayerAvatar p) => native.Id(p), StringComparer.Ordinal))
		{
			int num2 = rules.ReserveHealing(native.Id(medic), native.Health(item), native.MaxHealth(item));
			if (num2 > 0)
			{
				item.playerHealth.HealOther(num2, true);
				num += num2;
			}
		}
		if (num > 0 && rules.HealingRemaining == 0)
		{
			SetNotice($"Ally healing exhausted for this stage: {rules.HealingUsed}/{rules.HealingLimit} HP used.");
		}
	}

	private static bool Finite(Vector3 v)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		if (!float.IsNaN(v.x) && !float.IsNaN(v.y) && !float.IsNaN(v.z) && !float.IsInfinity(v.x) && !float.IsInfinity(v.y))
		{
			return !float.IsInfinity(v.z);
		}
		return false;
	}

	private void SetNotice(string text)
	{
		notice = text;
		noticeUntil = Time.unscaledTime + 8f;
	}

	private void DisableAfterError(Exception e)
	{
		((Behaviour)this).enabled = false;
		hostReady = false;
		menuOpen = false;
		rules.SetSession(null);
		repairRules.SetSession(null);
		((BaseUnityPlugin)this).Logger.LogError((object)("Guest-Role disabled after an error: " + e));
	}

	private void OnDisable()
	{
		roleAnnouncements.Clear();
		try
		{
			ReleaseMeleeSpecialist();
			meleeSpecialistSelected = null;
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)ex);
		}
		try
		{
			ReleaseBird();
			birdSelected = null;
		}
		catch (Exception ex2)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)ex2);
		}
		RunEngineer(delegate(NativeEngineer e)
		{
			e.RestoreAll();
			return true;
		});
		try
		{
			RestoreTankMaximum();
		}
		catch (Exception ex3)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)ex3);
		}
		tankRules.SetSession(null);
		tankRevivals.Clear();
		tankContexts.Clear();
		tankWrites.Clear();
		tankHitScope.Clear();
		hostReady = false;
		menuOpen = false;
		rules.SetSession(null);
		repairRules.SetSession(null);
	}

	private void OnDestroy()
	{
		if (Object.op_Implicit((Object)(object)panelPointer))
		{
			Object.Destroy((Object)(object)panelPointer);
		}
		roleBridge?.Dispose();
		Harmony obj = engineerPatches;
		if (obj != null)
		{
			obj.UnpatchSelf();
		}
		Harmony obj2 = tankPatches;
		if (obj2 != null)
		{
			obj2.UnpatchSelf();
		}
		Harmony obj3 = birdPatches;
		if (obj3 != null)
		{
			obj3.UnpatchSelf();
		}
		Harmony obj4 = meleeSpecialistPatches;
		if (obj4 != null)
		{
			obj4.UnpatchSelf();
		}
		Harmony obj5 = roleUpgradePatches;
		if (obj5 != null)
		{
			obj5.UnpatchSelf();
		}
		if (Current == this)
		{
			Current = null;
		}
	}

	private static bool ConsumePanelRequest(ref bool request)
	{
		bool result = request;
		request = false;
		return result;
	}

	private void PreparePanelStyles()
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Expected O, but got Unknown
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: 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_0069: Expected O, but got Unknown
		//IL_0070: 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_0082: Expected O, but got Unknown
		//IL_009c: 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_00b9: 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_00cc: Expected O, but got Unknown
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: 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)
		//IL_00fe: Expected O, but got Unknown
		//IL_0103: Expected O, but got Unknown
		if (panelText == null)
		{
			panelText = new GUIStyle(GUI.skin.label)
			{
				fontSize = 17,
				wordWrap = true,
				richText = false
			};
			panelText.normal.textColor = Color.white;
			panelTitle = new GUIStyle(panelText)
			{
				fontSize = 27,
				fontStyle = (FontStyle)1
			};
			panelSmall = new GUIStyle(panelText)
			{
				fontSize = 14
			};
			panelSmall.normal.textColor = new Color(0.79f, 0.8f, 0.82f);
			panelButton = new GUIStyle(panelText)
			{
				alignment = (TextAnchor)4,
				fontStyle = (FontStyle)1,
				clipping = (TextClipping)1
			};
			panelRow = new GUIStyle(panelText)
			{
				alignment = (TextAnchor)3,
				clipping = (TextClipping)1,
				wordWrap = false,
				padding = new RectOffset(16, 16, 0, 0)
			};
		}
	}

	private static void PanelFill(Rect rect, Color color)
	{
		//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_000b: Unknown result type (might be due to invalid IL or missing references)
		Color color2 = GUI.color;
		GUI.color = color;
		GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
		GUI.color = color2;
	}

	private bool PanelButton(Rect rect, string text, bool selected = false, bool row = false)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_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_0053: 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_0090: 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_01c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: 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_00db: Invalid comparison between Unknown and I4
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Expected O, but got Unknown
		//IL_0115: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Expected O, but got Unknown
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0171: Expected O, but got Unknown
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		//IL_0199: Unknown result type (might be due to invalid IL or missing references)
		bool num = ((Rect)(ref rect)).Contains(Event.current.mousePosition);
		Color val = (selected ? new Color(0.78f, 0.23f, 0.25f) : new Color(0.22f, 0.2f, 0.23f));
		if (num && GUI.enabled)
		{
			val = Color.Lerp(val, Color.white, 0.12f);
		}
		if (!GUI.enabled)
		{
			val *= 0.6f;
		}
		PanelFill(rect, val);
		if (selected)
		{
			PanelFill(new Rect(((Rect)(ref rect)).x, ((Rect)(ref rect)).y, 4f, ((Rect)(ref rect)).height), new Color(1f, 0.55f, 0.5f));
		}
		if (text == "Tank")
		{
			bool result = GUI.Button(rect, GUIContent.none, panelButton);
			if ((int)Event.current.type == 7)
			{
				GUIStyle val2 = new GUIStyle(panelButton)
				{
					alignment = (TextAnchor)3
				};
				float num2 = 0f;
				string text2 = text;
				for (int i = 0; i < text2.Length; i++)
				{
					num2 += val2.CalcSize(new GUIContent(text2[i].ToString())).x;
				}
				float num3 = ((Rect)(ref rect)).x + (((Rect)(ref rect)).width - num2) / 2f;
				text2 = text;
				for (int i = 0; i < text2.Length; i++)
				{
					GUIContent val3 = new GUIContent(text2[i].ToString());
					float x = val2.CalcSize(val3).x;
					val2.Draw(new Rect(num3, ((Rect)(ref rect)).y, x + 2f, ((Rect)(ref rect)).height), val3, false, false, false, false);
					num3 += x;
				}
			}
			return result;
		}
		return GUI.Button(rect, text, row ? panelRow : panelButton);
	}

	private void OnGUI()
	{
		//IL_06bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_06c3: 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_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: 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_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: 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_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_0241: Unknown result type (might be due to invalid IL or missing references)
		//IL_0280: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0326: Unknown result type (might be due to invalid IL or missing references)
		//IL_0337: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0519: Unknown result type (might be due to invalid IL or missing references)
		//IL_052d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0551: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_048b: Unknown result type (might be due to invalid IL or missing references)
		//IL_05b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_05fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0643: Unknown result type (might be due to invalid IL or missing references)
		//IL_0682: Unknown result type (might be due to invalid IL or missing references)
		//IL_0692: Unknown result type (might be due to invalid IL or missing references)
		//IL_0697: Unknown result type (might be due to invalid IL or missing references)
		//IL_06dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_06e3: Invalid comparison between Unknown and I4
		if (!hostReady || !((Behaviour)this).enabled)
		{
			return;
		}
		PreparePanelStyles();
		if (!menuOpen)
		{
			if (Time.unscaledTime < noticeUntil)
			{
				PanelFill(new Rect(20f, 20f, (float)Mathf.Min(680, Screen.width - 40), 85f), new Color(0.12f, 0.12f, 0.14f, 0.94f));
				GUI.Label(new Rect(32f, 30f, (float)Mathf.Min(656, Screen.width - 64), 65f), notice, panelText);
			}
			return;
		}
		Matrix4x4 matrix = GUI.matrix;
		Color color = GUI.color;
		bool enabled = GUI.enabled;
		try
		{
			PanelFill(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), new Color(0f, 0f, 0f, 0.5f));
			float num = 0.5f * Mathf.Min((float)Screen.width / 1000f, (float)Screen.height / 820f);
			float num2 = (float)Screen.width / num;
			float num3 = (float)Screen.height / num;
			GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one * num);
			float num4 = (num2 - 920f) / 2f;
			float num5 = (num3 - 748f) / 2f;
			PanelFill(new Rect(num4, num5, 920f, 748f), new Color(0.29f, 0.29f, 0.3f));
			PanelFill(new Rect(num4, num5, 920f, 66f), new Color(0.55f, 0.13f, 0.14f));
			GUI.Label(new Rect(num4 + 26f, num5 + 17f, 760f, 40f), "Guest-Role  /  ROLE ASSIGNMENT", panelTitle);
			if (PanelButton(new Rect(num4 + 858f, num5 + 14f, 44f, 38f), "X"))
			{
				menuOpen = false;
			}
			GUI.Label(new Rect(num4 + 26f, num5 + 82f, 840f, 25f), "01  SELECT ROLE", panelSmall);
			for (int i = 0; i < PanelRoles.Length; i++)
			{
				if (PanelButton(new Rect(num4 + 26f + (float)(i * 175), num5 + 112f, 166f, 48f), PanelRoles[i], selectedRole == i))
				{
					selectedRole = i;
				}
			}
			GUI.Label(new Rect(num4 + 26f, num5 + 179f, 840f, 26f), "02  SELECT PLAYER   /   " + players.Count + " connected", panelSmall);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(num4 + 26f, num5 + 212f, 868f, 214f);
			PanelFill(val, new Color(0.16f, 0.16f, 0.18f));
			if (lastPanelRow != selectedRow)
			{
				float num6 = selectedRow * 52;
				if (num6 < panelScroll.y)
				{
					panelScroll.y = num6;
				}
				if (num6 + 50f > panelScroll.y + 214f)
				{
					panelScroll.y = num6 + 50f - 214f;
				}
				lastPanelRow = selectedRow;
			}
			panelScroll = GUI.BeginScrollView(val, panelScroll, new Rect(0f, 0f, 842f, (float)Mathf.Max(214, players.Count * 52)));
			for (int j = 0; j < players.Count; j++)
			{
				if (Object.op_Implicit((Object)(object)players[j]))
				{
					string text = GrantedRoles(players[j]);
					string text2 = j + 1 + ".  " + PlayerLabel(players[j]) + (string.IsNullOrEmpty(text) ? "   [No role]" : ("   " + text));
					if (PanelButton(new Rect(4f, (float)(j * 52 + 3), 834f, 46f), text2, selectedRow == j, row: true))
					{
						selectedRow = j;
					}
				}
			}
			if (players.Count == 0)
			{
				GUI.Label(new Rect(16f, 18f, 810f, 80f), "Waiting for players...\nOpen this panel after your character appears.", panelText);
			}
			GUI.EndScrollView();
			PanelFill(new Rect(num4 + 26f, num5 + 443f, 868f, 112f), new Color(0.23f, 0.23f, 0.25f));
			GUI.Label(new Rect(num4 + 42f, num5 + 454f, 836f, 94f), RoleDescription, panelText);
			string text3 = ((PanelError != null) ? ("Unavailable: " + PanelError) : ((Time.unscaledTime < noticeUntil) ? notice : "One role per player. Assigning a role replaces their previous role."));
			GUI.Label(new Rect(num4 + 26f, num5 + 570f, 868f, 54f), text3, panelSmall);
			GUI.enabled = enabled && players.Count > 0;
			if (PanelButton(new Rect(num4 + 26f, num5 + 638f, 560f, 56f), "ASSIGN  " + RoleName, selected: true))
			{
				panelGrantRequested = true;
			}
			GUI.enabled = enabled;
			if (PanelButton(new Rect(num4 + 602f, num5 + 638f, 292f, 56f), "CLEAR  " + RoleName))
			{
				panelClearRequested = true;
			}
			GUI.Label(new Rect(num4 + 26f, num5 + 712f, 868f, 24f), "Mouse: select    Arrows: choose    Enter: assign    Backspace: clear role    " + ((object)menuKey.Value/*cast due to .constrained prefix*/).ToString() + " / Esc: close", panelSmall);
		}
		finally
		{
			GUI.matrix = matrix;
			GUI.color = color;
			GUI.enabled = enabled;
		}
		if (menuOpen && (int)Event.current.type == 7)
		{
			DrawPanelPointer();
		}
	}

	private void DrawPanelPointer()
	{
		//IL_019e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Expected O, but got Unknown
		//IL_0134: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0148: Unknown result type (might be due to invalid IL or missing references)
		//IL_014d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		//IL_018c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)panelPointer))
		{
			panelPointer = new Texture2D(24, 32, (TextureFormat)4, false);
			((Texture)panelPointer).filterMode = (FilterMode)0;
			Color[] array = (Color[])(object)new Color[768];
			for (int i = 0; i < 32; i++)
			{
				for (int j = 0; j < 24; j++)
				{
					bool flag = i < 25 && (float)j <= (float)i * 0.64f;
					bool flag2 = i >= 17 && i < 31 && j >= 7 && j <= 12;
					bool flag3 = i >= 4 && i < 22 && j >= 2 && (float)j <= (float)i * 0.64f - 2f;
					bool flag4 = i >= 18 && i < 29 && j >= 9 && j <= 10;
					array[(31 - i) * 24 + j] = ((flag || flag2) ? ((flag3 || flag4) ? Color.white : Color.black) : Color.clear);
				}
			}
			panelPointer.SetPixels(array);
			panelPointer.Apply();
		}
		Matrix4x4 matrix = GUI.matrix;
		Color color = GUI.color;
		try
		{
			GUI.matrix = Matrix4x4.identity;
			GUI.color = Color.white;
			Vector3 mousePosition = Input.mousePosition;
			float num = Mathf.Max(1f, (float)Screen.height / 1080f);
			GUI.DrawTexture(new Rect(mousePosition.x, (float)Screen.height - mousePosition.y, 24f * num, 32f * num), (Texture)(object)panelPointer);
		}
		finally
		{
			GUI.matrix = matrix;
			GUI.color = color;
		}
	}

	private void AnnounceRole(PlayerAvatar player, string id, int role, string text)
	{
		roleAnnouncements.RemoveAll((RoleAnnouncement a) => a.Id == id);
		roleAnnouncements.Add(new RoleAnnouncement
		{
			Player = player,
			Id = id,
			Role = role,
			Text = text,
			Room = session,
			Deadline = Time.unscaledTime + 15f
		});
		TickRoleSpeech();
	}

	private void TickRoleSpeech()
	{
		//IL_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Invalid comparison between Unknown and I4
		for (int num = roleAnnouncements.Count - 1; num >= 0; num--)
		{
			RoleAnnouncement roleAnnouncement = roleAnnouncements[num];
			string text = ((roleAnnouncement.Role == 0) ? rules.Selected : ((roleAnnouncement.Role == 1) ? repairRules.Selected : ((roleAnnouncement.Role == 2) ? tankRules.Selected : ((roleAnnouncement.Role == 3) ? birdSelected : meleeSpecialistSelected))));
			if (!hostReady || !modEnabled.Value || roleAnnouncement.Room != session || !Object.op_Implicit((Object)(object)roleAnnouncement.Player) || text != roleAnnouncement.Id || Time.unscaledTime > roleAnnouncement.Deadline)
			{
				roleAnnouncements.RemoveAt(num);
			}
			else
			{
				object? obj = roleVoiceChat?.GetValue(roleAnnouncement.Player);
				PlayerVoiceChat val = (PlayerVoiceChat)((obj is PlayerVoiceChat) ? obj : null);
				if (Object.op_Implicit((Object)(object)GameDirector.instance) && (int)GameDirector.instance.currentState == 2 && Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.ttsVoice))
				{
					roleAnnouncements.RemoveAt(num);
					try
					{
						roleAnnouncement.Player.ChatMessageSend(roleAnnouncement.Text);
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Role TTS could not be sent; role assignment retained: " + ex.GetBaseException().Message));
					}
				}
			}
		}
	}

	private bool IsTank(PlayerAvatar player)
	{
		if (Object.op_Implicit((Object)(object)player) && tankRules.Selected != null)
		{
			return native.Id(player) == tankRules.Selected;
		}
		return false;
	}

	private void InitializeTank()
	{
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Expected O, but got Unknown
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_016d: Expected O, but got Unknown
		try
		{
			tankLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "TankLogging", false, "Log Tank damage observations and cover decisions (rate-limited). F8 also shows the latest result.");
			if (headTimer == null || healthReady == null || detectionOnly == null || ignoreLocal == null || tankRpc == null)
			{
				throw new MissingMemberException("Tank game API changed.");
			}
			tankPatches = new Harmony("Guest-Role.Tank");
			Patch(typeof(HurtCollider), "PlayerHurt", "TankHitPrefix", null, "TankHitFinalizer");
			Patch(typeof(PlayerHealth), "Hurt", "TankHurtPrefix", "TankHurtPostfix", "TankContextFinalizer");
			Patch(typeof(PlayerHealth), "UpdateHealthRPC", "TankHealthPrefix", "TankHealthPostfix", "TankContextFinalizer");
			Type type = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "RoleShuffle")?.GetType("REPOJP.StageRoles.EventRoleRuntime");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "ApplyBodyguardTransfer", (Type[])null, (Type[])null));
			if (methodInfo != null)
			{
				tankPatches.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin), "TankOriginalBodyguardPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		catch (Exception ex)
		{
			Harmony obj = tankPatches;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			tankError = ex.GetBaseException().Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("Tank unavailable; Medic and Mechanic retained: " + ex));
		}
		static HarmonyMethod Hook(string name)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			if (name != null)
			{
				return new HarmonyMethod(typeof(Plugin), name, (Type[])null);
			}
			return null;
		}
		void Patch(Type type2, string method, string prefix = null, string postfix = null, string finalizer = null)
		{
			tankPatches.Patch((MethodBase)AccessTools.Method(type2, method, (Type[])null, (Type[])null), Hook(prefix), Hook(postfix), (HarmonyMethod)null, Hook(finalizer), (HarmonyMethod)null);
		}
	}

	private void TankSafely(Action action)
	{
		if (tankError != null)
		{
			return;
		}
		try
		{
			action();
		}
		catch (Exception ex)
		{
			tankError = ex.GetBaseException().Message;
			tankRules.ClearHits();
			((BaseUnityPlugin)this).Logger.LogError((object)("Tank stopped after an error; no uncertain transfer is retried: " + ex));
			try
			{
				RestoreTankMaximum();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex2);
			}
		}
	}

	private PlayerAvatar CurrentTank()
	{
		return ((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetList()).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => Object.op_Implicit((Object)(object)p) && native.Id(p) == tankRules.Selected));
	}

	private void TankDiagnostic(string result, PlayerAvatar victim, int before, int after)
	{
		//IL_003e: 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)
		if (tankRules.Selected != null && Object.op_Implicit((Object)(object)GameDirector.instance))
		{
			PlayerAvatar val = CurrentTank();
			string text = ((Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)victim)) ? (Vector3.Distance(((Component)val).transform.position, ((Component)victim).transform.position).ToString("0.00") + "m") : "unknown");
			tankLastResult = result + " | " + (Object.op_Implicit((Object)(object)victim) ? PlayerLabel(victim) : "unknown") + " " + before + "->" + after + " | Tank HP=" + native.Health(val) + " | distance=" + text;
			if (tankLogging != null && tankLogging.Value && Time.unscaledTime >= nextTankDiagnostic)
			{
				nextTankDiagnostic = Time.unscaledTime + 0.5f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Tank] " + tankLastResult + " | tank=" + (Object.op_Implicit((Object)(object)val) ? PlayerLabel(val) : "missing") + " | active=" + TankActive + " | living=" + native.CanUse(val)));
			}
		}
	}

	private PlayerAvatar CoveringTank(PlayerAvatar victim)
	{
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: 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_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		if (!TankActive || !Object.op_Implicit((Object)(object)victim) || tankRules.Selected == null)
		{
			return null;
		}
		PlayerAvatar val = CurrentTank();
		if (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)victim && native.CanUse(val) && native.Health(val) > 1 && Finite(((Component)val).transform.position) && Finite(((Component)victim).transform.position))
		{
			Vector3 val2 = ((Component)val).transform.position - ((Component)victim).transform.position;
			if (TankRules.InRange(((Vector3)(ref val2)).sqrMagnitude))
			{
				return val;
			}
		}
		return null;
	}

	private void TickTank()
	{
		if (tankRoom != session)
		{
			tankRoom = session;
			tankStage = null;
			tankName = "None";
			tankRevivals.Clear();
			tankWrites.Clear();
			tankRules.SetSession(session);
			tankHitScope.Clear();
			maxAvatar = null;
			maxRoom = null;
		}
		if (mapReady && tankStage != LevelGenerator.Instance)
		{
			tankStage = LevelGenerator.Instance;
			tankRules.SetMap(tankStage);
			tankRevivals.Clear();
		}
		if (!TankAuthority || !Object.op_Implicit((Object)(object)GameDirector.instance))
		{
			tankRules.ClearHits();
			return;
		}
		PlayerAvatar val = ((tankRules.Selected == null) ? null : CurrentTank());
		if (Object.op_Implicit((Object)(object)maxAvatar) && (!modEnabled.Value || (Object)(object)maxAvatar != (Object)(object)val))
		{
			RestoreTankMaximum();
		}
		if (modEnabled.Value && Object.op_Implicit((Object)(object)val) && native.CanUse(val) && Object.op_Implicit((Object)(object)StatsManager.instance) && (bool)healthReady.GetValue(val.playerHealth))
		{
			tankName = PlayerLabel(val);
			int playerMaxHealth = StatsManager.instance.GetPlayerMaxHealth(native.Id(val));
			if ((Object)(object)maxAvatar != (Object)(object)val)
			{
				maxAvatar = val;
				maxRoom = session;
				maxOriginal = native.MaxHealth(val);
				maxUpgrade = playerMaxHealth;
			}
			maxApplied = TankRules.Maximum(playerMaxHealth);
			bool flag = tankRules.InitializeHealth(native.Id(val));
			if (flag || native.MaxHealth(val) != maxApplied)
			{
				SetTankHealth(val, flag ? maxApplied : Math.Min(native.Health(val), maxApplied), maxApplied, effect: false);
			}
		}
		if (!TankActive)
		{
			tankRules.ClearHits();
		}
		if (mapReady)
		{
			FinishTankRevivals();
		}
	}

	private void RestoreTankMaximum()
	{
		if (Object.op_Implicit((Object)(object)maxAvatar) && Object.op_Implicit((Object)(object)GameManager.instance) && SemiFunc.IsMasterClient() && maxRoom == native.Room && Object.op_Implicit((Object)(object)StatsManager.instance) && native.MaxHealth(maxAvatar) == maxApplied)
		{
			int playerMaxHealth = StatsManager.instance.GetPlayerMaxHealth(native.Id(maxAvatar));
			int num = (int)Math.Max(1L, Math.Min(2147483647L, (long)maxOriginal + (long)playerMaxHealth - maxUpgrade));
			SetTankHealth(maxAvatar, Math.Min(native.Health(maxAvatar), num), num, effect: false);
		}
		maxAvatar = null;
		maxRoom = null;
	}

	private void SetTankHealth(PlayerAvatar player, int hp, int maximum, bool effect)
	{
		if (!Object.op_Implicit((Object)(object)GameManager.instance) || !SemiFunc.IsMasterClient() || !Object.op_Implicit((Object)(object)player) || !Ob