Decompiled source of RoleShuffle v4.1.1

RoleShuffle.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using MenuLib;
using MenuLib.MonoBehaviors;
using MenuLib.Structs;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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("Codex")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("4.1.1.0")]
[assembly: AssemblyInformationalVersion("4.1.1")]
[assembly: AssemblyProduct("RoleShuffle")]
[assembly: AssemblyTitle("RoleShuffle")]
[assembly: AssemblyVersion("4.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace REPOJP.StageRoles
{
	[BepInPlugin("REPOJP.RoleShuffle", "RoleShuffle", "4.1.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class StageRolesPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "REPOJP.RoleShuffle";

		public const string PluginName = "RoleShuffle";

		public const string PluginVersion = "4.1.1";

		public const string StageFluxGuid = "REPOJP.StagePhysicsEvents";

		public const string RepoConfigGuid = "nickklmao.repoconfig";

		public const string MenuLibGuid = "nickklmao.menulib";

		private Harmony? _harmony;

		private GameObject? _controllerObject;

		internal static StageRolesPlugin Instance { get; private set; }

		internal static ManualLogSource ModLogger { get; private set; }

		internal StageRolesConfig Settings { get; private set; }

		internal StageRoleController Controller { get; private set; }

		private void Awake()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			Instance = this;
			ModLogger = ((BaseUnityPlugin)this).Logger;
			Settings = new StageRolesConfig(((BaseUnityPlugin)this).Config);
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
			_controllerObject = new GameObject("RoleShuffle_Controller");
			((Object)_controllerObject).hideFlags = (HideFlags)61;
			_controllerObject.transform.SetParent(((Component)this).transform, false);
			Controller = _controllerObject.AddComponent<StageRoleController>();
			Controller.Initialize(Settings);
			((Component)this).gameObject.AddComponent<RoleHud>().Initialize(Settings);
			((Component)this).gameObject.AddComponent<RoleMenu>().Initialize();
			((Component)this).gameObject.AddComponent<RoleTestCommandService>().Initialize(Settings, Controller);
			_harmony = new Harmony("REPOJP.RoleShuffle");
			try
			{
				_harmony.PatchAll(typeof(LifecyclePatches));
				_harmony.PatchAll(typeof(EngineerTrapUpdatePatches));
				_harmony.PatchAll(typeof(EngineerEffectActivationPatches));
				_harmony.PatchAll(typeof(ShopPatches));
				_harmony.PatchAll(typeof(NotificationEnemyReactionPatches));
				_harmony.PatchAll(typeof(HunterBatteryPatches));
				SceneManager.activeSceneChanged += ActiveSceneChanged;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"RoleShuffle 4.1.1 loaded.");
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to apply Harmony patches: {arg}");
				_harmony.UnpatchSelf();
				((Behaviour)Controller).enabled = false;
			}
		}

		private void ActiveSceneChanged(Scene previous, Scene current)
		{
			Controller?.StageEnding();
		}

		private void OnDestroy()
		{
			SceneManager.activeSceneChanged -= ActiveSceneChanged;
			Controller?.Shutdown();
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			NotificationEnemyReactionGuard.Clear();
			if ((Object)(object)_controllerObject != (Object)null)
			{
				Object.Destroy((Object)(object)_controllerObject);
			}
		}
	}
	internal sealed class StageRolesConfig
	{
		internal ConfigEntry<bool> Enabled { get; }

		internal ConfigEntry<bool> UniqueRoles { get; }

		internal ConfigEntry<int> ShopUpgradeItemCount { get; }

		internal ConfigEntry<bool> AnnouncementsEnabled { get; }

		internal ConfigEntry<float> AnnouncementDelaySeconds { get; }

		internal ConfigEntry<float> StageFluxAnnouncementDelaySeconds { get; }

		internal ConfigEntry<bool> HudEnabled { get; }

		internal ConfigEntry<string> HudAnchor { get; }

		internal ConfigEntry<string> HudAlignment { get; }

		internal ConfigEntry<int> HudOffsetX { get; }

		internal ConfigEntry<int> HudOffsetY { get; }

		internal ConfigEntry<int> HudScalePercent { get; }

		internal ConfigEntry<int> HudPlayersPerPage { get; }

		internal ConfigEntry<float> HudPageIntervalSeconds { get; }

		internal ConfigEntry<float> HudTransitionDurationSeconds { get; }

		internal ConfigEntry<bool> HudPinLocalPlayer { get; }

		internal ConfigEntry<bool> SetRoleCommandEnabled { get; }

		internal ConfigEntry<int> BaseHealthLevels { get; }

		internal ConfigEntry<int> BaseStaminaLevels { get; }

		internal ConfigEntry<int> BaseExtraJumpLevels { get; }

		internal ConfigEntry<int> BaseSpeedLevels { get; }

		internal ConfigEntry<int> BaseStrengthLevels { get; }

		internal ConfigEntry<int> BaseRangeLevels { get; }

		internal ConfigEntry<int> BaseLaunchLevels { get; }

		internal ConfigEntry<int> BaseTumbleClimbLevels { get; }

		internal ConfigEntry<int> BaseTumbleWingsLevels { get; }

		internal ConfigEntry<int> BaseCrouchRestLevels { get; }

		internal ConfigEntry<int> BaseMapPlayerCountLevels { get; }

		internal ConfigEntry<int> BaseDeathHeadBatteryLevels { get; }

		internal ConfigEntry<bool> TankEnabled { get; }

		internal ConfigEntry<int> TankWeight { get; }

		internal ConfigEntry<int> TankHealthLevels { get; }

		internal ConfigEntry<bool> RunnerEnabled { get; }

		internal ConfigEntry<int> RunnerWeight { get; }

		internal ConfigEntry<int> RunnerSpeedLevels { get; }

		internal ConfigEntry<int> RunnerStaminaLevels { get; }

		internal ConfigEntry<bool> JumperEnabled { get; }

		internal ConfigEntry<int> JumperWeight { get; }

		internal ConfigEntry<int> JumperExtraJumpLevels { get; }

		internal ConfigEntry<bool> LifterEnabled { get; }

		internal ConfigEntry<int> LifterWeight { get; }

		internal ConfigEntry<int> LifterStrengthLevels { get; }

		internal ConfigEntry<bool> LauncherEnabled { get; }

		internal ConfigEntry<int> LauncherWeight { get; }

		internal ConfigEntry<int> LauncherLaunchLevels { get; }

		internal ConfigEntry<bool> ClimberEnabled { get; }

		internal ConfigEntry<int> ClimberWeight { get; }

		internal ConfigEntry<int> ClimberClimbLevels { get; }

		internal ConfigEntry<int> ClimberRangeLevels { get; }

		internal ConfigEntry<bool> FlyerEnabled { get; }

		internal ConfigEntry<int> FlyerWeight { get; }

		internal ConfigEntry<int> FlyerWingsLevels { get; }

		internal ConfigEntry<bool> TrackerEnabled { get; }

		internal ConfigEntry<int> TrackerWeight { get; }

		internal ConfigEntry<bool> GhostEnabled { get; }

		internal ConfigEntry<int> GhostWeight { get; }

		internal ConfigEntry<int> GhostDeathHeadBatteryLevels { get; }

		internal ConfigEntry<bool> BomberEnabled { get; }

		internal ConfigEntry<int> BomberWeight { get; }

		internal ConfigEntry<float> BomberDistance { get; }

		internal ConfigEntry<int> BomberMaximumActiveGrenades { get; }

		internal ConfigEntry<bool> BomberAllowTruckSpawns { get; }

		internal ConfigEntry<bool> BomberExplosiveEnabled { get; }

		internal ConfigEntry<bool> BomberStunEnabled { get; }

		internal ConfigEntry<bool> BomberShockwaveEnabled { get; }

		internal ConfigEntry<bool> BomberDuctTapedEnabled { get; }

		internal ConfigEntry<bool> MedicEnabled { get; }

		internal ConfigEntry<int> MedicWeight { get; }

		internal ConfigEntry<int> MedicHealAmount { get; }

		internal ConfigEntry<float> MedicHealIntervalSeconds { get; }

		internal ConfigEntry<float> MedicHealRadius { get; }

		internal ConfigEntry<int> MedicTotalHealingLimit { get; }

		internal ConfigEntry<bool> PhoenixEnabled { get; }

		internal ConfigEntry<int> PhoenixWeight { get; }

		internal ConfigEntry<int> PhoenixRevivalHealth { get; }

		internal ConfigEntry<float> PhoenixReviveDelaySeconds { get; }

		internal ConfigEntry<float> PhoenixFailureGraceSeconds { get; }

		internal ConfigEntry<bool> JoblessEnabled { get; }

		internal ConfigEntry<int> JoblessWeight { get; }

		internal ConfigEntry<int> JoblessDamage { get; }

		internal ConfigEntry<float> JoblessDamageIntervalSeconds { get; }

		internal ConfigEntry<bool> RescuerEnabled { get; }

		internal ConfigEntry<int> RescuerWeight { get; }

		internal ConfigEntry<int> RescuerRevivalHealth { get; }

		internal ConfigEntry<float> RescuerReviveDelaySeconds { get; }

		internal ConfigEntry<float> RescuerRadius { get; }

		internal ConfigEntry<int> RescuerMaximumRevives { get; }

		internal ConfigEntry<bool> VampireEnabled { get; }

		internal ConfigEntry<int> VampireWeight { get; }

		internal ConfigEntry<int> VampireTier1HealAmount { get; }

		internal ConfigEntry<int> VampireTier2HealAmount { get; }

		internal ConfigEntry<int> VampireTier3HealAmount { get; }

		internal ConfigEntry<float> VampireRadius { get; }

		internal ConfigEntry<bool> KingEnabled { get; }

		internal ConfigEntry<int> KingWeight { get; }

		internal ConfigEntry<bool> TunaEnabled { get; }

		internal ConfigEntry<int> TunaWeight { get; }

		internal ConfigEntry<float> TunaStationaryDelaySeconds { get; }

		internal ConfigEntry<int> TunaDamage { get; }

		internal ConfigEntry<float> TunaDamageIntervalSeconds { get; }

		internal ConfigEntry<bool> MusicianEnabled { get; }

		internal ConfigEntry<int> MusicianWeight { get; }

		internal ConfigEntry<int> MusicianHealAmount { get; }

		internal ConfigEntry<float> MusicianHealRadius { get; }

		internal ConfigEntry<bool> MageEnabled { get; }

		internal ConfigEntry<int> MageWeight { get; }

		internal ConfigEntry<float> MageCastIntervalSeconds { get; }

		internal ConfigEntry<int> MageStarHealthCost { get; }

		internal ConfigEntry<int> MageGravityHealthCost { get; }

		internal ConfigEntry<int> MageRollHealthCost { get; }

		internal ConfigEntry<int> MageVoidHealthCost { get; }

		internal ConfigEntry<int> MageLaserHealthCost { get; }

		internal ConfigEntry<bool> GamblerEnabled { get; }

		internal ConfigEntry<int> GamblerWeight { get; }

		internal ConfigEntry<int> GamblerWinChancePercent { get; }

		internal ConfigEntry<float> GamblerWinValueMultiplier { get; }

		internal ConfigEntry<bool> HunterEnabled { get; }

		internal ConfigEntry<int> HunterWeight { get; }

		internal ConfigEntry<int> HunterBatteryConsumptionPercent { get; }

		internal ConfigEntry<float> HunterDoubleOrbChancePercent { get; }

		internal ConfigEntry<float> HunterJackpotOrbChancePercent { get; }

		internal ConfigEntry<int> HunterJackpotOrbCount { get; }

		internal ConfigEntry<bool> StinkerEnabled { get; }

		internal ConfigEntry<int> StinkerWeight { get; }

		internal ConfigEntry<float> StinkerDistance { get; }

		internal ConfigEntry<float> StinkerSafetyDistance { get; }

		internal ConfigEntry<bool> StinkerAllowTruckSpawns { get; }

		internal ConfigEntry<bool> EngineerEnabled { get; }

		internal ConfigEntry<int> EngineerWeight { get; }

		internal float ClampedBomberDistance => Mathf.Clamp(BomberDistance.Value, 1f, 100f);

		internal int ClampedBomberMaximum => Mathf.Clamp(BomberMaximumActiveGrenades.Value, 1, 30);

		internal float ClampedHunterBatteryConsumption => (float)Mathf.Clamp(HunterBatteryConsumptionPercent.Value, 0, 100) / 100f;

		internal float ClampedStinkerDistance => Mathf.Clamp(StinkerDistance.Value, 1f, 100f);

		internal float ClampedStinkerSafetyDistance => Mathf.Clamp(StinkerSafetyDistance.Value, 1f, 30f);

		internal float ClampedJoblessInterval => Mathf.Clamp(JoblessDamageIntervalSeconds.Value, 0.05f, 10f);

		internal float ClampedTunaDamageInterval => Mathf.Clamp(TunaDamageIntervalSeconds.Value, 0.05f, 10f);

		internal StageRolesConfig(ConfigFile config)
		{
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Expected O, but got Unknown
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Expected O, but got Unknown
			//IL_0c73: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c7d: Expected O, but got Unknown
			//IL_0c89: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c93: Expected O, but got Unknown
			//IL_0c9f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ca9: Expected O, but got Unknown
			Enabled = BindBool(config, "General", "Enabled", defaultValue: true, "Enables stage role assignment.");
			UniqueRoles = BindBool(config, "General", "UniqueRoles", defaultValue: true, "Avoids duplicate roles until every enabled role has been assigned once.");
			ShopUpgradeItemCount = BindInt(config, "General", "ShopUpgradeItemCount", 0, 0, 30, "Number of upgrade items requested for each shop.");
			AnnouncementsEnabled = BindBool(config, "Notifications", "Enabled", defaultValue: true, "Enables role-name and role-effect chat and TTS notifications.");
			AnnouncementDelaySeconds = BindFloat(config, "Notifications", "AnnouncementDelaySeconds", 2.5f, 0f, 30f, "Delay before role announcements.");
			StageFluxAnnouncementDelaySeconds = BindFloat(config, "Notifications", "StageFluxAnnouncementDelaySeconds", 5f, 0f, 30f, "Delay used when Stage Flux is installed.");
			HudEnabled = BindBool(config, "HUD", "Enabled", defaultValue: true, "Shows every player's assigned role during a stage.");
			HudAnchor = config.Bind<string>("HUD", "Anchor", "BottomLeft", new ConfigDescription("Anchor point used to position the role HUD.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[9] { "TopLeft", "TopCenter", "TopRight", "MiddleLeft", "MiddleCenter", "MiddleRight", "BottomLeft", "BottomCenter", "BottomRight" }), Array.Empty<object>()));
			HudAlignment = config.Bind<string>("HUD", "Alignment", "Left", new ConfigDescription("Horizontal alignment of the role HUD text.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Left", "Center", "Right" }), Array.Empty<object>()));
			HudOffsetX = BindInt(config, "HUD", "OffsetX", 0, -3840, 3840, "Horizontal offset from the anchor in pixels.");
			HudOffsetY = BindInt(config, "HUD", "OffsetY", 0, -2160, 2160, "Vertical offset from the anchor in pixels.");
			HudScalePercent = BindInt(config, "HUD", "ScalePercent", 70, 50, 200, "Role HUD scale percentage.");
			HudPlayersPerPage = BindInt(config, "HUD", "PlayersPerPage", 8, 2, 20, "Maximum players shown at once, including the pinned local player.");
			HudPageIntervalSeconds = BindFloat(config, "HUD", "PageIntervalSeconds", 5f, 1f, 30f, "Seconds each role page remains visible.");
			HudTransitionDurationSeconds = BindFloat(config, "HUD", "TransitionDurationSeconds", 0.2f, 0f, 1f, "Duration of the fade between role pages.");
			HudPinLocalPlayer = BindBool(config, "HUD", "PinLocalPlayer", defaultValue: true, "Keeps the local player's role visible on every page.");
			SetRoleCommandEnabled = BindBool(config, "Testing", "SetRoleCommandEnabled", defaultValue: true, "Enables the /setrole, /sr, /hudplayers, and /hp testing commands.");
			BaseHealthLevels = BaseUpgradeLevel(config, "HealthUpgradeLevels", 1);
			BaseStaminaLevels = BaseUpgradeLevel(config, "StaminaUpgradeLevels", 0);
			BaseExtraJumpLevels = BaseUpgradeLevel(config, "ExtraJumpUpgradeLevels", 0);
			BaseSpeedLevels = BaseUpgradeLevel(config, "SpeedUpgradeLevels", 0);
			BaseStrengthLevels = BaseUpgradeLevel(config, "StrengthUpgradeLevels", 0);
			BaseRangeLevels = BaseUpgradeLevel(config, "RangeUpgradeLevels", 0);
			BaseLaunchLevels = BaseUpgradeLevel(config, "LaunchUpgradeLevels", 0);
			BaseTumbleClimbLevels = BaseUpgradeLevel(config, "TumbleClimbUpgradeLevels", 0);
			BaseTumbleWingsLevels = BaseUpgradeLevel(config, "TumbleWingsUpgradeLevels", 0);
			BaseCrouchRestLevels = BaseUpgradeLevel(config, "CrouchRestUpgradeLevels", 0);
			BaseMapPlayerCountLevels = BindInt(config, "Base Upgrades", "MapPlayerCountUpgradeLevels", 0, 0, 1, "Base vanilla Map Player Count level kept when no role overrides it.");
			BaseDeathHeadBatteryLevels = BaseUpgradeLevel(config, "DeathHeadBatteryUpgradeLevels", 0);
			TankEnabled = RoleEnabled(config, "Tank");
			TankWeight = RoleWeight(config, "Tank");
			TankHealthLevels = UpgradeLevel(config, "Tank", "HealthUpgradeLevels", 21);
			RunnerEnabled = RoleEnabled(config, "Runner");
			RunnerWeight = RoleWeight(config, "Runner");
			RunnerSpeedLevels = UpgradeLevel(config, "Runner", "SpeedUpgradeLevels", 6);
			RunnerStaminaLevels = UpgradeLevel(config, "Runner", "StaminaUpgradeLevels", 46);
			JumperEnabled = RoleEnabled(config, "Jumper");
			JumperWeight = RoleWeight(config, "Jumper");
			JumperExtraJumpLevels = UpgradeLevel(config, "Jumper", "ExtraJumpUpgradeLevels", 10);
			LifterEnabled = RoleEnabled(config, "Lifter");
			LifterWeight = RoleWeight(config, "Lifter");
			LifterStrengthLevels = UpgradeLevel(config, "Lifter", "StrengthUpgradeLevels", 25);
			LauncherEnabled = RoleEnabled(config, "Launcher");
			LauncherWeight = RoleWeight(config, "Launcher");
			LauncherLaunchLevels = UpgradeLevel(config, "Launcher", "LaunchUpgradeLevels", 10);
			ClimberEnabled = RoleEnabled(config, "Climber");
			ClimberWeight = RoleWeight(config, "Climber");
			ClimberClimbLevels = UpgradeLevel(config, "Climber", "ClimbUpgradeLevels", 50);
			ClimberRangeLevels = UpgradeLevel(config, "Climber", "RangeUpgradeLevels", 20);
			FlyerEnabled = RoleEnabled(config, "Flyer");
			FlyerWeight = RoleWeight(config, "Flyer");
			FlyerWingsLevels = UpgradeLevel(config, "Flyer", "WingsUpgradeLevels", 10);
			TrackerEnabled = RoleEnabled(config, "Tracker");
			TrackerWeight = RoleWeight(config, "Tracker");
			GhostEnabled = RoleEnabled(config, "Ghost");
			GhostWeight = RoleWeight(config, "Ghost");
			GhostDeathHeadBatteryLevels = UpgradeLevel(config, "Ghost", "DeathHeadBatteryUpgradeLevels", 50);
			BomberEnabled = RoleEnabled(config, "Bomber");
			BomberWeight = RoleWeight(config, "Bomber");
			BomberDistance = BindFloat(config, "Bomber", "DistancePerGrenade", 8f, 1f, 100f, "Travel distance required per grenade.");
			BomberMaximumActiveGrenades = BindInt(config, "Bomber", "MaximumActiveGrenades", 30, 1, 30, "Maximum tracked grenades per player.");
			BomberAllowTruckSpawns = BindBool(config, "Bomber", "AllowTruckSpawns", defaultValue: false, "Allows grenade placement inside the truck.");
			BomberExplosiveEnabled = BindBool(config, "Bomber", "ExplosiveGrenadesEnabled", defaultValue: true, "Includes the vanilla explosive grenade.");
			BomberStunEnabled = BindBool(config, "Bomber", "StunGrenadesEnabled", defaultValue: true, "Includes the vanilla stun grenade.");
			BomberShockwaveEnabled = BindBool(config, "Bomber", "ShockwaveGrenadesEnabled", defaultValue: true, "Includes the vanilla shockwave grenade.");
			BomberDuctTapedEnabled = BindBool(config, "Bomber", "DuctTapedGrenadesEnabled", defaultValue: true, "Includes the vanilla duct-taped grenade.");
			MedicEnabled = RoleEnabled(config, "Medic");
			MedicWeight = RoleWeight(config, "Medic");
			MedicHealAmount = BindInt(config, "Medic", "HealAmount", 5, 1, 100, "Health restored to each nearby player per tick.");
			MedicHealIntervalSeconds = BindFloat(config, "Medic", "HealIntervalSeconds", 2f, 0.1f, 30f, "Seconds between Medic healing ticks.");
			MedicHealRadius = BindFloat(config, "Medic", "HealRadius", 5f, 1f, 30f, "Maximum distance from the Medic for healing.");
			MedicTotalHealingLimit = BindInt(config, "Medic", "TotalHealingLimit", 200, 1, 10000, "Maximum total health restored by each Medic per stage.");
			PhoenixEnabled = RoleEnabled(config, "Phoenix");
			PhoenixWeight = RoleWeight(config, "Phoenix");
			PhoenixRevivalHealth = BindInt(config, "Phoenix", "RevivalHealth", 25, 1, 1000, "Health restored after Phoenix revival, capped at the player's maximum health.");
			PhoenixReviveDelaySeconds = BindFloat(config, "Phoenix", "ReviveDelaySeconds", 2f, 2f, 10f, "Delay before revival. Phoenix always waits at least two seconds.");
			PhoenixFailureGraceSeconds = BindFloat(config, "Phoenix", "FailureGraceSeconds", 5f, 1f, 15f, "Maximum time to hold a failed-stage transition while revival initializes.");
			JoblessEnabled = RoleEnabled(config, "Jobless");
			JoblessWeight = RoleWeight(config, "Jobless");
			JoblessDamage = BindInt(config, "Jobless", "Damage", 1, 1, 100, "Damage applied per tick outside the truck.");
			JoblessDamageIntervalSeconds = BindFloat(config, "Jobless", "DamageIntervalSeconds", 0.1f, 0.05f, 10f, "Seconds between damage ticks outside the truck.");
			RescuerEnabled = RoleEnabled(config, "Rescuer");
			RescuerWeight = RoleWeight(config, "Rescuer");
			RescuerRevivalHealth = BindInt(config, "Rescuer", "RevivalHealth", 25, 1, 1000, "Health restored to a player revived by Rescuer, capped at the player's maximum health.");
			RescuerReviveDelaySeconds = BindFloat(config, "Rescuer", "ReviveDelaySeconds", 2f, 0f, 10f, "Delay before reviving another player.");
			RescuerRadius = BindFloat(config, "Rescuer", "Radius", 3f, 1f, 50f, "Maximum distance to a dead player.");
			RescuerMaximumRevives = BindInt(config, "Rescuer", "MaximumRevives", 2, 1, 10, "Maximum revivals per stage.");
			VampireEnabled = RoleEnabled(config, "Vampire");
			VampireWeight = RoleWeight(config, "Vampire");
			VampireTier1HealAmount = BindInt(config, "Vampire", "Tier1HealAmount", 5, 1, 100, "Health restored by a nearby Tier 1 enemy death.");
			VampireTier2HealAmount = BindInt(config, "Vampire", "Tier2HealAmount", 10, 1, 100, "Health restored by a nearby Tier 2 enemy death.");
			VampireTier3HealAmount = BindInt(config, "Vampire", "Tier3HealAmount", 50, 1, 100, "Health restored by a nearby Tier 3 enemy death.");
			VampireRadius = BindFloat(config, "Vampire", "Radius", 10f, 1f, 50f, "Maximum distance from a dying enemy.");
			KingEnabled = RoleEnabled(config, "King");
			KingWeight = RoleWeight(config, "King");
			TunaEnabled = RoleEnabled(config, "Tuna");
			TunaWeight = RoleWeight(config, "Tuna");
			TunaStationaryDelaySeconds = BindFloat(config, "Tuna", "StationaryDelaySeconds", 3f, 0.1f, 30f, "Seconds without movement before damage starts.");
			TunaDamage = BindInt(config, "Tuna", "Damage", 1, 1, 100, "Damage applied per stationary tick.");
			TunaDamageIntervalSeconds = BindFloat(config, "Tuna", "DamageIntervalSeconds", 0.1f, 0.05f, 10f, "Seconds between stationary damage ticks.");
			MusicianEnabled = RoleEnabled(config, "Musician");
			MusicianWeight = RoleWeight(config, "Musician");
			MusicianHealAmount = BindInt(config, "Musician", "HealAmount", 5, 1, 100, "Health restored to the Musician and each nearby player when an instrument note is played.");
			MusicianHealRadius = BindFloat(config, "Musician", "HealRadius", 10f, 1f, 50f, "Maximum healing distance from the Musician when an instrument note is played.");
			MageEnabled = RoleEnabled(config, "Mage");
			MageWeight = RoleWeight(config, "Mage");
			MageCastIntervalSeconds = BindFloat(config, "Mage", "CastIntervalSeconds", 5f, 0.1f, 30f, "Minimum seconds between chat-triggered casts.");
			MageStarHealthCost = BindInt(config, "Mage", "StarHealthCost", 10, 0, 100, "Health consumed after a successful Star Wand cast.");
			MageGravityHealthCost = BindInt(config, "Mage", "GravityHealthCost", 10, 0, 100, "Health consumed after a successful Zero Gravity Staff cast.");
			MageRollHealthCost = BindInt(config, "Mage", "RollHealthCost", 15, 0, 100, "Health consumed after a successful Roll Staff cast.");
			MageVoidHealthCost = BindInt(config, "Mage", "VoidHealthCost", 30, 0, 100, "Health consumed after a successful Void Staff cast.");
			MageLaserHealthCost = BindInt(config, "Mage", "LaserHealthCost", 50, 0, 100, "Health consumed after a successful Wizard Staff beam cast.");
			GamblerEnabled = RoleEnabled(config, "Gambler");
			GamblerWeight = RoleWeight(config, "Gambler");
			GamblerWinChancePercent = BindInt(config, "Gambler", "WinChancePercent", 50, 0, 100, "Chance for a wagered valuable to use the win multiplier.");
			GamblerWinValueMultiplier = BindFloat(config, "Gambler", "WinValueMultiplier", 2f, 0f, 10f, "Value multiplier applied when a wager wins.");
			config.Remove(new ConfigDefinition("Gambler", "MaximumWagersPerStage"));
			config.Remove(new ConfigDefinition("Gambler", "HealAmountPerExtraction"));
			config.Remove(new ConfigDefinition("Gambler", "LoseValueMultiplier"));
			HunterEnabled = RoleEnabled(config, "Hunter");
			HunterWeight = RoleWeight(config, "Hunter");
			HunterBatteryConsumptionPercent = BindInt(config, "Hunter", "WeaponBatteryConsumptionPercent", 75, 0, 100, "Percentage of normal weapon battery consumption used by Hunter.");
			HunterDoubleOrbChancePercent = BindFloat(config, "Hunter", "DoubleOrbChancePercent", 10f, 0f, 100f, "Chance for an enemy defeated by Hunter to drop twice its normal orb count.");
			HunterJackpotOrbChancePercent = BindFloat(config, "Hunter", "JackpotOrbChancePercent", 0.01f, 0f, 100f, "Chance for an enemy defeated by Hunter to drop the configured jackpot orb count.");
			HunterJackpotOrbCount = BindInt(config, "Hunter", "JackpotOrbCount", 10, 1, 100, "Orb count used when Hunter's jackpot roll succeeds.");
			StinkerEnabled = RoleEnabled(config, "Stinker");
			StinkerWeight = RoleWeight(config, "Stinker");
			StinkerDistance = BindFloat(config, "Stinker", "DistancePerCloud", 1f, 1f, 100f, "Travel distance required per uranium cloud.");
			StinkerSafetyDistance = BindFloat(config, "Stinker", "MinimumSafetyDistance", 1f, 1f, 30f, "Minimum distance from the newest pending cloud when it is created.");
			StinkerAllowTruckSpawns = BindBool(config, "Stinker", "AllowTruckSpawns", defaultValue: false, "Allows uranium cloud creation inside the truck.");
			EngineerEnabled = RoleEnabled(config, "Engineer");
			EngineerWeight = RoleWeight(config, "Engineer");
		}

		internal bool RoleIsEnabled(StageRole role)
		{
			return role switch
			{
				StageRole.Tank => TankEnabled.Value, 
				StageRole.Runner => RunnerEnabled.Value, 
				StageRole.Jumper => JumperEnabled.Value, 
				StageRole.Lifter => LifterEnabled.Value, 
				StageRole.Launcher => LauncherEnabled.Value, 
				StageRole.Climber => ClimberEnabled.Value, 
				StageRole.Flyer => FlyerEnabled.Value, 
				StageRole.Tracker => TrackerEnabled.Value, 
				StageRole.Ghost => GhostEnabled.Value, 
				StageRole.Bomber => BomberEnabled.Value, 
				StageRole.Medic => MedicEnabled.Value, 
				StageRole.Phoenix => PhoenixEnabled.Value, 
				StageRole.Jobless => JoblessEnabled.Value, 
				StageRole.Rescuer => RescuerEnabled.Value, 
				StageRole.Vampire => VampireEnabled.Value, 
				StageRole.King => KingEnabled.Value, 
				StageRole.Tuna => TunaEnabled.Value, 
				StageRole.Musician => MusicianEnabled.Value, 
				StageRole.Mage => MageEnabled.Value, 
				StageRole.Gambler => GamblerEnabled.Value, 
				StageRole.Hunter => HunterEnabled.Value, 
				StageRole.Stinker => StinkerEnabled.Value, 
				StageRole.Engineer => EngineerEnabled.Value, 
				_ => false, 
			};
		}

		internal int RoleWeightValue(StageRole role)
		{
			return role switch
			{
				StageRole.Tank => TankWeight.Value, 
				StageRole.Runner => RunnerWeight.Value, 
				StageRole.Jumper => JumperWeight.Value, 
				StageRole.Lifter => LifterWeight.Value, 
				StageRole.Launcher => LauncherWeight.Value, 
				StageRole.Climber => ClimberWeight.Value, 
				StageRole.Flyer => FlyerWeight.Value, 
				StageRole.Tracker => TrackerWeight.Value, 
				StageRole.Ghost => GhostWeight.Value, 
				StageRole.Bomber => BomberWeight.Value, 
				StageRole.Medic => MedicWeight.Value, 
				StageRole.Phoenix => PhoenixWeight.Value, 
				StageRole.Jobless => JoblessWeight.Value, 
				StageRole.Rescuer => RescuerWeight.Value, 
				StageRole.Vampire => VampireWeight.Value, 
				StageRole.King => KingWeight.Value, 
				StageRole.Tuna => TunaWeight.Value, 
				StageRole.Musician => MusicianWeight.Value, 
				StageRole.Mage => MageWeight.Value, 
				StageRole.Gambler => GamblerWeight.Value, 
				StageRole.Hunter => HunterWeight.Value, 
				StageRole.Stinker => StinkerWeight.Value, 
				StageRole.Engineer => EngineerWeight.Value, 
				_ => 0, 
			};
		}

		private static ConfigEntry<bool> RoleEnabled(ConfigFile config, string section)
		{
			return BindBool(config, section, "Enabled", defaultValue: true, "Includes this role in stage assignment.");
		}

		private static ConfigEntry<int> RoleWeight(ConfigFile config, string section)
		{
			return BindInt(config, section, "Weight", 100, 0, 1000, "Relative assignment weight.");
		}

		private static ConfigEntry<int> UpgradeLevel(ConfigFile config, string section, string key, int defaultValue)
		{
			return BindInt(config, section, key, defaultValue, 0, 100, "Vanilla upgrade target level while this role is assigned.");
		}

		private static ConfigEntry<int> BaseUpgradeLevel(ConfigFile config, string key, int defaultValue)
		{
			return BindInt(config, "Base Upgrades", key, defaultValue, 0, 100, "Base vanilla upgrade level kept when no role overrides it.");
		}

		private static ConfigEntry<bool> BindBool(ConfigFile config, string section, string key, bool defaultValue, string description)
		{
			return config.Bind<bool>(section, key, defaultValue, description);
		}

		private static ConfigEntry<int> BindInt(ConfigFile config, string section, string key, int defaultValue, int minimum, int maximum, string description)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			return config.Bind<int>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(minimum, maximum), Array.Empty<object>()));
		}

		private static ConfigEntry<float> BindFloat(ConfigFile config, string section, string key, float defaultValue, float minimum, float maximum, string description)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			return config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(minimum, maximum), Array.Empty<object>()));
		}
	}
	internal enum StageRole
	{
		Tank,
		Runner,
		Jumper,
		Lifter,
		Launcher,
		Climber,
		Flyer,
		Tracker,
		Ghost,
		Bomber,
		Medic,
		Phoenix,
		Jobless,
		Rescuer,
		Vampire,
		King,
		Tuna,
		Musician,
		Mage,
		Gambler,
		Hunter,
		Stinker,
		Engineer
	}
	internal sealed class RoleAssignment
	{
		internal string SteamId { get; }

		internal PlayerAvatar Player { get; set; }

		internal StageRole Role { get; set; }

		internal Vector3 PreviousPosition { get; set; }

		internal Vector3 StinkerTrailAnchor { get; set; }

		internal float TravelDistance { get; set; }

		internal float JoblessDamageTimer { get; set; }

		internal float TunaStationaryTimer { get; set; }

		internal float TunaDamageTimer { get; set; }

		internal float MageNextCastAt { get; set; }

		internal bool WasAlive { get; set; }

		internal bool PhoenixUsed { get; set; }

		internal bool PhoenixRevivePending { get; set; }

		internal float PhoenixReadyAt { get; set; }

		internal int RescuerRevivesUsed { get; set; }

		internal int GamblerWagersUsed { get; set; }

		internal RoleAssignment(string steamId, PlayerAvatar player, StageRole role)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			SteamId = steamId;
			Player = player;
			Role = role;
			PreviousPosition = ((Component)player).transform.position;
			StinkerTrailAnchor = ((Component)player).transform.position;
			WasAlive = true;
		}
	}
	internal readonly struct UpgradeGrant
	{
		internal string CommandName { get; }

		internal string DictionaryName { get; }

		internal int Level { get; }

		internal UpgradeGrant(string commandName, string dictionaryName, int level)
		{
			CommandName = commandName;
			DictionaryName = dictionaryName;
			Level = level;
		}
	}
	internal static class RoleCatalog
	{
		internal static readonly IReadOnlyList<StageRole> AllRoles = (StageRole[])Enum.GetValues(typeof(StageRole));

		internal static IReadOnlyList<UpgradeGrant> BaseUpgrades(StageRolesConfig config)
		{
			return new UpgradeGrant[12]
			{
				new UpgradeGrant("Health", "playerUpgradeHealth", config.BaseHealthLevels.Value),
				new UpgradeGrant("Stamina", "playerUpgradeStamina", config.BaseStaminaLevels.Value),
				new UpgradeGrant("ExtraJump", "playerUpgradeExtraJump", config.BaseExtraJumpLevels.Value),
				new UpgradeGrant("Speed", "playerUpgradeSpeed", config.BaseSpeedLevels.Value),
				new UpgradeGrant("Strength", "playerUpgradeStrength", config.BaseStrengthLevels.Value),
				new UpgradeGrant("Range", "playerUpgradeRange", config.BaseRangeLevels.Value),
				new UpgradeGrant("Launch", "playerUpgradeLaunch", config.BaseLaunchLevels.Value),
				new UpgradeGrant("TumbleClimb", "playerUpgradeTumbleClimb", config.BaseTumbleClimbLevels.Value),
				new UpgradeGrant("TumbleWings", "playerUpgradeTumbleWings", config.BaseTumbleWingsLevels.Value),
				new UpgradeGrant("CrouchRest", "playerUpgradeCrouchRest", config.BaseCrouchRestLevels.Value),
				new UpgradeGrant("MapPlayerCount", "playerUpgradeMapPlayerCount", config.BaseMapPlayerCountLevels.Value),
				new UpgradeGrant("DeathHeadBattery", "playerUpgradeDeathHeadBattery", config.BaseDeathHeadBatteryLevels.Value)
			};
		}

		internal static IReadOnlyList<UpgradeGrant> TargetUpgrades(StageRole role, StageRolesConfig config)
		{
			List<UpgradeGrant> list = new List<UpgradeGrant>(BaseUpgrades(config));
			foreach (UpgradeGrant item in RoleUpgrades(role, config))
			{
				for (int i = 0; i < list.Count; i++)
				{
					if (string.Equals(list[i].DictionaryName, item.DictionaryName, StringComparison.Ordinal))
					{
						list[i] = item;
						break;
					}
				}
			}
			return list;
		}

		private static IReadOnlyList<UpgradeGrant> RoleUpgrades(StageRole role, StageRolesConfig config)
		{
			return role switch
			{
				StageRole.Tank => Grant("Health", "playerUpgradeHealth", config.TankHealthLevels.Value), 
				StageRole.Runner => new UpgradeGrant[2]
				{
					new UpgradeGrant("Speed", "playerUpgradeSpeed", config.RunnerSpeedLevels.Value),
					new UpgradeGrant("Stamina", "playerUpgradeStamina", config.RunnerStaminaLevels.Value)
				}, 
				StageRole.Jumper => Grant("ExtraJump", "playerUpgradeExtraJump", config.JumperExtraJumpLevels.Value), 
				StageRole.Lifter => Grant("Strength", "playerUpgradeStrength", config.LifterStrengthLevels.Value), 
				StageRole.Launcher => Grant("Launch", "playerUpgradeLaunch", config.LauncherLaunchLevels.Value), 
				StageRole.Climber => new UpgradeGrant[2]
				{
					new UpgradeGrant("TumbleClimb", "playerUpgradeTumbleClimb", config.ClimberClimbLevels.Value),
					new UpgradeGrant("Range", "playerUpgradeRange", config.ClimberRangeLevels.Value)
				}, 
				StageRole.Flyer => Grant("TumbleWings", "playerUpgradeTumbleWings", config.FlyerWingsLevels.Value), 
				StageRole.Tracker => Grant("MapPlayerCount", "playerUpgradeMapPlayerCount", 1), 
				StageRole.Ghost => Grant("DeathHeadBattery", "playerUpgradeDeathHeadBattery", config.GhostDeathHeadBatteryLevels.Value), 
				_ => Array.Empty<UpgradeGrant>(), 
			};
		}

		private static IReadOnlyList<UpgradeGrant> Grant(string commandName, string dictionaryName, int level)
		{
			return new UpgradeGrant[1]
			{
				new UpgradeGrant(commandName, dictionaryName, level)
			};
		}
	}
	internal readonly struct RoleSnapshot
	{
		internal string SteamId { get; }

		internal string PlayerName { get; }

		internal StageRole Role { get; }

		internal RoleSnapshot(string steamId, string playerName, StageRole role)
		{
			SteamId = steamId;
			PlayerName = playerName;
			Role = role;
		}
	}
	internal static class RoleAssignmentSync
	{
		private const string RoleMapKey = "RS.RoleMap";

		private static string _localRoleMap = string.Empty;

		private static int _previewPlayerCount;

		internal static int PreviewPlayerCount => _previewPlayerCount;

		internal static void SetPreviewPlayerCount(int count)
		{
			_previewPlayerCount = Math.Max(1, Math.Min(30, count));
		}

		internal static void ClearPreview()
		{
			_previewPlayerCount = 0;
		}

		internal static void Publish(IReadOnlyList<RoleAssignment> assignments)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			List<string> list = new List<string>();
			foreach (RoleAssignment assignment in assignments)
			{
				if (!string.IsNullOrEmpty(assignment.SteamId))
				{
					string text = PlayerIdentity.Name(assignment.Player);
					string arg = Convert.ToBase64String(Encoding.UTF8.GetBytes(text ?? string.Empty));
					list.Add($"{assignment.SteamId},{(int)assignment.Role},{arg}");
				}
			}
			_localRoleMap = string.Join(";", list);
			if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null)
			{
				PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { [(object)"RS.RoleMap"] = _localRoleMap }, (Hashtable)null, (WebFlags)null);
			}
		}

		internal static void Clear()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			_localRoleMap = string.Empty;
			if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null)
			{
				PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { [(object)"RS.RoleMap"] = null }, (Hashtable)null, (WebFlags)null);
			}
		}

		internal static IReadOnlyList<RoleSnapshot> Read()
		{
			string text = _localRoleMap;
			if (SemiFunc.IsMultiplayer() && PhotonNetwork.CurrentRoom != null && ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"RS.RoleMap", out object value) && value is string text2)
			{
				text = text2;
			}
			List<RoleSnapshot> list = new List<RoleSnapshot>();
			if (string.IsNullOrEmpty(text))
			{
				return ApplyPreview(list);
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(',');
				if (array2.Length == 3 && !string.IsNullOrEmpty(array2[0]) && int.TryParse(array2[1], out var result) && Enum.IsDefined(typeof(StageRole), result))
				{
					string playerName;
					try
					{
						playerName = Encoding.UTF8.GetString(Convert.FromBase64String(array2[2]));
					}
					catch
					{
						playerName = string.Empty;
					}
					list.Add(new RoleSnapshot(array2[0], playerName, (StageRole)result));
				}
			}
			return ApplyPreview(list);
		}

		private static IReadOnlyList<RoleSnapshot> ApplyPreview(List<RoleSnapshot> actual)
		{
			if (_previewPlayerCount <= 0)
			{
				return actual;
			}
			int num = Math.Max(1, Math.Min(30, _previewPlayerCount));
			List<RoleSnapshot> list = new List<RoleSnapshot>(num);
			string text = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal());
			int num2 = -1;
			for (int i = 0; i < actual.Count; i++)
			{
				if (!string.IsNullOrEmpty(text) && string.Equals(actual[i].SteamId, text, StringComparison.Ordinal))
				{
					num2 = i;
					break;
				}
			}
			if (num2 >= 0)
			{
				list.Add(actual[num2]);
			}
			else if (actual.Count == 0 && !string.IsNullOrEmpty(text))
			{
				string text2 = PlayerIdentity.Name(SemiFunc.PlayerGetLocal());
				list.Add(new RoleSnapshot(text, string.IsNullOrWhiteSpace(text2) ? "Local Player" : text2, StageRole.Tank));
			}
			for (int j = 0; j < actual.Count; j++)
			{
				if (list.Count >= num)
				{
					break;
				}
				if (j != num2)
				{
					list.Add(actual[j]);
				}
			}
			int num3 = 1;
			while (list.Count < num)
			{
				StageRole role = RoleCatalog.AllRoles[(list.Count - actual.Count + RoleCatalog.AllRoles.Count) % RoleCatalog.AllRoles.Count];
				list.Add(new RoleSnapshot($"preview:{num3}", $"Preview Player {num3:00}", role));
				num3++;
			}
			return list;
		}
	}
	internal sealed class RoleHud : MonoBehaviour
	{
		private const float RefreshIntervalSeconds = 0.25f;

		private const int MaximumNameCharacters = 20;

		private static readonly Vector2 LayoutSize = new Vector2(620f, 340f);

		private readonly List<TMP_Text> _fontTargets = new List<TMP_Text>();

		private readonly List<RoleSnapshot> _assignments = new List<RoleSnapshot>();

		private StageRolesConfig _config;

		private GameObject? _root;

		private RectTransform? _canvasRect;

		private RectTransform? _contentRect;

		private CanvasGroup? _canvasGroup;

		private TextMeshProUGUI? _text;

		private string _lastText = string.Empty;

		private string _lastLayout = string.Empty;

		private string _assignmentSignature = string.Empty;

		private float _nextRefreshAt;

		private float _nextFontProbeAt;

		private float _nextPageAt;

		private float _transitionStartedAt;

		private int _pageIndex;

		private bool _transitioning;

		private bool _pageSwitched;

		private bool _hasAssignments;

		private float ClampedPageInterval => Mathf.Clamp(_config.HudPageIntervalSeconds.Value, 1f, 30f);

		internal void Initialize(StageRolesConfig config)
		{
			_config = config;
		}

		private void Update()
		{
			EnsureCreated();
			if (!((Object)(object)_root == (Object)null))
			{
				if (Time.unscaledTime >= _nextRefreshAt)
				{
					_nextRefreshAt = Time.unscaledTime + 0.25f;
					RefreshAssignments();
				}
				bool flag = _config.HudEnabled.Value && _config.Enabled.Value && _hasAssignments && IsPlayableStageContext();
				if (_root.activeSelf != flag)
				{
					_root.SetActive(flag);
				}
				if (flag)
				{
					UpdatePageTransition();
					ApplyLayout();
					TryApplyRepoUiFont();
				}
			}
		}

		private void EnsureCreated()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: 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_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Expected O, but got Unknown
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Expected O, but got Unknown
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_root != (Object)null)
			{
				return;
			}
			object obj;
			if (!((Object)(object)HealthUI.instance != (Object)null))
			{
				HUDCanvas instance = HUDCanvas.instance;
				obj = ((instance != null) ? ((Component)instance).transform : null);
			}
			else
			{
				obj = ((Component)HealthUI.instance).transform.parent;
			}
			Transform val = (Transform)obj;
			if (!((Object)(object)val == (Object)null))
			{
				_root = new GameObject("RoleShuffle_HUD", new Type[1] { typeof(RectTransform) });
				((Object)_root).hideFlags = (HideFlags)61;
				_root.transform.SetParent(val, false);
				RectTransform val2 = (_canvasRect = _root.GetComponent<RectTransform>());
				val2.anchorMin = Vector2.zero;
				val2.anchorMax = Vector2.one;
				val2.pivot = new Vector2(0.5f, 0.5f);
				val2.offsetMin = Vector2.zero;
				val2.offsetMax = Vector2.zero;
				if ((Object)(object)HealthUI.instance != (Object)null && (Object)(object)((Component)HealthUI.instance).transform.parent == (Object)(object)val)
				{
					_root.transform.SetSiblingIndex(((Component)HealthUI.instance).transform.GetSiblingIndex() + 1);
				}
				GameObject val3 = new GameObject("RotatingRoles", new Type[2]
				{
					typeof(RectTransform),
					typeof(CanvasGroup)
				});
				val3.transform.SetParent(_root.transform, false);
				_contentRect = val3.GetComponent<RectTransform>();
				_contentRect.sizeDelta = LayoutSize;
				_canvasGroup = val3.GetComponent<CanvasGroup>();
				_canvasGroup.interactable = false;
				_canvasGroup.blocksRaycasts = false;
				GameObject val4 = new GameObject("RoleText", new Type[2]
				{
					typeof(RectTransform),
					typeof(TextMeshProUGUI)
				});
				val4.transform.SetParent(val3.transform, false);
				_text = val4.GetComponent<TextMeshProUGUI>();
				RectTransform rectTransform = ((TMP_Text)_text).rectTransform;
				rectTransform.anchorMin = Vector2.zero;
				rectTransform.anchorMax = Vector2.one;
				rectTransform.offsetMin = Vector2.zero;
				rectTransform.offsetMax = Vector2.zero;
				((TMP_Text)_text).fontSize = 28f;
				((Graphic)_text).color = new Color(0.9f, 0.91f, 0.87f, 1f);
				((TMP_Text)_text).outlineColor = Color32.op_Implicit(new Color(0f, 0f, 0f, 0.95f));
				((TMP_Text)_text).outlineWidth = 0.15f;
				((Graphic)_text).raycastTarget = false;
				((TMP_Text)_text).enableWordWrapping = false;
				((TMP_Text)_text).overflowMode = (TextOverflowModes)3;
				((TMP_Text)_text).alignment = (TextAlignmentOptions)513;
				_fontTargets.Add((TMP_Text)(object)_text);
				_lastLayout = string.Empty;
				_root.SetActive(false);
			}
		}

		private void RefreshAssignments()
		{
			IReadOnlyList<RoleSnapshot> readOnlyList = RoleAssignmentSync.Read();
			_hasAssignments = readOnlyList.Count > 0;
			string text = BuildAssignmentSignature(readOnlyList) + "|" + $"{_config.HudPlayersPerPage.Value}|" + $"{_config.HudPinLocalPlayer.Value}";
			if (string.Equals(text, _assignmentSignature, StringComparison.Ordinal))
			{
				return;
			}
			_assignmentSignature = text;
			_assignments.Clear();
			foreach (RoleSnapshot item in readOnlyList)
			{
				_assignments.Add(item);
			}
			_pageIndex = 0;
			_transitioning = false;
			_pageSwitched = false;
			_nextPageAt = Time.unscaledTime + ClampedPageInterval;
			if ((Object)(object)_canvasGroup != (Object)null)
			{
				_canvasGroup.alpha = 1f;
			}
			UpdateDisplayedText();
		}

		private void UpdatePageTransition()
		{
			if ((Object)(object)_canvasGroup == (Object)null)
			{
				return;
			}
			int pageCount = GetPageCount();
			if (pageCount <= 1)
			{
				_pageIndex = 0;
				_transitioning = false;
				_canvasGroup.alpha = 1f;
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!_transitioning)
			{
				if (unscaledTime < _nextPageAt || RoleMenu.IsOpen)
				{
					return;
				}
				_transitioning = true;
				_pageSwitched = false;
				_transitionStartedAt = unscaledTime;
			}
			float num = Mathf.Clamp(_config.HudTransitionDurationSeconds.Value, 0f, 1f);
			if (num <= 0.001f)
			{
				AdvancePage(pageCount);
				FinishTransition(unscaledTime);
				return;
			}
			float num2 = num * 0.5f;
			float num3 = unscaledTime - _transitionStartedAt;
			if (num3 < num2)
			{
				_canvasGroup.alpha = 1f - Mathf.Clamp01(num3 / num2);
				return;
			}
			if (!_pageSwitched)
			{
				AdvancePage(pageCount);
				_pageSwitched = true;
			}
			if (num3 < num)
			{
				_canvasGroup.alpha = Mathf.Clamp01((num3 - num2) / num2);
			}
			else
			{
				FinishTransition(unscaledTime);
			}
		}

		private void AdvancePage(int pageCount)
		{
			_pageIndex = (_pageIndex + 1) % pageCount;
			UpdateDisplayedText();
		}

		private void FinishTransition(float now)
		{
			_transitioning = false;
			_pageSwitched = false;
			if ((Object)(object)_canvasGroup != (Object)null)
			{
				_canvasGroup.alpha = 1f;
			}
			_nextPageAt = now + ClampedPageInterval;
		}

		private void UpdateDisplayedText()
		{
			string text = BuildPageText();
			if ((Object)(object)_text != (Object)null && !string.Equals(_lastText, text, StringComparison.Ordinal))
			{
				_lastText = text;
				((TMP_Text)_text).text = text;
			}
		}

		private string BuildPageText()
		{
			if (_assignments.Count == 0)
			{
				return string.Empty;
			}
			RoleSnapshot? roleSnapshot = FindLocalSnapshot();
			List<RoleSnapshot> list = new List<RoleSnapshot>(_assignments.Count);
			foreach (RoleSnapshot assignment in _assignments)
			{
				if (!roleSnapshot.HasValue || !string.Equals(assignment.SteamId, roleSnapshot.Value.SteamId, StringComparison.Ordinal))
				{
					list.Add(assignment);
				}
			}
			int num = Mathf.Clamp(_config.HudPlayersPerPage.Value, 2, 20);
			int num2 = Mathf.Max(1, num - (roleSnapshot.HasValue ? 1 : 0));
			int num3 = Mathf.Max(1, Mathf.CeilToInt((float)list.Count / (float)num2));
			_pageIndex = Mathf.Clamp(_pageIndex, 0, num3 - 1);
			List<string> list2 = new List<string>(num + 1) { (num3 > 1) ? $"ROLES  {_pageIndex + 1} / {num3}" : "ROLES" };
			if (roleSnapshot.HasValue)
			{
				list2.Add($"YOU: {roleSnapshot.Value.Role}");
			}
			int num4 = _pageIndex * num2;
			int num5 = Mathf.Min(num4 + num2, list.Count);
			for (int i = num4; i < num5; i++)
			{
				RoleSnapshot roleSnapshot2 = list[i];
				string text = $"Player {i + 1}";
				string arg = Ellipsize(string.IsNullOrWhiteSpace(roleSnapshot2.PlayerName) ? text : roleSnapshot2.PlayerName.Trim(), 20);
				list2.Add($"{arg}: {roleSnapshot2.Role}");
			}
			return string.Join("\n", list2);
		}

		private RoleSnapshot? FindLocalSnapshot()
		{
			if (!_config.HudPinLocalPlayer.Value)
			{
				return null;
			}
			string text = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal());
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			foreach (RoleSnapshot assignment in _assignments)
			{
				if (string.Equals(assignment.SteamId, text, StringComparison.Ordinal))
				{
					return assignment;
				}
			}
			return null;
		}

		private int GetPageCount()
		{
			if (_assignments.Count == 0)
			{
				return 1;
			}
			bool hasValue = FindLocalSnapshot().HasValue;
			int num = Mathf.Clamp(_config.HudPlayersPerPage.Value, 2, 20);
			int num2 = Mathf.Max(1, num - (hasValue ? 1 : 0));
			int num3 = Mathf.Max(0, _assignments.Count - (hasValue ? 1 : 0));
			return Mathf.Max(1, Mathf.CeilToInt((float)num3 / (float)num2));
		}

		private static string BuildAssignmentSignature(IReadOnlyList<RoleSnapshot> assignments)
		{
			List<string> list = new List<string>(assignments.Count);
			foreach (RoleSnapshot assignment in assignments)
			{
				list.Add($"{assignment.SteamId}\u001f{assignment.PlayerName}\u001f{assignment.Role}");
			}
			return string.Join("\u001e", list);
		}

		private static string Ellipsize(string value, int maximumCharacters)
		{
			if (value.Length <= maximumCharacters)
			{
				return value;
			}
			return value.Substring(0, Mathf.Max(1, maximumCharacters - 1)) + "…";
		}

		private void ApplyLayout()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: 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_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_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_contentRect == (Object)null) && !((Object)(object)_text == (Object)null))
			{
				int num;
				Rect rect;
				if (!((Object)(object)_canvasRect != (Object)null))
				{
					num = Screen.width;
				}
				else
				{
					rect = _canvasRect.rect;
					num = Mathf.RoundToInt(((Rect)(ref rect)).width);
				}
				int num2 = num;
				int num3;
				if (!((Object)(object)_canvasRect != (Object)null))
				{
					num3 = Screen.height;
				}
				else
				{
					rect = _canvasRect.rect;
					num3 = Mathf.RoundToInt(((Rect)(ref rect)).height);
				}
				int num4 = num3;
				string text = _config.HudAnchor.Value + ":" + _config.HudAlignment.Value + ":" + $"{_config.HudOffsetX.Value}:{_config.HudOffsetY.Value}:" + $"{_config.HudScalePercent.Value}:{num2}x{num4}:" + $"{Screen.width}x{Screen.height}";
				if (!(_lastLayout == text))
				{
					_lastLayout = text;
					Vector2 val = ResolveAnchor(_config.HudAnchor.Value);
					float num5 = ResolveResolutionScale();
					float userScale = (float)_config.HudScalePercent.Value / 100f;
					float num6 = CalculateResponsiveScale(LayoutSize, userScale, num5);
					_contentRect.anchorMin = val;
					_contentRect.anchorMax = val;
					_contentRect.pivot = val;
					_contentRect.anchoredPosition = new Vector2((float)_config.HudOffsetX.Value * num5, (float)_config.HudOffsetY.Value * num5);
					((Transform)_contentRect).localScale = Vector3.one * num6;
					TextMeshProUGUI text2 = _text;
					string value = _config.HudAlignment.Value;
					TextAlignmentOptions alignment = ((value == "Center") ? ((TextAlignmentOptions)514) : ((!(value == "Right")) ? ((TextAlignmentOptions)513) : ((TextAlignmentOptions)516)));
					((TMP_Text)text2).alignment = alignment;
				}
			}
		}

		private float ResolveResolutionScale()
		{
			//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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			float num;
			if ((Object)(object)_canvasRect != (Object)null)
			{
				Rect rect = _canvasRect.rect;
				if (((Rect)(ref rect)).height > 1f)
				{
					rect = _canvasRect.rect;
					num = ((Rect)(ref rect)).height;
					goto IL_0043;
				}
			}
			num = Screen.height;
			goto IL_0043;
			IL_0043:
			float num2 = num;
			return Mathf.Max(0.1f, num2 / 540f);
		}

		private float CalculateResponsiveScale(Vector2 layoutSize, float userScale, float resolutionScale)
		{
			//IL_0022: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Max(0.1f, resolutionScale * userScale);
			if (!((Object)(object)_canvasRect == (Object)null))
			{
				Rect rect = _canvasRect.rect;
				if (!(((Rect)(ref rect)).width <= 1f))
				{
					rect = _canvasRect.rect;
					if (!(((Rect)(ref rect)).height <= 1f))
					{
						float num2 = 8f * resolutionScale;
						rect = _canvasRect.rect;
						float num3 = Mathf.Max(1f, ((Rect)(ref rect)).width - num2 * 2f);
						rect = _canvasRect.rect;
						float num4 = Mathf.Max(1f, ((Rect)(ref rect)).height - num2 * 2f);
						float num5 = Mathf.Min(num3 / layoutSize.x, num4 / layoutSize.y);
						return Mathf.Max(0.1f, Mathf.Min(num, num5));
					}
				}
			}
			return num;
		}

		private void TryApplyRepoUiFont()
		{
			if (Time.unscaledTime < _nextFontProbeAt)
			{
				return;
			}
			_nextFontProbeAt = Time.unscaledTime + 2f;
			TMP_Text val = (TMP_Text)(object)(((Object)(object)HealthUI.instance != (Object)null) ? ((Component)HealthUI.instance).GetComponent<TextMeshProUGUI>() : null);
			if (val == null)
			{
				val = (TMP_Text)(object)ChatUI.instance?.chatText;
			}
			if ((Object)(object)val == (Object)null && (Object)(object)HUD.instance != (Object)null)
			{
				TMP_Text[] componentsInChildren = ((Component)HUD.instance).GetComponentsInChildren<TMP_Text>(true);
				foreach (TMP_Text val2 in componentsInChildren)
				{
					if ((Object)(object)val2 != (Object)null && !_fontTargets.Contains(val2) && (Object)(object)val2.font != (Object)null)
					{
						val = val2;
						break;
					}
				}
			}
			if ((Object)(object)((val != null) ? val.font : null) == (Object)null)
			{
				return;
			}
			foreach (TMP_Text fontTarget in _fontTargets)
			{
				if ((Object)(object)fontTarget != (Object)null && (Object)(object)fontTarget.font != (Object)(object)val.font)
				{
					fontTarget.font = val.font;
				}
			}
		}

		private static bool IsPlayableStageContext()
		{
			try
			{
				return SemiFunc.RunIsLevel() && !SemiFunc.RunIsLobby() && !SemiFunc.RunIsShop() && !SemiFunc.RunIsArena() && !SemiFunc.RunIsTutorial();
			}
			catch
			{
				return false;
			}
		}

		private static Vector2 ResolveAnchor(string anchor)
		{
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: 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_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: 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_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: 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_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			return (Vector2)(anchor switch
			{
				"TopLeft" => new Vector2(0f, 1f), 
				"TopCenter" => new Vector2(0.5f, 1f), 
				"TopRight" => new Vector2(1f, 1f), 
				"MiddleLeft" => new Vector2(0f, 0.5f), 
				"MiddleCenter" => new Vector2(0.5f, 0.5f), 
				"MiddleRight" => new Vector2(1f, 0.5f), 
				"BottomCenter" => new Vector2(0.5f, 0f), 
				"BottomRight" => new Vector2(1f, 0f), 
				_ => new Vector2(0f, 0f), 
			});
		}

		private void OnDestroy()
		{
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
			}
		}
	}
	internal sealed class RoleMenu : MonoBehaviour
	{
		private sealed class RoleMenuRow(REPOLabel label, REPOScrollViewElement element)
		{
			internal REPOLabel Label { get; } = label;

			internal REPOScrollViewElement Element { get; } = element;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static BuilderDelegate <>9__10_0;

			public static ShouldCloseMenuDelegate <>9__12_2;

			internal void <Initialize>b__10_0(Transform parent)
			{
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_003b: 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_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Unknown result type (might be due to invalid IL or missing references)
				RectTransform rectTransform = ((REPOElement)MenuAPI.CreateREPOButton("ROLES", (Action)OpenRolePage, parent, Vector2.zero)).rectTransform;
				rectTransform.anchorMin = new Vector2(1f, 0f);
				rectTransform.anchorMax = new Vector2(1f, 0f);
				rectTransform.pivot = new Vector2(1f, 0f);
				rectTransform.anchoredPosition = new Vector2(-36f, 36f);
			}

			internal bool <OpenRolePage>b__12_2()
			{
				ClearOpenPageTracking();
				return true;
			}
		}

		private const float RefreshIntervalSeconds = 0.25f;

		private static bool _registered;

		private static readonly List<RoleMenuRow> _roleRows = new List<RoleMenuRow>();

		private static REPOPopupPage? _openPage;

		private static string _openSignature = string.Empty;

		private float _nextRefreshAt;

		internal static bool IsOpen { get; private set; }

		internal void Initialize()
		{
			//IL_0022: 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_002d: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			_registered = true;
			object obj = <>c.<>9__10_0;
			if (obj == null)
			{
				BuilderDelegate val = delegate(Transform parent)
				{
					//IL_0021: Unknown result type (might be due to invalid IL or missing references)
					//IL_003b: 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_0065: Unknown result type (might be due to invalid IL or missing references)
					//IL_0079: Unknown result type (might be due to invalid IL or missing references)
					RectTransform rectTransform = ((REPOElement)MenuAPI.CreateREPOButton("ROLES", (Action)OpenRolePage, parent, Vector2.zero)).rectTransform;
					rectTransform.anchorMin = new Vector2(1f, 0f);
					rectTransform.anchorMax = new Vector2(1f, 0f);
					rectTransform.pivot = new Vector2(1f, 0f);
					rectTransform.anchoredPosition = new Vector2(-36f, 36f);
				};
				<>c.<>9__10_0 = val;
				obj = (object)val;
			}
			MenuAPI.AddElementToEscapeMenu((BuilderDelegate)obj);
		}

		private void Update()
		{
			if (!IsOpen)
			{
				return;
			}
			if ((Object)(object)_openPage == (Object)null)
			{
				ClearOpenPageTracking();
			}
			else if (!(Time.unscaledTime < _nextRefreshAt))
			{
				_nextRefreshAt = Time.unscaledTime + 0.25f;
				IReadOnlyList<RoleSnapshot> assignments = RoleAssignmentSync.Read();
				string text = BuildSignature(assignments);
				if (!string.Equals(text, _openSignature, StringComparison.Ordinal))
				{
					RefreshRoleRows(_openPage, assignments, text);
				}
			}
		}

		private static void OpenRolePage()
		{
			//IL_0044: 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_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			IReadOnlyList<RoleSnapshot> assignments = RoleAssignmentSync.Read();
			REPOPopupPage page = MenuAPI.CreateREPOPopupPage("Roles", (PresetSide)1, false, true, 1.5f);
			page.scrollView.scrollSpeed = 3f;
			Padding maskPadding = page.maskPadding;
			maskPadding.top = 24f;
			maskPadding.bottom = 12f;
			page.maskPadding = maskPadding;
			_openPage = page;
			_roleRows.Clear();
			RefreshRoleRows(page, assignments, BuildSignature(assignments));
			page.AddElement((BuilderDelegate)delegate(Transform parent)
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				MenuAPI.CreateREPOButton("Back", (Action)delegate
				{
					ClearOpenPageTracking();
					page.ClosePage(true);
				}, parent, new Vector2(66f, 18f));
			});
			REPOPopupPage obj = page;
			ShouldCloseMenuDelegate onEscapePressed = obj.onEscapePressed;
			object obj2 = <>c.<>9__12_2;
			if (obj2 == null)
			{
				ShouldCloseMenuDelegate val = delegate
				{
					ClearOpenPageTracking();
					return true;
				};
				<>c.<>9__12_2 = val;
				obj2 = (object)val;
			}
			obj.onEscapePressed = (ShouldCloseMenuDelegate)Delegate.Combine((Delegate?)(object)onEscapePressed, (Delegate?)obj2);
			IsOpen = true;
			page.OpenPage(false);
		}

		private static void RefreshRoleRows(REPOPopupPage page, IReadOnlyList<RoleSnapshot> assignments, string signature)
		{
			List<(string, bool)> list = new List<(string, bool)>();
			string text = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal());
			if (assignments.Count == 0)
			{
				list.Add(("No roles are currently assigned.", true));
			}
			else
			{
				int num = 1;
				foreach (RoleSnapshot assignment in assignments)
				{
					string arg = (string.IsNullOrWhiteSpace(assignment.PlayerName) ? $"Player {num}" : assignment.PlayerName.Trim());
					bool flag = !string.IsNullOrEmpty(text) && string.Equals(assignment.SteamId, text, StringComparison.Ordinal);
					list.Add((flag ? $"YOU - {assignment.Role}" : $"{arg} - {assignment.Role}", flag));
					num++;
				}
			}
			while (_roleRows.Count < list.Count)
			{
				_roleRows.Add(CreateRoleRow(page));
			}
			for (int i = 0; i < _roleRows.Count; i++)
			{
				RoleMenuRow roleMenuRow = _roleRows[i];
				bool flag2 = i < list.Count;
				roleMenuRow.Element.visibility = flag2;
				if (flag2)
				{
					(string, bool) tuple = list[i];
					string item = tuple.Item1;
					bool item2 = tuple.Item2;
					TextMeshProUGUI labelTMP = roleMenuRow.Label.labelTMP;
					((TMP_Text)labelTMP).text = item;
					((TMP_Text)labelTMP).alignment = (TextAlignmentOptions)513;
					((TMP_Text)labelTMP).fontSize = (item2 ? 24f : 21f);
					((TMP_Text)labelTMP).fontStyle = (FontStyles)(item2 ? 1 : 0);
				}
			}
			page.scrollView.UpdateElements();
			_openSignature = signature;
		}

		private static RoleMenuRow CreateRoleRow(REPOPopupPage page)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			REPOLabel createdLabel = null;
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform parent)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				createdLabel = MenuAPI.CreateREPOLabel(string.Empty, parent, Vector2.zero);
				return ((REPOElement)createdLabel).rectTransform;
			}, 3f, 3f);
			if ((Object)(object)createdLabel == (Object)null)
			{
				throw new InvalidOperationException("Failed to create a role menu row.");
			}
			REPOScrollViewElement component = ((Component)((REPOElement)createdLabel).rectTransform).GetComponent<REPOScrollViewElement>();
			return new RoleMenuRow(createdLabel, component);
		}

		private static string BuildSignature(IReadOnlyList<RoleSnapshot> assignments)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (RoleSnapshot assignment in assignments)
			{
				stringBuilder.Append(assignment.SteamId).Append('\u001f').Append(assignment.PlayerName)
					.Append('\u001f')
					.Append((int)assignment.Role)
					.Append('\u001e');
			}
			return stringBuilder.ToString();
		}

		private static void ClearOpenPageTracking()
		{
			IsOpen = false;
			_openPage = null;
			_roleRows.Clear();
			_openSignature = string.Empty;
		}
	}
	internal sealed class RoleTestCommandService : MonoBehaviour
	{
		private const string FullCommand = "setrole";

		private const string ShortCommand = "sr";

		private const string HudPlayersCommand = "hudplayers";

		private const string HudPlayersShortCommand = "hp";

		private static readonly FieldInfo? DebugConsoleInstanceField = AccessTools.Field(typeof(DebugConsoleUI), "instance");

		private static readonly MethodInfo? SetResponseTextMethod = AccessTools.Method(typeof(DebugConsoleUI), "SetResponseText", new Type[3]
		{
			typeof(string),
			typeof(Color),
			typeof(float)
		}, (Type[])null);

		private StageRolesConfig _config;

		private StageRoleController _controller;

		private DebugCommandHandler? _registeredHandler;

		internal void Initialize(StageRolesConfig config, StageRoleController controller)
		{
			_config = config;
			_controller = controller;
		}

		private void Update()
		{
			DebugCommandHandler instance = DebugCommandHandler.instance;
			if (!((Object)(object)instance == (Object)null) && instance != _registeredHandler)
			{
				_registeredHandler = instance;
				bool flag = TryRegister(instance, "setrole");
				bool flag2 = TryRegister(instance, "sr");
				bool flag3 = TryRegisterHudPlayers(instance, "hudplayers");
				bool flag4 = TryRegisterHudPlayers(instance, "hp");
				if (flag || flag2 || flag3 || flag4)
				{
					StageRolesPlugin.ModLogger.LogInfo((object)("Registered role testing commands: " + $"/setrole={flag}, /sr={flag2}, " + $"/hudplayers={flag3}, " + $"/hp={flag4}."));
				}
			}
		}

		private bool TryRegisterHudPlayers(DebugCommandHandler handler, string commandName)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			try
			{
				handler.Register(new ChatCommand(commandName, "Overrides the local role HUD player count for testing.", (Action<bool, string[]>)ExecuteHudPlayers, (Func<bool, string, string[], List<string>>)SuggestHudPlayers, (Func<bool>)(() => _config.SetRoleCommandEnabled.Value), false));
				return true;
			}
			catch (Exception ex)
			{
				StageRolesPlugin.ModLogger.LogWarning((object)("Could not register /" + commandName + ": " + ex.Message));
				return false;
			}
		}

		private static void ExecuteHudPlayers(bool isDebugConsole, string[] args)
		{
			int result;
			if (args.Length != 1)
			{
				Respond("Usage: /hudplayers <1-30|reset>", success: false);
			}
			else if (string.Equals(args[0], "reset", StringComparison.OrdinalIgnoreCase))
			{
				RoleAssignmentSync.ClearPreview();
				Respond("HUD player preview reset.", success: true);
			}
			else if (!int.TryParse(args[0], out result) || result < 1 || result > 30)
			{
				Respond("HUD player count must be from 1 to 30.", success: false);
			}
			else
			{
				RoleAssignmentSync.SetPreviewPlayerCount(result);
				Respond($"Local HUD preview player count: {result}.", success: true);
			}
		}

		private static List<string> SuggestHudPlayers(bool isDebugConsole, string partial, string[] args)
		{
			List<string> list = new List<string>();
			if (args.Length > 1)
			{
				return list;
			}
			string filter = partial ?? string.Empty;
			AddSuggestion(list, "1", filter);
			AddSuggestion(list, "8", filter);
			AddSuggestion(list, "20", filter);
			AddSuggestion(list, "30", filter);
			AddSuggestion(list, "reset", filter);
			return list;
		}

		private bool TryRegister(DebugCommandHandler handler, string commandName)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			try
			{
				handler.Register(new ChatCommand(commandName, "Changes a player's stage role for testing.", (Action<bool, string[]>)Execute, (Func<bool, string, string[], List<string>>)Suggest, (Func<bool>)(() => _config.SetRoleCommandEnabled.Value), false));
				return true;
			}
			catch (Exception ex)
			{
				StageRolesPlugin.ModLogger.LogWarning((object)("Could not register /" + commandName + ": " + ex.Message));
				return false;
			}
		}

		private void Execute(bool isDebugConsole, string[] args)
		{
			if (args.Length == 0)
			{
				Respond($"Usage: /setrole <role name or 1-{RoleCatalog.AllRoles.Count}> " + "[player name or Steam ID]", success: false);
				return;
			}
			if (string.Equals(args[0], "list", StringComparison.OrdinalIgnoreCase))
			{
				Respond(BuildRoleList(), success: true, 8f);
				return;
			}
			if (!TryParseRole(args[0], out var role))
			{
				Respond("Unknown role: " + args[0] + ". Use /setrole list.", success: false);
				return;
			}
			string targetIdentifier = ((args.Length > 1) ? string.Join(" ", args, 1, args.Length - 1) : string.Empty);
			string response;
			bool success = _controller.TrySetRole(targetIdentifier, role, out response);
			Respond(response, success);
		}

		private static List<string> Suggest(bool isDebugConsole, string partial, string[] args)
		{
			List<string> list = new List<string>();
			if (args.Length > 1)
			{
				return list;
			}
			string filter = partial ?? string.Empty;
			AddSuggestion(list, "list", filter);
			for (int i = 0; i < RoleCatalog.AllRoles.Count; i++)
			{
				AddSuggestion(list, RoleCatalog.AllRoles[i].ToString(), filter);
				AddSuggestion(list, (i + 1).ToString(), filter);
			}
			return list;
		}

		private static void AddSuggestion(ICollection<string> suggestions, string candidate, string filter)
		{
			if (candidate.StartsWith(filter, StringComparison.OrdinalIgnoreCase))
			{
				suggestions.Add(candidate);
			}
		}

		private static bool TryParseRole(string value, out StageRole role)
		{
			if (int.TryParse(value, out var result) && result >= 1 && result <= RoleCatalog.AllRoles.Count)
			{
				role = RoleCatalog.AllRoles[result - 1];
				return true;
			}
			if (Enum.TryParse<StageRole>(value, ignoreCase: true, out role))
			{
				return Enum.IsDefined(typeof(StageRole), role);
			}
			return false;
		}

		private static string BuildRoleList()
		{
			List<string> list = new List<string>(RoleCatalog.AllRoles.Count);
			for (int i = 0; i < RoleCatalog.AllRoles.Count; i++)
			{
				list.Add($"{i + 1}={RoleCatalog.AllRoles[i]}");
			}
			return string.Join(", ", list);
		}

		private static void Respond(string text, bool success, float duration = 4f)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			if (success)
			{
				StageRolesPlugin.ModLogger.LogInfo((object)text);
			}
			else
			{
				StageRolesPlugin.ModLogger.LogWarning((object)text);
			}
			try
			{
				object obj = DebugConsoleInstanceField?.GetValue(null);
				if (obj != null && SetResponseTextMethod != null)
				{
					SetResponseTextMethod.Invoke(obj, new object[3]
					{
						text,
						success ? Color.green : Color.red,
						duration
					});
				}
			}
			catch (Exception ex)
			{
				StageRolesPlugin.ModLogger.LogDebug((object)("Could not show command response: " + ex.Message));
			}
		}
	}
	internal static class PlayerState
	{
		private static readonly FieldInfo? DisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled");

		private static readonly FieldInfo? DeadField = AccessTools.Field(typeof(PlayerAvatar), "deadSet");

		private static readonly FieldInfo? SpectatingField = AccessTools.Field(typeof(PlayerAvatar), "spectating");

		private static readonly FieldInfo? HealthField = AccessTools.Field(typeof(PlayerHealth), "health");

		private static readonly FieldInfo? MaxHealthField = AccessTools.Field(typeof(PlayerHealth), "maxHealth");

		private static readonly FieldInfo? DeathHeadField = AccessTools.Field(typeof(PlayerAvatar), "playerDeathHead");

		private static readonly FieldInfo? InTruckField = AccessTools.Field(typeof(RoomVolumeCheck), "inTruck");

		internal static bool IsLiving(PlayerAvatar? player)
		{
			if ((Object)(object)player == (Object)null || !((Component)player).gameObject.activeInHierarchy || ReadBool(DisabledField, player) || ReadBool(DeadField, player) || ReadBool(SpectatingField, player))
			{
				return false;
			}
			if (!((Object)(object)player.playerHealth == (Object)null) && HealthField?.GetValue(player.playerHealth) is int num)
			{
				return num > 0;
			}
			return true;
		}

		internal static bool IsInTruck(PlayerAvatar? player)
		{
			if ((Object)(object)player?.RoomVolumeCheck != (Object)null)
			{
				return ReadBool(InTruckField, player.RoomVolumeCheck);
			}
			return false;
		}

		internal static bool HasDeathHead(PlayerAvatar? player)
		{
			if ((Object)(object)player != (Object)null)
			{
				object? obj = DeathHeadField?.GetValue(player);
				PlayerDeathHead val = (PlayerDeathHead)((obj is PlayerDeathHead) ? obj : null);
				if (val != null)
				{
					return (Object)(object)val != (Object)null;
				}
			}
			return false;
		}

		internal static bool TryGetCurrentHealth(PlayerAvatar? player, out int health)
		{
			health = 0;
			if ((Object)(object)player?.playerHealth != (Object)null && HealthField?.GetValue(player.playerHealth) is int num)
			{
				return (health = num) >= 0;
			}
			return false;
		}

		internal static bool SetHealthSynchronized(PlayerAvatar? player, int health)
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			PlayerHealth val = player?.playerHealth;
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			int num = ((MaxHealthField?.GetValue(val) is int num2 && num2 > 0) ? num2 : Math.Max(health, 1));
			int num3 = Math.Clamp(health, 1, Math.Max(1, num));
			if (SemiFunc.IsMultiplayer())
			{
				PhotonView val2 = ((Component)val).GetComponent<PhotonView>() ?? ((player != null) ? ((Component)player).GetComponent<PhotonView>() : null);
				if ((Object)(object)val2 == (Object)null)
				{
					return false;
				}
				val2.RPC("UpdateHealthRPC", (RpcTarget)0, new object[4] { num3, num, false, false });
				return true;
			}
			val.UpdateHealthRPC(num3, num, false, false, default(PhotonMessageInfo));
			return true;
		}

		private static bool ReadBool(FieldInfo? field, object instance)
		{
			object obj = field?.GetValue(instance);
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}
	}
	internal static class PlayerIdentity
	{
		internal static string SteamId(PlayerAvatar? player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return string.Empty;
			}
			try
			{
				string text = SemiFunc.PlayerGetSteamID(player);
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			catch
			{
			}
			try
			{
				PhotonView photonView = player.photonView;
				object obj2;
				if (photonView == null)
				{
					obj2 = null;
				}
				else
				{
					Player owner = photonView.Owner;
					obj2 = ((owner != null) ? owner.UserId : null);
				}
				if (obj2 == null)
				{
					obj2 = string.Empty;
				}
				return (string)obj2;
			}
			catch
			{
				return string.Empty;
			}
		}

		internal static string Name(PlayerAvatar? player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return string.Empty;
			}
			try
			{
				return SemiFunc.PlayerGetName(player) ?? string.Empty;
			}
			catch
			{
				try
				{
					PhotonView photonView = player.photonView;
					object obj2;
					if (photonView == null)
					{
						obj2 = null;
					}
					else
					{
						Player owner = photonView.Owner;
						obj2 = ((owner != null) ? owner.NickName : null);
					}
					if (obj2 == null)
					{
						obj2 = string.Empty;
					}
					return (string)obj2;
				}
				catch
				{
					return string.Empty;
				}
			}
		}
	}
	internal static class StageFluxCompatibility
	{
		private const string CarrierTypeName = "REPOJP.StagePhysicsEvents.StagePhysicsEffectCarrierMarker";

		private static Type? _carrierType;

		private static bool _resolved;

		internal static void MarkInternalCarrier(GameObject instance)
		{
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			Type type = ResolveCarrierType();
			if (type == null || (Object)(object)instance.GetComponent(type) != (Object)null)
			{
				return;
			}
			try
			{
				instance.AddComponent(type);
			}
			catch (Exception ex)
			{
				StageRolesPlugin.ModLogger.LogDebug((object)("Stage Flux carrier marker was not added: " + ex.Message));
			}
		}

		private static Type? ResolveCarrierType()
		{
			if (_resolved)
			{
				return _carrierType;
			}
			_resolved = true;
			if (!Chainloader.PluginInfos.TryGetValue("REPOJP.StagePhysicsEvents", out var value))
			{
				return null;
			}
			_carrierType = ((object)value.Instance).GetType().Assembly.GetType("REPOJP.StagePhysicsEvents.StagePhysicsEffectCarrierMarker", throwOnError: false);
			if (_carrierType != null && !typeof(Component).IsAssignableFrom(_carrierType))
			{
				_carrierType = null;
			}
			return _carrierType;
		}
	}
	internal readonly struct ResolvedRolePrefab
	{
		internal GameObject Prefab { get; }

		internal string ResourcePath { get; }

		internal VanillaGrenadeKind GrenadeKind { get; }

		internal ResolvedRolePrefab(GameObject prefab, string resourcePath, VanillaGrenadeKind grenadeKind = VanillaGrenadeKind.None)
		{
			Prefab = prefab;
			ResourcePath = resourcePath;
			GrenadeKind = grenadeKind;
		}
	}
	internal enum VanillaGrenadeKind
	{
		None,
		Explosive,
		Stun,
		Shockwave,
		DuctTaped
	}
	internal sealed class VanillaRolePrefabResolver
	{
		private const int MaximumResolveAttempts = 5;

		private readonly List<ResolvedRolePrefab> _grenades = new List<ResolvedRolePrefab>();

		private ResolvedRolePrefab _healOrb;

		private ResolvedRolePrefab _starProjectile;

		private ResolvedRolePrefab _rollProjectile;

		private ResolvedRolePrefab _zeroGravityProjectile;

		private ResolvedRolePrefab _voidProjectile;

		private PrefabRef? _voidImpactPrefab;

		private ResolvedRolePrefab _wizardStaff;

		private readonly List<ResolvedRolePrefab> _uraniumValuables = new List<ResolvedRolePrefab>();

		private bool _grenadesResolved;

		private int _healOrbResolveAttempts;

		private bool _healOrbResolveFailed;

		private int _starProjectileResolveAttempts;

		private bool _starProjectileResolveFailed;

		private int _rollProjectileResolveAttempts;

		private bool _rollProjectileResolveFailed;

		private int _zeroGravityProjectileResolveAttempts;

		private bool _zeroGravityProjectileResolveFailed;

		private int _voidProjectileResolveAttempts;

		private bool _voidProjectileResolveFailed;

		private int _wizardStaffResolveAttempts;

		private bool _wizardStaffResolveFailed;

		private int _uraniumResolveAttempts;

		private bool _uraniumResolveFailed;

		internal bool TryGetRandomUraniumValuable(out ResolvedRolePrefab resolved)
		{
			if (_uraniumValuables.Count == 0 && !_uraniumResolveFailed)
			{
				ResolveUraniumValuables();
			}
			if (_uraniumValuables.Count > 0)
			{
				resolved = _uraniumValuables[Random.Range(0, _uraniumValuables.Count)];
				return true;
			}
			_uraniumResolveAttempts++;
			if (_uraniumResolveAttempts >= 5)
			{
				_uraniumResolveFailed = true;
				StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla uranium valuable could not be resolved; Stinker is unavailable.");
			}
			resolved = default(ResolvedRolePrefab);
			return false;
		}

		internal bool TryGetRandomGrenade(StageRolesConfig config, out ResolvedRolePrefab resolved)
		{
			ResolveGrenades();
			List<ResolvedRolePrefab> list = new List<ResolvedRolePrefab>();
			foreach (ResolvedRolePrefab grenade in _grenades)
			{
				if (grenade.GrenadeKind switch
				{
					VanillaGrenadeKind.Explosive => config.BomberExplosiveEnabled.Value, 
					VanillaGrenadeKind.Stun => config.BomberStunEnabled.Value, 
					VanillaGrenadeKind.Shockwave => config.BomberShockwaveEnabled.Value, 
					VanillaGrenadeKind.DuctTaped => config.BomberDuctTapedEnabled.Value, 
					_ => false, 
				})
				{
					list.Add(grenade);
				}
			}
			if (list.Count == 0)
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
			resolved = list[Random.Range(0, list.Count)];
			return true;
		}

		internal bool TryGetHealOrb(out ResolvedRolePrefab resolved)
		{
			if ((Object)(object)_healOrb.Prefab != (Object)null && !string.IsNullOrEmpty(_healOrb.ResourcePath))
			{
				resolved = _healOrb;
				return true;
			}
			if (_healOrbResolveFailed)
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
			Item val = Resources.Load<Item>("Items/Item Orb Heal") ?? Resources.Load<Item>("Items/Removed Items/Item Orb Heal");
			if ((Object)(object)val != (Object)null && TryGetVanillaPrefab(val, out var resolved2) && IsHealOrb(resolved2.Prefab))
			{
				CacheHealOrb(resolved2);
			}
			else
			{
				foreach (Item item in EnumerateItems())
				{
					if (TryGetVanillaPrefab(item, out var resolved3) && IsHealOrb(resolved3.Prefab))
					{
						CacheHealOrb(resolved3);
						break;
					}
				}
			}
			resolved = _healOrb;
			if ((Object)(object)resolved.Prefab != (Object)null && !string.IsNullOrEmpty(resolved.ResourcePath))
			{
				return true;
			}
			_healOrbResolveAttempts++;
			if (_healOrbResolveAttempts >= 5)
			{
				_healOrbResolveFailed = true;
				StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla Heal Orb could not be resolved; Medic is unavailable.");
			}
			return false;
		}

		internal bool TryGetStarProjectile(out ResolvedRolePrefab resolved)
		{
			if (IsResolved(_starProjectile))
			{
				resolved = _starProjectile;
				return true;
			}
			if (_starProjectileResolveFailed)
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
			LevelValuables[] array = Resources.FindObjectsOfTypeAll<LevelValuables>();
			for (int i = 0; i < array.Length; i++)
			{
				foreach (PrefabRef item in EnumerateValuableRefs(array[i]))
				{
					if (TryGetVanillaPrefab(item, out var resolved2))
					{
						ValuableStarWand componentInChildren = resolved2.Prefab.GetComponentInChildren<ValuableStarWand>(true);
						if (!((Object)(object)componentInChildren == (Object)null) && TryGetVanillaPrefab(componentInChildren.bulletPrefab, out var resolved3) && !((Object)(object)resolved3.Prefab.GetComponentInChildren<SlowProjectile>(true) == (Object)null))
						{
							_starProjectile = resolved3;
							StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla Star Wand projectile prefab: " + resolved3.ResourcePath));
							break;
						}
					}
				}
				if (IsResolved(_starProjectile))
				{
					break;
				}
			}
			resolved = _starProjectile;
			if (IsResolved(resolved))
			{
				return true;
			}
			if (++_starProjectileResolveAttempts >= 5)
			{
				_starProjectileResolveFailed = true;
				StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla Star Wand projectile could not be resolved; Mage star casts are unavailable.");
			}
			return false;
		}

		internal bool TryGetWizardStaff(out ResolvedRolePrefab resolved)
		{
			if (IsResolved(_wizardStaff))
			{
				resolved = _wizardStaff;
				return true;
			}
			if (_wizardStaffResolveFailed)
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
			LevelValuables[] array = Resources.FindObjectsOfTypeAll<LevelValuables>();
			for (int i = 0; i < array.Length; i++)
			{
				foreach (PrefabRef item in EnumerateValuableRefs(array[i]))
				{
					if (TryGetVanillaPrefab(item, out var resolved2))
					{
						ValuableWizardStaff componentInChildren = resolved2.Prefab.GetComponentInChildren<ValuableWizardStaff>(true);
						if (!((Object)(object)componentInChildren?.semiLaser == (Object)null) && !((Object)(object)componentInChildren.laserTransform == (Object)null))
						{
							_wizardStaff = resolved2;
							StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla Wizard Staff prefab: " + resolved2.ResourcePath));
							break;
						}
					}
				}
				if (IsResolved(_wizardStaff))
				{
					break;
				}
			}
			resolved = _wizardStaff;
			if (IsResolved(resolved))
			{
				return true;
			}
			if (++_wizardStaffResolveAttempts >= 5)
			{
				_wizardStaffResolveFailed = true;
				StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla Wizard Staff could not be resolved; Mage beam casts are unavailable.");
			}
			return false;
		}

		internal bool TryGetRollProjectile(out ResolvedRolePrefab resolved)
		{
			return TryGetStaffProjectile(typeof(ItemStaffTorque), "Roll Staff", ref _rollProjectile, ref _rollProjectileResolveAttempts, ref _rollProjectileResolveFailed, out resolved);
		}

		internal bool TryGetZeroGravityProjectile(out ResolvedRolePrefab resolved)
		{
			return TryGetStaffProjectile(typeof(ItemStaffZeroGravity), "Zero Gravity Staff", ref _zeroGravityProjectile, ref _zeroGravityProjectileResolveAttempts, ref _zeroGravityProjectileResolveFailed, out resolved);
		}

		internal bool TryGetVoidProjectile(out ResolvedRolePrefab resolved)
		{
			return TryGetStaffProjectile(typeof(ItemStaffVoid), "Void Staff", ref _voidProjectile, ref _voidProjectileResolveAttempts, ref _voidProjectileResolveFailed, out resolved);
		}

		internal bool TryGetVoidImpactPrefab(out PrefabRef impactPrefab)
		{
			TryGetVoidProjectile(out var _);
			if (_voidImpactPrefab != null && ((PrefabRef<GameObject>)(object)_voidImpactPrefab).IsValid() && !string.IsNullOrEmpty(((PrefabRef<GameObject>)(object)_voidImpactPrefab).ResourcePath))
			{
				impactPrefab = _voidImpactPrefab;
				return true;
			}
			impactPrefab = null;
			return false;
		}

		private bool TryGetStaffProjectile(Type staffType, string displayName, ref ResolvedRolePrefab cached, ref int resolveAttempts, ref bool resolveFailed, out ResolvedRolePrefab resolved)
		{
			if (IsResolved(cached))
			{
				resolved = cached;
				return true;
			}
			if (resolveFailed)
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
			foreach (Item item in EnumerateItems())
			{
				if (TryGetVanillaPrefab(item, out var resolved2))
				{
					Component componentInChildren = resolved2.Prefab.GetComponentInChildren(staffType, true);
					ItemStaffTorque val = (ItemStaffTorque)(object)((componentInChildren is ItemStaffTorque) ? componentInChildren : null);
					PrefabRef val3;
					if (val == null)
					{
						ItemStaffZeroGravity val2 = (ItemStaffZeroGravity)(object)((componentInChildren is ItemStaffZeroGravity) ? componentInChildren : null);
						val3 = ((val2 != null) ? val2.projectilePrefab : ((ItemStaffVoid)(((componentInChildren is ItemStaffVoid) ? componentInChildren : null)?)).projectilePrefab);
					}
					else
					{
						val3 = val.projectilePrefab;
					}
					PrefabRef val4 = val3;
					ItemStaffVoid val5 = (ItemStaffVoid)(object)((componentInChildren is ItemStaffVoid) ? componentInChildren : null);
					if (val5 != null && val5.prefabToInstantiateOnHit != null && ((PrefabRef<GameObject>)(object)val5.prefabToInstantiateOnHit).IsValid())
					{
						_voidImpactPrefab = val5.prefabToInstantiateOnHit;
					}
					if (val4 != null && TryGetVanillaPrefab(val4, out var resolved3) && !((Object)(object)resolved3.Prefab.GetComponentInChildren<SlowProjectile>(true) == (Object)null))
					{
						cached = resolved3;
						StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla " + displayName + " projectile prefab: " + resolved3.ResourcePath));
						break;
					}
				}
			}
			resolved = cached;
			if (IsResolved(resolved))
			{
				return true;
			}
			if (++resolveAttempts >= 5)
			{
				resolveFailed = true;
				StageRolesPlugin.ModLogger.LogWarning((object)("Vanilla " + displayName + " projectile could not be resolved; Mage " + displayName + " casts are unavailable."));
			}
			return false;
		}

		private void CacheHealOrb(ResolvedRolePrefab candidate)
		{
			_healOrb = candidate;
			StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla Heal Orb prefab: " + candidate.ResourcePath));
		}

		private static bool IsHealOrb(GameObject prefab)
		{
			if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponentInChildren<ItemOrbHeal>(true) != (Object)null && (Object)(object)prefab.GetComponentInChildren<ItemOrb>(true) != (Object)null)
			{
				return (Object)(object)prefab.GetComponentInChildren<ItemToggle>(true) != (Object)null;
			}
			return false;
		}

		private void ResolveGrenades()
		{
			if (_grenadesResolved)
			{
				return;
			}
			_grenadesResolved = true;
			(Type, VanillaGrenadeKind)[] obj = new(Type, VanillaGrenadeKind)[4]
			{
				(typeof(ItemGrenadeExplosive), VanillaGrenadeKind.Explosive),
				(typeof(ItemGrenadeStun), VanillaGrenadeKind.Stun),
				(typeof(ItemGrenadeShockwave), VanillaGrenadeKind.Shockwave),
				(typeof(ItemGrenadeDuctTaped), VanillaGrenadeKind.DuctTaped)
			};
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			(Type, VanillaGrenadeKind)[] array = obj;
			for (int i = 0; i < array.Length; i++)
			{
				var (type, grenadeKind) = array[i];
				foreach (Item item in EnumerateItems())
				{
					if (TryGetVanillaPrefab(item, out var resolved) && !((Object)(object)resolved.Prefab.GetComponentInChildren(type, true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren<ItemGrenade>(true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren<ItemToggle>(true) == (Object)null) && hashSet.Add(resolved.ResourcePath))
					{
						_grenades.Add(new ResolvedRolePrefab(resolved.Prefab, resolved.ResourcePath, grenadeKind));
						break;
					}
				}
			}
			StageRolesPlugin.ModLogger.LogInfo((object)$"Resolved {_grenades.Count}/4 vanilla Bomber grenade prefabs.");
		}

		private void ResolveUraniumValuables()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			LevelValuables[] array = Resources.FindObjectsOfTypeAll<LevelValuables>();
			for (int i = 0; i < array.Length; i++)
			{
				foreach (PrefabRef item in EnumerateValuableRefs(array[i]))
				{
					if (TryGetVanillaPrefab(item, out var resolved) && hashSet.Add(resolved.ResourcePath) && !((Object)(object)resolved.Prefab.GetComponentInChildren<UraniumScript>(true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren<ValuableObject>(true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren<PhysGrabObjectImpactDetector>(true) == (Object)null))
					{
						_uraniumValuables.Add(resolved);
					}
				}
			}
			if (_uraniumValuables.Count > 0)
			{
				StageRolesPlugin.ModLogger.LogInfo((object)$"Resolved {_uraniumValuables.Count} vanilla uranium valuable prefab(s).");
			}
		}

		private static IEnumerable<Item> EnumerateItems()
		{
			HashSet<Item> seen = new HashSet<Item>();
			if ((Object)(object)StatsManager.instance != (Object)null)
			{
				foreach (Item value in StatsManager.instance.itemDictionary.Values)
				{
					if ((Object)(object)value != (Object)null && seen.Add(value))
					{
						yield return value;
					}
				}
			}
			Item[] array = Resources.FindObjectsOfTypeAll<Item>();
			foreach (Item val in array)
			{
				if ((Object)(object)val != (Object)null && seen.Add(val))
				{
					yield return val;
				}
			}
		}

		private static IEnumerable<PrefabRef> EnumerateValuableRefs(LevelValuables preset)
		{
			List<PrefabRef>[] array = new List<PrefabRef>[7] { preset.tiny, preset.small, preset.medium, preset.big, preset.wide, preset.tall, preset.veryTall };
			List<PrefabRef>[] array2 = array;
			foreach (List<PrefabRef> list in array2)
			{
				if (list == null)
				{
					continue;
				}
				foreach (PrefabRef item in list)
				{
					if (item != null)
					{
						yield return item;
					}
				}
			}
		}

		private static bool TryGetVanillaPrefab(Item item, out ResolvedRolePrefab resolved)
		{
			try
			{
				PrefabRef prefab = item.prefab;
				if (prefab == null || (Object)(object)((PrefabRef<GameObject>)(object)prefab).Bundle != (Object)null || !((PrefabRef<GameObject>)(object)prefab).IsValid() || string.IsNullOrEmpty(((PrefabRef<GameObject>)(object)prefab).ResourcePath) || (Object)(object)((PrefabRef<GameObject>)(object)prefab).Prefab == (Object)null)
				{
					resolved = default(ResolvedRolePrefab);
					return false;
				}
				resolved = new ResolvedRolePrefab(((PrefabRef<GameObject>)(object)prefab).Prefab, ((PrefabRef<GameObject>)(object)prefab).ResourcePath);
				return true;
			}
			catch
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
		}

		private static bool TryGetVanillaPrefab(PrefabRef prefabRef, out ResolvedRolePrefab resolved)
		{
			try
			{
				if (prefabRef == null || (Object)(object)((PrefabRef<GameObject>)(object)prefabRef).Bundle != (Object)null || !((PrefabRef<GameObject>)(object)prefabRef).IsValid() || string.IsNullOrEmpty(((PrefabRef<GameObject>)(object)prefabRef).ResourcePath) || (Object)(object)((PrefabRef<GameObject>)(object)prefabRef).Prefab == (Object)null)
				{
					resolved = default(ResolvedRolePrefab);
					return false;
				}
				resolved = new ResolvedRolePrefab(((PrefabRef<GameObject>)(object)prefabRef).Prefab, ((PrefabRef<GameObject>)(object)prefabRef).ResourcePath);
				return true;
			}
			catch
			{
				resolved = default(ResolvedRolePrefab);
				return false;
			}
		}

		private static bool IsResolved(ResolvedRolePrefab resolved)
		{
			if ((Object)(object)resolved.Prefab != (Object)null)
			{
				return !string.IsNullOrEmpty(resolved.ResourcePath);
			}
			return false;
		}
	}
	internal static class UpgradeService
	{
		internal static bool Ready
		{
			get
			{
				if ((Object)(object)PunManager.instance != (Object)null)
				{
					return (Object)(object)StatsManager.instance != (Object)null;
				}
				return false;
			}
		}

		internal static void SetLevels(string steamId, IReadOnlyList<UpgradeGrant> targets)
		{
			if (!Ready || string.IsNullOrEmpty(steamId))
			{
				return;
			}
			Dictionary<string, int> dictionary;
			try
			{
				dictionary = StatsManager.instance.FetchPlayerUpgrades(steamId);
			}
			catch (Exception ex)
			{
				StageRolesPlugin.ModLogger.LogWarning((object)("Could not read upgrade levels for player " + steamId + ": " + ex.Message));
				return;
			}
			foreach (UpgradeGrant target in targets)
			{
				int num = Math.Max(0, target.Level);
				int valueOrDefault = dictionary.GetValueOrDefault(target.DictionaryName, 0);
				int num2 = num - valueOrDefault;
				if (num2 != 0)
				{
					try
					{
						SendUpgradeDelta(steamId, target.CommandName, num2);
					}
					catch (Exception ex2)
					{
						StageRolesPlugin.ModLogger.LogWarning((object)($"Could not set {target.CommandName} to {num} for player " + steamId + ": " + ex2.Message));
					}
				}
			}
		}

		private static void SendUpgradeDelta(string steamId, string commandName,