Decompiled source of RepoHostSuite v0.1.0

BepInEx/plugins/RepoHostSuite/YoneRai12.AnomalyContractDirector.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using YoneRai12.AnomalyContractDirector.Core;
using YoneRai12.AnomalyContractDirector.Runtime;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("YoneRai12.AnomalyContractDirector")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0")]
[assembly: AssemblyProduct("YoneRai12.AnomalyContractDirector")]
[assembly: AssemblyTitle("YoneRai12.AnomalyContractDirector")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.0.0")]
[module: UnverifiableCode]
[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;
		}
	}
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace YoneRai12.AnomalyContractDirector
{
	[BepInPlugin("YoneRai12.AnomalyContractDirector", "R.E.P.O. Anomaly Contract Director", "0.2.0")]
	public sealed class AnomalyContractDirectorPlugin : BaseUnityPlugin
	{
		internal const string PluginGuid = "YoneRai12.AnomalyContractDirector";

		internal const string PluginName = "R.E.P.O. Anomaly Contract Director";

		internal const string PluginVersion = "0.2.0";

		private Harmony harmony;

		internal static AnomalyContractDirectorPlugin Instance { get; private set; }

		internal ContractDirectorRuntime Runtime { get; private set; }

		internal AnomalyEventRuntime EventRuntime { get; private set; }

		private void Awake()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			Instance = this;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			DirectorConfig directorConfig = new DirectorConfig(((BaseUnityPlugin)this).Config);
			Runtime = ((Component)this).gameObject.AddComponent<ContractDirectorRuntime>();
			Runtime.Initialize(directorConfig, ((BaseUnityPlugin)this).Logger);
			EventRuntime = ((Component)this).gameObject.AddComponent<AnomalyEventRuntime>();
			EventRuntime.Initialize(directorConfig, ((BaseUnityPlugin)this).Logger);
			harmony = new Harmony("YoneRai12.AnomalyContractDirector");
			harmony.PatchAll(Assembly.GetExecutingAssembly());
			((Component)this).gameObject.AddComponent<DirectorNetworkCallbacks>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"R.E.P.O. Anomaly Contract Director v0.2.0 loaded. Host-only, vanilla-client compatible mode.");
		}

		private void OnDestroy()
		{
			EventRuntime?.ResetRoomState("MODアンロード");
			Runtime?.ResetRoomState("MODアンロード");
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if (Instance == this)
			{
				Instance = null;
			}
		}
	}
	internal sealed class DirectorNetworkCallbacks : MonoBehaviourPunCallbacks
	{
		public override void OnJoinedRoom()
		{
			AnomalyContractDirectorPlugin.Instance?.Runtime.ResetRoomState("ルーム参加");
			AnomalyContractDirectorPlugin.Instance?.EventRuntime.ResetRoomState("ルーム参加");
		}

		public override void OnLeftRoom()
		{
			AnomalyContractDirectorPlugin.Instance?.Runtime.ResetRoomState("ルーム退出");
			AnomalyContractDirectorPlugin.Instance?.EventRuntime.ResetRoomState("ルーム退出");
		}

		public override void OnMasterClientSwitched(Player newMasterClient)
		{
			AnomalyContractDirectorPlugin.Instance?.Runtime.OnMasterClientChanged();
			AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnMasterClientChanged();
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			AnomalyContractDirectorPlugin.Instance?.Runtime.NotifyCurrentStateToRoom();
			AnomalyContractDirectorPlugin.Instance?.EventRuntime.NotifyCurrentStateToRoom();
		}
	}
}
namespace YoneRai12.AnomalyContractDirector.Runtime
{
	internal sealed class AnomalyEventRuntime : MonoBehaviour
	{
		private sealed class EnemyPoolExpansionTransaction
		{
			internal EnemyDirector Director { get; }

			internal List<EnemySetup> BaseList { get; }

			internal List<EnemySetup> CurrentList { get; }

			internal int OriginalBaseCount { get; }

			internal int OriginalCurrentCount { get; }

			internal int OriginalEnemyListIndex { get; }

			internal int AddedLogical { get; }

			internal IReadOnlyList<EnemySetup> AddedSetups { get; }

			internal EnemyPoolExpansionTransaction(EnemyDirector director, List<EnemySetup> baseList, List<EnemySetup> currentList, int originalBaseCount, int originalCurrentCount, int originalEnemyListIndex, int addedLogical, IReadOnlyList<EnemySetup> addedSetups)
			{
				Director = director;
				BaseList = baseList;
				CurrentList = currentList;
				OriginalBaseCount = originalBaseCount;
				OriginalCurrentCount = originalCurrentCount;
				OriginalEnemyListIndex = originalEnemyListIndex;
				AddedLogical = addedLogical;
				AddedSetups = addedSetups;
			}
		}

		private readonly AnomalyEventStateMachine stateMachine = new AnomalyEventStateMachine();

		private readonly List<EnemyParent> targets = new List<EnemyParent>();

		private readonly Random random = new Random();

		private DirectorConfig config;

		private ManualLogSource logger;

		private AnomalyEventKind? previousKind;

		private AnomalyEventKind? preselectedKind;

		private AnomalyEventKind? markedKind;

		private EnemyParent? markedEnemy;

		private bool levelActive;

		private bool levelEventDecided;

		private bool preRollCompleted;

		private bool preRollNoEvent;

		private bool enemyAmountSetupObserved;

		private bool enemyAmountSetupInProgress;

		private bool enemyPoolExpansionApplied;

		private bool lootApplied;

		private bool valuableSetupObserved;

		private bool valuableSetupFinalized;

		private bool setupTopUpProcessed;

		private bool setupLootApplied;

		private int setupLootSpawned;

		private int cosmeticPromotionCount;

		private float levelStartedAt;

		private float nextPollTime;

		private float lastPollTime;

		private float nextActionTime;

		private float nextSpawnTime;

		private float enemyDiscoveryDeadline;

		private int initialEnemyCount;

		private int totalSpawnRequestCount;

		private int preExpandedEnemyUnits;

		private int standardEnemyUnits;

		private ValuableDirector? valuableSetupDirector;

		private EnemyPoolExpansionTransaction? pendingEnemyPoolExpansion;

		internal AnomalyEventStateMachine StateMachine => stateMachine;

		internal bool IsLevelActive => levelActive;

		internal void Initialize(DirectorConfig directorConfig, ManualLogSource manualLogSource)
		{
			config = directorConfig;
			logger = manualLogSource;
			stateMachine.StateChanged += OnStateChanged;
			((Behaviour)this).enabled = true;
		}

		private void Update()
		{
			if (config == null || Time.unscaledTime < nextPollTime)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			float num = Clamp(config.AnomalyEventPollIntervalSeconds.Value, 0.25f, 1f);
			float deltaSeconds = ((lastPollTime > 0f) ? Clamp(unscaledTime - lastPollTime, 0.01f, 5f) : num);
			lastPollTime = unscaledTime;
			nextPollTime = unscaledTime + num;
			if (!config.Enabled.Value || !config.EnableAnomalyEvents.Value || !IsHost())
			{
				if (levelActive || pendingEnemyPoolExpansion != null || preRollCompleted || valuableSetupObserved)
				{
					ResetRoomState("異常イベント設定またはホスト権限が失われました");
				}
				return;
			}
			bool flag;
			bool flag2;
			bool flag3;
			try
			{
				flag = SemiFunc.RunIsLevel();
				flag2 = SemiFunc.RunIsShop();
				flag3 = SemiFunc.RunIsLobby();
			}
			catch (Exception ex)
			{
				LogDebug("異常イベントのゲーム状態読み取り失敗: " + ex.GetType().Name);
				return;
			}
			if (!flag || flag2 || flag3)
			{
				if (levelActive)
				{
					ResetRoomState(flag2 ? "ショップ遷移" : (flag3 ? "ロビー遷移" : "レベル外遷移"));
				}
				return;
			}
			if (!levelActive)
			{
				if (!IsLevelReady())
				{
					return;
				}
				BeginLevel();
			}
			if (!levelActive || !IsLevelReady())
			{
				if (levelActive)
				{
					ResetRoomState("レベル遷移準備");
				}
				return;
			}
			try
			{
				if (stateMachine.IsPending || stateMachine.IsActive)
				{
					stateMachine.Tick(deltaSeconds);
					if (stateMachine.IsActive)
					{
						EvaluateActiveEvent(deltaSeconds);
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogWarning((object)$"[ACD] 異常イベント内の例外を封じ込めました: {arg}");
				}
				if (stateMachine.IsPending || stateMachine.IsActive)
				{
					stateMachine.Fail("異常イベントの安全な監視に失敗しました");
				}
			}
		}

		internal bool HandleChatCommand(PlayerAvatar sender, string message)
		{
			if (string.IsNullOrWhiteSpace(message))
			{
				return false;
			}
			string text = message.Trim();
			if (!text.StartsWith("!acd", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			string[] array = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length < 2 || !string.Equals(array[1], "event", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			if (!IsHost() || (Object)(object)sender == (Object)null || (Object)(object)sender != (Object)(object)SafeLocalPlayer())
			{
				return true;
			}
			if (array.Length == 2 || string.Equals(array[2], "help", StringComparison.OrdinalIgnoreCase))
			{
				SendRoomMessage("!acd event random | !acd event <標的/群れ/地獄/ウェーブ/増加/再出現/金/紫/貴重品> | !acd event status | !acd event list | !acd event cancel");
				return true;
			}
			string text2 = array[2].ToLowerInvariant();
			switch (text2)
			{
			case "status":
				SendRoomMessage(FormatStatus());
				return true;
			case "list":
			{
				string text3 = string.Join(" / ", AnomalyEventCatalog.All.Select((AnomalyEventDefinition definition, int index) => $"{index + 1}:{definition.DisplayName}({definition.Id})"));
				SendRoomMessage("異常イベント候補: " + text3);
				return true;
			}
			case "cancel":
			case "off":
				if (!stateMachine.Cancel("ホストが異常イベントを中止しました"))
				{
					SendRoomMessage("現在、発動中または待機中の異常イベントはありません。");
				}
				levelEventDecided = true;
				return true;
			default:
			{
				if (!config.AnomalyEventManualEnabled.Value)
				{
					SendRoomMessage("異常イベントの手動指定は設定で無効です。");
					return true;
				}
				if (!levelActive || !IsLevelReady())
				{
					SendRoomMessage("異常イベントは生成済みのレベル内でだけ指定できます。");
					return true;
				}
				AnomalyEventKind kind;
				int result;
				if (text2 == "random")
				{
					if (!TryChooseEvent(out kind, allowPrevious: true))
					{
						SendRoomMessage("有効な異常イベント候補がありません。");
						return true;
					}
				}
				else if (int.TryParse(text2, out result) && result >= 1 && result <= AnomalyEventCatalog.All.Count)
				{
					kind = AnomalyEventCatalog.All[result - 1].Kind;
				}
				else if (!AnomalyEventCatalog.TryParse(array[2], out kind))
				{
					SendRoomMessage("候補を解釈できません。!acd event list で確認してください。");
					return true;
				}
				if (!IsEventEnabled(kind))
				{
					SendRoomMessage("その異常イベントは設定で無効です。");
					return true;
				}
				if ((kind == AnomalyEventKind.GoldRush || kind == AnomalyEventKind.PurpleChance || kind == AnomalyEventKind.ValuableSurge) && valuableSetupFinalized)
				{
					SendRoomMessage("金・紫コスメ箱と貴重品大量発生は、標準生成前のpre-rollが必要です。このレベルでは手動指定できません。");
					return true;
				}
				ScheduleEvent(kind, "ホストのチャット手動指定", manual: true);
				return true;
			}
			}
		}

		internal void ResetRoomState(string reason)
		{
			TryRollbackUnconsumedEnemyPoolExpansion(reason);
			((MonoBehaviour)this).StopAllCoroutines();
			levelActive = false;
			levelEventDecided = false;
			preRollCompleted = false;
			preRollNoEvent = false;
			enemyAmountSetupObserved = false;
			enemyAmountSetupInProgress = false;
			enemyPoolExpansionApplied = false;
			lootApplied = false;
			valuableSetupObserved = false;
			valuableSetupFinalized = false;
			setupTopUpProcessed = false;
			setupLootApplied = false;
			setupLootSpawned = 0;
			valuableSetupDirector = null;
			preselectedKind = null;
			cosmeticPromotionCount = 0;
			markedKind = null;
			markedEnemy = null;
			targets.Clear();
			initialEnemyCount = 0;
			totalSpawnRequestCount = 0;
			preExpandedEnemyUnits = 0;
			standardEnemyUnits = 0;
			EnemyMutationRegistry.Clear();
			StandardGameApi.ResetRuntimeTrackers();
			nextActionTime = 0f;
			nextSpawnTime = 0f;
			enemyDiscoveryDeadline = 0f;
			stateMachine.Reset(reason);
			LogDebug("異常イベント状態を解放: " + reason);
		}

		internal void OnMasterClientChanged()
		{
			ResetRoomState("マスター変更");
		}

		internal void NotifyCurrentStateToRoom()
		{
			if (IsHost() && levelActive)
			{
				AnomalyEventSnapshot snapshot = stateMachine.Snapshot;
				if (snapshot.Kind.HasValue)
				{
					AnomalyEventDefinition anomalyEventDefinition = AnomalyEventCatalog.Get(snapshot.Kind.Value);
					SendRoomMessage($"途中参加者向け再通知: 異常イベント {anomalyEventDefinition.DisplayName} / {snapshot.Phase}");
				}
			}
		}

		internal void OnEnemyAmountSetupPrefix()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Invalid comparison between Unknown and I4
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Invalid comparison between Unknown and I4
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Invalid comparison between Unknown and I4
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Invalid comparison between Unknown and I4
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Invalid comparison between Unknown and I4
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Invalid comparison between Unknown and I4
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Invalid comparison between Unknown and I4
			if (config == null || enemyAmountSetupObserved || !IsHost())
			{
				return;
			}
			try
			{
				if (!config.Enabled.Value || !config.EnableAnomalyEvents.Value || !SemiFunc.RunIsLevel())
				{
					return;
				}
				LevelGenerator instance = LevelGenerator.Instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance.Level == (Object)null || instance.Generated || ((int)instance.State != 13 && (int)instance.State != 14))
				{
					return;
				}
				GameDirector instance2 = GameDirector.instance;
				if ((Object)(object)instance2 == (Object)null || (int)instance2.currentState == 3 || (int)instance2.currentState == 4 || (int)instance2.currentState == 5 || (int)instance2.currentState == 6 || (int)instance2.currentState == 7)
				{
					return;
				}
			}
			catch
			{
				return;
			}
			enemyAmountSetupObserved = true;
			enemyAmountSetupInProgress = true;
			if (!preRollCompleted)
			{
				PreRollEvent("EnemyDirector.AmountSetup前");
			}
		}

		internal void OnEnemyAmountSetupPostfix()
		{
			if (!enemyAmountSetupObserved)
			{
				return;
			}
			try
			{
				if (config != null && config.Enabled.Value && config.EnableAnomalyEvents.Value && IsHost() && !enemyPoolExpansionApplied && preselectedKind.HasValue)
				{
					ExpandEnemyPoolBeforeLevelGeneration(preselectedKind.Value);
				}
			}
			catch (Exception ex)
			{
				LogDebug("通常敵プール追加を安全に縮退: " + ex.GetType().Name);
			}
			finally
			{
				enemyAmountSetupInProgress = false;
			}
		}

		internal void OnEnemyGetEnemyPrefix(EnemyDirector director)
		{
			EnemyPoolExpansionTransaction enemyPoolExpansionTransaction = pendingEnemyPoolExpansion;
			if (enemyPoolExpansionTransaction != null && enemyPoolExpansionTransaction.Director == director)
			{
				pendingEnemyPoolExpansion = null;
				LogDebug("生成前敵Setup追加を標準GetEnemyへcommit");
			}
		}

		private void PreRollEvent(string reason)
		{
			preRollCompleted = true;
			if (!TryChooseEvent(out var selected, allowPrevious: false))
			{
				preRollNoEvent = true;
				levelEventDecided = true;
				LogDebug(reason + ": 今レベルは異常なし");
			}
			else
			{
				preselectedKind = selected;
				levelEventDecided = true;
				LogDebug($"{reason}: {selected}");
			}
		}

		internal void OnValuableSetupHostPrefix(ValuableDirector director)
		{
			if (config == null || valuableSetupObserved || !IsHost())
			{
				return;
			}
			try
			{
				if (!config.Enabled.Value || !config.EnableAnomalyEvents.Value || !SemiFunc.RunIsLevel())
				{
					return;
				}
			}
			catch
			{
				return;
			}
			valuableSetupObserved = true;
			valuableSetupDirector = director;
			if (preRollCompleted)
			{
				LogDebug("Valuable setup開始前: 既存の生成前pre-rollを引き継ぎます");
			}
			else
			{
				PreRollEvent("Valuable setup開始前");
			}
		}

		internal void OnValuableSetupPrefix(ValuableDirector director)
		{
			if (config == null || setupTopUpProcessed || !IsHost())
			{
				return;
			}
			setupTopUpProcessed = true;
			valuableSetupFinalized = true;
			if (!valuableSetupObserved || (Object)(object)valuableSetupDirector == (Object)null || valuableSetupDirector != director)
			{
				preRollCompleted = true;
				preRollNoEvent = true;
				levelEventDecided = true;
				LogDebug("Valuable setup開始Prefixを観測できず、setup依存イベントを安全に無効化");
			}
			else if (preselectedKind == AnomalyEventKind.ValuableSurge)
			{
				int num = Clamp(config.LootEventRevealCount.Value, 0, 20);
				if (StandardGameApi.TrySpawnValuableTopUp(director, num, out int spawned, out string reason) && spawned > 0)
				{
					setupLootApplied = true;
					lootApplied = true;
					setupLootSpawned = spawned;
					LogDebug($"貴重品大量発生をSetupHost前に適用: {spawned}/{num}");
					if (!string.IsNullOrWhiteSpace(reason))
					{
						LogDebug("貴重品target同期警告: " + reason);
					}
				}
				else if (num == 0)
				{
					preRollNoEvent = true;
					preselectedKind = null;
					LogDebug("貴重品大量発生は追加数0のため無効化");
				}
				else
				{
					preRollNoEvent = true;
					preselectedKind = null;
					LogDebug("貴重品大量発生を安全に適用できず、イベントを縮退: " + reason);
				}
			}
			else
			{
				LogDebug("Valuable setup完了Prefix: 貴重品top-up対象ではありません");
			}
		}

		internal void TryPromoteCosmeticRarity(ValuableDirector director, ref Rarity rarity)
		{
			if (!IsHost() || (Object)(object)director == (Object)null || (Object)(object)valuableSetupDirector == (Object)null || valuableSetupDirector != director || !preselectedKind.HasValue || !preRollCompleted || !valuableSetupObserved || valuableSetupFinalized)
			{
				return;
			}
			float num = preselectedKind.Value switch
			{
				AnomalyEventKind.GoldRush => Clamp(config.CosmeticGoldUpgradeChancePercent.Value, 0f, 100f), 
				AnomalyEventKind.PurpleChance => Clamp(config.CosmeticPurpleUpgradeChancePercent.Value, 0f, 100f), 
				_ => 0f, 
			};
			if (num <= 0f || random.NextDouble() * 100.0 > (double)num)
			{
				return;
			}
			if (preselectedKind.Value == AnomalyEventKind.GoldRush)
			{
				if ((int)rarity != 3)
				{
					rarity = (Rarity)3;
					cosmeticPromotionCount++;
				}
			}
			else if (preselectedKind.Value == AnomalyEventKind.PurpleChance && ((int)rarity == 0 || (int)rarity == 1))
			{
				rarity = (Rarity)2;
				cosmeticPromotionCount++;
			}
		}

		private void BeginLevel()
		{
			levelActive = true;
			levelEventDecided = false;
			levelStartedAt = Time.unscaledTime;
			lootApplied = setupLootApplied;
			markedEnemy = null;
			targets.Clear();
			initialEnemyCount = 0;
			int num = StandardGameApi.CountPreparedEnemyParents();
			standardEnemyUnits = Math.Max(standardEnemyUnits, Math.Max(0, num - preExpandedEnemyUnits));
			totalSpawnRequestCount = Math.Min(100, preExpandedEnemyUnits);
			AnomalyEventKind? anomalyEventKind = preselectedKind;
			if (!anomalyEventKind.HasValue)
			{
				if (preRollCompleted && preRollNoEvent)
				{
					preselectedKind = null;
					preRollCompleted = false;
					preRollNoEvent = false;
					levelEventDecided = true;
					SendRoomMessage("【異常抽選】このレベルは異常なしです。");
					return;
				}
				if (!TryChooseEvent(out var selected, allowPrevious: false))
				{
					levelEventDecided = true;
					SendRoomMessage("【異常抽選】このレベルは異常なしです。");
					return;
				}
				anomalyEventKind = selected;
			}
			preselectedKind = null;
			preRollCompleted = false;
			preRollNoEvent = false;
			levelEventDecided = true;
			ScheduleEvent(anomalyEventKind.Value, "レベルごとの異常抽選", manual: false);
		}

		private void ScheduleEvent(AnomalyEventKind selected, string reason, bool manual)
		{
			if (!levelActive || !IsEventEnabled(selected))
			{
				return;
			}
			if (stateMachine.IsPending || stateMachine.IsActive)
			{
				if (!manual)
				{
					SendRoomMessage("異常イベントはすでに待機または発動中です。");
					return;
				}
				stateMachine.Cancel("手動指定で前の異常イベントを置換");
			}
			previousKind = selected;
			float num = (manual ? 0f : Clamp(config.AnomalyEventStartDelaySeconds.Value, 0f, 90f));
			if (stateMachine.BeginPending(selected, GetPlayerCount(), num, reason))
			{
				LogDebug($"異常イベントを予約: {selected}, manual={manual}, delay={num:0.00}s");
			}
		}

		private void EvaluateActiveEvent(float deltaSeconds)
		{
			AnomalyEventSnapshot snapshot = stateMachine.Snapshot;
			if (!snapshot.Kind.HasValue)
			{
				return;
			}
			float elapsedSeconds = snapshot.ElapsedSeconds;
			float val = Clamp(config.AnomalyEventDurationSeconds.Value, 5f, 180f);
			if (elapsedSeconds >= Math.Min(val, snapshot.TimeoutSeconds))
			{
				stateMachine.Complete("異常イベント時間を終了しました");
				return;
			}
			switch (snapshot.Kind.Value)
			{
			case AnomalyEventKind.MarkedEnemy:
				EvaluateMarkedEnemy();
				break;
			case AnomalyEventKind.PackFrenzy:
				EvaluatePackFrenzy();
				break;
			case AnomalyEventKind.HellAllEnemies:
				EvaluateAllEnemies();
				break;
			case AnomalyEventKind.ReinforcementWave:
				EvaluateReinforcementWave(elapsedSeconds);
				break;
			case AnomalyEventKind.EscalatingThreat:
				EvaluateEscalatingThreat(elapsedSeconds);
				break;
			case AnomalyEventKind.RelentlessRespawn:
				EvaluateRelentlessRespawn();
				break;
			case AnomalyEventKind.GoldRush:
			case AnomalyEventKind.PurpleChance:
			case AnomalyEventKind.ValuableSurge:
				break;
			}
		}

		private void PrepareActiveEvent(AnomalyEventKind kind)
		{
			markedKind = kind;
			targets.Clear();
			markedEnemy = null;
			initialEnemyCount = FindLiveEnemies().Count;
			enemyDiscoveryDeadline = Time.unscaledTime + 12f;
			nextActionTime = 0f;
			nextSpawnTime = Time.unscaledTime;
			switch (kind)
			{
			case AnomalyEventKind.MarkedEnemy:
			case AnomalyEventKind.PackFrenzy:
			case AnomalyEventKind.HellAllEnemies:
			case AnomalyEventKind.ReinforcementWave:
			case AnomalyEventKind.EscalatingThreat:
			case AnomalyEventKind.RelentlessRespawn:
				RefreshEnemyTargets(kind);
				break;
			case AnomalyEventKind.GoldRush:
			case AnomalyEventKind.PurpleChance:
			case AnomalyEventKind.ValuableSurge:
				ApplyLootEvent(kind);
				break;
			}
		}

		private void EvaluateMarkedEnemy()
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: 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)
			if ((Object)(object)markedEnemy == (Object)null || !IsLiveEnemy(markedEnemy))
			{
				if ((Object)(object)markedEnemy != (Object)null && !IsLiveEnemy(markedEnemy))
				{
					stateMachine.Complete("標的の既存敵が倒されました");
				}
				else if (Time.unscaledTime >= enemyDiscoveryDeadline)
				{
					stateMachine.Fail("標的にできる既存敵が見つかりませんでした");
				}
				else
				{
					RefreshEnemyTargets(AnomalyEventKind.MarkedEnemy);
				}
			}
			else if (!(Time.unscaledTime < nextActionTime))
			{
				nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f);
				Vector3 position = PlayerPositionOr(((Component)markedEnemy).transform.position);
				StandardGameApi.SetInvestigate(position, Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f));
			}
		}

		private void EvaluatePackFrenzy()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			RefreshEnemyTargets(AnomalyEventKind.PackFrenzy);
			if (targets.Count == 0)
			{
				if (Time.unscaledTime >= enemyDiscoveryDeadline)
				{
					stateMachine.Fail("同種の既存敵が見つかりませんでした");
				}
			}
			else if (!(Time.unscaledTime < nextActionTime))
			{
				nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f);
				StandardGameApi.SetInvestigate(PlayerPositionOr(((Component)targets[0]).transform.position), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f));
			}
		}

		private void EvaluateAllEnemies()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			RefreshEnemyTargets(AnomalyEventKind.HellAllEnemies);
			if (!(Time.unscaledTime < nextActionTime))
			{
				nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f);
				StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f));
				if (targets.Count == 0 && Time.unscaledTime >= enemyDiscoveryDeadline)
				{
					stateMachine.Fail("対象にできる既存敵が見つかりませんでした");
				}
			}
		}

		private void EvaluateReinforcementWave(float elapsed)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			RefreshEnemyTargets(AnomalyEventKind.ReinforcementWave);
			MaintainEnemyPopulation(Math.Max(1, initialEnemyCount + Clamp(config.EnemyEventWaveBatchSize.Value, 1, 20)), elapsed);
			if (!(Time.unscaledTime < nextActionTime))
			{
				nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f);
				StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f));
			}
		}

		private void EvaluateEscalatingThreat(float elapsed)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			RefreshEnemyTargets(AnomalyEventKind.EscalatingThreat);
			float num = Clamp(config.EnemyEventEscalationStepSeconds.Value, 5f, 60f);
			int desired = initialEnemyCount + (int)Math.Floor(elapsed / num) * Clamp(config.EnemyEventWaveBatchSize.Value, 1, 20);
			MaintainEnemyPopulation(desired, elapsed);
			if (!(Time.unscaledTime < nextActionTime))
			{
				nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f);
				StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f));
			}
		}

		private void EvaluateRelentlessRespawn()
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			RefreshEnemyTargets(AnomalyEventKind.RelentlessRespawn);
			MaintainEnemyPopulation(Clamp(config.EnemyEventRespawnTargetAlive.Value, 1, 100), 0f);
			if (!(Time.unscaledTime < nextActionTime))
			{
				nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventRespawnIntervalSeconds.Value, 0f, 30f);
				StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f));
			}
		}

		private void ExpandEnemyPoolBeforeLevelGeneration(AnomalyEventKind kind)
		{
			if (kind != AnomalyEventKind.PackFrenzy && kind != AnomalyEventKind.HellAllEnemies && kind != AnomalyEventKind.ReinforcementWave && kind != AnomalyEventKind.EscalatingThreat && kind != AnomalyEventKind.RelentlessRespawn)
			{
				return;
			}
			enemyPoolExpansionApplied = true;
			int val = Clamp(config.EnemyEventPoolAdditionalLimit.Value, 0, 100);
			int val2 = Math.Min(100, Clamp(config.EnemyEventTotalSpawnLimit.Value, 0, 100));
			val = Math.Min(val, val2);
			if (val <= 0)
			{
				return;
			}
			EnemyDirector instance = EnemyDirector.instance;
			if ((Object)(object)instance == (Object)null || instance.enemyList == null || instance.enemyListCurrent == null)
			{
				return;
			}
			List<EnemySetup> list = instance.enemyListCurrent.Where((EnemySetup setup) => (Object)(object)setup != (Object)null && setup.spawnObjects != null && setup.spawnObjects.Count > 0).ToList();
			if (list.Count == 0)
			{
				return;
			}
			int num = instance.enemyListCurrent.Where((EnemySetup setup) => (Object)(object)setup != (Object)null && setup.spawnObjects != null).Sum((EnemySetup setup) => setup.spawnObjects.Count);
			standardEnemyUnits = Math.Max(standardEnemyUnits, AnomalySafetyLimits.ClampHard100(num));
			val = AnomalySafetyLimits.EnemyPoolBudgetWithPhoton(num, val, StandardGameApi.CurrentPhotonViewCount());
			if (val <= 0)
			{
				return;
			}
			List<EnemySetup> enemyList = instance.enemyList;
			List<EnemySetup> enemyListCurrent = instance.enemyListCurrent;
			int count = enemyList.Count;
			int count2 = enemyListCurrent.Count;
			int totalAmount = instance.totalAmount;
			int enemyListIndex = instance.enemyListIndex;
			int num2 = preExpandedEnemyUnits;
			int num3 = 0;
			int num4 = 0;
			List<EnemySetup> list2 = new List<EnemySetup>();
			try
			{
				int num5 = 0;
				while (num4 < val && num3 < 100)
				{
					EnemySetup val3 = ((kind == AnomalyEventKind.PackFrenzy) ? list[0] : list[num5++ % list.Count]);
					int count3 = val3.spawnObjects.Count;
					if (count3 <= 0 || num4 + count3 > val || num4 + count3 > 100 || !StandardGameApi.CanReserveNetworkViews(num + num4 + count3))
					{
						break;
					}
					enemyList.Add(val3);
					if (enemyList != enemyListCurrent)
					{
						enemyListCurrent.Add(val3);
					}
					instance.totalAmount++;
					num3++;
					num4 += count3;
					list2.Add(val3);
					if (instance.enemyListIndex < 0 || instance.enemyListIndex + instance.totalAmount > enemyList.Count)
					{
						throw new InvalidOperationException("enemyListIndexと追加後enemyListの範囲が不整合");
					}
				}
				if (num3 > 0)
				{
					preExpandedEnemyUnits = Math.Min(100, num2 + num4);
					pendingEnemyPoolExpansion = new EnemyPoolExpansionTransaction(instance, enemyList, enemyListCurrent, count, count2, enemyListIndex, num3, list2.ToArray());
					LogDebug($"生成前敵Setup追加: logical={num3}, units={num4}, kind={kind}");
				}
			}
			catch (Exception ex)
			{
				if (enemyList.Count > count)
				{
					enemyList.RemoveRange(count, enemyList.Count - count);
				}
				if (enemyList != enemyListCurrent && enemyListCurrent.Count > count2)
				{
					enemyListCurrent.RemoveRange(count2, enemyListCurrent.Count - count2);
				}
				instance.totalAmount = totalAmount;
				preExpandedEnemyUnits = num2;
				pendingEnemyPoolExpansion = null;
				LogDebug("生成前敵Setup追加をロールバック: " + ex.GetType().Name);
			}
		}

		private void TryRollbackUnconsumedEnemyPoolExpansion(string reason)
		{
			EnemyPoolExpansionTransaction enemyPoolExpansionTransaction = pendingEnemyPoolExpansion;
			pendingEnemyPoolExpansion = null;
			if (enemyPoolExpansionTransaction == null)
			{
				return;
			}
			try
			{
				EnemyDirector director = enemyPoolExpansionTransaction.Director;
				if ((Object)(object)director == (Object)null || director.enemyListIndex != enemyPoolExpansionTransaction.OriginalEnemyListIndex)
				{
					LogDebug("生成前敵Setup追加は消費開始済みのため巻戻しなし: " + reason);
					return;
				}
				bool flag = SequenceMatches(enemyPoolExpansionTransaction.BaseList, enemyPoolExpansionTransaction.OriginalBaseCount, enemyPoolExpansionTransaction.AddedSetups);
				bool flag2 = enemyPoolExpansionTransaction.BaseList == enemyPoolExpansionTransaction.CurrentList || SequenceMatches(enemyPoolExpansionTransaction.CurrentList, enemyPoolExpansionTransaction.OriginalCurrentCount, enemyPoolExpansionTransaction.AddedSetups);
				if (!flag || !flag2)
				{
					LogDebug("生成前敵Setup追加は他パッチ変更を検出したため巻戻しなし: " + reason);
					return;
				}
				enemyPoolExpansionTransaction.BaseList.RemoveRange(enemyPoolExpansionTransaction.OriginalBaseCount, enemyPoolExpansionTransaction.AddedLogical);
				if (enemyPoolExpansionTransaction.BaseList != enemyPoolExpansionTransaction.CurrentList)
				{
					enemyPoolExpansionTransaction.CurrentList.RemoveRange(enemyPoolExpansionTransaction.OriginalCurrentCount, enemyPoolExpansionTransaction.AddedLogical);
				}
				director.totalAmount = Math.Max(0, director.totalAmount - enemyPoolExpansionTransaction.AddedLogical);
				LogDebug($"未消費の生成前敵Setup追加を巻戻し: {enemyPoolExpansionTransaction.AddedLogical}件 / {reason}");
			}
			catch (Exception ex)
			{
				LogDebug("生成前敵Setup追加の巻戻しを安全に縮退: " + ex.GetType().Name);
			}
		}

		private static bool SequenceMatches(IReadOnlyList<EnemySetup> list, int start, IReadOnlyList<EnemySetup> expected)
		{
			if (start < 0 || expected.Count < 1 || start + expected.Count > list.Count)
			{
				return false;
			}
			for (int i = 0; i < expected.Count; i++)
			{
				if (list[start + i] != expected[i])
				{
					return false;
				}
			}
			return true;
		}

		private void MaintainEnemyPopulation(int desired, float elapsed)
		{
			if (Time.unscaledTime < nextSpawnTime)
			{
				return;
			}
			int num = Math.Min(100, Clamp(config.EnemyEventMaxAliveEnemies.Value, 1, 100));
			desired = Math.Min(num, Math.Max(0, desired));
			List<EnemyParent> list = FindLiveEnemies();
			int configuredEventRequestLimit = Math.Min(100, Clamp(config.EnemyEventTotalSpawnLimit.Value, 1, 100));
			int num2 = desired - list.Count;
			if (num2 <= 0)
			{
				nextSpawnTime = Time.unscaledTime + GetSpawnIntervalSeconds();
				return;
			}
			int val = Math.Min(num2, Clamp(config.EnemyEventWaveBatchSize.Value, 1, 20));
			val = Math.Min(val, num - list.Count);
			val = AnomalySafetyLimits.EnemyRuntimeReactivationBudget(standardEnemyUnits, totalSpawnRequestCount, configuredEventRequestLimit, val);
			if (val <= 0)
			{
				nextSpawnTime = Time.unscaledTime + GetSpawnIntervalSeconds();
				return;
			}
			int num3 = 0;
			for (int i = 0; i < val; i++)
			{
				if (!StandardGameApi.TryReactivatePooledEnemy(GetSpawnIntervalSeconds(), out EnemyParent _))
				{
					LogDebug("敵イベントの生成済みEnemyParentプールに再出現可能な個体がありません");
					break;
				}
				num3++;
				totalSpawnRequestCount++;
			}
			nextSpawnTime = Time.unscaledTime + GetSpawnIntervalSeconds();
			LogDebug($"異常イベント敵プール再出現要求: {num3}/{val}体, desired={desired}, liveBefore={list.Count}, totalSpawn={totalSpawnRequestCount}");
		}

		private float GetSpawnIntervalSeconds()
		{
			if (markedKind != AnomalyEventKind.RelentlessRespawn)
			{
				return Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f);
			}
			return Clamp(config.EnemyEventRespawnIntervalSeconds.Value, 0f, 30f);
		}

		private void RefreshEnemyTargets(AnomalyEventKind kind)
		{
			List<EnemyParent> list = FindLiveEnemies();
			int count = Math.Min(100, Clamp(config.EnemyEventTargetLimit.Value, 1, 100));
			if (kind == AnomalyEventKind.MarkedEnemy && (Object)(object)markedEnemy == (Object)null)
			{
				markedEnemy = list.OrderBy((EnemyParent enemy) => Vector3.Distance(((Component)enemy).transform.position, PlayerPositionOr(Vector3.zero))).FirstOrDefault();
			}
			if (kind == AnomalyEventKind.PackFrenzy && (Object)(object)markedEnemy == (Object)null)
			{
				markedEnemy = list.OrderBy((EnemyParent enemy) => Vector3.Distance(((Component)enemy).transform.position, PlayerPositionOr(Vector3.zero))).FirstOrDefault();
			}
			targets.Clear();
			switch (kind)
			{
			case AnomalyEventKind.MarkedEnemy:
				if ((Object)(object)markedEnemy != (Object)null && IsLiveEnemy(markedEnemy))
				{
					targets.Add(markedEnemy);
				}
				EnemyMutationRegistry.Replace((IEnumerable<EnemyParent>)(((Object)(object)markedEnemy == (Object)null) ? ((Array)Array.Empty<EnemyParent>()) : ((Array)new EnemyParent[1] { markedEnemy })), config.MarkedEnemyHealthMultiplier.Value, config.MarkedEnemySpeedMultiplier.Value);
				break;
			case AnomalyEventKind.PackFrenzy:
			{
				string packTypeName = (((Object)(object)markedEnemy == (Object)null) ? string.Empty : NormalizeName(StandardGameApi.GetEnemyDisplayName(markedEnemy)));
				List<EnemyParent> list2 = list.Where((EnemyParent enemy) => NormalizeName(StandardGameApi.GetEnemyDisplayName(enemy)) == packTypeName).ToList();
				targets.AddRange(list2.Take(count));
				EnemyMutationRegistry.Replace(list2, config.PackEnemyHealthMultiplier.Value, config.PackEnemySpeedMultiplier.Value);
				break;
			}
			case AnomalyEventKind.HellAllEnemies:
				targets.AddRange(list.Take(count));
				EnemyMutationRegistry.Replace(list, config.HellEnemyHealthMultiplier.Value, config.HellEnemySpeedMultiplier.Value);
				break;
			default:
				targets.AddRange(list.Take(count));
				EnemyMutationRegistry.Clear();
				break;
			}
		}

		private void ApplyLootEvent(AnomalyEventKind kind)
		{
			lootApplied = true;
			if (kind == AnomalyEventKind.GoldRush || kind == AnomalyEventKind.PurpleChance)
			{
				string arg = ((kind == AnomalyEventKind.GoldRush) ? "UltraRare(金)" : "Rare(紫)");
				if (cosmeticPromotionCount > 0)
				{
					SendRoomMessage($"異常イベント {AnomalyEventCatalog.Get(kind).DisplayName}: 標準コスメ箱{cosmeticPromotionCount}件を{arg}へ確率昇格しました。箱数は増やしていません。");
				}
				else
				{
					SendRoomMessage("異常イベント " + AnomalyEventCatalog.Get(kind).DisplayName + ": 標準コスメ箱の生成が無い、または昇格抽選に外れました。箱数は変更していません。");
				}
				return;
			}
			int num = Clamp(config.LootEventRevealCount.Value, 0, 20);
			int num2 = (setupLootApplied ? setupLootSpawned : 0);
			string displayName = AnomalyEventCatalog.Get(kind).DisplayName;
			if (num2 > 0)
			{
				SendRoomMessage($"異常イベント {displayName}: 現行レベルの標準ValuableDirector経路で貴重品を{num2}件追加しました。通貨・コスメ分類や抽出額は変更していません。");
			}
			else if (num == 0)
			{
				SendRoomMessage("異常イベント " + displayName + ": 戦利品の安全代替数が0のため、生成を行いません。");
			}
			else
			{
				stateMachine.Fail(displayName + "は標準生成前のtop-upを確保できませんでした");
			}
		}

		private List<EnemyParent> FindLiveEnemies()
		{
			try
			{
				return Object.FindObjectsOfType<EnemyParent>().Where(IsLiveEnemy).Take(100)
					.ToList();
			}
			catch
			{
				return new List<EnemyParent>();
			}
		}

		private static bool IsLiveEnemy(EnemyParent enemy)
		{
			try
			{
				return (Object)(object)enemy != (Object)null && enemy.SetupDone && enemy.Spawned && (Object)(object)enemy.Enemy != (Object)null && (Object)(object)enemy.Enemy.Health != (Object)null && !enemy.Enemy.Health.dead;
			}
			catch
			{
				return false;
			}
		}

		private Vector3 PlayerPositionOr(Vector3 fallback)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			PlayerAvatar val = SafeLocalPlayer();
			if (!((Object)(object)val != (Object)null) || !((Object)(object)val.playerTransform != (Object)null))
			{
				return fallback;
			}
			return val.playerTransform.position;
		}

		private static string NormalizeName(string value)
		{
			return (value ?? string.Empty).Replace("(Clone)", string.Empty, StringComparison.OrdinalIgnoreCase).Replace("Enemy - ", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
				.ToLowerInvariant();
		}

		private bool TryChooseEvent(out AnomalyEventKind selected, bool allowPrevious)
		{
			selected = AnomalyEventKind.MarkedEnemy;
			if (!config.EnableAnomalyEvents.Value || Clamp(config.AnomalyEventChancePercent.Value, 0f, 100f) <= 0f)
			{
				return false;
			}
			if (!allowPrevious && random.NextDouble() * 100.0 > (double)Clamp(config.AnomalyEventChancePercent.Value, 0f, 100f))
			{
				return false;
			}
			List<AnomalyEventDefinition> list = (from definition in AnomalyEventCatalog.All
				where IsEventEnabled(definition.Kind)
				where GetEventWeight(definition.Kind) > 0f
				where IsSetupDependentEventAvailable(definition.Kind)
				where allowPrevious || !config.AnomalyEventPreventConsecutive.Value || !previousKind.HasValue || previousKind.Value != definition.Kind
				select definition).ToList();
			if (list.Count == 0)
			{
				list = (from definition in AnomalyEventCatalog.All
					where IsEventEnabled(definition.Kind)
					where GetEventWeight(definition.Kind) > 0f
					where IsSetupDependentEventAvailable(definition.Kind)
					select definition).ToList();
			}
			if (list.Count == 0)
			{
				return false;
			}
			float num = list.Sum((AnomalyEventDefinition definition) => GetEventWeight(definition.Kind));
			if (num <= 0f)
			{
				return false;
			}
			double num2 = random.NextDouble() * (double)num;
			foreach (AnomalyEventDefinition item in list)
			{
				num2 -= (double)GetEventWeight(item.Kind);
				if (num2 <= 0.0)
				{
					selected = item.Kind;
					return true;
				}
			}
			selected = list[list.Count - 1].Kind;
			return true;
		}

		private float GetEventWeight(AnomalyEventKind kind)
		{
			return Clamp(kind switch
			{
				AnomalyEventKind.MarkedEnemy => config.MarkedEnemyEventWeight.Value, 
				AnomalyEventKind.PackFrenzy => config.PackFrenzyEventWeight.Value, 
				AnomalyEventKind.HellAllEnemies => config.HellAllEnemiesEventWeight.Value, 
				AnomalyEventKind.ReinforcementWave => config.ReinforcementWaveEventWeight.Value, 
				AnomalyEventKind.EscalatingThreat => config.EscalatingThreatEventWeight.Value, 
				AnomalyEventKind.RelentlessRespawn => config.RelentlessRespawnEventWeight.Value, 
				AnomalyEventKind.GoldRush => config.GoldRushEventWeight.Value, 
				AnomalyEventKind.PurpleChance => config.PurpleChanceEventWeight.Value, 
				AnomalyEventKind.ValuableSurge => config.ValuableSurgeEventWeight.Value, 
				_ => 0f, 
			}, 0f, 10f);
		}

		private bool IsSetupDependentEventAvailable(AnomalyEventKind kind)
		{
			if ((kind == AnomalyEventKind.GoldRush || kind == AnomalyEventKind.PurpleChance || kind == AnomalyEventKind.ValuableSurge) && (!valuableSetupObserved || valuableSetupFinalized))
			{
				return enemyAmountSetupInProgress;
			}
			return true;
		}

		private bool IsEventEnabled(AnomalyEventKind kind)
		{
			return kind switch
			{
				AnomalyEventKind.MarkedEnemy => config.EnableMarkedEnemyEvent.Value, 
				AnomalyEventKind.PackFrenzy => config.EnablePackFrenzyEvent.Value, 
				AnomalyEventKind.HellAllEnemies => config.EnableHellAllEnemiesEvent.Value, 
				AnomalyEventKind.ReinforcementWave => config.EnableReinforcementWaveEvent.Value, 
				AnomalyEventKind.EscalatingThreat => config.EnableEscalatingThreatEvent.Value, 
				AnomalyEventKind.RelentlessRespawn => config.EnableRelentlessRespawnEvent.Value, 
				AnomalyEventKind.GoldRush => config.EnableGoldRushEvent.Value, 
				AnomalyEventKind.PurpleChance => config.EnablePurpleChanceEvent.Value, 
				AnomalyEventKind.ValuableSurge => config.EnableValuableSurgeEvent.Value, 
				_ => false, 
			};
		}

		private int GetPlayerCount()
		{
			try
			{
				return Math.Max(1, SemiFunc.PlayerGetAll().Count((PlayerAvatar player) => (Object)(object)player != (Object)null));
			}
			catch
			{
				return 1;
			}
		}

		private string FormatStatus()
		{
			AnomalyEventSnapshot snapshot = stateMachine.Snapshot;
			string text = (snapshot.Kind.HasValue ? AnomalyEventCatalog.Get(snapshot.Kind.Value).DisplayName : "なし");
			string text2 = (levelEventDecided ? "抽選済み" : "未抽選");
			return string.Format("異常イベント状態: {0} / {1} / 経過 {2:0.0}s / {3} / 前回 {4}", snapshot.Phase, text, snapshot.ElapsedSeconds, text2, previousKind?.ToString() ?? "なし");
		}

		private void OnStateChanged(AnomalyEventSnapshot snapshot)
		{
			try
			{
				if (snapshot.Phase == AnomalyEventPhase.Pending && snapshot.Kind.HasValue)
				{
					AnomalyEventDefinition anomalyEventDefinition = AnomalyEventCatalog.Get(snapshot.Kind.Value);
					SendRoomMessage($"【異常予告】{anomalyEventDefinition.DisplayName}: {anomalyEventDefinition.Description}({snapshot.StartDelaySeconds:0}秒後に開始)");
				}
				else if (snapshot.Phase == AnomalyEventPhase.Active && snapshot.Kind.HasValue)
				{
					PrepareActiveEvent(snapshot.Kind.Value);
					if (!stateMachine.IsActive)
					{
						return;
					}
					AnomalyEventDefinition anomalyEventDefinition2 = AnomalyEventCatalog.Get(snapshot.Kind.Value);
					SendRoomMessage("【異常開始】" + anomalyEventDefinition2.DisplayName + "。" + anomalyEventDefinition2.SafetyNote);
				}
				else if (snapshot.Phase == AnomalyEventPhase.Completed)
				{
					EnemyMutationRegistry.Clear();
					StandardGameApi.CancelPendingEnemyReactivations();
					SendRoomMessage("【異常終了】" + snapshot.Reason);
				}
				else if (snapshot.Phase == AnomalyEventPhase.Cancelled || snapshot.Phase == AnomalyEventPhase.Failed || snapshot.Phase == AnomalyEventPhase.TimedOut)
				{
					EnemyMutationRegistry.Clear();
					StandardGameApi.CancelPendingEnemyReactivations();
					SendRoomMessage("【異常イベント終了】" + snapshot.Reason);
				}
				LogDebug(string.Format("異常イベント状態: {0}, kind={1}, reason={2}", snapshot.Phase, snapshot.Kind?.ToString() ?? "none", snapshot.Reason));
			}
			catch (Exception ex)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogWarning((object)("[ACD] 異常イベント通知処理を縮退しました: " + ex.GetType().Name));
				}
			}
		}

		private bool IsHost()
		{
			try
			{
				return SemiFunc.IsMasterClientOrSingleplayer();
			}
			catch
			{
				return false;
			}
		}

		private bool IsLevelReady()
		{
			try
			{
				if (!SemiFunc.LevelGenDone())
				{
					return false;
				}
				RunManager instance = RunManager.instance;
				if ((Object)(object)instance == (Object)null || instance.restarting || instance.waitToChangeScene)
				{
					return false;
				}
				RoundDirector roundDirectorInstance = GetRoundDirectorInstance();
				if ((Object)(object)roundDirectorInstance == (Object)null)
				{
					return false;
				}
				if (!TryReadBool(roundDirectorInstance, "extractionPointActive", out var value) || !TryReadBool(roundDirectorInstance, "allExtractionPointsCompleted", out var value2))
				{
					return false;
				}
				return !value && !value2;
			}
			catch
			{
				return false;
			}
		}

		private static PlayerAvatar? SafeLocalPlayer()
		{
			try
			{
				return SemiFunc.PlayerAvatarLocal();
			}
			catch
			{
				return null;
			}
		}

		private static RoundDirector? GetRoundDirectorInstance()
		{
			try
			{
				object? obj = typeof(RoundDirector).GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
				return (RoundDirector?)((obj is RoundDirector) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		private static bool TryReadBool(object target, string name, out bool value)
		{
			value = false;
			try
			{
				FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field?.FieldType != typeof(bool))
				{
					return false;
				}
				value = (bool)field.GetValue(target);
				return true;
			}
			catch
			{
				return false;
			}
		}

		private void SendRoomMessage(string message)
		{
			PlayerAvatar val = SafeLocalPlayer();
			if ((Object)(object)val != (Object)null)
			{
				try
				{
					val.ChatMessageSend("[契約局/異常] " + message);
					return;
				}
				catch
				{
				}
			}
			try
			{
				ChatManager instance = ChatManager.instance;
				if (instance != null)
				{
					instance.ForceSendMessage("[契約局/異常] " + message);
				}
			}
			catch
			{
				LogDebug("異常イベントのチャット通知を送信できませんでした");
			}
		}

		private void LogDebug(string message)
		{
			if (config != null && config.DetailedLogging.Value)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogInfo((object)("[ACD] " + message));
				}
			}
		}

		private static float Clamp(float value, float min, float max)
		{
			return AnomalySafetyLimits.ClampFinite(value, min, max, min);
		}

		private static int Clamp(int value, int min, int max)
		{
			return Math.Max(min, Math.Min(max, value));
		}
	}
	internal sealed class ContractDirectorRuntime : MonoBehaviour
	{
		private sealed class TokenBinding
		{
			internal ItemAttributes Item { get; }

			internal ContractKind Kind { get; }

			internal string ItemName
			{
				get
				{
					if (string.IsNullOrWhiteSpace(Item.itemName))
					{
						if (!((Object)(object)Item.item != (Object)null) || string.IsNullOrWhiteSpace(Item.item.itemName))
						{
							return $"ViewID:{Item.photonView.ViewID}";
						}
						return Item.item.itemName;
					}
					return Item.itemName;
				}
			}

			internal int ContractIndex => ContractCatalog.All.ToList().FindIndex((ContractDefinition definition) => definition.Kind == Kind);

			internal string KindDisplayName => ContractCatalog.Get(Kind).DisplayName;

			internal TokenBinding(ItemAttributes item, ContractKind kind)
			{
				Item = item;
				Kind = kind;
			}
		}

		private readonly ContractStateMachine stateMachine = new ContractStateMachine();

		private readonly ItemRoleRegistry roleRegistry = new ItemRoleRegistry();

		private readonly Dictionary<int, TokenBinding> tokens = new Dictionary<int, TokenBinding>();

		private readonly HashSet<int> completedBatteryViewIds = new HashSet<int>();

		private ContractKind? previousKind;

		private readonly Dictionary<int, float> batteryHoldTimers = new Dictionary<int, float>();

		private readonly Dictionary<int, bool> deviceActivationState = new Dictionary<int, bool>();

		private readonly Random random = new Random();

		private DirectorConfig config;

		private ManualLogSource logger;

		private ContractKind? activeKind;

		private ValuableRoleBinding? cargo;

		private ValuableRoleBinding? bountyEvidence;

		private EnemyParent? bountyTarget;

		private bool bountyAssigned;

		private int bountyTargetViewId;

		private int bountyEvidenceViewId;

		private readonly List<ItemAttributes> batteries = new List<ItemAttributes>();

		private readonly List<Transform> dualAnchors = new List<Transform>();

		private readonly List<Transform> recoveryAnchors = new List<Transform>();

		private ExtractionPoint? defensePoint;

		private bool bountyDefeated;

		private float bountyDiscoveryDeadline;

		private bool defenseStarted;

		private bool defenseThreatSeen;

		private float defenseThreatDeadline;

		private float defenseTimer;

		private float dualStableTimer;

		private float nextPollTime;

		private float lastPollTime;

		private float levelStartedAt;

		private float selectionDeadline;

		private bool levelActive;

		private bool tokenNoticeSent;

		private bool tokenShortageLogged;

		private float nextMedicalTime;

		private float nextRadarTime;

		private float nextNoiseTime;

		private float lastDefenseInvestigationTime;

		internal ContractStateMachine StateMachine => stateMachine;

		internal ItemRoleRegistry RoleRegistry => roleRegistry;

		internal bool IsLevelActive => levelActive;

		internal void Initialize(DirectorConfig directorConfig, ManualLogSource manualLogSource)
		{
			config = directorConfig;
			logger = manualLogSource;
			stateMachine.StateChanged += OnStateChanged;
			((Behaviour)this).enabled = true;
		}

		private void Update()
		{
			if (config == null || Time.unscaledTime < nextPollTime)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			float num = AnomalySafetyLimits.ClampFinite(config.PollIntervalSeconds.Value, 0.25f, 1f, 0.25f);
			float deltaSeconds = ((lastPollTime > 0f) ? Math.Max(0.01f, Math.Min(5f, unscaledTime - lastPollTime)) : num);
			lastPollTime = unscaledTime;
			nextPollTime = unscaledTime + num;
			if (!config.Enabled.Value || !IsHost())
			{
				if (levelActive)
				{
					ResetRoomState("ホスト権限または設定が失われました");
				}
				return;
			}
			bool flag;
			bool flag2;
			bool flag3;
			try
			{
				flag = SemiFunc.RunIsLevel();
				flag2 = SemiFunc.RunIsShop();
				flag3 = SemiFunc.RunIsLobby();
			}
			catch (Exception ex)
			{
				LogDebug("ゲーム状態の読み取りに失敗: " + ex.GetType().Name);
				return;
			}
			if (!flag || flag2 || flag3)
			{
				if (levelActive)
				{
					ResetRoomState(flag2 ? "ショップ遷移" : (flag3 ? "ロビー遷移" : "レベル外遷移"));
				}
				return;
			}
			if (!levelActive)
			{
				if (!IsLevelReady())
				{
					return;
				}
				BeginLevel();
			}
			if (!levelActive || !config.Enabled.Value)
			{
				return;
			}
			if (!IsLevelReady())
			{
				ResetRoomState("レベル遷移準備");
				return;
			}
			if (stateMachine.IsSelecting)
			{
				DiscoverTokens();
				TrySelectHeldToken();
				CheckSelectionTimeout();
				return;
			}
			try
			{
				if (stateMachine.IsActive)
				{
					stateMachine.Tick(deltaSeconds);
					if (stateMachine.IsActive)
					{
						EvaluateActiveContract(deltaSeconds);
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogWarning((object)$"[ACD] 契約監視中の例外を契約内へ封じ込めました: {arg}");
				}
				if (stateMachine.IsActive)
				{
					stateMachine.Fail("対象の状態を安全に読み取れなくなりました");
				}
			}
			UpdateDevices();
		}

		internal void HandleChatCommand(PlayerAvatar sender, string message)
		{
			if (!IsHost() || (Object)(object)sender == (Object)null || string.IsNullOrWhiteSpace(message))
			{
				return;
			}
			PlayerAvatar val = SafeLocalPlayer();
			if ((Object)(object)val == (Object)null || (Object)(object)sender != (Object)(object)val)
			{
				return;
			}
			string text = message.Trim();
			if (!text.StartsWith("!acd", StringComparison.OrdinalIgnoreCase))
			{
				return;
			}
			string[] array = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 1 || string.Equals(array[1], "help", StringComparison.OrdinalIgnoreCase))
			{
				SendRoomMessage("!acd contract 1~5 | !acd random | !acd status | !acd cancel");
				return;
			}
			switch (array[1].ToLowerInvariant())
			{
			case "status":
			{
				ContractSnapshot snapshot = stateMachine.Snapshot;
				string text2 = (snapshot.Kind.HasValue ? ContractCatalog.Get(snapshot.Kind.Value).DisplayName : "なし");
				SendRoomMessage($"契約状態: {snapshot.Phase} / {text2} / {snapshot.Progress}/{snapshot.RequiredUnits}");
				break;
			}
			case "cancel":
				if (stateMachine.IsSelecting || stateMachine.IsActive)
				{
					stateMachine.Fail("ホストが契約を中止しました");
				}
				break;
			case "random":
				SelectRandomContract("ホストのチャット指定");
				break;
			case "contract":
			{
				if (array.Length < 3)
				{
					break;
				}
				if (int.TryParse(array[2], out var result) && result >= 1 && result <= ContractCatalog.All.Count)
				{
					SelectContractByIndex(result - 1, "ホストのチャット指定");
					break;
				}
				ContractKind? contractKind = ParseContract(array[2]);
				if (contractKind.HasValue)
				{
					SelectContract(contractKind.Value, "ホストのチャット指定");
				}
				break;
			}
			}
		}

		internal void ResetRoomState(string reason)
		{
			((MonoBehaviour)this).StopAllCoroutines();
			levelActive = false;
			activeKind = null;
			tokenNoticeSent = false;
			tokenShortageLogged = false;
			tokens.Clear();
			completedBatteryViewIds.Clear();
			batteryHoldTimers.Clear();
			deviceActivationState.Clear();
			cargo = null;
			bountyEvidence = null;
			bountyTarget = null;
			bountyAssigned = false;
			bountyTargetViewId = 0;
			bountyEvidenceViewId = 0;
			batteries.Clear();
			dualAnchors.Clear();
			recoveryAnchors.Clear();
			defensePoint = null;
			bountyDefeated = false;
			bountyDiscoveryDeadline = 0f;
			defenseStarted = false;
			defenseThreatSeen = false;
			defenseThreatDeadline = 0f;
			defenseTimer = 0f;
			dualStableTimer = 0f;
			nextMedicalTime = 0f;
			nextRadarTime = 0f;
			nextNoiseTime = 0f;
			lastDefenseInvestigationTime = 0f;
			roleRegistry.ReleaseAll();
			stateMachine.Reset(reason);
			LogDebug("契約状態を解放: " + reason);
		}

		internal void OnMasterClientChanged()
		{
			ResetRoomState("マスター変更");
		}

		internal void NotifyCurrentStateToRoom()
		{
			if (IsHost() && levelActive)
			{
				ContractSnapshot snapshot = stateMachine.Snapshot;
				if (snapshot.Phase == ContractPhase.Selecting)
				{
					SendRoomMessage("途中参加者向け再通知: 現在は契約選択中です。ホストは !acd status で確認できます。");
				}
				else if (snapshot.Kind.HasValue)
				{
					ContractDefinition contractDefinition = ContractCatalog.Get(snapshot.Kind.Value);
					SendRoomMessage($"途中参加者向け再通知: {contractDefinition.DisplayName} / {contractDefinition.Description} / 進捗 {snapshot.Progress}/{snapshot.RequiredUnits}");
				}
			}
		}

		private void BeginLevel()
		{
			levelActive = true;
			levelStartedAt = Time.unscaledTime;
			selectionDeadline = levelStartedAt + Math.Max(5f, config.SelectionSeconds.Value);
			stateMachine.BeginSelection(GetPlayerCount(), "レベル開始");
			tokenNoticeSent = false;
			tokenShortageLogged = false;
			roleRegistry.ReleaseAll();
			tokens.Clear();
			if (config.PhysicalTokenSelection.Value)
			{
				DiscoverTokens();
			}
			SendRoomMessage("【契約選択】!acd contract 1~5 または、表示された既存アイテムを掴んでください。");
			LogDebug("レベル契約の選択待ちを開始");
		}

		private void DiscoverTokens()
		{
			if (!stateMachine.IsSelecting || !config.PhysicalTokenSelection.Value || tokenNoticeSent)
			{
				return;
			}
			IReadOnlyList<ContractKind> enabledKinds = GetEnabledKinds();
			if (enabledKinds.Count == 0)
			{
				return;
			}
			int num = Math.Min(3, enabledKinds.Count);
			IReadOnlyList<ItemAttributes> readOnlyList = roleRegistry.FindSafeTokenCandidates(Math.Min(5, ContractCatalog.All.Count));
			if (readOnlyList.Count < num && Time.unscaledTime - levelStartedAt < config.DiscoveryGraceSeconds.Value)
			{
				return;
			}
			if (readOnlyList.Count < num)
			{
				if (!tokenShortageLogged && Time.unscaledTime - levelStartedAt >= config.DiscoveryGraceSeconds.Value)
				{
					tokenShortageLogged = true;
					LogDebug($"安全な契約トークンが不足 ({readOnlyList.Count}/{num})。ランダム縮退を待機");
				}
				return;
			}
			int num2 = Math.Min(readOnlyList.Count, enabledKinds.Count);
			for (int i = 0; i < num2; i++)
			{
				ItemAttributes val = readOnlyList[i];
				if (!((Object)(object)val.photonView == (Object)null) && val.photonView.ViewID > 0)
				{
					int viewID = val.photonView.ViewID;
					if (!tokens.ContainsKey(viewID))
					{
						tokens[viewID] = new TokenBinding(val, enabledKinds[i]);
					}
				}
			}
			if (tokens.Count != 0 && !tokenNoticeSent)
			{
				tokenNoticeSent = true;
				string arg = string.Join(" / ", tokens.Values.Select((TokenBinding token) => $"{token.ContractIndex + 1}:{token.KindDisplayName}({token.ItemName})"));
				SendRoomMessage($"契約トークン: {arg}。1個を掴むと決定します。未選択は{Math.Max(0f, selectionDeadline - Time.unscaledTime):0}秒後にランダム。");
				LogDebug($"物理契約トークンを{tokens.Count}個登録");
			}
		}

		private void TrySelectHeldToken()
		{
			foreach (TokenBinding item in tokens.Values.ToList())
			{
				if (!((Object)(object)item.Item == (Object)null) && !((Object)(object)item.Item.physGrabObject == (Object)null) && item.Item.physGrabObject.grabbed)
				{
					SelectContract(item.Kind, "物理トークンを取得");
					break;
				}
			}
		}

		private void CheckSelectionTimeout()
		{
			if (!(Time.unscaledTime < selectionDeadline))
			{
				if (config.FallbackRandom.Value)
				{
					SelectRandomContract("選択時間切れの自動ランダム");
				}
				else
				{
					stateMachine.Fail("契約選択が時間切れになりました(自動ランダムOFF)");
				}
			}
		}

		private void SelectRandomContract(string reason)
		{
			if (stateMachine.IsSelecting)
			{
				List<ContractKind> list = (from kind in GetEnabledKinds()
					where !config.PreventConsecutive.Value || !previousKind.HasValue || previousKind.Value != kind
					select kind).ToList();
				if (list.Count == 0)
				{
					list = GetEnabledKinds().ToList();
				}
				if (list.Count == 0)
				{
					stateMachine.Fail("有効な契約がありません");
				}
				else
				{
					SelectContract(list[random.Next(list.Count)], reason);
				}
			}
		}

		private void SelectContractByIndex(int index, string reason)
		{
			if (stateMachine.IsSelecting)
			{
				if (index < 0 || index >= ContractCatalog.All.Count)
				{
					SendRoomMessage("指定番号の契約は現在無効です。");
				}
				else
				{
					SelectContract(ContractCatalog.All[index].Kind, reason);
				}
			}
		}

		private void SelectContract(ContractKind kind, string reason)
		{
			if (!stateMachine.IsSelecting)
			{
				return;
			}
			if (!IsEnabled(kind))
			{
				SendRoomMessage("その契約は設定で無効です。");
			}
			else if (!IsAvailableNow(kind))
			{
				SendRoomMessage("その契約は現在の人数またはレベル条件では成立しません。");
			}
			else if (config.PreventConsecutive.Value && previousKind.HasValue && previousKind.Value == kind && GetEnabledKinds().Any((ContractKind candidate) => candidate != kind))
			{
				SendRoomMessage("直前と同じ契約は連続しない設定です。別の契約を選んでください。");
			}
			else if (stateMachine.Start(kind, GetPlayerCount(), reason))
			{
				activeKind = kind;
				PrepareContract(kind);
				if (stateMachine.IsActive)
				{
					previousKind = kind;
					SendRoomMessage("【契約開始】" + ContractCatalog.Get(kind).DisplayName + ": " + ContractCatalog.Get(kind).Description);
					LogDebug($"契約開始: {kind}, プレイヤー数={GetPlayerCount()}");
				}
			}
		}

		private void PrepareContract(ContractKind kind)
		{
			roleRegistry.ReleaseAll();
			completedBatteryViewIds.Clear();
			batteryHoldTimers.Clear();
			deviceActivationState.Clear();
			cargo = null;
			bountyEvidence = null;
			bountyTarget = null;
			bountyAssigned = false;
			bountyTargetViewId = 0;
			bountyEvidenceViewId = 0;
			batteries.Clear();
			dualAnchors.Clear();
			recoveryAnchors.Clear();
			defensePoint = null;
			bountyDefeated = false;
			bountyDiscoveryDeadline = Time.unscaledTime + Math.Max(5f, config.DiscoveryGraceSeconds.Value * 5f);
			defenseStarted = false;
			defenseThreatSeen = false;
			defenseThreatDeadline = 0f;
			defenseTimer = 0f;
			dualStableTimer = 0f;
			RegisterDevices();
			switch (kind)
			{
			case ContractKind.CursedCargo:
				cargo = roleRegistry.FindValuable(new HashSet<int>(tokens.Keys));
				if (cargo != null)
				{
					GameInterop.TryDiscoverValuable(cargo.Valuable);
					SendRoomMessage("呪われた貨物は『" + cargo.DisplayName + "』です。標準の貴重品アイコンをマップへ表示しました。");
				}
				if (cargo == null)
				{
					stateMachine.Fail("既存の貴重品ViewIDを安全に登録できませんでした");
				}
				break;
			case ContractKind.BountyEvidence:
				if (!TryAssignBountyTargets())
				{
					SendRoomMessage("賞金首・証拠品の出現を待機しています。");
				}
				break;
			case ContractKind.BlackoutRecovery:
				PrepareBatteries();
				break;
			case ContractKind.ExtractionDefense:
				defensePoint = FindDefensePoint();
				if ((Object)(object)defensePoint == (Object)null)
				{
					stateMachine.Fail("抽出地点アンカーを発見できませんでした");
				}
				break;
			case ContractKind.DualActivation:
				PrepareDualPoints();
				break;
			}
		}

		private bool TryAssignBountyTargets()
		{
			if (bountyAssigned)
			{
				return true;
			}
			EnemyParent val = FindBountyTarget();
			ValuableRoleBinding valuableRoleBinding = roleRegistry.FindValuable(new HashSet<int>(tokens.Keys));
			PhotonView val2 = (((Object)(object)val == (Object)null || (Object)(object)val.Enemy == (Object)null) ? null : val.Enemy.PhotonView);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || val2.ViewID <= 0 || valuableRoleBinding == null || (Object)(object)valuableRoleBinding.Valuable == (Object)null)
			{
				return false;
			}
			bountyTarget = val;
			bountyEvidence = valuableRoleBinding;
			bountyTargetViewId = val2.ViewID;
			bountyEvidenceViewId = valuableRoleBinding.ViewId;
			bountyAssigned = true;
			GameInterop.TryDiscoverValuable(valuableRoleBinding.Valuable);
			string text = ((!string.IsNullOrWhiteSpace(((Object)val).name)) ? ((Object)val).name.Replace("(Clone)", string.Empty).Trim() : "既存の敵");
			SendRoomMessage("賞金首は『" + text + "』、証拠品は『" + valuableRoleBinding.DisplayName + "』です。証拠品は標準マップへ表示しました。");
			return true;
		}

		private void RegisterDevices()
		{
			HashSet<int> excludedViewIds = new HashSet<int>(tokens.Keys);
			if (config.EnableMedicalCore.Value)
			{
				roleRegistry.FindAndRegisterDevice(DeviceKind.MedicalCore, excludedViewIds);
			}
			if (config.EnablePortableRadar.Value)
			{
				roleRegistry.FindAndRegisterDevice(DeviceKind.PortableRadar, excludedViewIds);
			}
			if (config.EnableNoiseBeacon.Value)
			{
				roleRegistry.FindAndRegisterDevice(DeviceKind.NoiseBeacon, excludedViewIds);
			}
			foreach (DeviceRoleRecord record in roleRegistry.Records)
			{
				LogDebug($"装置ViewID登録: {record.Kind} / {record.ViewId} / {record.ItemName}");
			}
			if (roleRegistry.Records.Count > 0)
			{
				string text = string.Join(" / ", roleRegistry.Records.Select((DeviceRoleRecord record) => DeviceDisplayName(record.Kind) + "=" + record.ItemName));
				SendRoomMessage("既存装置を登録しました: " + text);
				if (roleRegistry.TryGet(DeviceKind.PortableRadar, out ItemRoleBinding binding))
				{
					SendRoomMessage("携帯レーダー: トグル付きはON中、トグルなしは手持ち中に標準マップ表示を更新します。");
				}
				if (roleRegistry.TryGet(DeviceKind.NoiseBeacon, out binding))
				{
					SendRoomMessage("ノイズビーコン: 登録された既存アイテムのトグルをONにした間だけ敵を誘導します。");
				}
			}
			else
			{
				SendRoomMessage("既存装置候補が見つからないため、装置効果は安全に縮退します。");
			}
		}

		private static string DeviceDisplayName(DeviceKind kind)
		{
			return kind switch
			{
				DeviceKind.MedicalCore => "大型医療コア", 
				DeviceKind.PortableRadar => "携帯レーダー", 
				DeviceKind.NoiseBeacon => "ノイズビーコン", 
				_ => kind.ToString(), 
			};
		}

		private void PrepareBatteries()
		{
			int num = RequiredUnits();
			batteries.AddRange(roleRegistry.FindBatteryCandidates(num, new HashSet<int>(tokens.Keys)));
			recoveryAnchors.AddRange(FindKnownRecoveryAnchors(num));
			if (batteries.Count < num || recoveryAnchors.Count < num)
			{
				stateMachine.Fail($"既存バッテリーまたは既知ランドマークが不足 (電池{batteries.Count}/{num}, 地点{recoveryAnchors.Count}/{num})");
			}
			else
			{
				IEnumerable<string> values = batteries.Take(num).Select((ItemAttributes battery, int index) => "『" + GetItemDisplayName(battery) + "』→" + DescribeKnownAnchor(recoveryAnchors[index]));
				SendRoomMessage("復旧対象: " + string.Join(" / ", values) + "。各バッテリーを対応地点へ置いてください。");
			}
		}

		private void PrepareDualPoints()
		{
			try
			{
				int num = RequiredUnits();
				dualAnchors.AddRange(FindKnownDualAnchors(num));
				if (dualAnchors.Count < num)
				{
					stateMachine.Fail("トラックと抽出地点など既知のランドマークを2か所確保できませんでした");
				}
				else
				{
					SendRoomMessage(string.Format("二地点は {0} です。{1}人で同時に滞在してください。", string.Join(" / ", dualAnchors.Select(DescribeKnownAnchor)), num));
				}
			}
			catch (Exception ex)
			{
				LogDebug("二地点アンカー発見失敗: " + ex.GetType().Name);
				stateMachine.Fail("二地点アンカーを発見できませんでした");
			}
		}

		private void EvaluateActiveContract(float deltaSeconds)
		{
			if (activeKind.HasValue)
			{
				switch (activeKind.Value)
				{
				case ContractKind.CursedCargo:
					EvaluateCursedCargo();
					break;
				case ContractKind.BountyEvidence:
					EvaluateBountyEvidence();
					break;
				case ContractKind.BlackoutRecovery:
					EvaluateBlackoutRecovery(deltaSeconds);
					break;
				case ContractKind.ExtractionDefense:
					EvaluateExtractionDefense(deltaSeconds);
					break;
				case ContractKind.DualActivation:
					EvaluateDualActivation(deltaSeconds);
					break;
				}
			}
		}

		private void EvaluateCursedCargo()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if (cargo == null || (Object)(object)cargo.Valuable == (Object)null || (Object)(object)cargo.PhysGrabObject == (Object)null || cargo.PhysGrabObject.dead)
			{
				stateMachine.Fail("呪われた貨物が破損または消失しました");
				return;
			}
			ExtractionPoint val = SemiFunc.ExtractionPointGetNearest(cargo.Transform.position);
			if (!((Object)(object)val == (Object)null))
			{
				bool flag = (((Object)(object)cargo.Valuable.roomVolumeCheck != (Object)null) ? cargo.Valuable.roomVolumeCheck.inExtractionPoint : (Vector3.Distance(((Component)val).transform.position, cargo.Transform.position) <= 4f));
				bool flag2 = SemiFunc.PhysGrabObjectIsGrabbed(cargo.PhysGrabObject);
				if (flag && !flag2)
				{
					stateMachine.AddProgress(1, "呪われた貨物を抽出地点へ安定配置");
				}
			}
		}

		private void EvaluateBountyEvidence()
		{
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			if (!bountyAssigned)
			{
				if (!TryAssignBountyTargets() && Time.unscaledTime >= bountyDiscoveryDeadline)
				{
					stateMachine.Fail("賞金首または証拠品候補を発見できませんでした");
				}
				return;
			}
			if ((Object)(object)bountyTarget == (Object)null || (Object)(object)bountyTarget.Enemy == (Object)null || (Object)(object)bountyTarget.Enemy.PhotonView == (Object)null || bountyTarget.Enemy.PhotonView.ViewID != bountyTargetViewId)
			{
				stateMachine.Fail("賞金首が討伐確認前に消失しました");
				return;
			}
			if (bountyEvidence == null || (Object)(object)bountyEvidence.Valuable == (Object)null || bountyEvidence.ViewId != bountyEvidenceViewId || (Object)(object)bountyEvidence.PhysGrabObject == (Object)null || bountyEvidence.PhysGrabObject.dead)
			{
				stateMachine.Fail("証拠品が破損または消失しました");
				return;
			}
			EnemyHealth health = bountyTarget.Enemy.Health;
			if ((Object)(object)health == (Object)null)
			{
				stateMachine.Fail("賞金首の体力状態を読み取れませんでした");
				return;
			}
			if (!bountyDefeated && (Object)(object)health != (Object)null && health.dead)
			{
				bountyDefeated = true;
				stateMachine.AddProgress(1, "賞金首を討伐。証拠品を抽出地点へ運んでください");
				SendRoomMessage("賞金首を確認しました。証拠品を抽出地点へ運んでください。");
			}
			if (bountyDefeated)
			{
				ExtractionPoint val = SemiFunc.ExtractionPointGetNearest(bountyEvidence.Transform.position);
				if (!((Object)(object)val == (Object)null) && (((Object)(object)bountyEvidence.Valuable.roomVolumeCheck != (Object)null) ? bountyEvidence.Valuable.roomVolumeCheck.inExtractionPoint : (Vector3.Distance(((Component)val).transform.position, bountyEvidence.Transform.position) <= 4f)) && !SemiFunc.PhysGrabObjectIsGrabbed(bountyEvidence.PhysGrabObject))
				{
					stateMachine.AddProgress(1, "証拠品を抽出地点へ回収");
				}
			}
		}

		private void EvaluateBlackoutRecovery(float deltaSeconds)
		{
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			int num = RequiredUnits();
			for (int i = 0; i < batteries.Count && i < num; i++)
			{
				ItemAttributes val = batteries[i];
				if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null || (Object)(object)val.physGrabObject == (Object)null || val.physGrabObject.dead)
				{
					stateMachine.Fail($"復旧バッテリー{i + 1}が破損または消失しました");
					break;
				}
				int viewID = val.photonView.ViewID;
				if (viewID <= 0)
				{
					stateMachine.Fail($"復旧バッテリー{i + 1}のネットワーク個体を確認できません");
					break;
				}
				if (completedBatteryViewIds.Contains(viewID))
				{
					continue;
				}
				Transform val2 = recoveryAnchors[i];
				if ((Object)(object)val2 == (Object)null)
				{
					stateMachine.Fail($"復旧地点{i + 1}が消失しました");
					break;
				}
				bool flag = Vector3.Distance(((Component)val).transform.position, val2.position) <= 3f;
				bool flag2 = SemiFunc.PhysGrabObjectIsGrabbed(val.physGrabObject);
				if (!flag || flag2 || !IsPhysicallyStationary(val.physGrabObject))
				{
					batteryHoldTimers.Remove(viewID);
					continue;
				}
				float value;
				float num2 = (batteryHoldTimers.TryGetValue(viewID, out value) ? (value + deltaSeconds) : deltaSeconds);
				batteryHoldTimers[viewID] = num2;
				if (!(num2 < 1.5f))
				{
					completedBatteryViewIds.Add(viewID);
					stateMachine.AddProgress(1, $"復旧地点{i + 1}を起動");
				}
			}
		}

		private void EvaluateExtractionDefense(float deltaSeconds)
		{
			//IL_0143: 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)
			if ((Object)(object)defensePoint == (Object)null)
			{
				stateMachine.Fail("防衛対象の抽出地点が消失しました");
				return;
			}
			string text = ((object)Unsafe.As<State, State>(ref defensePoint.currentState)/*cast due to .constrained prefix*/).ToString();
			if (string.Equals(text, "Complete", StringComparison.OrdinalIgnoreCase))
			{
				stateMachine.Fail("通常の抽出完了へ移行したため、防衛契約だけを終了しました");
				return;
			}
			if (!GetPlayers().Any((PlayerAvatar player) => IsNear(player, ((Component)defensePoint).transform.position, 7f)))
			{
				defenseTimer = 0f;
				defenseThreatSeen = false;
				defenseThreatDeadline = 0f;
				return;
			}
			if (!defenseStarted)
			{
				defenseStarted = true;
				defenseTimer = 0f;
				SendRoomMessage("抽出地点へ集合しました(状態:" + text + ")。周囲へ既存敵を誘導し、脅威接近後に防衛タイマーを開始します。");
			}
			if (defenseThreatDeadline <= 0f)
			{
				defenseThreatDeadline = Time.unscaledTime + config.DefenseThreatWaitSeconds.Value;
			}
			if (Time.unscaledTime - lastDefenseInvestigationTime >= 4f)
			{
				lastDefenseInvestigationTime = Time.unscaledTime;
				EnemyDirector instance = EnemyDirector.instance;
				if (instance != null)
				{
					instance.SetInvestigate(((Component)defensePoint).transform.position, config.DefenseThreatRadius.Value, false);
				}
			}
			int num = CountLiveEnemiesNear(((Component)defensePoint).transform.position, config.DefenseThreatRadius.Value);
			if (!defenseThreatSeen)
			{
				if (num <= 0)
				{
					if (Time.unscaledTime >= defenseThreatDeadline)
					{
						stateMachine.Fail("既存敵が接近しなかったため、防衛契約だけを安全終了しました");
					}
					return;
				}
				defenseThreatSeen = true;
				defenseTimer = 0f;
				SendRoomMessage($"脅威を{num}体検知。{config.DefenseSeconds.Value:0}秒の防衛を開始します。");
			}
			defenseTimer += deltaSeconds;
			if (defenseTimer >= config.DefenseSeconds.Value)
			{
				stateMachine.AddProgress(1, "防衛時間を耐えました");
			}
		}

		private void EvaluateDualActivation(float deltaSeconds)
		{
			if (dualAnchors.Count < 2)
			{
				return;
			}
			List<PlayerAvatar> players = GetPlayers();
			PlayerAvatar first = ((IEnumerable<PlayerAvatar>)players).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar player) => IsNear(player, dualAnchors[0].position, config.DualRadius.Value)));
			PlayerAvatar val = ((IEnumerable<PlayerAvatar>)players).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar player) => (Object)(object)player != (Object)(object)first && IsNear(player, dualAnchors[1].position, config.DualRadius.Value)));
			if ((Object)(object)first == (Object)null || (Object)(object)val == (Object)null)
			{
				dualStableTimer = 0f;
				return;
			}
			dualStableTimer += deltaSeconds;
			if (dualStableTimer >= config.DualHoldSeconds.Value)
			{
				stateMachine.AddProgress(2, "二地点を同時起動");
			}
		}

		private void UpdateDevices()
		{
			TryUpdateDevice(DeviceKind.MedicalCore, config.EnableMedicalCore.Value, UpdateMedicalCore);
			TryUpdateDevice(DeviceKind.PortableRadar, config.EnablePortableRadar.Value, UpdatePortableRadar);
			TryUpdateDevice(DeviceKind.NoiseBeacon, config.EnableNoiseBeacon.Value, UpdateNoiseBeacon);
		}

		private void TryUpdateDevice(DeviceKind kind, bool featureEnabled, Action<ItemRoleBinding> update)
		{
			if (!featureEnabled || !roleRegistry.TryGet(kind, out ItemRoleBinding binding))
			{
				return;
			}
			try
			{
				update(binding);
			}
			catch (Exception ex)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogWarning((object)("[ACD] " + DeviceDisplayName(kind) + "だけを安全に解除しました: " + ex.GetType().Name));
				}
				deviceActivationState.Remove(binding.ViewId);
				roleRegistry.Release(kind);
			}
		}

		private void UpdateMedicalCore(ItemRoleBinding binding)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			bool flag = IsPhysicallyStationary(binding.PhysGrabObject);
			if (!(!binding.IsHeld && flag) || !GetPlayers().Any((PlayerAvatar player) => IsNear(player, binding.Transform.position, config.MedicalRadius.Value)) || Time.unscaledTime < nextMedicalTime)
			{
				return;
			}
			nextMedicalTime = Time.unscaledTime + Math.Max(0.5f, config.DeviceIntervalSeconds.Value);
			roleRegistry.MarkActive(DeviceKind.MedicalCore);
			foreach (PlayerAvatar player in GetPlayers())
			{
				if (IsNear(player, binding.Transform.position, config.MedicalRadius.Value) && !((Object)(object)player.playerHealth == (Object)null) && !player.isDisabled && !player.deadSet && player.playerHealth.health > 0 && player.playerHealth.health < player.playerHealth.maxHealth)
				{
					player.playerHealth.HealOther(config.MedicalHealAmount.Value, true);
				}
			}
		}

		private void UpdatePortableRadar(ItemRoleBinding binding)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			ItemToggle toggle = binding.Toggle;
			bool flag = (((Object)(object)toggle != (Object)null) ? toggle.toggleState : binding.IsHeld);
			bool value;
			bool flag2 = deviceActivationState.TryGetValue(binding.ViewId, out value) && value;
			deviceActivationState[binding.ViewId] = flag;
			if (!flag || Time.unscaledTime < nextRadarTime)
			{
				return;
			}
			nextRadarTime = Time.unscaledTime + Math.Max(1f, config.DeviceIntervalSeconds.Value * 3f);
			roleRegistry.MarkActive(DeviceKind.PortableRadar);
			Vector3 position = binding.Transform.position;
			int num = 0;
			ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>();
			foreach (ValuableObject val in array)
			{
				if (!((Object)(object)val == (Object)null) && !val.discovered && !((Object)(object)((Component)val).transform == (Object)null) && !(Vector3.Distance(position, ((Component)val).transform.position) > config.RadarRadius.Value) && GameInterop.TryDiscoverValuable(val))
				{
					num++;
				}
			}
			if (num > 0)
			{
				SendRoomMessage($"携帯レーダー: 周囲の貴重品を{num}件マップへ表示しました。");
			}
			else if (!flag2)
			{
				SendRoomMessage("携帯レーダー: 現在の範囲に未発見の貴重品はありません。");
			}
		}

		private void UpdateNoiseBeacon(ItemRoleBinding binding)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			ItemToggle toggle = binding.Toggle;
			if ((Object)(object)toggle == (Object)null)
			{
				throw new InvalidOperationException("ノイズビーコンの既存トグルが消失しました");
			}
			bool toggleState = toggle.toggleState;
			bool value;
			bool flag = deviceActivationState.TryGetValue(binding.ViewId, out value) && value;
			deviceActivationState[binding.ViewId] = toggleState;
			if (toggleState && !(Time.unscaledTime < nextNoiseTime))
			{
				nextNoiseTime = Time.unscaledTime + Math.Max(2f, config.DeviceIntervalSeconds.Value * 4f);
				roleRegistry.MarkActive(DeviceKind.NoiseBeacon);
				EnemyDirector instance = EnemyDirector.instance;
				if (instance != null)
				{
					instance.SetInvestigate(binding.Transform.position, config.NoiseRadius.Value, false);
				}
				if (!flag)
				{
					SendRoomMessage("ノイズビーコン: 周囲の敵を別方向へ誘導しています。");
				}
			}
		}

		private EnemyParent? FindBountyTarget()
		{
			return (from enemy in Object.FindObjectsOfType<EnemyParent>()
				where (Object)(object)enemy != (Object)null && enemy.SetupDone && enemy.Spawned && (Object)(object)enemy.Enemy != (Object)null && (Object)(object)enemy.Enemy.Health != (Object)null
				where !enemy.Enemy.Health.dead && (Object)(object)enemy.Enemy.PhotonView != (Object)null && enemy.Enemy.PhotonView.ViewID > 0
				orderby enemy.Enemy.Health.healthCurrent
				select enemy).FirstOrDefault();
		}

		private ExtractionPoint? FindDefensePoint()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			List<PlayerAvatar> players = GetPlayers();
			Vector3 origin = ((players.Count > 0) ? players[0].playerTransform.position : Vector3.zero);
			return (from point in Object.FindObjectsOfType<ExtractionPoint>()
				where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy
				where !string.Equals(((object)Unsafe.As<State, State>(ref point.currentState)/*cast due to .constrained prefix*/).ToString(), "Complete", StringComparison.OrdinalIgnoreCase)
				orderby Vector3.Distance(((Component)point).transform.position, origin)
				select point).FirstOrDefault();
		}

		private IReadOnlyList<Transform> FindKnownRecoveryAnchors(int required)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			List<Transform> list = new List<Transform>();
			try
			{
				List<ExtractionPoint> list2 = (from point in Object.FindObjectsOfType<ExtractionPoint>()
					where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy
					where !string.Equals(((object)Unsafe.As<State, State>(ref point.currentState)/*cast due to .constrained prefix*/).ToString(), "Complete", StringComparison.OrdinalIgnoreCase)
					select point).ToList();
				if (list2.Count > 0)
				{
					list.Add(((Component)list2[0]).transform);
				}
				LevelPoint val = ((IEnumerable<LevelPoint>)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func<LevelPoint, bool>)((LevelPoint point) => (Object)(object)point != (Object)null && point.Truck));
				if (required > 1 && (Object)(object)val != (Object)null)
				{
					list.Add(((Component)val).transform);
				}
				int num = 1;
				while (list.Count < required && num < list2.Count)
				{
					if (!(Vector3.Distance(list[0].position, ((Component)list2[num]).transform.position) < config.DualRadius.Value * 4f))
					{
						list.Add(((Component)list2[num]).transform);
					}
					num++;
				}
				return list.Take(required).ToList();
			}
			catch
			{
				return list;
			}
		}

		private IReadOnlyList<Transform> FindKnownDualAnchors(int required)
		{
			List<Transform> list = new List<Transform>();
			try
			{
				LevelPoint truck = ((IEnumerable<LevelPoint>)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func<LevelPoint, bool>)((LevelPoint val) => (Object)(object)val != (Object)null && val.Truck));
				if ((Object)(object)truck != (Object)null)
				{
					list.Add(((Component)truck).transform);
				}
				List<ExtractionPoint> list2 = (from val in Object.FindObjectsOfType<ExtractionPoint>()
					where (Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy
					where !string.Equals(((object)Unsafe.As<State, State>(ref val.currentState)/*cast due to .constrained prefix*/).ToString(), "Complete", StringComparison.OrdinalIgnoreCase)
					orderby (!((Object)(object)truck == (Object)null)) ? Vector3.Distance(((Component)truck).transform.position, ((Component)val).transform.position) : 0f
					select val).ToList();
				foreach (ExtractionPoint point in list2)
				{
					if (list.Count < required)
					{
						if (!list.Any((Transform existing) => Vector3.Distance(existing.position, ((Component)point).transform.position) < config.DualRadius.Value * 4f))
						{
							list.Add(((Component)point).transform);
						}
						continue;
					}
					break;
				}
				return list.Take(required).ToList();
			}
			catch
			{
				return list;
			}
		}

		private IReadOnlyList<ContractKind> GetEnabledKinds()
		{
			List<ContractKind> list = new List<ContractKind>();
			foreach (ContractKind item in ContractCatalog.All.Select((ContractDefinition definition) => definition.Kind))
			{
				if (IsEnabled(item) && IsAvailableNow(item))
				{
					list.Add(item);
				}
			}
			return list;
		}

		private bool IsEnabled(ContractKind kind)
		{
			return kind switch
			{
				ContractKind.CursedCargo => config.EnableCursedCargo.Value, 
				ContractKind.BountyEvidence => config.EnableBountyEvidence.Value, 
				ContractKind.BlackoutRecovery => config.EnableBlackoutRecovery.Value, 
				ContractKind.ExtractionDefense => config.EnableExtractionDefense.Value, 
				ContractKind.DualActivation => config.EnableDualActivation.Value, 
				_ => false, 
			};
		}

		private bool IsAvailableNow(ContractKind kind)
		{
			if (kind == ContractKind.DualActivation)
			{
				return GetPlayerCount() >= 2;
			}
			return true;
		}

		private ContractKind? ParseContract(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			switch (value.Trim().ToLowerInvariant())
			{
			case "cursed":
			case "呪われた貨物":
			case "cargo":
			case "cursed_cargo":
				return ContractKind.CursedCargo;
			case "bounty":
			case "evidence":
			case "bounty_evidence":
			case "賞金首":
				return ContractKind.BountyEvidence;
			case "blackout":
			case "recovery":
			case "ブラックアウト":
			case "blackout_recovery":
				return ContractKind.BlackoutRecovery;
			case "defense":
			case "extraction_defense":
			case "防衛":
				return ContractKind.ExtractionDefense;
			case "dual_activation":
			case "二地点":
			case "dual":
			case "activation":
				return ContractKind.DualActivation;
			default:
				return null;
			}
		}

		private int RequiredUnits()
		{
			if (!activeKind.HasValue)
			{
				return 1;
			}
			ContractDefinition contractDefinition = ContractCatalog.Get(activeKind.Value);
			if (!config.PlayerScaling.Value)
			{
				return contractDefinition.BaseUnits;
			}
			return contractDefinition.ScaleUnits(GetPlayerCount());
		}

		private List<PlayerAvatar> GetPlayers()
		{
			try
			{
				return (from player in SemiFunc.PlayerGetAll()
					where (Object)(object)player != (Object)null && (Object)(object)player.playerTransform != (Object)null
					select player).ToList();
			}
			catch
			{
				return new List<PlayerAvatar>();
			}
		}

		private int GetPlayerCount()
		{
			int count = GetPlayers().Count;
			return Math.Max(1, count);
		}

		private static bool IsNear(PlayerAvatar player, Vector3 position, float radius)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player != (Object)null && (Object)(object)player.playerTransform != (Object)null)
			{
				return Vector3.Distance(player.playerTransform.position, position) <= radius;
			}
			return false;
		}

		private static bool IsPhysicallyStationary(PhysGrabObject physGrabObject)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)physGrabObject == (Object)null || physGrabObject.grabbed || (Object)(object)physGrabObject.rb == (Object)null)
			{
				return false;
			}
			Vector3 val = physGrabObject.rb.velocity;
			if (((Vector3)(ref val)).sqrMagnitude <= 0.25f)
			{
				val = physGrabObject.rb.angularVelocity;
				return ((Vector3)(ref val)).sqrMagnitude <= 0.25f;
			}
			return false;
		}

		private static string GetItemDisplayName(ItemAttributes item)
		{
			if ((Object)(object)item == (Object)null)
			{
				return "消失した既存アイテム";
			}
			if (!string.IsNullOrWhiteSpace(item.itemName))
			{
				return item.itemName;
			}
			if ((Object)(object)item.item != (Object)null && !string.IsNullOrWhiteSpace(item.item.itemName))
			{
				return item.item.itemName;
			}
			if (!((Object)(object)item.photonView != (Object)null))
			{
				return "既存アイテム";
			}
			return $"既存アイテム#{item.photonView.ViewID}";
		}

		private static string DescribeKnownAnchor(Transform anchor)
		{
			try
			{
				LevelPoint val = ((IEnumerable<LevelPoint>)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func<LevelPoint, bool>)((LevelPoint point) => (Object)(object)point != (Object)null && point.Truck && (Object)(object)((Component)point).transform == (Object)(object)anchor));
				if ((Object)(object)val != (Object)null)
				{
					return "トラック";
				}
				List<ExtractionPoint> list = (from point in Object.FindObjectsOfType<ExtractionPoint>()
					where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy
					orderby ((Component)point).transform.position.x, ((Component)point).transform.position.z
					select point).ToList();
				int num = list.FindIndex((ExtractionPoint point) => (Object)(object)((Component)point).transform == (Object)(object)anchor);
				return (num >= 0) ? $"抽出地点#{num + 1}" : "既知ランドマーク";
			}
			catch
			{
				return "既知ランドマーク";
			}
		}

		private static int CountLiveEnemiesNear(Vector3 position, float radius)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			EnemyParent[] array = Object.FindObjectsOfType<EnemyParent>();
			foreach (EnemyParent val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Enemy == (Object)null) && !((Object)(object)val.Enemy.Health == (Object)null) && !val.Enemy.Health.dead && val.Spawned && Vector3.Distance(((Component)val).transform.position, position) <= radius)
				{
					num++;
				}
			}
			return num;
		}

		private bool IsHost()
		{
			try
			{
				return SemiFunc.IsMasterClientOrSingleplayer();
			}
			catch
			{
				return false;
			}
		}

		private bool IsLevelReady()
		{
			try
			{
				if (!SemiFunc.LevelGenDone())
				{
					return false;
				}
				RunManager instance = RunManager.instance;
				if ((Object)(object)instance == (Object)null || instance.restarting || instance.waitToChangeScene)
				{
					return false;
				}
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static PlayerAvatar? SafeLocalPlayer()
		{
			try
			{
				return SemiFunc.PlayerAvatarLocal();
			}
			catch
			{
				return null;
			}
		}

		private void SendRoomMessage(string message)
		{
			PlayerAvatar val = SafeLocalPlayer();
			if ((Object)(object)val != (Object)null)
			{
				try
				{
					val.ChatMessageSend("[契約局] " + message);
					return;
				}
				catch (Exception ex)
				{
					LogDebug("標準チャットRPC送信を縮退: " + ex.GetType().Name);
				}
			}
			try
			{
				ChatManager instance = ChatManager.instance;
				if (instance != null)
				{
					instance.ForceSendMessage("[契約局] " + message);
				}
			}
			catch
			{
				LogDebug("チャット通知を送信できませんでした");
			}
		}

		private void OnStateChanged(ContractSnapshot snapshot)
		{
			if (snapshot.Phase == ContractPhase.Succeeded)
			{
				SendRoomMessage("【契約成功】" + snapshot.Reason);
				ApplySuccessReward();
			}
			else if (snapshot.Phase == ContractPhase.Failed || snapshot.Phase == ContractPhase.TimedOut)
			{
				SendRoomMessage("【契約終了】" + snapshot.Reason);
			}
			LogDebug(string.Format("状態遷移: {0}, kind={1}, progress={2}/{3}, reason={4}", snapshot.Phase, snapshot.Kind?.ToString() ?? "none", snapshot.Progress, snapshot.RequiredUnits, snapshot.Reason));
		}

		private void ApplySuccessReward()
		{
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			if (config == null || !config.EnableSuccessReward.Value || !IsHost() || !levelActive)
			{
				return;
			}
			try
			{
				int num = 0;
				if (config.SuccessHealAmount.Value > 0)
				{
					foreach (PlayerAvatar player in GetPlayers())
					{
						if (!((Object)(object)player.playerHealth == (Object)null) && !player.isDisabled && !player.deadSet && player.playerHealth.health > 0 && player.playerHealth.health < player.playerHealth.maxHealth)
						{
							player.playerHealth.HealOther(config.SuccessHealAmount.Value, true);
							num++;
						}
					}
				}
				int num2 = 0;
				if (config.SuccessRevealCount.Value > 0)
				{
					PlayerAvatar val = SafeLocalPlayer();
					Vector3 origin = (((Object)(object)val != (Object)null && (Object)(object)val.playerTransform != (Object)null) ? val.playerTransform.position : Vector3.zero);
					foreach (ValuableObject item in (from value in Object.FindObjectsOfType<ValuableObject>()
						where (Object)(object)value != (Object)null && !value.discovered && !value.inStartRoom
						orderby Vector3.Distance(origin, ((Component)value).transform.position)
						select value).Take(config.SuccessRevealCount.Value))
					{
						if (GameInterop.TryDiscoverValuable(item))
						{
							num2++;
						}
					}
				}
				SendRoomMessage($"成功報酬: HP回復 {num}人 / 標準マップ発見 {num2}件(通貨・抽出額の書換えなし)");
			}
			catch (Exception ex)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogWarning((object)("[ACD] 成功報酬だけを安全に縮退しました: " + ex.GetType().Name));
				}
			}
		}

		private void LogDebug(string message)
		{
			if (config != null && config.DetailedLogging.Value)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogInfo((object)("[ACD] " + message));
				}
			}
		}
	}
	internal sealed class DirectorConfig
	{
		internal ConfigEntry<bool> Enabled { get; }

		internal ConfigEntry<bool> DetailedLogging { get; }

		internal ConfigEntry<bool> PhysicalTokenSelection { get; }

		internal ConfigEntry<bool> FallbackRandom { get; }

		internal ConfigEntry<float> SelectionSeconds { get; }

		internal ConfigEntry<float> DiscoveryGraceSeconds { get; }

		internal ConfigEntry<float> PollIntervalSeconds { get; }

		internal ConfigEntry<bool> PlayerScaling { get; }

		internal ConfigEntry<bool> PreventConsecutive { get; }

		internal ConfigEntry<bool> EnableSuccessReward { get; }

		internal ConfigEntry<int> SuccessHealAmount { get; }

		internal ConfigEntry<int> SuccessRevealCount { get; }

		internal ConfigEntry<bool> EnableCursedCargo { get; }

		internal ConfigEntry<bool> EnableBountyEvidence { get; }

		internal ConfigEntry<bool> EnableBlackoutRecovery { get; }

		internal ConfigEntry<bool> EnableExtractionDefense { get; }

		internal ConfigEntry<bool> EnableDualActivation { get; }

		internal ConfigEntry<bool> EnableMedicalCore { get; }

		internal ConfigEntry<int> MedicalHealAmount { get; }

		internal ConfigEntry<float> MedicalRadius { get; }

		internal ConfigEntry<bool> EnablePortableRadar { get; }

		internal ConfigEntry<float> RadarRadius { get; }

		internal ConfigEntry<bool> EnableNoiseBeacon { get; }

		internal ConfigEntry<float> NoiseRadius { get; }

		internal ConfigEntry<float> DeviceIntervalSeconds { get; }

		internal ConfigEntry<float> DefenseSeconds { get; }

		internal ConfigEntry<float> DefenseThreatRadius { get; }

		internal ConfigEntry<float> DefenseThreatWaitSeconds { get; }

		internal ConfigEntry<float> DualRadius { get; }

		internal ConfigEntry<float> DualHoldSeconds { get; }

		internal ConfigEntry<bool> EnableAnomalyEvents { get; }

		internal 

BepInEx/plugins/RepoHostSuite/YoneRai12.HostSkillManager.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using YoneRai12.HostSkillManager.Models;
using YoneRai12.HostSkillManager.Patches;
using YoneRai12.HostSkillManager.Services;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("YoneRai12.HostSkillManager")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("YoneRai12.HostSkillManager")]
[assembly: AssemblyTitle("YoneRai12.HostSkillManager")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 YoneRai12.HostSkillManager
{
	[BepInPlugin("YoneRai12.HostSkillManager", "Host Skill Manager", "1.0.0")]
	public sealed class HostSkillManager : BaseUnityPlugin
	{
		internal const string PluginGuid = "YoneRai12.HostSkillManager";

		internal const string PluginName = "Host Skill Manager";

		internal const string PluginVersion = "1.0.0";

		internal const string UpgradePrefix = "playerUpgrade";

		internal static readonly HashSet<string> VanillaUpgradeKeys = new HashSet<string>(StringComparer.Ordinal)
		{
			"playerUpgradeCrouchRest", "playerUpgradeDeathHeadBattery", "playerUpgradeExtraJump", "playerUpgradeHealth", "playerUpgradeLaunch", "playerUpgradeMapPlayerCount", "playerUpgradeRange", "playerUpgradeSpeed", "playerUpgradeStamina", "playerUpgradeStrength",
			"playerUpgradeThrow", "playerUpgradeTumbleClimb", "playerUpgradeTumbleWings"
		};

		private readonly Dictionary<string, SkillPolicy> skillPolicies = new Dictionary<string, SkillPolicy>(StringComparer.Ordinal);

		private readonly Dictionary<string, PlayerPolicy> playerPolicies = new Dictionary<string, PlayerPolicy>(StringComparer.Ordinal);

		private readonly HashSet<string> seenPlayers = new HashSet<string>(StringComparer.Ordinal);

		private readonly Random random = new Random();

		private Harmony harmony;

		private ConfigEntry<bool> managerEnabled;

		private ConfigEntry<bool> purchaseSharingEnabled;

		private ConfigEntry<bool> syncNewPlayers;

		private ConfigEntry<bool> manageModdedUpgrades;

		private ConfigEntry<bool> realtimeConfigApply;

		private ConfigEntry<bool> verboseLogging;

		private int applicationDepth;

		private bool applyRequested = true;

		private bool repoLibPatched;

		private float nextScanTime;

		private float nextEnforcementTime;

		internal static HostSkillManager Instance { get; private set; } = null;

		internal bool IsApplyingPolicy => applicationDepth > 0;

		private void Awake()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			Instance = this;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			BindGeneralConfig();
			foreach (string item in VanillaUpgradeKeys.OrderBy(SkillDisplayName))
			{
				BindSkillPolicy(item);
			}
			harmony = new Harmony("YoneRai12.HostSkillManager");
			harmony.PatchAll(Assembly.GetExecutingAssembly());
			((Component)this).gameObject.AddComponent<HostSkillNetworkCallbacks>();
			TryPatchRepoLib();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Host Skill Manager v1.0.0 loaded. This manager acts only on the host.");
		}

		private void Update()
		{
			if (Time.unscaledTime < nextScanTime)
			{
				return;
			}
			nextScanTime = Time.unscaledTime + 0.5f;
			TryPatchRepoLib();
			if (!managerEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null)
			{
				return;
			}
			foreach (string item in UpgradeBridge.DiscoverUpgradeKeys())
			{
				if (UpgradeBridge.IsVanilla(item) || manageModdedUpgrades.Value)
				{
					BindSkillPolicy(item);
				}
			}
			List<PlayerAvatar> currentPlayers = GetCurrentPlayers();
			foreach (PlayerAvatar item2 in currentPlayers)
			{
				if (!string.IsNullOrWhiteSpace(item2.steamID))
				{
					bool num = seenPlayers.Add(item2.steamID);
					BindPlayerPolicy(item2.steamID, item2.playerName);
					if (num)
					{
						ApplyPlayerPolicy(item2, isNewPlayer: true, currentPlayers);
					}
				}
			}
			if (applyRequested && realtimeConfigApply.Value)
			{
				applyRequested = false;
				ApplyAllFixedTargets(currentPlayers);
			}
			if (Time.unscaledTime >= nextEnforcementTime)
			{
				nextEnforcementTime = Time.unscaledTime + 3f;
				ApplyAllFixedTargets(currentPlayers);
			}
		}

		internal PurchaseSnapshot? CaptureVanillaPurchase(ItemUpgrade upgrade)
		{
			if (!CanDistributePurchase())
			{
				return null;
			}
			ItemToggle itemToggle = upgrade.itemToggle;
			if (itemToggle == null || !itemToggle.toggleState)
			{
				return null;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(itemToggle.playerTogglePhotonID);
			if (!Object.op_Implicit((Object)(object)val) || string.IsNullOrWhiteSpace(val.steamID))
			{
				return null;
			}
			return new PurchaseSnapshot(val.steamID, itemToggle.playerTogglePhotonID, UpgradeBridge.SnapshotVanilla(val.steamID));
		}

		internal void FinishVanillaPurchase(PurchaseSnapshot snapshot)
		{
			if (!CanDistributePurchase())
			{
				return;
			}
			foreach (string vanillaUpgradeKey in VanillaUpgradeKeys)
			{
				int value;
				int num = (snapshot.LevelsBefore.TryGetValue(vanillaUpgradeKey, out value) ? value : 0);
				int num2 = UpgradeBridge.ReadLevel(vanillaUpgradeKey, snapshot.SteamId);
				if (num2 > num)
				{
					HandlePurchasedIncrease(snapshot.SteamId, vanillaUpgradeKey, num2 - num);
				}
			}
		}

		internal void HandlePurchasedIncrease(string buyerSteamId, string upgradeKey, int purchasedLevels)
		{
			if (purchasedLevels <= 0 || !CanDistributePurchase() || (!UpgradeBridge.IsVanilla(upgradeKey) && !manageModdedUpgrades.Value))
			{
				return;
			}
			SkillPolicy skillPolicy = BindSkillPolicy(upgradeKey);
			if (!skillPolicy.SharePurchases.Value || skillPolicy.ShareAmount.Value <= 0)
			{
				return;
			}
			List<PlayerAvatar> currentPlayers = GetCurrentPlayers();
			int num = 0;
			foreach (PlayerAvatar item in currentPlayers)
			{
				if (!Object.op_Implicit((Object)(object)item) || string.IsNullOrWhiteSpace(item.steamID) || item.steamID == buyerSteamId)
				{
					continue;
				}
				BindPlayerPolicy(item.steamID, item.playerName);
				if (GetFixedTarget(item.steamID, skillPolicy).HasValue || (skillPolicy.ShareChance.Value < 100 && random.Next(0, 100) >= skillPolicy.ShareChance.Value))
				{
					continue;
				}
				int num2 = UpgradeBridge.ReadLevel(upgradeKey, item.steamID);
				int num3 = Math.Min(100, num2 + purchasedLevels * skillPolicy.ShareAmount.Value);
				if (num3 == num2)
				{
					continue;
				}
				applicationDepth++;
				try
				{
					if (UpgradeBridge.SetExactLevel(upgradeKey, item.steamID, num3))
					{
						num++;
					}
				}
				finally
				{
					applicationDepth--;
				}
			}
			LogDebug($"共有: {SkillDisplayName(upgradeKey)} +{purchasedLevels}, 配布先 {num}人");
		}

		internal void ResetRoomState()
		{
			seenPlayers.Clear();
			applyRequested = true;
		}

		internal void LogWarning(string message)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		}

		private bool CanDistributePurchase()
		{
			if (managerEnabled.Value && purchaseSharingEnabled.Value && !IsApplyingPolicy && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)StatsManager.instance != (Object)null)
			{
				return (Object)(object)PunManager.instance != (Object)null;
			}
			return false;
		}

		private void BindGeneralConfig()
		{
			managerEnabled = Bind("00 全般", "管理MODを有効", defaultValue: true, "ホストのスキル共有・人物別管理を有効にします。クライアントにはこのMODは不要です。");
			purchaseSharingEnabled = Bind("00 全般", "購入スキルを共有", defaultValue: true, "誰かが購入したスキルを他のメンバーへ配布します。各スキルの項目で量と確率を変更できます。");
			syncNewPlayers = Bind("00 全般", "新規参加者をチーム最大値へ同期", defaultValue: true, "途中参加者に、固定値が指定されていないスキルのチーム内最大レベルを適用します。");
			manageModdedUpgrades = Bind("00 全般", "追加MODスキルも管理", defaultValue: true, "REPOLibに登録されたSelfReviveなどの追加スキルも同じ画面へ自動追加します。");
			realtimeConfigApply = Bind("00 全般", "設定変更をリアルタイム反映", defaultValue: true, "REPOConfigで変更した全体・人物別の固定レベルをプレイ中すぐ反映します。");
			verboseLogging = Bind("00 全般", "詳細ログ", defaultValue: false, "共有・固定値の処理内容をBepInExログへ詳しく記録します。");
			Watch<bool>(managerEnabled);
			Watch<bool>(purchaseSharingEnabled);
			Watch<bool>(syncNewPlayers);
			Watch<bool>(manageModdedUpgrades);
			Watch<bool>(realtimeConfigApply);
		}

		private SkillPolicy BindSkillPolicy(string upgradeKey)
		{
			if (skillPolicies.TryGetValue(upgradeKey, out SkillPolicy value))
			{
				return value;
			}
			string text = SkillDisplayName(upgradeKey);
			string section = (UpgradeBridge.IsVanilla(upgradeKey) ? "10 標準スキル" : "20 追加スキル") + " - " + text;
			SkillPolicy skillPolicy = new SkillPolicy(upgradeKey, Bind(section, "01 購入共有", defaultValue: true, text + "を購入した時、他のメンバーへ共有します。"), Bind(section, "02 1レベル購入時の配布量", 1, "購入者が1レベル得た時に他の各メンバーへ渡す" + text + "の量です。0で配布なし。", 0, 20), Bind(section, "03 共有確率パーセント", 100, "各メンバーへ配布する確率です。100で必ず共有します。", 0, 100), Bind(section, "04 全員を目標レベルへ固定", defaultValue: false, "ONにすると購入共有より下の目標レベルを優先し、全員を同じ値へ合わせます。"), Bind(section, "05 全員の目標レベル", 0, "固定をONにした時の" + text + "レベルです。0なら削除、最大100。", 0, 100));
			Watch<bool>(skillPolicy.SharePurchases);
			Watch<int>(skillPolicy.ShareAmount);
			Watch<int>(skillPolicy.ShareChance);
			Watch<bool>(skillPolicy.ForceTeamTarget);
			Watch<int>(skillPolicy.TeamTarget);
			skillPolicies.Add(upgradeKey, skillPolicy);
			foreach (PlayerPolicy value2 in playerPolicies.Values)
			{
				BindPlayerSkillTarget(value2, skillPolicy);
			}
			LogDebug("設定へスキルを追加: " + upgradeKey);
			return skillPolicy;
		}

		private PlayerPolicy BindPlayerPolicy(string steamId, string playerName)
		{
			if (playerPolicies.TryGetValue(steamId, out PlayerPolicy value))
			{
				if (!string.IsNullOrWhiteSpace(playerName) && value.DisplayName.Value != playerName)
				{
					value.DisplayName.Value = playerName;
				}
				{
					foreach (SkillPolicy value2 in skillPolicies.Values)
					{
						BindPlayerSkillTarget(value, value2);
					}
					return value;
				}
			}
			string section = "30 人物別 - " + steamId;
			ConfigEntry<string> displayName = Bind(section, "00 表示名", playerName ?? string.Empty, "このSteam IDで最後に確認したプレイヤー名です。識別用です。");
			ConfigEntry<bool> val = Bind(section, "01 この人物の個別設定を有効", defaultValue: true, "各スキルの値が-1以外の時、その人物だけに個別目標を適用します。");
			PlayerPolicy playerPolicy = new PlayerPolicy(steamId, displayName, val);
			playerPolicies.Add(steamId, playerPolicy);
			Watch<bool>(val);
			foreach (SkillPolicy value3 in skillPolicies.Values)
			{
				BindPlayerSkillTarget(playerPolicy, value3);
			}
			LogDebug("設定へ人物を追加: " + playerName + " (" + steamId + ")");
			return playerPolicy;
		}

		private void BindPlayerSkillTarget(PlayerPolicy player, SkillPolicy skill)
		{
			if (!player.Targets.ContainsKey(skill.Key))
			{
				string section = "30 人物別 - " + player.SteamId;
				ConfigEntry<int> val = Bind(section, SkillDisplayName(skill.Key) + " 目標レベル", -1, "-1は個別指定なし(全体設定を使用)、0はその人物から削除、1以上はその人物だけの目標レベルです。", -1, 100);
				player.Targets.Add(skill.Key, val);
				Watch<int>(val);
			}
		}

		private void ApplyAllFixedTargets(IReadOnlyList<PlayerAvatar> players)
		{
			if (!managerEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			foreach (PlayerAvatar player in players)
			{
				ApplyPlayerPolicy(player, isNewPlayer: false, players);
			}
		}

		private void ApplyPlayerPolicy(PlayerAvatar player, bool isNewPlayer, IReadOnlyList<PlayerAvatar> players)
		{
			if (!Object.op_Implicit((Object)(object)player) || string.IsNullOrWhiteSpace(player.steamID))
			{
				return;
			}
			BindPlayerPolicy(player.steamID, player.playerName);
			foreach (SkillPolicy value in skillPolicies.Values)
			{
				if (!UpgradeBridge.IsVanilla(value.Key) && !manageModdedUpgrades.Value)
				{
					continue;
				}
				int? num = GetFixedTarget(player.steamID, value);
				if (!num.HasValue && isNewPlayer && syncNewPlayers.Value && value.SharePurchases.Value)
				{
					int num2 = 0;
					foreach (PlayerAvatar player2 in players)
					{
						if (Object.op_Implicit((Object)(object)player2) && !string.IsNullOrWhiteSpace(player2.steamID))
						{
							num2 = Math.Max(num2, UpgradeBridge.ReadLevel(value.Key, player2.steamID));
						}
					}
					if (num2 > UpgradeBridge.ReadLevel(value.Key, player.steamID))
					{
						num = num2;
					}
				}
				if (!num.HasValue)
				{
					continue;
				}
				applicationDepth++;
				try
				{
					if (!UpgradeBridge.SetExactLevel(value.Key, player.steamID, num.Value))
					{
						LogDebug($"適用待ち: {player.playerName} / {SkillDisplayName(value.Key)} = {num.Value}");
					}
				}
				finally
				{
					applicationDepth--;
				}
			}
		}

		private int? GetFixedTarget(string steamId, SkillPolicy skill)
		{
			if (playerPolicies.TryGetValue(steamId, out PlayerPolicy value) && value.Enabled.Value && value.Targets.TryGetValue(skill.Key, out ConfigEntry<int> value2) && value2.Value >= 0)
			{
				return value2.Value;
			}
			if (!skill.ForceTeamTarget.Value)
			{
				return null;
			}
			return skill.TeamTarget.Value;
		}

		private List<PlayerAvatar> GetCurrentPlayers()
		{
			try
			{
				return (from player in SemiFunc.PlayerGetAll()
					where (Object)(object)player != (Object)null && !string.IsNullOrWhiteSpace(player.steamID)
					select player).ToList();
			}
			catch
			{
				return new List<PlayerAvatar>();
			}
		}

		private void TryPatchRepoLib()
		{
			//IL_003f: 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)
			//IL_0061: Expected O, but got Unknown
			//IL_0061: Expected O, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_009b: Expected O, but got Unknown
			if (!repoLibPatched)
			{
				MethodInfo setLevelMethod = UpgradeBridge.SetLevelMethod;
				MethodInfo applyUpgradeMethod = UpgradeBridge.ApplyUpgradeMethod;
				if (!(setLevelMethod == null) && !(applyUpgradeMethod == null))
				{
					harmony.Patch((MethodBase)setLevelMethod, new HarmonyMethod(typeof(RepoLibUpgradeHooks), "SetLevelPrefix", (Type[])null), new HarmonyMethod(typeof(RepoLibUpgradeHooks), "SetLevelPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					harmony.Patch((MethodBase)applyUpgradeMethod, new HarmonyMethod(typeof(RepoLibUpgradeHooks), "ApplyUpgradePrefix", (Type[])null), new HarmonyMethod(typeof(RepoLibUpgradeHooks), "ApplyUpgradePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					repoLibPatched = true;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"REPOLib upgrade synchronization detected and connected.");
				}
			}
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description);
		}

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

		private void Watch<T>(ConfigEntry<T> entry)
		{
			entry.SettingChanged += delegate
			{
				applyRequested = true;
			};
		}

		private void LogDebug(string message)
		{
			if (verboseLogging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			}
		}

		internal static string SkillDisplayName(string upgradeKey)
		{
			string text;
			if (!upgradeKey.StartsWith("playerUpgrade", StringComparison.Ordinal))
			{
				text = upgradeKey;
			}
			else
			{
				string text2 = upgradeKey;
				int length = "playerUpgrade".Length;
				text = text2.Substring(length, text2.Length - length);
			}
			string text3 = text;
			return text3 switch
			{
				"CrouchRest" => "しゃがみ回復", 
				"DeathHeadBattery" => "デスヘッド電池", 
				"ExtraJump" => "追加ジャンプ", 
				"Health" => "体力", 
				"Launch" => "タンブル射出", 
				"MapPlayerCount" => "マップ表示人数", 
				"Range" => "掴み距離", 
				"Speed" => "移動速度", 
				"Stamina" => "スタミナ", 
				"Strength" => "掴み力", 
				"Throw" => "投擲力", 
				"TumbleClimb" => "タンブル登攀", 
				"TumbleWings" => "タンブル飛行", 
				"SelfRevive" => "自己蘇生", 
				_ => text3, 
			};
		}
	}
	internal sealed class HostSkillNetworkCallbacks : MonoBehaviourPunCallbacks
	{
		public override void OnJoinedRoom()
		{
			HostSkillManager.Instance?.ResetRoomState();
		}

		public override void OnLeftRoom()
		{
			HostSkillManager.Instance?.ResetRoomState();
		}

		public override void OnMasterClientSwitched(Player newMasterClient)
		{
			HostSkillManager.Instance?.ResetRoomState();
		}
	}
}
namespace YoneRai12.HostSkillManager.Services
{
	internal static class UpgradeBridge
	{
		private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static Type? playerUpgradeType;

		private static FieldInfo? upgradeIdField;

		private static FieldInfo? playerDictionaryField;

		private static FieldInfo? playerUpgradesField;

		private static PropertyInfo? playerUpgradesProperty;

		private static MethodInfo? setLevelMethod;

		private static MethodInfo? applyUpgradeMethod;

		internal static MethodInfo? SetLevelMethod
		{
			get
			{
				ResolveRepoLib();
				return setLevelMethod;
			}
		}

		internal static MethodInfo? ApplyUpgradeMethod
		{
			get
			{
				ResolveRepoLib();
				return applyUpgradeMethod;
			}
		}

		internal static bool IsVanilla(string upgradeKey)
		{
			return HostSkillManager.VanillaUpgradeKeys.Contains(upgradeKey);
		}

		internal static HashSet<string> DiscoverUpgradeKeys()
		{
			HashSet<string> hashSet = new HashSet<string>(HostSkillManager.VanillaUpgradeKeys, StringComparer.Ordinal);
			if (StatsManager.instance?.dictionaryOfDictionaries != null)
			{
				foreach (string key in StatsManager.instance.dictionaryOfDictionaries.Keys)
				{
					if (key.StartsWith("playerUpgrade", StringComparison.Ordinal))
					{
						hashSet.Add(key);
					}
				}
			}
			ResolveRepoLib();
			foreach (string item in EnumerateRepoUpgradeIds())
			{
				hashSet.Add("playerUpgrade" + item);
			}
			return hashSet;
		}

		internal static int ReadLevel(string upgradeKey, string steamId)
		{
			if (!IsVanilla(upgradeKey) && TryGetRepoUpgrade(upgradeKey, out object playerUpgrade))
			{
				return ReadRepoLevel(playerUpgrade, steamId);
			}
			if (StatsManager.instance?.dictionaryOfDictionaries != null && StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value) && value.TryGetValue(steamId, out var value2))
			{
				return value2;
			}
			return 0;
		}

		internal static Dictionary<string, int> SnapshotVanilla(string steamId)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (string vanillaUpgradeKey in HostSkillManager.VanillaUpgradeKeys)
			{
				dictionary[vanillaUpgradeKey] = ReadLevel(vanillaUpgradeKey, steamId);
			}
			return dictionary;
		}

		internal static bool SetExactLevel(string upgradeKey, string steamId, int requestedLevel)
		{
			int num = Math.Max(0, Math.Min(100, requestedLevel));
			int num2 = ReadLevel(upgradeKey, steamId);
			if (num2 == num)
			{
				return true;
			}
			if (IsVanilla(upgradeKey))
			{
				if ((Object)(object)PunManager.instance == (Object)null)
				{
					return false;
				}
				PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
				if ((Object)(object)component == (Object)null)
				{
					return false;
				}
				object[] obj = new object[3] { steamId, null, null };
				int length = "playerUpgrade".Length;
				obj[1] = upgradeKey.Substring(length, upgradeKey.Length - length);
				obj[2] = num - num2;
				component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, obj);
				return true;
			}
			if (!TryGetRepoUpgrade(upgradeKey, out object playerUpgrade) || setLevelMethod == null)
			{
				return false;
			}
			try
			{
				setLevelMethod.Invoke(playerUpgrade, new object[2] { steamId, num });
				return true;
			}
			catch (Exception ex)
			{
				Exception ex2 = ((ex is TargetInvocationException ex3 && ex.InnerException != null) ? ex3.InnerException : ex);
				HostSkillManager.Instance.LogWarning("REPOLib SetLevel failed: " + upgradeKey + ", " + steamId + ", " + ex2.GetType().Name + ": " + ex2.Message);
				return false;
			}
		}

		internal static bool TryGetUpgradeKey(object playerUpgrade, out string upgradeKey)
		{
			upgradeKey = string.Empty;
			ResolveRepoLib();
			if (!(upgradeIdField?.GetValue(playerUpgrade) is string text) || string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			upgradeKey = "playerUpgrade" + text;
			return true;
		}

		internal static int ReadRepoLevel(object playerUpgrade, string steamId)
		{
			ResolveRepoLib();
			if (!(playerDictionaryField?.GetValue(playerUpgrade) is IDictionary dictionary))
			{
				return 0;
			}
			if (!dictionary.Contains(steamId))
			{
				return 0;
			}
			object obj = dictionary[steamId];
			if (obj is int)
			{
				return (int)obj;
			}
			return 0;
		}

		private static void ResolveRepoLib()
		{
			if (!(playerUpgradeType != null) || !(setLevelMethod != null))
			{
				Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades");
				Type type2 = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade");
				if (!(type == null) && !(type2 == null))
				{
					playerUpgradeType = type2;
					upgradeIdField = playerUpgradeType.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					playerDictionaryField = playerUpgradeType.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					playerUpgradesField = type.GetField("_playerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					playerUpgradesProperty = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					setLevelMethod = playerUpgradeType.GetMethod("SetLevel", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
					{
						typeof(string),
						typeof(int)
					}, null);
					applyUpgradeMethod = playerUpgradeType.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
					{
						typeof(string),
						typeof(int)
					}, null);
				}
			}
		}

		private static IEnumerable<string> EnumerateRepoUpgradeIds()
		{
			object obj = playerUpgradesProperty?.GetValue(null) ?? playerUpgradesField?.GetValue(null);
			if (obj is IDictionary dictionary)
			{
				foreach (DictionaryEntry item in dictionary)
				{
					if (item.Key is string text && !string.IsNullOrWhiteSpace(text))
					{
						yield return text;
					}
				}
			}
			else
			{
				if (!(obj is IEnumerable enumerable) || upgradeIdField == null)
				{
					yield break;
				}
				foreach (object item2 in enumerable)
				{
					if (item2 != null && upgradeIdField.GetValue(item2) is string text2 && !string.IsNullOrWhiteSpace(text2))
					{
						yield return text2;
					}
				}
			}
		}

		private static bool TryGetRepoUpgrade(string upgradeKey, out object? playerUpgrade)
		{
			playerUpgrade = null;
			ResolveRepoLib();
			if (!upgradeKey.StartsWith("playerUpgrade", StringComparison.Ordinal))
			{
				return false;
			}
			int length = "playerUpgrade".Length;
			string key = upgradeKey.Substring(length, upgradeKey.Length - length);
			if (!(playerUpgradesField?.GetValue(null) is IDictionary dictionary) || !dictionary.Contains(key))
			{
				return false;
			}
			playerUpgrade = dictionary[key];
			return playerUpgrade != null;
		}
	}
}
namespace YoneRai12.HostSkillManager.Patches
{
	internal static class RepoLibUpgradeHooks
	{
		[ThreadStatic]
		private static int? stashedPreviousLevel;

		public static void SetLevelPrefix(object __instance, string steamId)
		{
			stashedPreviousLevel = UpgradeBridge.ReadRepoLevel(__instance, steamId);
		}

		public static void SetLevelPostfix()
		{
			stashedPreviousLevel = null;
		}

		public static void ApplyUpgradePrefix(object __instance, string steamId, out int __state)
		{
			if (stashedPreviousLevel.HasValue)
			{
				__state = stashedPreviousLevel.Value;
				stashedPreviousLevel = null;
			}
			else
			{
				__state = UpgradeBridge.ReadRepoLevel(__instance, steamId);
			}
		}

		public static void ApplyUpgradePostfix(object __instance, string steamId, int level, int __state)
		{
			if (level > __state && UpgradeBridge.TryGetUpgradeKey(__instance, out string upgradeKey))
			{
				HostSkillManager.Instance?.HandlePurchasedIncrease(steamId, upgradeKey, level - __state);
			}
		}
	}
	[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
	internal static class VanillaPurchasePatch
	{
		[HarmonyPrefix]
		private static void Prefix(ItemUpgrade __instance, out PurchaseSnapshot? __state)
		{
			__state = HostSkillManager.Instance?.CaptureVanillaPurchase(__instance);
		}

		[HarmonyPostfix]
		private static void Postfix(PurchaseSnapshot? __state)
		{
			if (__state != null)
			{
				HostSkillManager.Instance?.FinishVanillaPurchase(__state);
			}
		}
	}
}
namespace YoneRai12.HostSkillManager.Models
{
	internal sealed class PlayerPolicy
	{
		internal string SteamId { get; }

		internal ConfigEntry<string> DisplayName { get; }

		internal ConfigEntry<bool> Enabled { get; }

		internal Dictionary<string, ConfigEntry<int>> Targets { get; } = new Dictionary<string, ConfigEntry<int>>();

		internal PlayerPolicy(string steamId, ConfigEntry<string> displayName, ConfigEntry<bool> enabled)
		{
			SteamId = steamId;
			DisplayName = displayName;
			Enabled = enabled;
		}
	}
	internal sealed class PurchaseSnapshot
	{
		internal string SteamId { get; }

		internal int ViewId { get; }

		internal Dictionary<string, int> LevelsBefore { get; }

		internal PurchaseSnapshot(string steamId, int viewId, Dictionary<string, int> levelsBefore)
		{
			SteamId = steamId;
			ViewId = viewId;
			LevelsBefore = levelsBefore;
		}
	}
	internal sealed class SkillPolicy
	{
		internal string Key { get; }

		internal string CleanName
		{
			get
			{
				if (!Key.StartsWith("playerUpgrade"))
				{
					return Key;
				}
				string key = Key;
				int length = "playerUpgrade".Length;
				return key.Substring(length, key.Length - length);
			}
		}

		internal ConfigEntry<bool> SharePurchases { get; }

		internal ConfigEntry<int> ShareAmount { get; }

		internal ConfigEntry<int> ShareChance { get; }

		internal ConfigEntry<bool> ForceTeamTarget { get; }

		internal ConfigEntry<int> TeamTarget { get; }

		internal SkillPolicy(string key, ConfigEntry<bool> sharePurchases, ConfigEntry<int> shareAmount, ConfigEntry<int> shareChance, ConfigEntry<bool> forceTeamTarget, ConfigEntry<int> teamTarget)
		{
			Key = key;
			SharePurchases = sharePurchases;
			ShareAmount = shareAmount;
			ShareChance = shareChance;
			ForceTeamTarget = forceTeamTarget;
			TeamTarget = teamTarget;
		}
	}
}