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(); Runtime.Initialize(directorConfig, ((BaseUnityPlugin)this).Logger); EventRuntime = ((Component)this).gameObject.AddComponent(); EventRuntime.Initialize(directorConfig, ((BaseUnityPlugin)this).Logger); harmony = new Harmony("YoneRai12.AnomalyContractDirector"); harmony.PatchAll(Assembly.GetExecutingAssembly()); ((Component)this).gameObject.AddComponent(); ((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 BaseList { get; } internal List CurrentList { get; } internal int OriginalBaseCount { get; } internal int OriginalCurrentCount { get; } internal int OriginalEnemyListIndex { get; } internal int AddedLogical { get; } internal IReadOnlyList AddedSetups { get; } internal EnemyPoolExpansionTransaction(EnemyDirector director, List baseList, List currentList, int originalBaseCount, int originalCurrentCount, int originalEnemyListIndex, int addedLogical, IReadOnlyList 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 targets = new List(); 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 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 enemyList = instance.enemyList; List 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 list2 = new List(); 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 list, int start, IReadOnlyList 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 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 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)(((Object)(object)markedEnemy == (Object)null) ? ((Array)Array.Empty()) : ((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 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 FindLiveEnemies() { try { return Object.FindObjectsOfType().Where(IsLiveEnemy).Take(100) .ToList(); } catch { return new List(); } } 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 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 tokens = new Dictionary(); private readonly HashSet completedBatteryViewIds = new HashSet(); private ContractKind? previousKind; private readonly Dictionary batteryHoldTimers = new Dictionary(); private readonly Dictionary deviceActivationState = new Dictionary(); 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 batteries = new List(); private readonly List dualAnchors = new List(); private readonly List recoveryAnchors = new List(); 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 enabledKinds = GetEnabledKinds(); if (enabledKinds.Count == 0) { return; } int num = Math.Min(3, enabledKinds.Count); IReadOnlyList 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 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(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(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 excludedViewIds = new HashSet(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(tokens.Keys))); recoveryAnchors.AddRange(FindKnownRecoveryAnchors(num)); if (batteries.Count < num || recoveryAnchors.Count < num) { stateMachine.Fail($"既存バッテリーまたは既知ランドマークが不足 (電池{batteries.Count}/{num}, 地点{recoveryAnchors.Count}/{num})"); } else { IEnumerable 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(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 players = GetPlayers(); PlayerAvatar first = ((IEnumerable)players).FirstOrDefault((Func)((PlayerAvatar player) => IsNear(player, dualAnchors[0].position, config.DualRadius.Value))); PlayerAvatar val = ((IEnumerable)players).FirstOrDefault((Func)((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 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(); 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() 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 players = GetPlayers(); Vector3 origin = ((players.Count > 0) ? players[0].playerTransform.position : Vector3.zero); return (from point in Object.FindObjectsOfType() where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy where !string.Equals(((object)Unsafe.As(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 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 list = new List(); try { List list2 = (from point in Object.FindObjectsOfType() where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy where !string.Equals(((object)Unsafe.As(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)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func)((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 FindKnownDualAnchors(int required) { List list = new List(); try { LevelPoint truck = ((IEnumerable)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func)((LevelPoint val) => (Object)(object)val != (Object)null && val.Truck)); if ((Object)(object)truck != (Object)null) { list.Add(((Component)truck).transform); } List list2 = (from val in Object.FindObjectsOfType() where (Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy where !string.Equals(((object)Unsafe.As(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 GetEnabledKinds() { List list = new List(); 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 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(); } } 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)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func)((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 list = (from point in Object.FindObjectsOfType() 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(); 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() 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 Enabled { get; } internal ConfigEntry DetailedLogging { get; } internal ConfigEntry PhysicalTokenSelection { get; } internal ConfigEntry FallbackRandom { get; } internal ConfigEntry SelectionSeconds { get; } internal ConfigEntry DiscoveryGraceSeconds { get; } internal ConfigEntry PollIntervalSeconds { get; } internal ConfigEntry PlayerScaling { get; } internal ConfigEntry PreventConsecutive { get; } internal ConfigEntry EnableSuccessReward { get; } internal ConfigEntry SuccessHealAmount { get; } internal ConfigEntry SuccessRevealCount { get; } internal ConfigEntry EnableCursedCargo { get; } internal ConfigEntry EnableBountyEvidence { get; } internal ConfigEntry EnableBlackoutRecovery { get; } internal ConfigEntry EnableExtractionDefense { get; } internal ConfigEntry EnableDualActivation { get; } internal ConfigEntry EnableMedicalCore { get; } internal ConfigEntry MedicalHealAmount { get; } internal ConfigEntry MedicalRadius { get; } internal ConfigEntry EnablePortableRadar { get; } internal ConfigEntry RadarRadius { get; } internal ConfigEntry EnableNoiseBeacon { get; } internal ConfigEntry NoiseRadius { get; } internal ConfigEntry DeviceIntervalSeconds { get; } internal ConfigEntry DefenseSeconds { get; } internal ConfigEntry DefenseThreatRadius { get; } internal ConfigEntry DefenseThreatWaitSeconds { get; } internal ConfigEntry DualRadius { get; } internal ConfigEntry DualHoldSeconds { get; } internal ConfigEntry EnableAnomalyEvents { get; } internal ConfigEntry AnomalyEventChancePercent { get; } internal ConfigEntry AnomalyEventStartDelaySeconds { get; } internal ConfigEntry AnomalyEventDurationSeconds { get; } internal ConfigEntry AnomalyEventPollIntervalSeconds { get; } internal ConfigEntry AnomalyEventPreventConsecutive { get; } internal ConfigEntry AnomalyEventManualEnabled { get; } internal ConfigEntry AnomalyEventDetailedCandidates { get; } internal ConfigEntry EnableMarkedEnemyEvent { get; } internal ConfigEntry EnablePackFrenzyEvent { get; } internal ConfigEntry EnableHellAllEnemiesEvent { get; } internal ConfigEntry EnableReinforcementWaveEvent { get; } internal ConfigEntry EnableEscalatingThreatEvent { get; } internal ConfigEntry EnableRelentlessRespawnEvent { get; } internal ConfigEntry MarkedEnemyEventWeight { get; } internal ConfigEntry PackFrenzyEventWeight { get; } internal ConfigEntry HellAllEnemiesEventWeight { get; } internal ConfigEntry ReinforcementWaveEventWeight { get; } internal ConfigEntry EscalatingThreatEventWeight { get; } internal ConfigEntry RelentlessRespawnEventWeight { get; } internal ConfigEntry EnemyEventTargetLimit { get; } internal ConfigEntry EnemyEventMaxAliveEnemies { get; } internal ConfigEntry EnemyEventTotalSpawnLimit { get; } internal ConfigEntry EnemyEventPoolAdditionalLimit { get; } internal ConfigEntry EnemyEventInvestigationRadius { get; } internal ConfigEntry EnemyEventWaveIntervalSeconds { get; } internal ConfigEntry EnemyEventWaveBatchSize { get; } internal ConfigEntry EnemyEventEscalationStepSeconds { get; } internal ConfigEntry EnemyEventRespawnIntervalSeconds { get; } internal ConfigEntry EnemyEventRespawnTargetAlive { get; } internal ConfigEntry MarkedEnemyHealthMultiplier { get; } internal ConfigEntry MarkedEnemySpeedMultiplier { get; } internal ConfigEntry PackEnemyHealthMultiplier { get; } internal ConfigEntry PackEnemySpeedMultiplier { get; } internal ConfigEntry HellEnemyHealthMultiplier { get; } internal ConfigEntry HellEnemySpeedMultiplier { get; } internal ConfigEntry EnableGoldRushEvent { get; } internal ConfigEntry EnablePurpleChanceEvent { get; } internal ConfigEntry GoldRushEventWeight { get; } internal ConfigEntry PurpleChanceEventWeight { get; } internal ConfigEntry CosmeticGoldUpgradeChancePercent { get; } internal ConfigEntry CosmeticPurpleUpgradeChancePercent { get; } internal ConfigEntry EnableValuableSurgeEvent { get; } internal ConfigEntry ValuableSurgeEventWeight { get; } internal ConfigEntry LootEventRevealCount { get; } internal DirectorConfig(ConfigFile config) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Expected O, but got Unknown //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Expected O, but got Unknown //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Expected O, but got Unknown //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Expected O, but got Unknown //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Expected O, but got Unknown //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Expected O, but got Unknown //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03c3: Expected O, but got Unknown //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Expected O, but got Unknown //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Expected O, but got Unknown //IL_0464: Unknown result type (might be due to invalid IL or missing references) //IL_046e: Expected O, but got Unknown //IL_049d: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Expected O, but got Unknown //IL_04d6: Unknown result type (might be due to invalid IL or missing references) //IL_04e0: Expected O, but got Unknown //IL_052b: Unknown result type (might be due to invalid IL or missing references) //IL_0535: Expected O, but got Unknown //IL_0564: Unknown result type (might be due to invalid IL or missing references) //IL_056e: Expected O, but got Unknown //IL_059d: Unknown result type (might be due to invalid IL or missing references) //IL_05a7: Expected O, but got Unknown //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_05e0: Expected O, but got Unknown //IL_070b: Unknown result type (might be due to invalid IL or missing references) //IL_0715: Expected O, but got Unknown //IL_0744: Unknown result type (might be due to invalid IL or missing references) //IL_074e: Expected O, but got Unknown //IL_077d: Unknown result type (might be due to invalid IL or missing references) //IL_0787: Expected O, but got Unknown //IL_07b6: Unknown result type (might be due to invalid IL or missing references) //IL_07c0: Expected O, but got Unknown //IL_07ef: Unknown result type (might be due to invalid IL or missing references) //IL_07f9: Expected O, but got Unknown //IL_0828: Unknown result type (might be due to invalid IL or missing references) //IL_0832: Expected O, but got Unknown //IL_0857: Unknown result type (might be due to invalid IL or missing references) //IL_0861: Expected O, but got Unknown //IL_0886: Unknown result type (might be due to invalid IL or missing references) //IL_0890: Expected O, but got Unknown //IL_08b5: Unknown result type (might be due to invalid IL or missing references) //IL_08bf: Expected O, but got Unknown //IL_08e4: Unknown result type (might be due to invalid IL or missing references) //IL_08ee: Expected O, but got Unknown //IL_091d: Unknown result type (might be due to invalid IL or missing references) //IL_0927: Expected O, but got Unknown //IL_0956: Unknown result type (might be due to invalid IL or missing references) //IL_0960: Expected O, but got Unknown //IL_0984: Unknown result type (might be due to invalid IL or missing references) //IL_098e: Expected O, but got Unknown //IL_09bd: Unknown result type (might be due to invalid IL or missing references) //IL_09c7: Expected O, but got Unknown //IL_09f6: Unknown result type (might be due to invalid IL or missing references) //IL_0a00: Expected O, but got Unknown //IL_0a24: Unknown result type (might be due to invalid IL or missing references) //IL_0a2e: Expected O, but got Unknown //IL_0a5d: Unknown result type (might be due to invalid IL or missing references) //IL_0a67: Expected O, but got Unknown //IL_0a96: Unknown result type (might be due to invalid IL or missing references) //IL_0aa0: Expected O, but got Unknown //IL_0acf: Unknown result type (might be due to invalid IL or missing references) //IL_0ad9: Expected O, but got Unknown //IL_0b08: Unknown result type (might be due to invalid IL or missing references) //IL_0b12: Expected O, but got Unknown //IL_0b41: Unknown result type (might be due to invalid IL or missing references) //IL_0b4b: Expected O, but got Unknown //IL_0b7a: Unknown result type (might be due to invalid IL or missing references) //IL_0b84: Expected O, but got Unknown //IL_0beb: Unknown result type (might be due to invalid IL or missing references) //IL_0bf5: Expected O, but got Unknown //IL_0c24: Unknown result type (might be due to invalid IL or missing references) //IL_0c2e: Expected O, but got Unknown //IL_0c5d: Unknown result type (might be due to invalid IL or missing references) //IL_0c67: Expected O, but got Unknown //IL_0c96: Unknown result type (might be due to invalid IL or missing references) //IL_0ca0: Expected O, but got Unknown //IL_0ceb: Unknown result type (might be due to invalid IL or missing references) //IL_0cf5: Expected O, but got Unknown //IL_0d19: Unknown result type (might be due to invalid IL or missing references) //IL_0d23: Expected O, but got Unknown Enabled = config.Bind("00 全般", "MODを有効", true, "ホスト側の契約・装置管理を有効にします。参加者はこのMOD不要です。"); DetailedLogging = config.Bind("00 全般", "詳細ログ", false, "ViewID登録、契約進捗、縮退理由を詳しくBepInExログへ記録します。"); PhysicalTokenSelection = config.Bind("01 契約選択", "物理トークン選択", true, "安全に取得できる既存アイテムを契約トークンとして発見します。"); FallbackRandom = config.Bind("01 契約選択", "物理選択失敗時は自動ランダム", true, "トークンが安全に成立しない、または時間切れの時にランダムで開始します。"); SelectionSeconds = config.Bind("01 契約選択", "選択待ち秒数", 20f, new ConfigDescription("物理トークンまたはチャット選択を待つ秒数です。", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); DiscoveryGraceSeconds = config.Bind("01 契約選択", "トークン発見猶予秒数", 2f, new ConfigDescription("レベル生成直後に既存アイテムが現れるまで待つ秒数です。", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 10f), Array.Empty())); PollIntervalSeconds = config.Bind("01 契約選択", "ホスト監視間隔秒数", 0.25f, new ConfigDescription("ローカル監視間隔です。0.25秒未満にはできません(毎フレーム処理なし)。", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 1f), Array.Empty())); PlayerScaling = config.Bind("01 契約選択", "人数スケール", true, "参加人数に応じて復旧数・起動数を1~3へ調整します。"); PreventConsecutive = config.Bind("01 契約選択", "同じ契約の連続を防ぐ", true, "直前に成功または開始した契約を次レベルの第一候補から外します。"); EnableSuccessReward = config.Bind("02 契約報酬", "成功報酬を有効", true, "契約成功時に標準HP回復と既存貴重品のマップ発見を一度だけ行います。通貨や抽出額は書き換えません。"); SuccessHealAmount = config.Bind("02 契約報酬", "全員HP回復量", 35, new ConfigDescription("成功時に生存中の負傷プレイヤーへ送る標準HP回復量です。0で無効です。", (AcceptableValueBase)(object)new AcceptableValueRange(0, 200), Array.Empty())); SuccessRevealCount = config.Bind("02 契約報酬", "貴重品発見数", 3, new ConfigDescription("成功時に未発見の既存貴重品を標準マップへ表示する最大数です。0で無効です。", (AcceptableValueBase)(object)new AcceptableValueRange(0, 12), Array.Empty())); EnableCursedCargo = config.Bind("10 契約 - 呪われた貨物", "有効", true, "既存の貴重品1個を呪われた貨物としてViewID登録します。"); EnableBountyEvidence = config.Bind("11 契約 - 賞金首", "有効", true, "既存の敵個体と証拠品を対象にします。新しい敵Prefabは生成しません。"); EnableBlackoutRecovery = config.Bind("12 契約 - ブラックアウト復旧", "有効", true, "既存バッテリーを既存地点へ運ぶ独立契約です。照明状態は書き換えません。"); EnableExtractionDefense = config.Bind("13 契約 - 抽出地点防衛", "有効", true, "通常抽出の完了判定を変更せず、未完了の抽出地点付近で短時間耐える契約です。"); EnableDualActivation = config.Bind("14 契約 - 二地点同時起動", "有効", true, "トラックと抽出地点など、見分けられる既知ランドマーク二地点へ異なるプレイヤーが同時に滞在する契約です。"); EnableMedicalCore = config.Bind("20 装置 - 大型医療コア", "有効", true, "既存の回復オーブ等をViewID登録し、周囲の所有者へ標準HP回復RPCを送ります。"); MedicalHealAmount = config.Bind("20 装置 - 大型医療コア", "一回のHP回復量", 12, new ConfigDescription("1秒ごとに範囲内のプレイヤーへ送るHP回復量です。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); MedicalRadius = config.Bind("20 装置 - 大型医療コア", "回復半径", 8f, new ConfigDescription("停止して置かれた医療コアの回復半径です。", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 20f), Array.Empty())); EnablePortableRadar = config.Bind("21 装置 - 携帯レーダー", "有効", true, "既存トラッカー等をViewID登録し、標準ValuableObject.Discover経路で表示します。"); RadarRadius = config.Bind("21 装置 - 携帯レーダー", "探索半径", 28f, new ConfigDescription("レーダー発動時に既存貴重品を検索する半径です。", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 80f), Array.Empty())); EnableNoiseBeacon = config.Bind("22 装置 - ノイズビーコン", "有効", true, "既存トグルを持つアイテムだけをViewID登録し、ON中に標準EnemyDirector.SetInvestigateを呼びます。"); NoiseRadius = config.Bind("22 装置 - ノイズビーコン", "誘導半径", 18f, new ConfigDescription("敵の調査を誘導する半径です。", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); DeviceIntervalSeconds = config.Bind("20 装置 - 共通", "発動間隔秒数", 1f, new ConfigDescription("装置のローカル監視・標準API発動間隔です。", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 5f), Array.Empty())); DefenseSeconds = config.Bind("13 契約 - 抽出地点防衛", "防衛秒数", 25f, new ConfigDescription("未完了の抽出地点付近に留まる秒数です。通常の抽出状態機械は変更しません。", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 90f), Array.Empty())); DefenseThreatRadius = config.Bind("13 契約 - 抽出地点防衛", "脅威検知半径", 22f, new ConfigDescription("防衛開始に必要な生存敵を検知する半径です。敵を新規生成せず、既存敵だけを標準AIで誘導します。", (AcceptableValueBase)(object)new AcceptableValueRange(8f, 50f), Array.Empty())); DefenseThreatWaitSeconds = config.Bind("13 契約 - 抽出地点防衛", "脅威接近待ち秒数", 45f, new ConfigDescription("集合後、この秒数以内に既存敵が来なければ契約だけを安全終了します。", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 120f), Array.Empty())); DualRadius = config.Bind("14 契約 - 二地点同時起動", "地点判定半径", 3.5f, new ConfigDescription("既知ランドマークを起動中とみなす半径です。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 10f), Array.Empty())); DualHoldSeconds = config.Bind("14 契約 - 二地点同時起動", "同時起動維持秒数", 2f, new ConfigDescription("異なるプレイヤーが両地点に留まる秒数です。", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 10f), Array.Empty())); EnableAnomalyEvents = config.Bind("30 ランダム異常イベント", "有効", true, "契約とは独立して、自動抽選はレベルごとに0または1件です。ホストの明示コマンドは置換・再指定できますが、追加敵の1レベル累計上限は引き継ぎます。参加者側MODは不要です。"); AnomalyEventChancePercent = config.Bind("30 ランダム異常イベント", "抽選確率パーセント", 35f, new ConfigDescription("レベルごとにイベントを引く確率です。0で常に無効、100で毎レベル候補から1件です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); AnomalyEventStartDelaySeconds = config.Bind("30 ランダム異常イベント", "開始遅延秒数", 12f, new ConfigDescription("レベル生成安定後、イベントを実際に発動するまでの秒数です。遅延中はチャット指定で上書きできます。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 90f), Array.Empty())); AnomalyEventDurationSeconds = config.Bind("30 ランダム異常イベント", "発動継続秒数", 60f, new ConfigDescription("異常イベントが標準APIを監視・発動する上限秒数です。", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 180f), Array.Empty())); AnomalyEventPollIntervalSeconds = config.Bind("30 ランダム異常イベント", "監視間隔秒数", 0.25f, new ConfigDescription("イベントのローカル監視間隔です。毎フレーム送信せず、0.25秒未満にはできません。", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 1f), Array.Empty())); AnomalyEventPreventConsecutive = config.Bind("30 ランダム異常イベント", "同じイベントの連続を防ぐ", true, "ランダム抽選で直前のイベントを候補から外します。手動指定は明示命令として許可します。"); AnomalyEventManualEnabled = config.Bind("30 ランダム異常イベント", "チャット手動指定を許可", true, "ホスト本人の !acd event コマンドだけを受け付けます。専用キーや新規HUDは追加しません。"); AnomalyEventDetailedCandidates = config.Bind("30 ランダム異常イベント", "候補の詳細を通知", false, "抽選候補・安全縮退理由を開始チャットへ追加します。通常は短い通知だけです。"); EnableMarkedEnemyEvent = config.Bind("31 異常イベント - 敵", "標的指定を候補に含める", true, "既存敵1体へ標準調査を誘導し、host側の標準Hurt/UpdateAgent入口で実効HP・速度倍率を適用します。サイズと汎用攻撃ダメージは変更しません。"); EnablePackFrenzyEvent = config.Bind("31 異常イベント - 敵", "同種群れを候補に含める", true, "存在する同種敵を標準調査へ誘導します。生成前は同一の既存EnemySetupを通常プールへ追加できます。"); EnableHellAllEnemiesEvent = config.Bind("31 異常イベント - 敵", "全敵地獄を候補に含める", true, "存在する生存敵全体への標準調査誘導です。生成前の追加は上限100体の予算内だけです。"); EnableReinforcementWaveEvent = config.Bind("31 異常イベント - 敵", "追加ウェーブを候補に含める", true, "生成前は既存EnemySetupを通常プールへ追加し、生成後は未Spawned EnemyParentを標準DespawnedTimerSetで再出現させます。"); EnableEscalatingThreatEvent = config.Bind("31 異常イベント - 敵", "段階増殖する脅威を候補に含める", true, "生成前の既存EnemySetup追加と、時間経過後のEnemyParentプール再出現で同時生存目標を増やします。上限100です。"); EnableRelentlessRespawnEvent = config.Bind("31 異常イベント - 敵", "執拗な再出現を候補に含める", true, "生成前は既存EnemySetupを追加し、死亡や減少後は既存EnemyParentプールを標準再出現させます。キャンセル後の既存敵は破棄しません。"); MarkedEnemyEventWeight = config.Bind("31 異常イベント - 敵", "抽選ウェイト - 標的指定", 1f, new ConfigDescription("0でランダム抽選から実質除外。大きいほど他候補より出やすくなります。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); PackFrenzyEventWeight = config.Bind("31 異常イベント - 敵", "抽選ウェイト - 同種群れ", 1f, new ConfigDescription("同種群れイベントの相対抽選ウェイトです。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); HellAllEnemiesEventWeight = config.Bind("31 異常イベント - 敵", "抽選ウェイト - 全敵地獄", 0.65f, new ConfigDescription("全敵地獄イベントの相対抽選ウェイトです。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); ReinforcementWaveEventWeight = config.Bind("31 異常イベント - 敵", "抽選ウェイト - 追加ウェーブ", 0.8f, new ConfigDescription("追加ウェーブイベントの相対抽選ウェイトです。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); EscalatingThreatEventWeight = config.Bind("31 異常イベント - 敵", "抽選ウェイト - 段階増加", 0.9f, new ConfigDescription("段階増加イベントの相対抽選ウェイトです。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); RelentlessRespawnEventWeight = config.Bind("31 異常イベント - 敵", "抽選ウェイト - 執拗な再出現", 0.75f, new ConfigDescription("執拗な再出現イベントの相対抽選ウェイトです。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); EnemyEventTargetLimit = config.Bind("31 異常イベント - 敵", "同時対象上限", 20, new ConfigDescription("イベントが観測・誘導する敵の上限です。設定上限は100ですが、初期値は20です。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); EnemyEventMaxAliveEnemies = config.Bind("31 異常イベント - 敵", "同時生存上限", 20, new ConfigDescription("追加ウェーブ・段階増加で許可する敵の同時生存上限です。設定上限は100ですが、初期値は20です。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); EnemyEventTotalSpawnLimit = config.Bind("31 異常イベント - 敵", "1レベル総生成上限", 20, new ConfigDescription("イベントが要求できる敵追加生成の総数です。設定上限は100です。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); EnemyEventPoolAdditionalLimit = config.Bind("31 異常イベント - 敵", "敵プール追加実体上限", 12, new ConfigDescription("通常レベル生成前に既存EnemySetupを追加する実体数上限です。0で追加なし、最大100です。", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); EnemyEventInvestigationRadius = config.Bind("31 異常イベント - 敵", "敵誘導半径", 18f, new ConfigDescription("標準EnemyDirector.SetInvestigateへ渡す半径です。", (AcceptableValueBase)(object)new AcceptableValueRange(4f, 60f), Array.Empty())); EnemyEventWaveIntervalSeconds = config.Bind("31 異常イベント - 敵", "再誘導ウェーブ間隔秒数", 8f, new ConfigDescription("再誘導ウェーブと段階増加の標準AI発動間隔です。毎フレーム呼び出しません。", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 30f), Array.Empty())); EnemyEventWaveBatchSize = config.Bind("31 異常イベント - 敵", "1回の対象数", 4, new ConfigDescription("再誘導1回で対象にする既存敵の数です。絶対上限100の範囲にクランプします。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); EnemyEventEscalationStepSeconds = config.Bind("31 異常イベント - 敵", "段階増加間隔秒数", 15f, new ConfigDescription("段階増加イベントで対象数を増やす間隔です。", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); EnemyEventRespawnIntervalSeconds = config.Bind("31 異常イベント - 敵", "再出現間隔秒数", 0f, new ConfigDescription("執拗な再出現が標準DespawnedTimerSetへ渡す待ち時間です。0でタイマー即時化。監視は最大4Hz、1回20体、1レベル累計100体、既存プール限定です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); EnemyEventRespawnTargetAlive = config.Bind("31 異常イベント - 敵", "再出現後の同時生存目標", 4, new ConfigDescription("執拗な再出現で維持する既存敵数です。最大100です。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); MarkedEnemyHealthMultiplier = config.Bind("32 異常イベント - 敵倍率", "標的指定HP倍率", 1.35f, new ConfigDescription("標的指定イベントの実効HP倍率です。Hurtの即死相当ダメージは変更せず、1~3倍にクランプします。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 3f), Array.Empty())); MarkedEnemySpeedMultiplier = config.Bind("32 異常イベント - 敵倍率", "標的指定速度倍率", 1.15f, new ConfigDescription("標的指定イベントの標準UpdateAgent速度倍率です。host側の移動結果は既存Photon同期に任せます。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 2f), Array.Empty())); PackEnemyHealthMultiplier = config.Bind("32 異常イベント - 敵倍率", "同種群れHP倍率", 1.25f, new ConfigDescription("同種群れイベントの実効HP倍率です。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 3f), Array.Empty())); PackEnemySpeedMultiplier = config.Bind("32 異常イベント - 敵倍率", "同種群れ速度倍率", 1.1f, new ConfigDescription("同種群れイベントの標準UpdateAgent速度倍率です。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 2f), Array.Empty())); HellEnemyHealthMultiplier = config.Bind("32 異常イベント - 敵倍率", "全敵地獄HP倍率", 1.15f, new ConfigDescription("全敵地獄イベントの実効HP倍率です。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 3f), Array.Empty())); HellEnemySpeedMultiplier = config.Bind("32 異常イベント - 敵倍率", "全敵地獄速度倍率", 1.05f, new ConfigDescription("全敵地獄イベントの標準UpdateAgent速度倍率です。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 2f), Array.Empty())); EnableGoldRushEvent = config.Bind("33 異常イベント - コスメ箱", "金コスメ箱イベントを候補に含める", true, "CosmeticWorldObjectの標準SpawnCosmeticWorldObjectでUltraRare昇格だけを行います。箱総数は増やしません。"); EnablePurpleChanceEvent = config.Bind("33 異常イベント - コスメ箱", "紫コスメ箱イベントを候補に含める", true, "CosmeticWorldObjectの標準SpawnCosmeticWorldObjectでRare昇格だけを行います。箱総数は増やしません。"); GoldRushEventWeight = config.Bind("33 異常イベント - コスメ箱", "抽選ウェイト - 金", 0.6f, new ConfigDescription("金コスメ箱イベント自体の相対出現率です。箱1個ごとの昇格確率とは別です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); PurpleChanceEventWeight = config.Bind("33 異常イベント - コスメ箱", "抽選ウェイト - 紫", 0.35f, new ConfigDescription("紫コスメ箱イベント自体の相対出現率です。箱1個ごとの昇格確率とは別です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); CosmeticGoldUpgradeChancePercent = config.Bind("33 異常イベント - コスメ箱", "金昇格確率パーセント", 35f, new ConfigDescription("標準コスメ箱生成1回ごとにUltraRareへ昇格する確率です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); CosmeticPurpleUpgradeChancePercent = config.Bind("33 異常イベント - コスメ箱", "紫昇格確率パーセント", 45f, new ConfigDescription("標準コスメ箱生成1回ごとにRareへ昇格する確率です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); EnableValuableSurgeEvent = config.Bind("34 異常イベント - 貴重品大量発生", "貴重品大量発生を候補に含める", true, "SetupHostが用意した現行ValuableDirectorの未使用Volumeへ標準SpawnValuableで追加生成します。通貨・コスメ分類はしません。"); ValuableSurgeEventWeight = config.Bind("34 異常イベント - 貴重品大量発生", "抽選ウェイト", 0.7f, new ConfigDescription("貴重品大量発生イベントの相対出現率です。", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); LootEventRevealCount = config.Bind("34 異常イベント - 貴重品大量発生", "追加生成数", 3, new ConfigDescription("貴重品大量発生で標準SpawnValuableを要求する数です。", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); } } internal readonly struct EnemyMutation { internal float HealthMultiplier { get; } internal float SpeedMultiplier { get; } internal EnemyMutation(float healthMultiplier, float speedMultiplier) { HealthMultiplier = healthMultiplier; SpeedMultiplier = speedMultiplier; } } internal static class EnemyMutationRegistry { private static readonly Dictionary Entries = new Dictionary(); internal static void Clear() { Entries.Clear(); } internal static void Replace(IEnumerable enemies, float healthMultiplier, float speedMultiplier) { Entries.Clear(); AddRange(enemies, healthMultiplier, speedMultiplier); } internal static void AddRange(IEnumerable enemies, float healthMultiplier, float speedMultiplier) { float healthMultiplier2 = AnomalySafetyLimits.ClampFinite(healthMultiplier, 1f, 3f, 1f); float speedMultiplier2 = AnomalySafetyLimits.ClampFinite(speedMultiplier, 1f, 2f, 1f); foreach (EnemyParent enemy in enemies) { int viewId = GetViewId(enemy?.Enemy); if (viewId > 0) { Entries[viewId] = new EnemyMutation(healthMultiplier2, speedMultiplier2); } } } internal static bool TryGet(EnemyHealth health, out EnemyMutation mutation) { mutation = default(EnemyMutation); if ((Object)(object)health == (Object)null) { return false; } return TryGet(GetViewId(health.enemy), out mutation); } internal static bool TryGet(EnemyNavMeshAgent agent, out EnemyMutation mutation) { mutation = default(EnemyMutation); if ((Object)(object)agent == (Object)null) { return false; } try { Enemy enemy = ((Component)agent).GetComponent() ?? ((Component)agent).GetComponentInParent(); return TryGet(GetViewId(enemy), out mutation); } catch { return false; } } private static bool TryGet(int viewId, out EnemyMutation mutation) { mutation = default(EnemyMutation); if (viewId > 0) { return Entries.TryGetValue(viewId, out mutation); } return false; } private static int GetViewId(Enemy? enemy) { try { return ((Object)(object)enemy != (Object)null && (Object)(object)enemy.PhotonView != (Object)null) ? enemy.PhotonView.ViewID : 0; } catch { return 0; } } } internal static class GameInterop { private static readonly BindingFlags MethodFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static MethodInfo? valuableDiscoverMethod; private static object? valuableDiscoverState; private static bool valuableDiscoverResolved; internal static bool TryDiscoverValuable(ValuableObject valuable) { if ((Object)(object)valuable == (Object)null) { return false; } ResolveValuableDiscover(); if (valuableDiscoverMethod == null || valuableDiscoverState == null) { return false; } try { object obj = valuableDiscoverMethod.Invoke(valuable, new object[1] { valuableDiscoverState }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static void ResolveValuableDiscover() { if (valuableDiscoverResolved) { return; } valuableDiscoverResolved = true; try { valuableDiscoverMethod = typeof(ValuableObject).GetMethod("Discover", MethodFlags); Type nestedType = typeof(ValuableDiscoverGraphic).GetNestedType("State", BindingFlags.Public | BindingFlags.NonPublic); if (!(valuableDiscoverMethod == null) && !(nestedType == null) && nestedType.IsEnum) { valuableDiscoverState = Enum.Parse(nestedType, "Discover", ignoreCase: false); } } catch { valuableDiscoverMethod = null; valuableDiscoverState = null; } } } internal sealed class ItemRoleRegistry { private readonly Dictionary devices = new Dictionary(); private readonly Dictionary recordsByViewId = new Dictionary(); private readonly Dictionary bindingsByViewId = new Dictionary(); internal IReadOnlyCollection Records => recordsByViewId.Values; internal bool TryGet(DeviceKind kind, out ItemRoleBinding binding) { if (!devices.TryGetValue(kind, out binding) || !binding.IsValid) { Release(kind); binding = null; return false; } return true; } internal IReadOnlyList FindSafeTokenCandidates(int maximum) { if (maximum <= 0) { return Array.Empty(); } List source = (from item in Object.FindObjectsOfType().Where(IsSafePhysicalCandidate) where item.inStartRoom orderby item.inStartRoom descending select item).ThenBy((ItemAttributes item) => item.itemName ?? string.Empty, StringComparer.OrdinalIgnoreCase).ThenBy((ItemAttributes item) => item.photonView.ViewID).ToList(); return source.Take(maximum).ToList(); } internal ItemRoleBinding? RegisterDevice(DeviceKind kind, ItemAttributes attributes) { if (!IsSafePhysicalCandidate(attributes) || (Object)(object)attributes.photonView == (Object)null) { return null; } int viewID = attributes.photonView.ViewID; if (viewID <= 0) { return null; } if (devices.TryGetValue(kind, out ItemRoleBinding value) && value.ViewId == viewID) { return value; } if (bindingsByViewId.ContainsKey(viewID)) { return null; } ItemRoleBinding itemRoleBinding = new ItemRoleBinding(kind, attributes, viewID); devices[kind] = itemRoleBinding; bindingsByViewId[viewID] = itemRoleBinding; recordsByViewId[viewID] = new DeviceRoleRecord(kind, viewID, GetDisplayName(attributes), DeviceRoleState.Registered); return itemRoleBinding; } internal ItemRoleBinding? FindAndRegisterDevice(DeviceKind kind, ISet excludedViewIds) { List list = (from item in Object.FindObjectsOfType().Where(IsSafePhysicalCandidate) where IsDeviceCandidate(kind, item) where (Object)(object)item.photonView != (Object)null && !excludedViewIds.Contains(item.photonView.ViewID) orderby Score(kind, item) descending, item.photonView.ViewID select item).ToList(); foreach (ItemAttributes item in list) { ItemRoleBinding itemRoleBinding = RegisterDevice(kind, item); if (itemRoleBinding != null && itemRoleBinding.RoleKind == kind) { return itemRoleBinding; } } return null; } internal ValuableRoleBinding? FindValuable(ISet excludedViewIds) { List list = (from value in Object.FindObjectsOfType() where (Object)(object)value != (Object)null && (Object)(object)value.photonView != (Object)null && value.photonView.ViewID > 0 where (Object)(object)value.physGrabObject != (Object)null && !value.physGrabObject.dead && value.physGrabObject.isActive where !value.physGrabObject.grabbed where !value.inStartRoom where (Object)(object)value.roomVolumeCheck != (Object)null && !value.roomVolumeCheck.inExtractionPoint where !excludedViewIds.Contains(value.photonView.ViewID) orderby value.dollarValueCurrent descending, value.photonView.ViewID select value).ToList(); foreach (ValuableObject item in list) { if (!bindingsByViewId.ContainsKey(item.photonView.ViewID)) { return new ValuableRoleBinding(item, item.photonView.ViewID); } } return null; } internal IReadOnlyList FindBatteryCandidates(int maximum, ISet excludedViewIds) { if (maximum <= 0) { return Array.Empty(); } return (from item in Object.FindObjectsOfType().Where(IsSafePhysicalCandidate) where (Object)(object)item.photonView != (Object)null && !excludedViewIds.Contains(item.photonView.ViewID) where (Object)(object)item.photonView != (Object)null && !bindingsByViewId.ContainsKey(item.photonView.ViewID) where (Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null || (item.itemName ?? string.Empty).IndexOf("battery", StringComparison.OrdinalIgnoreCase) >= 0 || (item.itemName ?? string.Empty).IndexOf("バッテリー", StringComparison.OrdinalIgnoreCase) >= 0 orderby item.inStartRoom descending, item.photonView.ViewID select item).Take(maximum).ToList(); } internal IReadOnlyList FindValuables(int maximum, ISet excludedViewIds) { if (maximum <= 0) { return Array.Empty(); } return (from value in (from value in Object.FindObjectsOfType() where (Object)(object)value != (Object)null && (Object)(object)value.photonView != (Object)null && value.photonView.ViewID > 0 where (Object)(object)value.physGrabObject != (Object)null && !value.physGrabObject.dead && value.physGrabObject.isActive where !value.physGrabObject.grabbed where !value.inStartRoom where (Object)(object)value.roomVolumeCheck != (Object)null && !value.roomVolumeCheck.inExtractionPoint where !excludedViewIds.Contains(value.photonView.ViewID) orderby value.photonView.ViewID select value).Take(maximum) select new ValuableRoleBinding(value, value.photonView.ViewID)).ToList(); } internal void MarkActive(DeviceKind kind) { if (devices.TryGetValue(kind, out ItemRoleBinding value) && recordsByViewId.TryGetValue(value.ViewId, out DeviceRoleRecord value2)) { value2.State = DeviceRoleState.Active; } } internal void MarkSpent(DeviceKind kind) { if (devices.TryGetValue(kind, out ItemRoleBinding value) && recordsByViewId.TryGetValue(value.ViewId, out DeviceRoleRecord value2)) { value2.State = DeviceRoleState.Spent; } } internal void ReleaseAll() { foreach (DeviceRoleRecord value in recordsByViewId.Values) { value.State = DeviceRoleState.Released; } devices.Clear(); recordsByViewId.Clear(); bindingsByViewId.Clear(); } internal void Release(DeviceKind kind) { if (devices.TryGetValue(kind, out ItemRoleBinding value)) { devices.Remove(kind); bindingsByViewId.Remove(value.ViewId); recordsByViewId.Remove(value.ViewId); } } private static bool IsSafePhysicalCandidate(ItemAttributes? attributes) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Invalid comparison between Unknown and I4 if ((Object)(object)attributes == (Object)null || (Object)(object)attributes.photonView == (Object)null || (Object)(object)attributes.physGrabObject == (Object)null) { return false; } if (attributes.photonView.ViewID <= 0 || attributes.shopItem || (Object)(object)attributes.item == (Object)null) { return false; } if (attributes.item.disabled || !attributes.item.physicalItem) { return false; } if ((int)attributes.itemType == 13) { return false; } if (attributes.physGrabObject.dead || !attributes.physGrabObject.isActive) { return false; } if (attributes.physGrabObject.grabbed) { return false; } return true; } private static int Score(DeviceKind kind, ItemAttributes item) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Invalid comparison between Unknown and I4 //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Invalid comparison between Unknown and I4 //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Invalid comparison between Unknown and I4 string text = (item.itemName ?? string.Empty).ToLowerInvariant(); int num = (item.inStartRoom ? 10 : 0); switch (kind) { case DeviceKind.MedicalCore: if ((int)item.itemType == 1) { num += 50; } if (text.Contains("heal") || text.Contains("health") || text.Contains("回復")) { num += 30; } if ((Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null) { num += 100; } if ((Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null) { num += 80; } break; case DeviceKind.PortableRadar: if ((int)item.itemType == 10) { num += 100; } if (text.Contains("tracker") || text.Contains("radar") || text.Contains("レーダー")) { num += 30; } if ((Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null) { num += 80; } break; case DeviceKind.NoiseBeacon: if ((int)item.itemType == 6 || (int)item.itemType == 11) { num += 50; } if ((Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null) { num += 80; } if ((Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null) { num += 20; } break; } return num; } private static bool IsDeviceCandidate(DeviceKind kind, ItemAttributes item) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 string text = (item.itemName ?? item.item?.itemName ?? string.Empty).ToLowerInvariant(); return kind switch { DeviceKind.MedicalCore => (Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null || (int)item.itemType == 8 || text.Contains("heal") || text.Contains("health") || text.Contains("回復"), DeviceKind.PortableRadar => (Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null || (int)item.itemType == 10 || text.Contains("tracker") || text.Contains("radar") || text.Contains("レーダー"), DeviceKind.NoiseBeacon => (Object)(object)item.itemToggle != (Object)null || (Object)(object)((Component)item).GetComponentInChildren(true) != (Object)null, _ => false, }; } private static string GetDisplayName(ItemAttributes attributes) { if (!string.IsNullOrWhiteSpace(attributes.itemName)) { return attributes.itemName; } if ((Object)(object)attributes.item != (Object)null && !string.IsNullOrWhiteSpace(attributes.item.itemName)) { return attributes.item.itemName; } return $"既存アイテム#{attributes.photonView.ViewID}"; } } internal sealed class ItemRoleBinding { internal DeviceKind RoleKind { get; } internal ItemAttributes Attributes { get; } internal int ViewId { get; } internal Transform Transform => ((Component)Attributes).transform; internal PhysGrabObject PhysGrabObject => Attributes.physGrabObject; internal PhotonView PhotonView => Attributes.photonView; internal ItemToggle? Toggle => Attributes.itemToggle ?? ((Component)Attributes).GetComponentInChildren(true); internal bool IsHeld { get { if ((Object)(object)PhysGrabObject != (Object)null) { return PhysGrabObject.grabbed; } return false; } } internal bool IsValid { get { if ((Object)(object)Attributes != (Object)null && (Object)(object)Attributes.photonView != (Object)null && Attributes.photonView.ViewID == ViewId && (Object)(object)Attributes.physGrabObject != (Object)null && !Attributes.physGrabObject.dead) { return Attributes.physGrabObject.isActive; } return false; } } internal ItemRoleBinding(DeviceKind roleKind, ItemAttributes attributes, int viewId) { RoleKind = roleKind; Attributes = attributes; ViewId = viewId; } } internal sealed class ValuableRoleBinding { internal ValuableObject Valuable { get; } internal int ViewId { get; } internal Transform Transform => ((Component)Valuable).transform; internal PhysGrabObject PhysGrabObject => Valuable.physGrabObject; internal string DisplayName { get { if ((Object)(object)Valuable == (Object)null) { return $"消失した貴重品#{ViewId}"; } ItemAttributes val = ((Component)Valuable).GetComponent() ?? ((Component)Valuable).GetComponentInParent(); if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(val.itemName)) { return val.itemName; } if ((Object)(object)val != (Object)null && (Object)(object)val.item != (Object)null && !string.IsNullOrWhiteSpace(val.item.itemName)) { return val.item.itemName; } if (string.IsNullOrWhiteSpace(((Object)Valuable).name)) { return $"既存貴重品#{ViewId}"; } return ((Object)Valuable).name; } } internal ValuableRoleBinding(ValuableObject valuable, int viewId) { Valuable = valuable; ViewId = viewId; } } internal static class StandardGameApi { private sealed class EnemyRespawnReservation { internal EnemyParent Parent { get; } internal float OriginalTimer { get; } internal float ReservedAt { get; } internal EnemyRespawnReservation(EnemyParent parent, float originalTimer, float reservedAt) { Parent = parent; OriginalTimer = originalTimer; ReservedAt = reservedAt; } } private static readonly BindingFlags AnyMember = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly HashSet UsedValuableVolumeIds = new HashSet(); private static readonly Dictionary ReservedPoolEnemies = new Dictionary(); internal static bool TryReactivatePooledEnemy(float safeDelaySeconds, out EnemyParent? reactivated) { reactivated = null; if (!IsHost()) { return false; } try { EnemyParent[] array = Object.FindObjectsOfType(); foreach (EnemyParent val in array) { if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (val.Spawned) { ReservedPoolEnemies.Remove(instanceID); } else if (val.SetupDone && !((Object)(object)val.Enemy == (Object)null) && !ReservedPoolEnemies.ContainsKey(instanceID)) { EnemyRespawnReservation value = new EnemyRespawnReservation(val, val.DespawnedTimer, Time.unscaledTime); ReservedPoolEnemies[instanceID] = value; try { val.DespawnedTimerSet(Mathf.Max(0f, safeDelaySeconds), true); reactivated = val; return true; } catch { ReservedPoolEnemies.Remove(instanceID); } } } return false; } catch { return false; } } internal static bool TrySpawnValuableTopUp(ValuableDirector expectedDirector, int requested, out int spawned, out string reason) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Invalid comparison between Unknown and I4 spawned = 0; reason = string.Empty; if (!IsHost()) { reason = "ホスト権限がありません"; return false; } requested = Math.Clamp(requested, 0, 20); if (requested == 0) { return true; } if (!CanReserveNetworkViews(requested)) { reason = "Photon View予約が少なく、追加RoomObjectを停止しました"; return false; } try { object staticMember = GetStaticMember(typeof(ValuableDirector), "instance"); if (staticMember == null || (Object)(object)expectedDirector == (Object)null || staticMember != expectedDirector) { reason = "SetupHostで観測したValuableDirector世代と一致しません"; return false; } LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null || instance.Generated || (int)instance.State != 11) { reason = "Valuable標準生成中ではありません"; return false; } object member = GetMember(staticMember, "setupComplete"); bool flag = default(bool); int num; if (member is bool) { flag = (bool)member; num = 1; } else { num = 0; } if (num == 0 || flag) { reason = "ValuableDirector setup完了後の追加を拒否しました"; return false; } MethodInfo methodInfo = (from candidate in typeof(ValuableDirector).GetMethods(AnyMember) where string.Equals(candidate.Name, "SpawnValuable", StringComparison.Ordinal) select candidate).FirstOrDefault((MethodInfo candidate) => candidate.GetParameters().Length == 3); if (methodInfo == null) { reason = "ValuableDirector.SpawnValuable(prefab, volume, resourcePath)が見つかりません"; return false; } string[][] array = new string[7][] { new string[3] { "tinyValuables", "tinyVolumes", "tinyPath" }, new string[3] { "smallValuables", "smallVolumes", "smallPath" }, new string[3] { "mediumValuables", "mediumVolumes", "mediumPath" }, new string[3] { "bigValuables", "bigVolumes", "bigPath" }, new string[3] { "wideValuables", "wideVolumes", "widePath" }, new string[3] { "tallValuables", "tallVolumes", "tallPath" }, new string[3] { "veryTallValuables", "veryTallVolumes", "veryTallPath" } }; string[][] array2 = array; foreach (string[] array3 in array2) { if (spawned >= requested) { break; } IList list = GetMember(staticMember, array3[0]) as IList; IList list2 = GetMember(staticMember, array3[1]) as IList; if (list == null || list2 == null || list.Count == 0 || list2.Count == 0) { continue; } string text = (GetMember(staticMember, array3[2]) as string) ?? string.Empty; string resourcePath; object obj = FirstValidPrefab(list, out resourcePath); if (obj == null) { continue; } if (string.IsNullOrWhiteSpace(text)) { text = resourcePath; } if (string.IsNullOrWhiteSpace(text)) { continue; } foreach (object item in list2) { if (spawned >= requested || !CanReserveNetworkViews(spawned + 1)) { break; } ValuableVolume val = (ValuableVolume)((item is ValuableVolume) ? item : null); if (val != null && IsUsableVolume(val) && !IsValuableVolumeUsed(val)) { try { methodInfo.Invoke(staticMember, new object[3] { obj, val, text }); MarkValuableVolumeUsed(val); spawned++; } catch { } } } } string reason2; if (spawned == 0) { reason = "現行レベルの未使用ValuableVolumeまたはPrefabがありません"; } else if (!TrySynchronizeValuableTarget(expectedDirector, out reason2)) { reason = reason2; } return spawned > 0; } catch (Exception ex) { reason = ex.GetBaseException().GetType().Name; return false; } } internal static void ResetRuntimeTrackers() { CancelPendingEnemyReactivations(); UsedValuableVolumeIds.Clear(); } internal static void CancelPendingEnemyReactivations() { EnemyRespawnReservation[] array = ReservedPoolEnemies.Values.ToArray(); foreach (EnemyRespawnReservation enemyRespawnReservation in array) { try { EnemyParent parent = enemyRespawnReservation.Parent; if (!((Object)(object)parent == (Object)null) && !parent.Spawned) { float num = Mathf.Max(0f, Time.unscaledTime - enemyRespawnReservation.ReservedAt); float num2 = Mathf.Max(0f, enemyRespawnReservation.OriginalTimer - num); parent.DespawnedTimerSet(num2, false); } } catch { } } ReservedPoolEnemies.Clear(); } internal static int CountPreparedEnemyParents() { try { GameObject val = LevelGenerator.Instance?.EnemyParent; EnemyParent[] source = (((Object)(object)val != (Object)null) ? val.GetComponentsInChildren(true) : Object.FindObjectsOfType()); return AnomalySafetyLimits.ClampHard100(source.Count((EnemyParent parent) => (Object)(object)parent != (Object)null && parent.SetupDone)); } catch { return 0; } } private static bool TrySynchronizeValuableTarget(ValuableDirector director, out string reason) { reason = string.Empty; try { if (!SemiFunc.IsMultiplayer()) { return true; } if ((Object)(object)director == (Object)null || (Object)(object)director.PhotonView == (Object)null) { reason = "ValuableDirectorの標準PhotonViewがありません"; return false; } director.PhotonView.RPC("ValuablesTargetSetRPC", (RpcTarget)0, new object[1] { director.valuableTargetAmount }); return true; } catch (Exception ex) { reason = "標準ValuablesTargetSetRPC再送失敗: " + ex.GetBaseException().GetType().Name; return false; } } internal static void TrackValuableSpawn(object[]? args) { if (args == null) { return; } foreach (object obj in args) { ValuableVolume val = (ValuableVolume)((obj is ValuableVolume) ? obj : null); if (val != null) { MarkValuableVolumeUsed(val); } } } internal static void TrackCosmeticVolumeDiff(int[]? before, int[]? after, List[]? volumes) { if (before == null || after == null || volumes == null) { return; } int num = Math.Min(Math.Min(before.Length, after.Length), volumes.Length); for (int i = 0; i < num; i++) { List list = volumes[i]; if (list == null) { continue; } int num2 = Math.Max(0, before[i]); int num3 = Math.Min(list.Count, Math.Max(num2, after[i])); for (int j = num2; j < num3; j++) { ValuableVolume val = list[j]; if ((Object)(object)val != (Object)null) { MarkValuableVolumeUsed(val); } } } } internal static bool IsHostAuthority() { return IsHost(); } internal static bool CanReserveNetworkViews(int predictedObjects) { try { if (!SemiFunc.IsMultiplayer()) { return true; } int num = Math.Max(1, predictedObjects); return PhotonNetwork.ViewCount + (long)num * 2L < 900; } catch { return false; } } internal static int CurrentPhotonViewCount() { try { return SemiFunc.IsMultiplayer() ? PhotonNetwork.ViewCount : 0; } catch { return 1000; } } private static object? FirstValidPrefab(IList valuables, out string resourcePath) { resourcePath = string.Empty; foreach (object valuable in valuables) { if (valuable != null && IsValidPrefabRef(valuable)) { resourcePath = (GetMember(valuable, "ResourcePath") as string) ?? string.Empty; if (!string.IsNullOrWhiteSpace(resourcePath)) { return valuable; } } } return null; } internal static string GetEnemyDisplayName(EnemyParent enemy) { if ((Object)(object)enemy == (Object)null) { return string.Empty; } string text = GetMember(enemy, "enemyName") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } return ((Object)enemy).name ?? string.Empty; } internal static void SetInvestigate(Vector3 position, float radius) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) try { if (IsHost()) { EnemyDirector instance = EnemyDirector.instance; if (instance != null) { instance.SetInvestigate(position, radius, false); } } } catch { } } private static bool IsValidPrefabRef(object prefabRef) { try { MethodInfo method = prefabRef.GetType().GetMethod("IsValid", AnyMember, null, Type.EmptyTypes, null); if (method == null) { return true; } object obj = method.Invoke(prefabRef, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static bool IsUsableVolume(ValuableVolume volume) { try { return (Object)(object)volume != (Object)null && (Object)(object)((Component)volume).gameObject != (Object)null && ((Component)volume).gameObject.activeInHierarchy; } catch { return false; } } private static bool IsValuableVolumeUsed(ValuableVolume volume) { try { return UsedValuableVolumeIds.Contains(((Object)volume).GetInstanceID()); } catch { return false; } } private static void MarkValuableVolumeUsed(ValuableVolume volume) { try { if ((Object)(object)volume != (Object)null) { UsedValuableVolumeIds.Add(((Object)volume).GetInstanceID()); } } catch { } } private static object? GetStaticMember(Type type, string name) { FieldInfo field = type.GetField(name, AnyMember); if (field != null) { return field.GetValue(null); } return type.GetProperty(name, AnyMember)?.GetValue(null, null); } private static object? GetMember(object target, string name) { if (target == null) { return null; } Type type = target.GetType(); FieldInfo field = type.GetField(name, AnyMember); if (field != null) { return field.GetValue(target); } return type.GetProperty(name, AnyMember)?.GetValue(target, null); } private static bool IsHost() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return false; } } } } namespace YoneRai12.AnomalyContractDirector.Patches { [HarmonyPatch(typeof(EnemyDirector), "AmountSetup")] internal static class EnemyAmountSetupPatch { [HarmonyPrefix] private static void Prefix() { try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnEnemyAmountSetupPrefix(); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] 敵AmountSetup前処理を縮退しました: " + ex.GetType().Name)); } } [HarmonyPostfix] private static void Postfix() { try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnEnemyAmountSetupPostfix(); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] 敵AmountSetup後処理を縮退しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(EnemyDirector), "GetEnemy")] internal static class EnemyGetEnemyCommitPatch { [HarmonyPrefix] private static void Prefix(EnemyDirector __instance) { try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnEnemyGetEnemyPrefix(__instance); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] 敵Setup commit観測を縮退しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(EnemyHealth), "Hurt")] internal static class EnemyHealthMutationPatch { [HarmonyPrefix] private static void Prefix(EnemyHealth __instance, ref int __0) { try { if (IsHost() && __0 > 0 && EnemyMutationRegistry.TryGet(__instance, out var mutation)) { int healthCurrent = __instance.healthCurrent; if (healthCurrent > 0 && __0 < healthCurrent && !(mutation.HealthMultiplier <= 1f)) { int val = (int)Math.Floor((float)__0 / mutation.HealthMultiplier); __0 = Math.Max(1, Math.Min(__0, val)); } } } catch (Exception ex) { Debug.LogWarning((object)("[ACD] 敵HP倍率を安全に縮退しました: " + ex.GetType().Name)); } } private static bool IsHost() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return false; } } } [HarmonyPatch(typeof(EnemyNavMeshAgent), "UpdateAgent")] internal static class EnemySpeedMutationPatch { [HarmonyPrefix] private static void Prefix(EnemyNavMeshAgent __instance, ref float __0, ref float __1) { try { if (IsHost() && EnemyMutationRegistry.TryGet(__instance, out var mutation) && !(mutation.SpeedMultiplier <= 1f)) { if (__0 > 0f) { __0 = Mathf.Clamp(__0 * mutation.SpeedMultiplier, 0f, __0 * 2f); } if (__1 > 0f) { __1 = Mathf.Clamp(__1 * mutation.SpeedMultiplier, 0f, __1 * 2f); } } } catch (Exception ex) { Debug.LogWarning((object)("[ACD] 敵速度倍率を安全に縮退しました: " + ex.GetType().Name)); } } private static bool IsHost() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return false; } } } [HarmonyPatch(typeof(ValuableDirector), "SpawnValuable")] internal static class ValuableSpawnTrackerPatch { [HarmonyPostfix] private static void Postfix(object[] __args) { try { StandardGameApi.TrackValuableSpawn(__args); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] ValuableVolume追跡を縮退しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(ValuableDirector), "SetupHost")] internal static class ValuableSetupHostPatch { [HarmonyPrefix] private static void Prefix(ValuableDirector __instance) { try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnValuableSetupHostPrefix(__instance); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] Valuable setup開始前処理を縮退しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(ValuableDirector), "VolumesAndSwitchSetup")] internal static class ValuableSetupPatch { [HarmonyPrefix] private static void Prefix(ValuableDirector __instance) { try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnValuableSetupPrefix(__instance); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] Valuable setup前処理を縮退しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(ValuableDirector), "SpawnCosmeticWorldObject")] internal static class CosmeticRarityPatch { private sealed class CosmeticSpawnState { internal int[]? VolumeIndex; internal List[]? Volumes; } [HarmonyPrefix] private static void Prefix(ValuableDirector __instance, ref Rarity __0, int[] __1, List[] __2, out CosmeticSpawnState __state) { __state = new CosmeticSpawnState { VolumeIndex = ((__1 == null) ? null : ((int[])__1.Clone())), Volumes = __2 }; try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.TryPromoteCosmeticRarity(__instance, ref __0); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] コスメrarity昇格を縮退しました: " + ex.GetType().Name)); } } [HarmonyPostfix] private static void Postfix(int[] __1, List[] __2, CosmeticSpawnState __state) { try { StandardGameApi.TrackCosmeticVolumeDiff(__state?.VolumeIndex, __1, __2); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] コスメVolume追跡を縮退しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSendRPC")] internal static class ChatCommandPatch { private static void Prefix(PlayerAvatar __instance, string message) { try { AnomalyContractDirectorPlugin instance = AnomalyContractDirectorPlugin.Instance; if (instance == null || !instance.EventRuntime.HandleChatCommand(__instance, message)) { instance?.Runtime.HandleChatCommand(__instance, message); } } catch (Exception ex) { Debug.LogWarning((object)("[ACD] チャットコマンドを安全に無視しました: " + ex.GetType().Name)); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class RunManagerChangeLevelPatch { [HarmonyPrefix] private static void Prefix() { LifecycleSafety.SafeReset("RunManager.ChangeLevel前"); } } [HarmonyPatch(typeof(RunManager), "RestartScene")] internal static class RunManagerRestartScenePatch { [HarmonyPrefix] private static void Prefix() { LifecycleSafety.SafeReset("シーン再開始"); } } [HarmonyPatch(typeof(SemiFunc), "OnSceneSwitch")] internal static class SemiFuncSceneSwitchPatch { [HarmonyPostfix] private static void Postfix() { LifecycleSafety.SafeReset("SemiFunc.OnSceneSwitch後"); } } internal static class LifecycleSafety { internal static void SafeReset(string reason) { try { AnomalyContractDirectorPlugin.Instance?.EventRuntime.ResetRoomState(reason); AnomalyContractDirectorPlugin.Instance?.Runtime.ResetRoomState(reason); } catch (Exception ex) { Debug.LogWarning((object)("[ACD] 遷移時cleanupを安全に縮退しました: " + ex.GetType().Name)); } } } } namespace YoneRai12.AnomalyContractDirector.Core { public enum AnomalyEventKind { MarkedEnemy = 0, PackFrenzy = 1, HellAllEnemies = 2, ReinforcementWave = 3, EscalatingThreat = 4, RelentlessRespawn = 5, GoldRush = 6, PurpleChance = 7, ValuableSurge = 8, ValuableSignal = 8 } public enum AnomalyEventCategory { Enemy, Loot } public enum AnomalyEventPhase { Idle, Pending, Active, Completed, Cancelled, Failed, TimedOut, Reset } public enum AnomalyEventCapability { ExistingEnemyInvestigate, ExistingValuableDiscover, EnemyHealthMutation, EnemySpeedMutation, EnemyDamageMutation, EnemySizeMutation, NetworkEnemyPoolRespawn, CosmeticRarityUpgrade, CurrencyMutation } public sealed class AnomalyEventDefinition { public AnomalyEventKind Kind { get; } public string Id { get; } public string DisplayName { get; } public string Description { get; } public AnomalyEventCategory Category { get; } public float Weight { get; } public float TimeoutSeconds { get; } public bool SafeFallback { get; } public string SafetyNote { get; } public AnomalyEventDefinition(AnomalyEventKind kind, string id, string displayName, string description, AnomalyEventCategory category, float weight, float timeoutSeconds, bool safeFallback, string safetyNote) { Kind = kind; Id = id; DisplayName = displayName; Description = description; Category = category; Weight = Math.Max(0f, weight); TimeoutSeconds = Math.Max(1f, timeoutSeconds); SafeFallback = safeFallback; SafetyNote = safetyNote; } } public static class AnomalyEventCatalog { private static readonly IReadOnlyDictionary Definitions = new Dictionary { [AnomalyEventKind.MarkedEnemy] = new AnomalyEventDefinition(AnomalyEventKind.MarkedEnemy, "marked_enemy", "標的指定", "既存の敵1体をHP・速度倍率付きの標的として標準AIの調査を集中させる", AnomalyEventCategory.Enemy, 1f, 150f, safeFallback: true, "標準Hurt/UpdateAgentのhost-only倍率とSetInvestigateを使用。即死ダメージは縮退"), [AnomalyEventKind.PackFrenzy] = new AnomalyEventDefinition(AnomalyEventKind.PackFrenzy, "pack_frenzy", "同種群れの接近", "存在する同種の敵をHP・速度倍率付きで最大設定数まで同じ地点へ誘導する", AnomalyEventCategory.Enemy, 1f, 150f, safeFallback: true, "同一種のViewIDだけに標準Hurt/UpdateAgent倍率を適用し、独自同期は追加しない"), [AnomalyEventKind.HellAllEnemies] = new AnomalyEventDefinition(AnomalyEventKind.HellAllEnemies, "hell_all_enemies", "全敵地獄", "現在生存している既存敵全体をHP・速度倍率付きでプレイヤー側へ段階的に誘導する", AnomalyEventCategory.Enemy, 0.65f, 150f, safeFallback: true, "ViewIDレジストリ対象だけを標準Hurt/UpdateAgentで倍率化し、healthCurrentは直書きしない"), [AnomalyEventKind.ReinforcementWave] = new AnomalyEventDefinition(AnomalyEventKind.ReinforcementWave, "reinforcement_wave", "再誘導ウェーブ", "生成前に既存EnemySetupを通常プールへ追加し、生成後は未Spawned EnemyParentを標準再出現させて増援する", AnomalyEventCategory.Enemy, 0.8f, 150f, safeFallback: true, "生成済みEnemyParentの標準LogicとDespawnedTimerSetだけを使う"), [AnomalyEventKind.EscalatingThreat] = new AnomalyEventDefinition(AnomalyEventKind.EscalatingThreat, "escalating_threat", "段階増加する脅威", "生成前の既存EnemySetup追加と、生成済みEnemyParentプールの再出現で同時生存目標を段階的に増やす", AnomalyEventCategory.Enemy, 0.9f, 180f, safeFallback: true, "新規Prefabを作らず、標準生成分込みの追加実体・再出現要求とも100体を超えない"), [AnomalyEventKind.RelentlessRespawn] = new AnomalyEventDefinition(AnomalyEventKind.RelentlessRespawn, "relentless_respawn", "執拗な再出現", "生成前に既存EnemySetupを追加し、死亡や減少後も一定間隔で生成済みEnemyParentを安全上限まで補充する", AnomalyEventCategory.Enemy, 0.75f, 180f, safeFallback: true, "標準DespawnedTimerSetとEnemyParent Logicだけを使い、キャンセル後の既存敵は破棄しない"), [AnomalyEventKind.GoldRush] = new AnomalyEventDefinition(AnomalyEventKind.GoldRush, "gold_rush", "金コスメ箱の気配", "通常のコスメ箱生成数を増やさず、標準生成のrarityをUltraRareへ確率昇格する", AnomalyEventCategory.Loot, 0.6f, 45f, safeFallback: true, "ValuableDirector.SpawnCosmeticWorldObjectのrarity引数だけをhost Prefixで変更"), [AnomalyEventKind.PurpleChance] = new AnomalyEventDefinition(AnomalyEventKind.PurpleChance, "purple_chance", "紫コスメ箱の気配", "通常のコスメ箱生成数を増やさず、標準生成のrarityをRareへ確率昇格する", AnomalyEventCategory.Loot, 0.35f, 45f, safeFallback: true, "ValuableDirector.SpawnCosmeticWorldObjectのrarity引数だけをhost Prefixで変更"), [AnomalyEventKind.ValuableSurge] = new AnomalyEventDefinition(AnomalyEventKind.ValuableSurge, "valuable_surge", "貴重品大量発生", "現行レベルのValuablePresetsと空きValuableVolumeから既存貴重品を追加生成する", AnomalyEventCategory.Loot, 0.7f, 45f, safeFallback: true, "ValuableDirector.SpawnValuableの標準RoomObject経路を使い、通貨・色分類は行わない") }; public static IReadOnlyList All { get; } = new List { Definitions[AnomalyEventKind.MarkedEnemy], Definitions[AnomalyEventKind.PackFrenzy], Definitions[AnomalyEventKind.HellAllEnemies], Definitions[AnomalyEventKind.ReinforcementWave], Definitions[AnomalyEventKind.EscalatingThreat], Definitions[AnomalyEventKind.RelentlessRespawn], Definitions[AnomalyEventKind.GoldRush], Definitions[AnomalyEventKind.PurpleChance], Definitions[AnomalyEventKind.ValuableSurge] }; public static AnomalyEventDefinition Get(AnomalyEventKind kind) { if (!Definitions.TryGetValue(kind, out AnomalyEventDefinition value)) { return Definitions[AnomalyEventKind.MarkedEnemy]; } return value; } public static bool TryParse(string value, out AnomalyEventKind kind) { kind = AnomalyEventKind.MarkedEnemy; if (string.IsNullOrWhiteSpace(value)) { return false; } string text = value.Trim().ToLowerInvariant(); foreach (AnomalyEventDefinition item in All) { if (item.Id == text || item.DisplayName == value.Trim()) { kind = item.Kind; return true; } } switch (text) { case "marked": case "target": case "enemy": case "標的": return Set(AnomalyEventKind.MarkedEnemy, out kind); case "群れ": case "pack": return Set(AnomalyEventKind.PackFrenzy, out kind); case "地獄": case "hell": case "all": return Set(AnomalyEventKind.HellAllEnemies, out kind); case "wave": case "ウェーブ": case "reinforcement": return Set(AnomalyEventKind.ReinforcementWave, out kind); case "増加": case "escalate": case "escalating": return Set(AnomalyEventKind.EscalatingThreat, out kind); case "再出現": case "relentless": case "respawn": return Set(AnomalyEventKind.RelentlessRespawn, out kind); case "gold": case "金": return Set(AnomalyEventKind.GoldRush, out kind); case "purple": case "紫": return Set(AnomalyEventKind.PurpleChance, out kind); case "signal": case "surge": case "loot": case "貴重品": case "valuable": return Set(AnomalyEventKind.ValuableSurge, out kind); default: return false; } } private static bool Set(AnomalyEventKind value, out AnomalyEventKind kind) { kind = value; return true; } } public sealed class AnomalyEventSnapshot { public AnomalyEventPhase Phase { get; } public AnomalyEventKind? Kind { get; } public float ElapsedSeconds { get; } public float TimeoutSeconds { get; } public float StartDelaySeconds { get; } public int PlayerCount { get; } public string Reason { get; } public bool IsTerminal { get { if (Phase != AnomalyEventPhase.Completed && Phase != AnomalyEventPhase.Cancelled && Phase != AnomalyEventPhase.Failed) { return Phase == AnomalyEventPhase.TimedOut; } return true; } } public AnomalyEventSnapshot(AnomalyEventPhase phase, AnomalyEventKind? kind, float elapsedSeconds, float timeoutSeconds, float startDelaySeconds, int playerCount, string reason) { Phase = phase; Kind = kind; ElapsedSeconds = elapsedSeconds; TimeoutSeconds = timeoutSeconds; StartDelaySeconds = startDelaySeconds; PlayerCount = playerCount; Reason = reason; } } public sealed class AnomalyEventStateMachine { private AnomalyEventDefinition? definition; private AnomalyEventPhase phase; private AnomalyEventKind? kind; private float elapsedSeconds; private float startDelaySeconds; private int playerCount; public AnomalyEventSnapshot Snapshot => CreateSnapshot(string.Empty); public bool IsPending => phase == AnomalyEventPhase.Pending; public bool IsActive => phase == AnomalyEventPhase.Active; public bool IsTerminal { get { if (phase != AnomalyEventPhase.Completed && phase != AnomalyEventPhase.Cancelled && phase != AnomalyEventPhase.Failed) { return phase == AnomalyEventPhase.TimedOut; } return true; } } public event Action? StateChanged; public bool BeginPending(AnomalyEventKind selectedKind, int currentPlayerCount, float delaySeconds, string reason) { if (phase != AnomalyEventPhase.Idle && phase != AnomalyEventPhase.Reset && !IsTerminal) { return false; } definition = AnomalyEventCatalog.Get(selectedKind); kind = definition.Kind; playerCount = Math.Max(1, currentPlayerCount); elapsedSeconds = 0f; startDelaySeconds = Math.Max(0f, delaySeconds); phase = AnomalyEventPhase.Pending; RaiseChanged(reason); return true; } public bool Tick(float deltaSeconds) { if ((!IsPending && !IsActive) || deltaSeconds <= 0f) { return false; } elapsedSeconds = Math.Max(0f, elapsedSeconds + deltaSeconds); if (IsPending && elapsedSeconds >= startDelaySeconds) { phase = AnomalyEventPhase.Active; elapsedSeconds = 0f; RaiseChanged("開始遅延が終了しました"); return true; } if (IsActive && definition != null && elapsedSeconds >= definition.TimeoutSeconds) { phase = AnomalyEventPhase.TimedOut; RaiseChanged("イベント制限時間切れ"); return true; } return false; } public bool Complete(string reason) { if (!IsActive) { return false; } phase = AnomalyEventPhase.Completed; RaiseChanged(reason); return true; } public bool Cancel(string reason) { if (!IsPending && !IsActive) { return false; } phase = AnomalyEventPhase.Cancelled; RaiseChanged(reason); return true; } public bool Fail(string reason) { if (!IsPending && !IsActive) { return false; } phase = AnomalyEventPhase.Failed; RaiseChanged(reason); return true; } public void Reset(string reason) { definition = null; kind = null; phase = AnomalyEventPhase.Reset; elapsedSeconds = 0f; startDelaySeconds = 0f; playerCount = 0; RaiseChanged(reason); phase = AnomalyEventPhase.Idle; RaiseChanged("イベント待機状態へ戻りました"); } private AnomalyEventSnapshot CreateSnapshot(string reason) { return new AnomalyEventSnapshot(phase, kind, elapsedSeconds, definition?.TimeoutSeconds ?? 0f, startDelaySeconds, playerCount, reason); } private void RaiseChanged(string reason) { this.StateChanged?.Invoke(CreateSnapshot(reason)); } } public static class AnomalySafetyLimits { public const int HardEnemyLimit = 100; public const int MaxPhotonViewIds = 1000; public const int PhotonViewReserve = 100; public static int ClampHard100(int value) { return Math.Max(0, Math.Min(100, value)); } public static float ClampFinite(float value, float minimum, float maximum, float fallback) { if (float.IsNaN(value) || float.IsInfinity(value)) { value = fallback; } if (minimum > maximum) { float num = maximum; maximum = minimum; minimum = num; } return Math.Max(minimum, Math.Min(maximum, value)); } public static int EnemyPoolBudget(int standardUnits, int requestedUnits) { int num = ClampHard100(standardUnits); int val = ClampHard100(requestedUnits); return Math.Max(0, Math.Min(val, 100 - num)); } public static int PhotonPoolBudget(int currentViewCount, int requestedUnits) { int num = ClampHard100(requestedUnits); int num2 = Math.Min(900, 900); if (currentViewCount < 0 || currentViewCount >= num2 || num == 0) { return 0; } int val = Math.Max(0, (num2 - 1 - currentViewCount) / 2); return Math.Max(0, Math.Min(num, val)); } public static int EnemyPoolBudgetWithPhoton(int standardUnits, int requestedUnits, int currentViewCount) { int num = ClampHard100(standardUnits); int num2 = EnemyPoolBudget(num, requestedUnits); if (num2 == 0) { return 0; } int num3 = PhotonPoolBudget(currentViewCount, 100); int val = Math.Max(0, num3 - num); return Math.Min(num2, val); } public static int EnemyRuntimeReactivationBudget(int standardUnits, int eventRequestsSoFar, int configuredEventRequestLimit, int requestedNow) { int num = ClampHard100(standardUnits); int num2 = ClampHard100(eventRequestsSoFar); int num3 = ClampHard100(configuredEventRequestLimit); int val = ClampHard100(requestedNow); int val2 = Math.Max(0, num3 - num2); int val3 = Math.Max(0, 100 - num - num2); return Math.Min(val, Math.Min(val2, val3)); } } public enum ContractKind { CursedCargo, BountyEvidence, BlackoutRecovery, ExtractionDefense, DualActivation } public enum ContractPhase { Idle, Selecting, Active, Succeeded, Failed, TimedOut, Reset } public enum DeviceKind { MedicalCore, PortableRadar, NoiseBeacon } public enum DeviceRoleState { Unassigned, Registered, Active, Spent, Released } public sealed class ContractDefinition { public ContractKind Kind { get; } public string Id { get; } public string DisplayName { get; } public string Description { get; } public float TimeoutSeconds { get; } public int BaseUnits { get; } public int MaxUnits { get; } public ContractDefinition(ContractKind kind, string id, string displayName, string description, float timeoutSeconds, int baseUnits, int maxUnits) { Kind = kind; Id = id; DisplayName = displayName; Description = description; TimeoutSeconds = Math.Max(1f, timeoutSeconds); BaseUnits = Math.Max(1, baseUnits); MaxUnits = Math.Max(BaseUnits, maxUnits); } public int ScaleUnits(int playerCount) { int num = Math.Max(1, playerCount); int num2 = Math.Max(0, num - 1); return Math.Min(MaxUnits, BaseUnits + num2); } } public static class ContractCatalog { private static readonly IReadOnlyDictionary Definitions = new Dictionary { [ContractKind.CursedCargo] = new ContractDefinition(ContractKind.CursedCargo, "cursed_cargo", "呪われた貨物", "呪い箱を抽出地点へ運び、短時間その場で安定化する", 360f, 1, 1), [ContractKind.BountyEvidence] = new ContractDefinition(ContractKind.BountyEvidence, "bounty_evidence", "賞金首+証拠回収", "指定された敵を倒し、残された証拠品を抽出する", 420f, 2, 2), [ContractKind.BlackoutRecovery] = new ContractDefinition(ContractKind.BlackoutRecovery, "blackout_recovery", "ブラックアウト復旧", "既存バッテリーを復旧地点へ運び、暗所から段階的に復旧する", 360f, 2, 3), [ContractKind.ExtractionDefense] = new ContractDefinition(ContractKind.ExtractionDefense, "extraction_defense", "抽出地点防衛", "未完了の抽出地点付近で短い防衛時間を耐える", 300f, 1, 1), [ContractKind.DualActivation] = new ContractDefinition(ContractKind.DualActivation, "dual_activation", "二地点同時起動", "離れた二つの地点を同時に起動し、契約を完了する", 300f, 2, 2) }; public static IReadOnlyList All { get; } = new List { Definitions[ContractKind.CursedCargo], Definitions[ContractKind.BountyEvidence], Definitions[ContractKind.BlackoutRecovery], Definitions[ContractKind.ExtractionDefense], Definitions[ContractKind.DualActivation] }; public static ContractDefinition Get(ContractKind kind) { if (!Definitions.TryGetValue(kind, out ContractDefinition value)) { return Definitions[ContractKind.CursedCargo]; } return value; } } public sealed class ContractSnapshot { public ContractPhase Phase { get; } public ContractKind? Kind { get; } public float ElapsedSeconds { get; } public float TimeoutSeconds { get; } public int Progress { get; } public int RequiredUnits { get; } public int PlayerCount { get; } public string Reason { get; } public bool IsTerminal { get { if (Phase != ContractPhase.Succeeded && Phase != ContractPhase.Failed) { return Phase == ContractPhase.TimedOut; } return true; } } public ContractSnapshot(ContractPhase phase, ContractKind? kind, float elapsedSeconds, float timeoutSeconds, int progress, int requiredUnits, int playerCount, string reason) { Phase = phase; Kind = kind; ElapsedSeconds = elapsedSeconds; TimeoutSeconds = timeoutSeconds; Progress = progress; RequiredUnits = requiredUnits; PlayerCount = playerCount; Reason = reason; } } public sealed class DeviceRoleRecord { public DeviceKind Kind { get; } public int ViewId { get; } public string ItemName { get; } public DeviceRoleState State { get; set; } public DeviceRoleRecord(DeviceKind kind, int viewId, string itemName, DeviceRoleState state) { Kind = kind; ViewId = viewId; ItemName = itemName; State = state; } } public sealed class ContractStateMachine { private ContractDefinition? definition; private ContractPhase phase; private ContractKind? kind; private float elapsedSeconds; private int progress; private int requiredUnits; private int playerCount; public ContractSnapshot Snapshot => CreateSnapshot(string.Empty); public bool IsActive => phase == ContractPhase.Active; public bool IsSelecting => phase == ContractPhase.Selecting; public bool IsTerminal { get { if (phase != ContractPhase.Succeeded && phase != ContractPhase.Failed) { return phase == ContractPhase.TimedOut; } return true; } } public event Action? StateChanged; public bool BeginSelection(int currentPlayerCount, string reason) { if (phase != ContractPhase.Idle && phase != ContractPhase.Reset && !IsTerminal) { return false; } playerCount = Math.Max(1, currentPlayerCount); definition = null; kind = null; elapsedSeconds = 0f; progress = 0; requiredUnits = 0; phase = ContractPhase.Selecting; RaiseChanged(reason); return true; } public bool Start(ContractKind selectedKind, int currentPlayerCount, string reason) { if (phase != ContractPhase.Selecting) { return false; } definition = ContractCatalog.Get(selectedKind); kind = definition.Kind; playerCount = Math.Max(1, currentPlayerCount); elapsedSeconds = 0f; progress = 0; requiredUnits = definition.ScaleUnits(playerCount); phase = ContractPhase.Active; RaiseChanged(reason); return true; } public bool AddProgress(int units, string reason) { if (!IsActive || units <= 0) { return false; } int num = progress; progress = Math.Min(requiredUnits, progress + units); if (progress == num) { return false; } RaiseChanged(reason); if (progress >= requiredUnits) { Complete("必要数を満たしました"); } return true; } public bool SetProgress(int units, string reason) { if (!IsActive) { return false; } int num = Math.Max(0, Math.Min(requiredUnits, units)); if (num == progress) { return false; } progress = num; RaiseChanged(reason); if (progress >= requiredUnits) { Complete("必要数を満たしました"); } return true; } public bool Tick(float deltaSeconds) { if (!IsActive || deltaSeconds <= 0f) { return false; } elapsedSeconds = Math.Max(0f, elapsedSeconds + deltaSeconds); if (definition != null && elapsedSeconds >= definition.TimeoutSeconds) { phase = ContractPhase.TimedOut; RaiseChanged("制限時間切れ"); return true; } return false; } public bool Complete(string reason) { if (!IsActive) { return false; } phase = ContractPhase.Succeeded; RaiseChanged(reason); return true; } public bool Fail(string reason) { if (!IsActive && !IsSelecting) { return false; } phase = ContractPhase.Failed; RaiseChanged(reason); return true; } public void Reset(string reason) { definition = null; kind = null; phase = ContractPhase.Reset; elapsedSeconds = 0f; progress = 0; requiredUnits = 0; playerCount = 0; RaiseChanged(reason); phase = ContractPhase.Idle; RaiseChanged("待機状態へ戻りました"); } private ContractSnapshot CreateSnapshot(string reason) { float timeoutSeconds = definition?.TimeoutSeconds ?? 0f; return new ContractSnapshot(phase, kind, elapsedSeconds, timeoutSeconds, progress, requiredUnits, playerCount, reason); } private void RaiseChanged(string reason) { this.StateChanged?.Invoke(CreateSnapshot(reason)); } } }