Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RepoHostSuite v0.1.0
BepInEx/plugins/RepoHostSuite/YoneRai12.AnomalyContractDirector.dll
Decompiled 12 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using YoneRai12.AnomalyContractDirector.Core; using YoneRai12.AnomalyContractDirector.Runtime; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("YoneRai12.AnomalyContractDirector")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0")] [assembly: AssemblyProduct("YoneRai12.AnomalyContractDirector")] [assembly: AssemblyTitle("YoneRai12.AnomalyContractDirector")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } } namespace YoneRai12.AnomalyContractDirector { [BepInPlugin("YoneRai12.AnomalyContractDirector", "R.E.P.O. Anomaly Contract Director", "0.2.0")] public sealed class AnomalyContractDirectorPlugin : BaseUnityPlugin { internal const string PluginGuid = "YoneRai12.AnomalyContractDirector"; internal const string PluginName = "R.E.P.O. Anomaly Contract Director"; internal const string PluginVersion = "0.2.0"; private Harmony harmony; internal static AnomalyContractDirectorPlugin Instance { get; private set; } internal ContractDirectorRuntime Runtime { get; private set; } internal AnomalyEventRuntime EventRuntime { get; private set; } private void Awake() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown Instance = this; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; DirectorConfig directorConfig = new DirectorConfig(((BaseUnityPlugin)this).Config); Runtime = ((Component)this).gameObject.AddComponent<ContractDirectorRuntime>(); Runtime.Initialize(directorConfig, ((BaseUnityPlugin)this).Logger); EventRuntime = ((Component)this).gameObject.AddComponent<AnomalyEventRuntime>(); EventRuntime.Initialize(directorConfig, ((BaseUnityPlugin)this).Logger); harmony = new Harmony("YoneRai12.AnomalyContractDirector"); harmony.PatchAll(Assembly.GetExecutingAssembly()); ((Component)this).gameObject.AddComponent<DirectorNetworkCallbacks>(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"R.E.P.O. Anomaly Contract Director v0.2.0 loaded. Host-only, vanilla-client compatible mode."); } private void OnDestroy() { EventRuntime?.ResetRoomState("MODアンロード"); Runtime?.ResetRoomState("MODアンロード"); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } if (Instance == this) { Instance = null; } } } internal sealed class DirectorNetworkCallbacks : MonoBehaviourPunCallbacks { public override void OnJoinedRoom() { AnomalyContractDirectorPlugin.Instance?.Runtime.ResetRoomState("ルーム参加"); AnomalyContractDirectorPlugin.Instance?.EventRuntime.ResetRoomState("ルーム参加"); } public override void OnLeftRoom() { AnomalyContractDirectorPlugin.Instance?.Runtime.ResetRoomState("ルーム退出"); AnomalyContractDirectorPlugin.Instance?.EventRuntime.ResetRoomState("ルーム退出"); } public override void OnMasterClientSwitched(Player newMasterClient) { AnomalyContractDirectorPlugin.Instance?.Runtime.OnMasterClientChanged(); AnomalyContractDirectorPlugin.Instance?.EventRuntime.OnMasterClientChanged(); } public override void OnPlayerEnteredRoom(Player newPlayer) { AnomalyContractDirectorPlugin.Instance?.Runtime.NotifyCurrentStateToRoom(); AnomalyContractDirectorPlugin.Instance?.EventRuntime.NotifyCurrentStateToRoom(); } } } namespace YoneRai12.AnomalyContractDirector.Runtime { internal sealed class AnomalyEventRuntime : MonoBehaviour { private sealed class EnemyPoolExpansionTransaction { internal EnemyDirector Director { get; } internal List<EnemySetup> BaseList { get; } internal List<EnemySetup> CurrentList { get; } internal int OriginalBaseCount { get; } internal int OriginalCurrentCount { get; } internal int OriginalEnemyListIndex { get; } internal int AddedLogical { get; } internal IReadOnlyList<EnemySetup> AddedSetups { get; } internal EnemyPoolExpansionTransaction(EnemyDirector director, List<EnemySetup> baseList, List<EnemySetup> currentList, int originalBaseCount, int originalCurrentCount, int originalEnemyListIndex, int addedLogical, IReadOnlyList<EnemySetup> addedSetups) { Director = director; BaseList = baseList; CurrentList = currentList; OriginalBaseCount = originalBaseCount; OriginalCurrentCount = originalCurrentCount; OriginalEnemyListIndex = originalEnemyListIndex; AddedLogical = addedLogical; AddedSetups = addedSetups; } } private readonly AnomalyEventStateMachine stateMachine = new AnomalyEventStateMachine(); private readonly List<EnemyParent> targets = new List<EnemyParent>(); private readonly Random random = new Random(); private DirectorConfig config; private ManualLogSource logger; private AnomalyEventKind? previousKind; private AnomalyEventKind? preselectedKind; private AnomalyEventKind? markedKind; private EnemyParent? markedEnemy; private bool levelActive; private bool levelEventDecided; private bool preRollCompleted; private bool preRollNoEvent; private bool enemyAmountSetupObserved; private bool enemyAmountSetupInProgress; private bool enemyPoolExpansionApplied; private bool lootApplied; private bool valuableSetupObserved; private bool valuableSetupFinalized; private bool setupTopUpProcessed; private bool setupLootApplied; private int setupLootSpawned; private int cosmeticPromotionCount; private float levelStartedAt; private float nextPollTime; private float lastPollTime; private float nextActionTime; private float nextSpawnTime; private float enemyDiscoveryDeadline; private int initialEnemyCount; private int totalSpawnRequestCount; private int preExpandedEnemyUnits; private int standardEnemyUnits; private ValuableDirector? valuableSetupDirector; private EnemyPoolExpansionTransaction? pendingEnemyPoolExpansion; internal AnomalyEventStateMachine StateMachine => stateMachine; internal bool IsLevelActive => levelActive; internal void Initialize(DirectorConfig directorConfig, ManualLogSource manualLogSource) { config = directorConfig; logger = manualLogSource; stateMachine.StateChanged += OnStateChanged; ((Behaviour)this).enabled = true; } private void Update() { if (config == null || Time.unscaledTime < nextPollTime) { return; } float unscaledTime = Time.unscaledTime; float num = Clamp(config.AnomalyEventPollIntervalSeconds.Value, 0.25f, 1f); float deltaSeconds = ((lastPollTime > 0f) ? Clamp(unscaledTime - lastPollTime, 0.01f, 5f) : num); lastPollTime = unscaledTime; nextPollTime = unscaledTime + num; if (!config.Enabled.Value || !config.EnableAnomalyEvents.Value || !IsHost()) { if (levelActive || pendingEnemyPoolExpansion != null || preRollCompleted || valuableSetupObserved) { ResetRoomState("異常イベント設定またはホスト権限が失われました"); } return; } bool flag; bool flag2; bool flag3; try { flag = SemiFunc.RunIsLevel(); flag2 = SemiFunc.RunIsShop(); flag3 = SemiFunc.RunIsLobby(); } catch (Exception ex) { LogDebug("異常イベントのゲーム状態読み取り失敗: " + ex.GetType().Name); return; } if (!flag || flag2 || flag3) { if (levelActive) { ResetRoomState(flag2 ? "ショップ遷移" : (flag3 ? "ロビー遷移" : "レベル外遷移")); } return; } if (!levelActive) { if (!IsLevelReady()) { return; } BeginLevel(); } if (!levelActive || !IsLevelReady()) { if (levelActive) { ResetRoomState("レベル遷移準備"); } return; } try { if (stateMachine.IsPending || stateMachine.IsActive) { stateMachine.Tick(deltaSeconds); if (stateMachine.IsActive) { EvaluateActiveEvent(deltaSeconds); } } } catch (Exception arg) { ManualLogSource obj = logger; if (obj != null) { obj.LogWarning((object)$"[ACD] 異常イベント内の例外を封じ込めました: {arg}"); } if (stateMachine.IsPending || stateMachine.IsActive) { stateMachine.Fail("異常イベントの安全な監視に失敗しました"); } } } internal bool HandleChatCommand(PlayerAvatar sender, string message) { if (string.IsNullOrWhiteSpace(message)) { return false; } string text = message.Trim(); if (!text.StartsWith("!acd", StringComparison.OrdinalIgnoreCase)) { return false; } string[] array = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2 || !string.Equals(array[1], "event", StringComparison.OrdinalIgnoreCase)) { return false; } if (!IsHost() || (Object)(object)sender == (Object)null || (Object)(object)sender != (Object)(object)SafeLocalPlayer()) { return true; } if (array.Length == 2 || string.Equals(array[2], "help", StringComparison.OrdinalIgnoreCase)) { SendRoomMessage("!acd event random | !acd event <標的/群れ/地獄/ウェーブ/増加/再出現/金/紫/貴重品> | !acd event status | !acd event list | !acd event cancel"); return true; } string text2 = array[2].ToLowerInvariant(); switch (text2) { case "status": SendRoomMessage(FormatStatus()); return true; case "list": { string text3 = string.Join(" / ", AnomalyEventCatalog.All.Select((AnomalyEventDefinition definition, int index) => $"{index + 1}:{definition.DisplayName}({definition.Id})")); SendRoomMessage("異常イベント候補: " + text3); return true; } case "cancel": case "off": if (!stateMachine.Cancel("ホストが異常イベントを中止しました")) { SendRoomMessage("現在、発動中または待機中の異常イベントはありません。"); } levelEventDecided = true; return true; default: { if (!config.AnomalyEventManualEnabled.Value) { SendRoomMessage("異常イベントの手動指定は設定で無効です。"); return true; } if (!levelActive || !IsLevelReady()) { SendRoomMessage("異常イベントは生成済みのレベル内でだけ指定できます。"); return true; } AnomalyEventKind kind; int result; if (text2 == "random") { if (!TryChooseEvent(out kind, allowPrevious: true)) { SendRoomMessage("有効な異常イベント候補がありません。"); return true; } } else if (int.TryParse(text2, out result) && result >= 1 && result <= AnomalyEventCatalog.All.Count) { kind = AnomalyEventCatalog.All[result - 1].Kind; } else if (!AnomalyEventCatalog.TryParse(array[2], out kind)) { SendRoomMessage("候補を解釈できません。!acd event list で確認してください。"); return true; } if (!IsEventEnabled(kind)) { SendRoomMessage("その異常イベントは設定で無効です。"); return true; } if ((kind == AnomalyEventKind.GoldRush || kind == AnomalyEventKind.PurpleChance || kind == AnomalyEventKind.ValuableSurge) && valuableSetupFinalized) { SendRoomMessage("金・紫コスメ箱と貴重品大量発生は、標準生成前のpre-rollが必要です。このレベルでは手動指定できません。"); return true; } ScheduleEvent(kind, "ホストのチャット手動指定", manual: true); return true; } } } internal void ResetRoomState(string reason) { TryRollbackUnconsumedEnemyPoolExpansion(reason); ((MonoBehaviour)this).StopAllCoroutines(); levelActive = false; levelEventDecided = false; preRollCompleted = false; preRollNoEvent = false; enemyAmountSetupObserved = false; enemyAmountSetupInProgress = false; enemyPoolExpansionApplied = false; lootApplied = false; valuableSetupObserved = false; valuableSetupFinalized = false; setupTopUpProcessed = false; setupLootApplied = false; setupLootSpawned = 0; valuableSetupDirector = null; preselectedKind = null; cosmeticPromotionCount = 0; markedKind = null; markedEnemy = null; targets.Clear(); initialEnemyCount = 0; totalSpawnRequestCount = 0; preExpandedEnemyUnits = 0; standardEnemyUnits = 0; EnemyMutationRegistry.Clear(); StandardGameApi.ResetRuntimeTrackers(); nextActionTime = 0f; nextSpawnTime = 0f; enemyDiscoveryDeadline = 0f; stateMachine.Reset(reason); LogDebug("異常イベント状態を解放: " + reason); } internal void OnMasterClientChanged() { ResetRoomState("マスター変更"); } internal void NotifyCurrentStateToRoom() { if (IsHost() && levelActive) { AnomalyEventSnapshot snapshot = stateMachine.Snapshot; if (snapshot.Kind.HasValue) { AnomalyEventDefinition anomalyEventDefinition = AnomalyEventCatalog.Get(snapshot.Kind.Value); SendRoomMessage($"途中参加者向け再通知: 異常イベント {anomalyEventDefinition.DisplayName} / {snapshot.Phase}"); } } } internal void OnEnemyAmountSetupPrefix() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Invalid comparison between Unknown and I4 //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Invalid comparison between Unknown and I4 //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Invalid comparison between Unknown and I4 //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Invalid comparison between Unknown and I4 //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Invalid comparison between Unknown and I4 if (config == null || enemyAmountSetupObserved || !IsHost()) { return; } try { if (!config.Enabled.Value || !config.EnableAnomalyEvents.Value || !SemiFunc.RunIsLevel()) { return; } LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.Level == (Object)null || instance.Generated || ((int)instance.State != 13 && (int)instance.State != 14)) { return; } GameDirector instance2 = GameDirector.instance; if ((Object)(object)instance2 == (Object)null || (int)instance2.currentState == 3 || (int)instance2.currentState == 4 || (int)instance2.currentState == 5 || (int)instance2.currentState == 6 || (int)instance2.currentState == 7) { return; } } catch { return; } enemyAmountSetupObserved = true; enemyAmountSetupInProgress = true; if (!preRollCompleted) { PreRollEvent("EnemyDirector.AmountSetup前"); } } internal void OnEnemyAmountSetupPostfix() { if (!enemyAmountSetupObserved) { return; } try { if (config != null && config.Enabled.Value && config.EnableAnomalyEvents.Value && IsHost() && !enemyPoolExpansionApplied && preselectedKind.HasValue) { ExpandEnemyPoolBeforeLevelGeneration(preselectedKind.Value); } } catch (Exception ex) { LogDebug("通常敵プール追加を安全に縮退: " + ex.GetType().Name); } finally { enemyAmountSetupInProgress = false; } } internal void OnEnemyGetEnemyPrefix(EnemyDirector director) { EnemyPoolExpansionTransaction enemyPoolExpansionTransaction = pendingEnemyPoolExpansion; if (enemyPoolExpansionTransaction != null && enemyPoolExpansionTransaction.Director == director) { pendingEnemyPoolExpansion = null; LogDebug("生成前敵Setup追加を標準GetEnemyへcommit"); } } private void PreRollEvent(string reason) { preRollCompleted = true; if (!TryChooseEvent(out var selected, allowPrevious: false)) { preRollNoEvent = true; levelEventDecided = true; LogDebug(reason + ": 今レベルは異常なし"); } else { preselectedKind = selected; levelEventDecided = true; LogDebug($"{reason}: {selected}"); } } internal void OnValuableSetupHostPrefix(ValuableDirector director) { if (config == null || valuableSetupObserved || !IsHost()) { return; } try { if (!config.Enabled.Value || !config.EnableAnomalyEvents.Value || !SemiFunc.RunIsLevel()) { return; } } catch { return; } valuableSetupObserved = true; valuableSetupDirector = director; if (preRollCompleted) { LogDebug("Valuable setup開始前: 既存の生成前pre-rollを引き継ぎます"); } else { PreRollEvent("Valuable setup開始前"); } } internal void OnValuableSetupPrefix(ValuableDirector director) { if (config == null || setupTopUpProcessed || !IsHost()) { return; } setupTopUpProcessed = true; valuableSetupFinalized = true; if (!valuableSetupObserved || (Object)(object)valuableSetupDirector == (Object)null || valuableSetupDirector != director) { preRollCompleted = true; preRollNoEvent = true; levelEventDecided = true; LogDebug("Valuable setup開始Prefixを観測できず、setup依存イベントを安全に無効化"); } else if (preselectedKind == AnomalyEventKind.ValuableSurge) { int num = Clamp(config.LootEventRevealCount.Value, 0, 20); if (StandardGameApi.TrySpawnValuableTopUp(director, num, out int spawned, out string reason) && spawned > 0) { setupLootApplied = true; lootApplied = true; setupLootSpawned = spawned; LogDebug($"貴重品大量発生をSetupHost前に適用: {spawned}/{num}"); if (!string.IsNullOrWhiteSpace(reason)) { LogDebug("貴重品target同期警告: " + reason); } } else if (num == 0) { preRollNoEvent = true; preselectedKind = null; LogDebug("貴重品大量発生は追加数0のため無効化"); } else { preRollNoEvent = true; preselectedKind = null; LogDebug("貴重品大量発生を安全に適用できず、イベントを縮退: " + reason); } } else { LogDebug("Valuable setup完了Prefix: 貴重品top-up対象ではありません"); } } internal void TryPromoteCosmeticRarity(ValuableDirector director, ref Rarity rarity) { if (!IsHost() || (Object)(object)director == (Object)null || (Object)(object)valuableSetupDirector == (Object)null || valuableSetupDirector != director || !preselectedKind.HasValue || !preRollCompleted || !valuableSetupObserved || valuableSetupFinalized) { return; } float num = preselectedKind.Value switch { AnomalyEventKind.GoldRush => Clamp(config.CosmeticGoldUpgradeChancePercent.Value, 0f, 100f), AnomalyEventKind.PurpleChance => Clamp(config.CosmeticPurpleUpgradeChancePercent.Value, 0f, 100f), _ => 0f, }; if (num <= 0f || random.NextDouble() * 100.0 > (double)num) { return; } if (preselectedKind.Value == AnomalyEventKind.GoldRush) { if ((int)rarity != 3) { rarity = (Rarity)3; cosmeticPromotionCount++; } } else if (preselectedKind.Value == AnomalyEventKind.PurpleChance && ((int)rarity == 0 || (int)rarity == 1)) { rarity = (Rarity)2; cosmeticPromotionCount++; } } private void BeginLevel() { levelActive = true; levelEventDecided = false; levelStartedAt = Time.unscaledTime; lootApplied = setupLootApplied; markedEnemy = null; targets.Clear(); initialEnemyCount = 0; int num = StandardGameApi.CountPreparedEnemyParents(); standardEnemyUnits = Math.Max(standardEnemyUnits, Math.Max(0, num - preExpandedEnemyUnits)); totalSpawnRequestCount = Math.Min(100, preExpandedEnemyUnits); AnomalyEventKind? anomalyEventKind = preselectedKind; if (!anomalyEventKind.HasValue) { if (preRollCompleted && preRollNoEvent) { preselectedKind = null; preRollCompleted = false; preRollNoEvent = false; levelEventDecided = true; SendRoomMessage("【異常抽選】このレベルは異常なしです。"); return; } if (!TryChooseEvent(out var selected, allowPrevious: false)) { levelEventDecided = true; SendRoomMessage("【異常抽選】このレベルは異常なしです。"); return; } anomalyEventKind = selected; } preselectedKind = null; preRollCompleted = false; preRollNoEvent = false; levelEventDecided = true; ScheduleEvent(anomalyEventKind.Value, "レベルごとの異常抽選", manual: false); } private void ScheduleEvent(AnomalyEventKind selected, string reason, bool manual) { if (!levelActive || !IsEventEnabled(selected)) { return; } if (stateMachine.IsPending || stateMachine.IsActive) { if (!manual) { SendRoomMessage("異常イベントはすでに待機または発動中です。"); return; } stateMachine.Cancel("手動指定で前の異常イベントを置換"); } previousKind = selected; float num = (manual ? 0f : Clamp(config.AnomalyEventStartDelaySeconds.Value, 0f, 90f)); if (stateMachine.BeginPending(selected, GetPlayerCount(), num, reason)) { LogDebug($"異常イベントを予約: {selected}, manual={manual}, delay={num:0.00}s"); } } private void EvaluateActiveEvent(float deltaSeconds) { AnomalyEventSnapshot snapshot = stateMachine.Snapshot; if (!snapshot.Kind.HasValue) { return; } float elapsedSeconds = snapshot.ElapsedSeconds; float val = Clamp(config.AnomalyEventDurationSeconds.Value, 5f, 180f); if (elapsedSeconds >= Math.Min(val, snapshot.TimeoutSeconds)) { stateMachine.Complete("異常イベント時間を終了しました"); return; } switch (snapshot.Kind.Value) { case AnomalyEventKind.MarkedEnemy: EvaluateMarkedEnemy(); break; case AnomalyEventKind.PackFrenzy: EvaluatePackFrenzy(); break; case AnomalyEventKind.HellAllEnemies: EvaluateAllEnemies(); break; case AnomalyEventKind.ReinforcementWave: EvaluateReinforcementWave(elapsedSeconds); break; case AnomalyEventKind.EscalatingThreat: EvaluateEscalatingThreat(elapsedSeconds); break; case AnomalyEventKind.RelentlessRespawn: EvaluateRelentlessRespawn(); break; case AnomalyEventKind.GoldRush: case AnomalyEventKind.PurpleChance: case AnomalyEventKind.ValuableSurge: break; } } private void PrepareActiveEvent(AnomalyEventKind kind) { markedKind = kind; targets.Clear(); markedEnemy = null; initialEnemyCount = FindLiveEnemies().Count; enemyDiscoveryDeadline = Time.unscaledTime + 12f; nextActionTime = 0f; nextSpawnTime = Time.unscaledTime; switch (kind) { case AnomalyEventKind.MarkedEnemy: case AnomalyEventKind.PackFrenzy: case AnomalyEventKind.HellAllEnemies: case AnomalyEventKind.ReinforcementWave: case AnomalyEventKind.EscalatingThreat: case AnomalyEventKind.RelentlessRespawn: RefreshEnemyTargets(kind); break; case AnomalyEventKind.GoldRush: case AnomalyEventKind.PurpleChance: case AnomalyEventKind.ValuableSurge: ApplyLootEvent(kind); break; } } private void EvaluateMarkedEnemy() { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)markedEnemy == (Object)null || !IsLiveEnemy(markedEnemy)) { if ((Object)(object)markedEnemy != (Object)null && !IsLiveEnemy(markedEnemy)) { stateMachine.Complete("標的の既存敵が倒されました"); } else if (Time.unscaledTime >= enemyDiscoveryDeadline) { stateMachine.Fail("標的にできる既存敵が見つかりませんでした"); } else { RefreshEnemyTargets(AnomalyEventKind.MarkedEnemy); } } else if (!(Time.unscaledTime < nextActionTime)) { nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f); Vector3 position = PlayerPositionOr(((Component)markedEnemy).transform.position); StandardGameApi.SetInvestigate(position, Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f)); } } private void EvaluatePackFrenzy() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) RefreshEnemyTargets(AnomalyEventKind.PackFrenzy); if (targets.Count == 0) { if (Time.unscaledTime >= enemyDiscoveryDeadline) { stateMachine.Fail("同種の既存敵が見つかりませんでした"); } } else if (!(Time.unscaledTime < nextActionTime)) { nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f); StandardGameApi.SetInvestigate(PlayerPositionOr(((Component)targets[0]).transform.position), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f)); } } private void EvaluateAllEnemies() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) RefreshEnemyTargets(AnomalyEventKind.HellAllEnemies); if (!(Time.unscaledTime < nextActionTime)) { nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f); StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f)); if (targets.Count == 0 && Time.unscaledTime >= enemyDiscoveryDeadline) { stateMachine.Fail("対象にできる既存敵が見つかりませんでした"); } } } private void EvaluateReinforcementWave(float elapsed) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) RefreshEnemyTargets(AnomalyEventKind.ReinforcementWave); MaintainEnemyPopulation(Math.Max(1, initialEnemyCount + Clamp(config.EnemyEventWaveBatchSize.Value, 1, 20)), elapsed); if (!(Time.unscaledTime < nextActionTime)) { nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f); StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f)); } } private void EvaluateEscalatingThreat(float elapsed) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) RefreshEnemyTargets(AnomalyEventKind.EscalatingThreat); float num = Clamp(config.EnemyEventEscalationStepSeconds.Value, 5f, 60f); int desired = initialEnemyCount + (int)Math.Floor(elapsed / num) * Clamp(config.EnemyEventWaveBatchSize.Value, 1, 20); MaintainEnemyPopulation(desired, elapsed); if (!(Time.unscaledTime < nextActionTime)) { nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f); StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f)); } } private void EvaluateRelentlessRespawn() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) RefreshEnemyTargets(AnomalyEventKind.RelentlessRespawn); MaintainEnemyPopulation(Clamp(config.EnemyEventRespawnTargetAlive.Value, 1, 100), 0f); if (!(Time.unscaledTime < nextActionTime)) { nextActionTime = Time.unscaledTime + Clamp(config.EnemyEventRespawnIntervalSeconds.Value, 0f, 30f); StandardGameApi.SetInvestigate(PlayerPositionOr(Vector3.zero), Clamp(config.EnemyEventInvestigationRadius.Value, 4f, 60f)); } } private void ExpandEnemyPoolBeforeLevelGeneration(AnomalyEventKind kind) { if (kind != AnomalyEventKind.PackFrenzy && kind != AnomalyEventKind.HellAllEnemies && kind != AnomalyEventKind.ReinforcementWave && kind != AnomalyEventKind.EscalatingThreat && kind != AnomalyEventKind.RelentlessRespawn) { return; } enemyPoolExpansionApplied = true; int val = Clamp(config.EnemyEventPoolAdditionalLimit.Value, 0, 100); int val2 = Math.Min(100, Clamp(config.EnemyEventTotalSpawnLimit.Value, 0, 100)); val = Math.Min(val, val2); if (val <= 0) { return; } EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null || instance.enemyList == null || instance.enemyListCurrent == null) { return; } List<EnemySetup> list = instance.enemyListCurrent.Where((EnemySetup setup) => (Object)(object)setup != (Object)null && setup.spawnObjects != null && setup.spawnObjects.Count > 0).ToList(); if (list.Count == 0) { return; } int num = instance.enemyListCurrent.Where((EnemySetup setup) => (Object)(object)setup != (Object)null && setup.spawnObjects != null).Sum((EnemySetup setup) => setup.spawnObjects.Count); standardEnemyUnits = Math.Max(standardEnemyUnits, AnomalySafetyLimits.ClampHard100(num)); val = AnomalySafetyLimits.EnemyPoolBudgetWithPhoton(num, val, StandardGameApi.CurrentPhotonViewCount()); if (val <= 0) { return; } List<EnemySetup> enemyList = instance.enemyList; List<EnemySetup> enemyListCurrent = instance.enemyListCurrent; int count = enemyList.Count; int count2 = enemyListCurrent.Count; int totalAmount = instance.totalAmount; int enemyListIndex = instance.enemyListIndex; int num2 = preExpandedEnemyUnits; int num3 = 0; int num4 = 0; List<EnemySetup> list2 = new List<EnemySetup>(); try { int num5 = 0; while (num4 < val && num3 < 100) { EnemySetup val3 = ((kind == AnomalyEventKind.PackFrenzy) ? list[0] : list[num5++ % list.Count]); int count3 = val3.spawnObjects.Count; if (count3 <= 0 || num4 + count3 > val || num4 + count3 > 100 || !StandardGameApi.CanReserveNetworkViews(num + num4 + count3)) { break; } enemyList.Add(val3); if (enemyList != enemyListCurrent) { enemyListCurrent.Add(val3); } instance.totalAmount++; num3++; num4 += count3; list2.Add(val3); if (instance.enemyListIndex < 0 || instance.enemyListIndex + instance.totalAmount > enemyList.Count) { throw new InvalidOperationException("enemyListIndexと追加後enemyListの範囲が不整合"); } } if (num3 > 0) { preExpandedEnemyUnits = Math.Min(100, num2 + num4); pendingEnemyPoolExpansion = new EnemyPoolExpansionTransaction(instance, enemyList, enemyListCurrent, count, count2, enemyListIndex, num3, list2.ToArray()); LogDebug($"生成前敵Setup追加: logical={num3}, units={num4}, kind={kind}"); } } catch (Exception ex) { if (enemyList.Count > count) { enemyList.RemoveRange(count, enemyList.Count - count); } if (enemyList != enemyListCurrent && enemyListCurrent.Count > count2) { enemyListCurrent.RemoveRange(count2, enemyListCurrent.Count - count2); } instance.totalAmount = totalAmount; preExpandedEnemyUnits = num2; pendingEnemyPoolExpansion = null; LogDebug("生成前敵Setup追加をロールバック: " + ex.GetType().Name); } } private void TryRollbackUnconsumedEnemyPoolExpansion(string reason) { EnemyPoolExpansionTransaction enemyPoolExpansionTransaction = pendingEnemyPoolExpansion; pendingEnemyPoolExpansion = null; if (enemyPoolExpansionTransaction == null) { return; } try { EnemyDirector director = enemyPoolExpansionTransaction.Director; if ((Object)(object)director == (Object)null || director.enemyListIndex != enemyPoolExpansionTransaction.OriginalEnemyListIndex) { LogDebug("生成前敵Setup追加は消費開始済みのため巻戻しなし: " + reason); return; } bool flag = SequenceMatches(enemyPoolExpansionTransaction.BaseList, enemyPoolExpansionTransaction.OriginalBaseCount, enemyPoolExpansionTransaction.AddedSetups); bool flag2 = enemyPoolExpansionTransaction.BaseList == enemyPoolExpansionTransaction.CurrentList || SequenceMatches(enemyPoolExpansionTransaction.CurrentList, enemyPoolExpansionTransaction.OriginalCurrentCount, enemyPoolExpansionTransaction.AddedSetups); if (!flag || !flag2) { LogDebug("生成前敵Setup追加は他パッチ変更を検出したため巻戻しなし: " + reason); return; } enemyPoolExpansionTransaction.BaseList.RemoveRange(enemyPoolExpansionTransaction.OriginalBaseCount, enemyPoolExpansionTransaction.AddedLogical); if (enemyPoolExpansionTransaction.BaseList != enemyPoolExpansionTransaction.CurrentList) { enemyPoolExpansionTransaction.CurrentList.RemoveRange(enemyPoolExpansionTransaction.OriginalCurrentCount, enemyPoolExpansionTransaction.AddedLogical); } director.totalAmount = Math.Max(0, director.totalAmount - enemyPoolExpansionTransaction.AddedLogical); LogDebug($"未消費の生成前敵Setup追加を巻戻し: {enemyPoolExpansionTransaction.AddedLogical}件 / {reason}"); } catch (Exception ex) { LogDebug("生成前敵Setup追加の巻戻しを安全に縮退: " + ex.GetType().Name); } } private static bool SequenceMatches(IReadOnlyList<EnemySetup> list, int start, IReadOnlyList<EnemySetup> expected) { if (start < 0 || expected.Count < 1 || start + expected.Count > list.Count) { return false; } for (int i = 0; i < expected.Count; i++) { if (list[start + i] != expected[i]) { return false; } } return true; } private void MaintainEnemyPopulation(int desired, float elapsed) { if (Time.unscaledTime < nextSpawnTime) { return; } int num = Math.Min(100, Clamp(config.EnemyEventMaxAliveEnemies.Value, 1, 100)); desired = Math.Min(num, Math.Max(0, desired)); List<EnemyParent> list = FindLiveEnemies(); int configuredEventRequestLimit = Math.Min(100, Clamp(config.EnemyEventTotalSpawnLimit.Value, 1, 100)); int num2 = desired - list.Count; if (num2 <= 0) { nextSpawnTime = Time.unscaledTime + GetSpawnIntervalSeconds(); return; } int val = Math.Min(num2, Clamp(config.EnemyEventWaveBatchSize.Value, 1, 20)); val = Math.Min(val, num - list.Count); val = AnomalySafetyLimits.EnemyRuntimeReactivationBudget(standardEnemyUnits, totalSpawnRequestCount, configuredEventRequestLimit, val); if (val <= 0) { nextSpawnTime = Time.unscaledTime + GetSpawnIntervalSeconds(); return; } int num3 = 0; for (int i = 0; i < val; i++) { if (!StandardGameApi.TryReactivatePooledEnemy(GetSpawnIntervalSeconds(), out EnemyParent _)) { LogDebug("敵イベントの生成済みEnemyParentプールに再出現可能な個体がありません"); break; } num3++; totalSpawnRequestCount++; } nextSpawnTime = Time.unscaledTime + GetSpawnIntervalSeconds(); LogDebug($"異常イベント敵プール再出現要求: {num3}/{val}体, desired={desired}, liveBefore={list.Count}, totalSpawn={totalSpawnRequestCount}"); } private float GetSpawnIntervalSeconds() { if (markedKind != AnomalyEventKind.RelentlessRespawn) { return Clamp(config.EnemyEventWaveIntervalSeconds.Value, 2f, 30f); } return Clamp(config.EnemyEventRespawnIntervalSeconds.Value, 0f, 30f); } private void RefreshEnemyTargets(AnomalyEventKind kind) { List<EnemyParent> list = FindLiveEnemies(); int count = Math.Min(100, Clamp(config.EnemyEventTargetLimit.Value, 1, 100)); if (kind == AnomalyEventKind.MarkedEnemy && (Object)(object)markedEnemy == (Object)null) { markedEnemy = list.OrderBy((EnemyParent enemy) => Vector3.Distance(((Component)enemy).transform.position, PlayerPositionOr(Vector3.zero))).FirstOrDefault(); } if (kind == AnomalyEventKind.PackFrenzy && (Object)(object)markedEnemy == (Object)null) { markedEnemy = list.OrderBy((EnemyParent enemy) => Vector3.Distance(((Component)enemy).transform.position, PlayerPositionOr(Vector3.zero))).FirstOrDefault(); } targets.Clear(); switch (kind) { case AnomalyEventKind.MarkedEnemy: if ((Object)(object)markedEnemy != (Object)null && IsLiveEnemy(markedEnemy)) { targets.Add(markedEnemy); } EnemyMutationRegistry.Replace((IEnumerable<EnemyParent>)(((Object)(object)markedEnemy == (Object)null) ? ((Array)Array.Empty<EnemyParent>()) : ((Array)new EnemyParent[1] { markedEnemy })), config.MarkedEnemyHealthMultiplier.Value, config.MarkedEnemySpeedMultiplier.Value); break; case AnomalyEventKind.PackFrenzy: { string packTypeName = (((Object)(object)markedEnemy == (Object)null) ? string.Empty : NormalizeName(StandardGameApi.GetEnemyDisplayName(markedEnemy))); List<EnemyParent> list2 = list.Where((EnemyParent enemy) => NormalizeName(StandardGameApi.GetEnemyDisplayName(enemy)) == packTypeName).ToList(); targets.AddRange(list2.Take(count)); EnemyMutationRegistry.Replace(list2, config.PackEnemyHealthMultiplier.Value, config.PackEnemySpeedMultiplier.Value); break; } case AnomalyEventKind.HellAllEnemies: targets.AddRange(list.Take(count)); EnemyMutationRegistry.Replace(list, config.HellEnemyHealthMultiplier.Value, config.HellEnemySpeedMultiplier.Value); break; default: targets.AddRange(list.Take(count)); EnemyMutationRegistry.Clear(); break; } } private void ApplyLootEvent(AnomalyEventKind kind) { lootApplied = true; if (kind == AnomalyEventKind.GoldRush || kind == AnomalyEventKind.PurpleChance) { string arg = ((kind == AnomalyEventKind.GoldRush) ? "UltraRare(金)" : "Rare(紫)"); if (cosmeticPromotionCount > 0) { SendRoomMessage($"異常イベント {AnomalyEventCatalog.Get(kind).DisplayName}: 標準コスメ箱{cosmeticPromotionCount}件を{arg}へ確率昇格しました。箱数は増やしていません。"); } else { SendRoomMessage("異常イベント " + AnomalyEventCatalog.Get(kind).DisplayName + ": 標準コスメ箱の生成が無い、または昇格抽選に外れました。箱数は変更していません。"); } return; } int num = Clamp(config.LootEventRevealCount.Value, 0, 20); int num2 = (setupLootApplied ? setupLootSpawned : 0); string displayName = AnomalyEventCatalog.Get(kind).DisplayName; if (num2 > 0) { SendRoomMessage($"異常イベント {displayName}: 現行レベルの標準ValuableDirector経路で貴重品を{num2}件追加しました。通貨・コスメ分類や抽出額は変更していません。"); } else if (num == 0) { SendRoomMessage("異常イベント " + displayName + ": 戦利品の安全代替数が0のため、生成を行いません。"); } else { stateMachine.Fail(displayName + "は標準生成前のtop-upを確保できませんでした"); } } private List<EnemyParent> FindLiveEnemies() { try { return Object.FindObjectsOfType<EnemyParent>().Where(IsLiveEnemy).Take(100) .ToList(); } catch { return new List<EnemyParent>(); } } private static bool IsLiveEnemy(EnemyParent enemy) { try { return (Object)(object)enemy != (Object)null && enemy.SetupDone && enemy.Spawned && (Object)(object)enemy.Enemy != (Object)null && (Object)(object)enemy.Enemy.Health != (Object)null && !enemy.Enemy.Health.dead; } catch { return false; } } private Vector3 PlayerPositionOr(Vector3 fallback) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar val = SafeLocalPlayer(); if (!((Object)(object)val != (Object)null) || !((Object)(object)val.playerTransform != (Object)null)) { return fallback; } return val.playerTransform.position; } private static string NormalizeName(string value) { return (value ?? string.Empty).Replace("(Clone)", string.Empty, StringComparison.OrdinalIgnoreCase).Replace("Enemy - ", string.Empty, StringComparison.OrdinalIgnoreCase).Trim() .ToLowerInvariant(); } private bool TryChooseEvent(out AnomalyEventKind selected, bool allowPrevious) { selected = AnomalyEventKind.MarkedEnemy; if (!config.EnableAnomalyEvents.Value || Clamp(config.AnomalyEventChancePercent.Value, 0f, 100f) <= 0f) { return false; } if (!allowPrevious && random.NextDouble() * 100.0 > (double)Clamp(config.AnomalyEventChancePercent.Value, 0f, 100f)) { return false; } List<AnomalyEventDefinition> list = (from definition in AnomalyEventCatalog.All where IsEventEnabled(definition.Kind) where GetEventWeight(definition.Kind) > 0f where IsSetupDependentEventAvailable(definition.Kind) where allowPrevious || !config.AnomalyEventPreventConsecutive.Value || !previousKind.HasValue || previousKind.Value != definition.Kind select definition).ToList(); if (list.Count == 0) { list = (from definition in AnomalyEventCatalog.All where IsEventEnabled(definition.Kind) where GetEventWeight(definition.Kind) > 0f where IsSetupDependentEventAvailable(definition.Kind) select definition).ToList(); } if (list.Count == 0) { return false; } float num = list.Sum((AnomalyEventDefinition definition) => GetEventWeight(definition.Kind)); if (num <= 0f) { return false; } double num2 = random.NextDouble() * (double)num; foreach (AnomalyEventDefinition item in list) { num2 -= (double)GetEventWeight(item.Kind); if (num2 <= 0.0) { selected = item.Kind; return true; } } selected = list[list.Count - 1].Kind; return true; } private float GetEventWeight(AnomalyEventKind kind) { return Clamp(kind switch { AnomalyEventKind.MarkedEnemy => config.MarkedEnemyEventWeight.Value, AnomalyEventKind.PackFrenzy => config.PackFrenzyEventWeight.Value, AnomalyEventKind.HellAllEnemies => config.HellAllEnemiesEventWeight.Value, AnomalyEventKind.ReinforcementWave => config.ReinforcementWaveEventWeight.Value, AnomalyEventKind.EscalatingThreat => config.EscalatingThreatEventWeight.Value, AnomalyEventKind.RelentlessRespawn => config.RelentlessRespawnEventWeight.Value, AnomalyEventKind.GoldRush => config.GoldRushEventWeight.Value, AnomalyEventKind.PurpleChance => config.PurpleChanceEventWeight.Value, AnomalyEventKind.ValuableSurge => config.ValuableSurgeEventWeight.Value, _ => 0f, }, 0f, 10f); } private bool IsSetupDependentEventAvailable(AnomalyEventKind kind) { if ((kind == AnomalyEventKind.GoldRush || kind == AnomalyEventKind.PurpleChance || kind == AnomalyEventKind.ValuableSurge) && (!valuableSetupObserved || valuableSetupFinalized)) { return enemyAmountSetupInProgress; } return true; } private bool IsEventEnabled(AnomalyEventKind kind) { return kind switch { AnomalyEventKind.MarkedEnemy => config.EnableMarkedEnemyEvent.Value, AnomalyEventKind.PackFrenzy => config.EnablePackFrenzyEvent.Value, AnomalyEventKind.HellAllEnemies => config.EnableHellAllEnemiesEvent.Value, AnomalyEventKind.ReinforcementWave => config.EnableReinforcementWaveEvent.Value, AnomalyEventKind.EscalatingThreat => config.EnableEscalatingThreatEvent.Value, AnomalyEventKind.RelentlessRespawn => config.EnableRelentlessRespawnEvent.Value, AnomalyEventKind.GoldRush => config.EnableGoldRushEvent.Value, AnomalyEventKind.PurpleChance => config.EnablePurpleChanceEvent.Value, AnomalyEventKind.ValuableSurge => config.EnableValuableSurgeEvent.Value, _ => false, }; } private int GetPlayerCount() { try { return Math.Max(1, SemiFunc.PlayerGetAll().Count((PlayerAvatar player) => (Object)(object)player != (Object)null)); } catch { return 1; } } private string FormatStatus() { AnomalyEventSnapshot snapshot = stateMachine.Snapshot; string text = (snapshot.Kind.HasValue ? AnomalyEventCatalog.Get(snapshot.Kind.Value).DisplayName : "なし"); string text2 = (levelEventDecided ? "抽選済み" : "未抽選"); return string.Format("異常イベント状態: {0} / {1} / 経過 {2:0.0}s / {3} / 前回 {4}", snapshot.Phase, text, snapshot.ElapsedSeconds, text2, previousKind?.ToString() ?? "なし"); } private void OnStateChanged(AnomalyEventSnapshot snapshot) { try { if (snapshot.Phase == AnomalyEventPhase.Pending && snapshot.Kind.HasValue) { AnomalyEventDefinition anomalyEventDefinition = AnomalyEventCatalog.Get(snapshot.Kind.Value); SendRoomMessage($"【異常予告】{anomalyEventDefinition.DisplayName}: {anomalyEventDefinition.Description}({snapshot.StartDelaySeconds:0}秒後に開始)"); } else if (snapshot.Phase == AnomalyEventPhase.Active && snapshot.Kind.HasValue) { PrepareActiveEvent(snapshot.Kind.Value); if (!stateMachine.IsActive) { return; } AnomalyEventDefinition anomalyEventDefinition2 = AnomalyEventCatalog.Get(snapshot.Kind.Value); SendRoomMessage("【異常開始】" + anomalyEventDefinition2.DisplayName + "。" + anomalyEventDefinition2.SafetyNote); } else if (snapshot.Phase == AnomalyEventPhase.Completed) { EnemyMutationRegistry.Clear(); StandardGameApi.CancelPendingEnemyReactivations(); SendRoomMessage("【異常終了】" + snapshot.Reason); } else if (snapshot.Phase == AnomalyEventPhase.Cancelled || snapshot.Phase == AnomalyEventPhase.Failed || snapshot.Phase == AnomalyEventPhase.TimedOut) { EnemyMutationRegistry.Clear(); StandardGameApi.CancelPendingEnemyReactivations(); SendRoomMessage("【異常イベント終了】" + snapshot.Reason); } LogDebug(string.Format("異常イベント状態: {0}, kind={1}, reason={2}", snapshot.Phase, snapshot.Kind?.ToString() ?? "none", snapshot.Reason)); } catch (Exception ex) { ManualLogSource obj = logger; if (obj != null) { obj.LogWarning((object)("[ACD] 異常イベント通知処理を縮退しました: " + ex.GetType().Name)); } } } private bool IsHost() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return false; } } private bool IsLevelReady() { try { if (!SemiFunc.LevelGenDone()) { return false; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || instance.restarting || instance.waitToChangeScene) { return false; } RoundDirector roundDirectorInstance = GetRoundDirectorInstance(); if ((Object)(object)roundDirectorInstance == (Object)null) { return false; } if (!TryReadBool(roundDirectorInstance, "extractionPointActive", out var value) || !TryReadBool(roundDirectorInstance, "allExtractionPointsCompleted", out var value2)) { return false; } return !value && !value2; } catch { return false; } } private static PlayerAvatar? SafeLocalPlayer() { try { return SemiFunc.PlayerAvatarLocal(); } catch { return null; } } private static RoundDirector? GetRoundDirectorInstance() { try { object? obj = typeof(RoundDirector).GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); return (RoundDirector?)((obj is RoundDirector) ? obj : null); } catch { return null; } } private static bool TryReadBool(object target, string name, out bool value) { value = false; try { FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field?.FieldType != typeof(bool)) { return false; } value = (bool)field.GetValue(target); return true; } catch { return false; } } private void SendRoomMessage(string message) { PlayerAvatar val = SafeLocalPlayer(); if ((Object)(object)val != (Object)null) { try { val.ChatMessageSend("[契約局/異常] " + message); return; } catch { } } try { ChatManager instance = ChatManager.instance; if (instance != null) { instance.ForceSendMessage("[契約局/異常] " + message); } } catch { LogDebug("異常イベントのチャット通知を送信できませんでした"); } } private void LogDebug(string message) { if (config != null && config.DetailedLogging.Value) { ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)("[ACD] " + message)); } } } private static float Clamp(float value, float min, float max) { return AnomalySafetyLimits.ClampFinite(value, min, max, min); } private static int Clamp(int value, int min, int max) { return Math.Max(min, Math.Min(max, value)); } } internal sealed class ContractDirectorRuntime : MonoBehaviour { private sealed class TokenBinding { internal ItemAttributes Item { get; } internal ContractKind Kind { get; } internal string ItemName { get { if (string.IsNullOrWhiteSpace(Item.itemName)) { if (!((Object)(object)Item.item != (Object)null) || string.IsNullOrWhiteSpace(Item.item.itemName)) { return $"ViewID:{Item.photonView.ViewID}"; } return Item.item.itemName; } return Item.itemName; } } internal int ContractIndex => ContractCatalog.All.ToList().FindIndex((ContractDefinition definition) => definition.Kind == Kind); internal string KindDisplayName => ContractCatalog.Get(Kind).DisplayName; internal TokenBinding(ItemAttributes item, ContractKind kind) { Item = item; Kind = kind; } } private readonly ContractStateMachine stateMachine = new ContractStateMachine(); private readonly ItemRoleRegistry roleRegistry = new ItemRoleRegistry(); private readonly Dictionary<int, TokenBinding> tokens = new Dictionary<int, TokenBinding>(); private readonly HashSet<int> completedBatteryViewIds = new HashSet<int>(); private ContractKind? previousKind; private readonly Dictionary<int, float> batteryHoldTimers = new Dictionary<int, float>(); private readonly Dictionary<int, bool> deviceActivationState = new Dictionary<int, bool>(); private readonly Random random = new Random(); private DirectorConfig config; private ManualLogSource logger; private ContractKind? activeKind; private ValuableRoleBinding? cargo; private ValuableRoleBinding? bountyEvidence; private EnemyParent? bountyTarget; private bool bountyAssigned; private int bountyTargetViewId; private int bountyEvidenceViewId; private readonly List<ItemAttributes> batteries = new List<ItemAttributes>(); private readonly List<Transform> dualAnchors = new List<Transform>(); private readonly List<Transform> recoveryAnchors = new List<Transform>(); private ExtractionPoint? defensePoint; private bool bountyDefeated; private float bountyDiscoveryDeadline; private bool defenseStarted; private bool defenseThreatSeen; private float defenseThreatDeadline; private float defenseTimer; private float dualStableTimer; private float nextPollTime; private float lastPollTime; private float levelStartedAt; private float selectionDeadline; private bool levelActive; private bool tokenNoticeSent; private bool tokenShortageLogged; private float nextMedicalTime; private float nextRadarTime; private float nextNoiseTime; private float lastDefenseInvestigationTime; internal ContractStateMachine StateMachine => stateMachine; internal ItemRoleRegistry RoleRegistry => roleRegistry; internal bool IsLevelActive => levelActive; internal void Initialize(DirectorConfig directorConfig, ManualLogSource manualLogSource) { config = directorConfig; logger = manualLogSource; stateMachine.StateChanged += OnStateChanged; ((Behaviour)this).enabled = true; } private void Update() { if (config == null || Time.unscaledTime < nextPollTime) { return; } float unscaledTime = Time.unscaledTime; float num = AnomalySafetyLimits.ClampFinite(config.PollIntervalSeconds.Value, 0.25f, 1f, 0.25f); float deltaSeconds = ((lastPollTime > 0f) ? Math.Max(0.01f, Math.Min(5f, unscaledTime - lastPollTime)) : num); lastPollTime = unscaledTime; nextPollTime = unscaledTime + num; if (!config.Enabled.Value || !IsHost()) { if (levelActive) { ResetRoomState("ホスト権限または設定が失われました"); } return; } bool flag; bool flag2; bool flag3; try { flag = SemiFunc.RunIsLevel(); flag2 = SemiFunc.RunIsShop(); flag3 = SemiFunc.RunIsLobby(); } catch (Exception ex) { LogDebug("ゲーム状態の読み取りに失敗: " + ex.GetType().Name); return; } if (!flag || flag2 || flag3) { if (levelActive) { ResetRoomState(flag2 ? "ショップ遷移" : (flag3 ? "ロビー遷移" : "レベル外遷移")); } return; } if (!levelActive) { if (!IsLevelReady()) { return; } BeginLevel(); } if (!levelActive || !config.Enabled.Value) { return; } if (!IsLevelReady()) { ResetRoomState("レベル遷移準備"); return; } if (stateMachine.IsSelecting) { DiscoverTokens(); TrySelectHeldToken(); CheckSelectionTimeout(); return; } try { if (stateMachine.IsActive) { stateMachine.Tick(deltaSeconds); if (stateMachine.IsActive) { EvaluateActiveContract(deltaSeconds); } } } catch (Exception arg) { ManualLogSource obj = logger; if (obj != null) { obj.LogWarning((object)$"[ACD] 契約監視中の例外を契約内へ封じ込めました: {arg}"); } if (stateMachine.IsActive) { stateMachine.Fail("対象の状態を安全に読み取れなくなりました"); } } UpdateDevices(); } internal void HandleChatCommand(PlayerAvatar sender, string message) { if (!IsHost() || (Object)(object)sender == (Object)null || string.IsNullOrWhiteSpace(message)) { return; } PlayerAvatar val = SafeLocalPlayer(); if ((Object)(object)val == (Object)null || (Object)(object)sender != (Object)(object)val) { return; } string text = message.Trim(); if (!text.StartsWith("!acd", StringComparison.OrdinalIgnoreCase)) { return; } string[] array = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 1 || string.Equals(array[1], "help", StringComparison.OrdinalIgnoreCase)) { SendRoomMessage("!acd contract 1~5 | !acd random | !acd status | !acd cancel"); return; } switch (array[1].ToLowerInvariant()) { case "status": { ContractSnapshot snapshot = stateMachine.Snapshot; string text2 = (snapshot.Kind.HasValue ? ContractCatalog.Get(snapshot.Kind.Value).DisplayName : "なし"); SendRoomMessage($"契約状態: {snapshot.Phase} / {text2} / {snapshot.Progress}/{snapshot.RequiredUnits}"); break; } case "cancel": if (stateMachine.IsSelecting || stateMachine.IsActive) { stateMachine.Fail("ホストが契約を中止しました"); } break; case "random": SelectRandomContract("ホストのチャット指定"); break; case "contract": { if (array.Length < 3) { break; } if (int.TryParse(array[2], out var result) && result >= 1 && result <= ContractCatalog.All.Count) { SelectContractByIndex(result - 1, "ホストのチャット指定"); break; } ContractKind? contractKind = ParseContract(array[2]); if (contractKind.HasValue) { SelectContract(contractKind.Value, "ホストのチャット指定"); } break; } } } internal void ResetRoomState(string reason) { ((MonoBehaviour)this).StopAllCoroutines(); levelActive = false; activeKind = null; tokenNoticeSent = false; tokenShortageLogged = false; tokens.Clear(); completedBatteryViewIds.Clear(); batteryHoldTimers.Clear(); deviceActivationState.Clear(); cargo = null; bountyEvidence = null; bountyTarget = null; bountyAssigned = false; bountyTargetViewId = 0; bountyEvidenceViewId = 0; batteries.Clear(); dualAnchors.Clear(); recoveryAnchors.Clear(); defensePoint = null; bountyDefeated = false; bountyDiscoveryDeadline = 0f; defenseStarted = false; defenseThreatSeen = false; defenseThreatDeadline = 0f; defenseTimer = 0f; dualStableTimer = 0f; nextMedicalTime = 0f; nextRadarTime = 0f; nextNoiseTime = 0f; lastDefenseInvestigationTime = 0f; roleRegistry.ReleaseAll(); stateMachine.Reset(reason); LogDebug("契約状態を解放: " + reason); } internal void OnMasterClientChanged() { ResetRoomState("マスター変更"); } internal void NotifyCurrentStateToRoom() { if (IsHost() && levelActive) { ContractSnapshot snapshot = stateMachine.Snapshot; if (snapshot.Phase == ContractPhase.Selecting) { SendRoomMessage("途中参加者向け再通知: 現在は契約選択中です。ホストは !acd status で確認できます。"); } else if (snapshot.Kind.HasValue) { ContractDefinition contractDefinition = ContractCatalog.Get(snapshot.Kind.Value); SendRoomMessage($"途中参加者向け再通知: {contractDefinition.DisplayName} / {contractDefinition.Description} / 進捗 {snapshot.Progress}/{snapshot.RequiredUnits}"); } } } private void BeginLevel() { levelActive = true; levelStartedAt = Time.unscaledTime; selectionDeadline = levelStartedAt + Math.Max(5f, config.SelectionSeconds.Value); stateMachine.BeginSelection(GetPlayerCount(), "レベル開始"); tokenNoticeSent = false; tokenShortageLogged = false; roleRegistry.ReleaseAll(); tokens.Clear(); if (config.PhysicalTokenSelection.Value) { DiscoverTokens(); } SendRoomMessage("【契約選択】!acd contract 1~5 または、表示された既存アイテムを掴んでください。"); LogDebug("レベル契約の選択待ちを開始"); } private void DiscoverTokens() { if (!stateMachine.IsSelecting || !config.PhysicalTokenSelection.Value || tokenNoticeSent) { return; } IReadOnlyList<ContractKind> enabledKinds = GetEnabledKinds(); if (enabledKinds.Count == 0) { return; } int num = Math.Min(3, enabledKinds.Count); IReadOnlyList<ItemAttributes> readOnlyList = roleRegistry.FindSafeTokenCandidates(Math.Min(5, ContractCatalog.All.Count)); if (readOnlyList.Count < num && Time.unscaledTime - levelStartedAt < config.DiscoveryGraceSeconds.Value) { return; } if (readOnlyList.Count < num) { if (!tokenShortageLogged && Time.unscaledTime - levelStartedAt >= config.DiscoveryGraceSeconds.Value) { tokenShortageLogged = true; LogDebug($"安全な契約トークンが不足 ({readOnlyList.Count}/{num})。ランダム縮退を待機"); } return; } int num2 = Math.Min(readOnlyList.Count, enabledKinds.Count); for (int i = 0; i < num2; i++) { ItemAttributes val = readOnlyList[i]; if (!((Object)(object)val.photonView == (Object)null) && val.photonView.ViewID > 0) { int viewID = val.photonView.ViewID; if (!tokens.ContainsKey(viewID)) { tokens[viewID] = new TokenBinding(val, enabledKinds[i]); } } } if (tokens.Count != 0 && !tokenNoticeSent) { tokenNoticeSent = true; string arg = string.Join(" / ", tokens.Values.Select((TokenBinding token) => $"{token.ContractIndex + 1}:{token.KindDisplayName}({token.ItemName})")); SendRoomMessage($"契約トークン: {arg}。1個を掴むと決定します。未選択は{Math.Max(0f, selectionDeadline - Time.unscaledTime):0}秒後にランダム。"); LogDebug($"物理契約トークンを{tokens.Count}個登録"); } } private void TrySelectHeldToken() { foreach (TokenBinding item in tokens.Values.ToList()) { if (!((Object)(object)item.Item == (Object)null) && !((Object)(object)item.Item.physGrabObject == (Object)null) && item.Item.physGrabObject.grabbed) { SelectContract(item.Kind, "物理トークンを取得"); break; } } } private void CheckSelectionTimeout() { if (!(Time.unscaledTime < selectionDeadline)) { if (config.FallbackRandom.Value) { SelectRandomContract("選択時間切れの自動ランダム"); } else { stateMachine.Fail("契約選択が時間切れになりました(自動ランダムOFF)"); } } } private void SelectRandomContract(string reason) { if (stateMachine.IsSelecting) { List<ContractKind> list = (from kind in GetEnabledKinds() where !config.PreventConsecutive.Value || !previousKind.HasValue || previousKind.Value != kind select kind).ToList(); if (list.Count == 0) { list = GetEnabledKinds().ToList(); } if (list.Count == 0) { stateMachine.Fail("有効な契約がありません"); } else { SelectContract(list[random.Next(list.Count)], reason); } } } private void SelectContractByIndex(int index, string reason) { if (stateMachine.IsSelecting) { if (index < 0 || index >= ContractCatalog.All.Count) { SendRoomMessage("指定番号の契約は現在無効です。"); } else { SelectContract(ContractCatalog.All[index].Kind, reason); } } } private void SelectContract(ContractKind kind, string reason) { if (!stateMachine.IsSelecting) { return; } if (!IsEnabled(kind)) { SendRoomMessage("その契約は設定で無効です。"); } else if (!IsAvailableNow(kind)) { SendRoomMessage("その契約は現在の人数またはレベル条件では成立しません。"); } else if (config.PreventConsecutive.Value && previousKind.HasValue && previousKind.Value == kind && GetEnabledKinds().Any((ContractKind candidate) => candidate != kind)) { SendRoomMessage("直前と同じ契約は連続しない設定です。別の契約を選んでください。"); } else if (stateMachine.Start(kind, GetPlayerCount(), reason)) { activeKind = kind; PrepareContract(kind); if (stateMachine.IsActive) { previousKind = kind; SendRoomMessage("【契約開始】" + ContractCatalog.Get(kind).DisplayName + ": " + ContractCatalog.Get(kind).Description); LogDebug($"契約開始: {kind}, プレイヤー数={GetPlayerCount()}"); } } } private void PrepareContract(ContractKind kind) { roleRegistry.ReleaseAll(); completedBatteryViewIds.Clear(); batteryHoldTimers.Clear(); deviceActivationState.Clear(); cargo = null; bountyEvidence = null; bountyTarget = null; bountyAssigned = false; bountyTargetViewId = 0; bountyEvidenceViewId = 0; batteries.Clear(); dualAnchors.Clear(); recoveryAnchors.Clear(); defensePoint = null; bountyDefeated = false; bountyDiscoveryDeadline = Time.unscaledTime + Math.Max(5f, config.DiscoveryGraceSeconds.Value * 5f); defenseStarted = false; defenseThreatSeen = false; defenseThreatDeadline = 0f; defenseTimer = 0f; dualStableTimer = 0f; RegisterDevices(); switch (kind) { case ContractKind.CursedCargo: cargo = roleRegistry.FindValuable(new HashSet<int>(tokens.Keys)); if (cargo != null) { GameInterop.TryDiscoverValuable(cargo.Valuable); SendRoomMessage("呪われた貨物は『" + cargo.DisplayName + "』です。標準の貴重品アイコンをマップへ表示しました。"); } if (cargo == null) { stateMachine.Fail("既存の貴重品ViewIDを安全に登録できませんでした"); } break; case ContractKind.BountyEvidence: if (!TryAssignBountyTargets()) { SendRoomMessage("賞金首・証拠品の出現を待機しています。"); } break; case ContractKind.BlackoutRecovery: PrepareBatteries(); break; case ContractKind.ExtractionDefense: defensePoint = FindDefensePoint(); if ((Object)(object)defensePoint == (Object)null) { stateMachine.Fail("抽出地点アンカーを発見できませんでした"); } break; case ContractKind.DualActivation: PrepareDualPoints(); break; } } private bool TryAssignBountyTargets() { if (bountyAssigned) { return true; } EnemyParent val = FindBountyTarget(); ValuableRoleBinding valuableRoleBinding = roleRegistry.FindValuable(new HashSet<int>(tokens.Keys)); PhotonView val2 = (((Object)(object)val == (Object)null || (Object)(object)val.Enemy == (Object)null) ? null : val.Enemy.PhotonView); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || val2.ViewID <= 0 || valuableRoleBinding == null || (Object)(object)valuableRoleBinding.Valuable == (Object)null) { return false; } bountyTarget = val; bountyEvidence = valuableRoleBinding; bountyTargetViewId = val2.ViewID; bountyEvidenceViewId = valuableRoleBinding.ViewId; bountyAssigned = true; GameInterop.TryDiscoverValuable(valuableRoleBinding.Valuable); string text = ((!string.IsNullOrWhiteSpace(((Object)val).name)) ? ((Object)val).name.Replace("(Clone)", string.Empty).Trim() : "既存の敵"); SendRoomMessage("賞金首は『" + text + "』、証拠品は『" + valuableRoleBinding.DisplayName + "』です。証拠品は標準マップへ表示しました。"); return true; } private void RegisterDevices() { HashSet<int> excludedViewIds = new HashSet<int>(tokens.Keys); if (config.EnableMedicalCore.Value) { roleRegistry.FindAndRegisterDevice(DeviceKind.MedicalCore, excludedViewIds); } if (config.EnablePortableRadar.Value) { roleRegistry.FindAndRegisterDevice(DeviceKind.PortableRadar, excludedViewIds); } if (config.EnableNoiseBeacon.Value) { roleRegistry.FindAndRegisterDevice(DeviceKind.NoiseBeacon, excludedViewIds); } foreach (DeviceRoleRecord record in roleRegistry.Records) { LogDebug($"装置ViewID登録: {record.Kind} / {record.ViewId} / {record.ItemName}"); } if (roleRegistry.Records.Count > 0) { string text = string.Join(" / ", roleRegistry.Records.Select((DeviceRoleRecord record) => DeviceDisplayName(record.Kind) + "=" + record.ItemName)); SendRoomMessage("既存装置を登録しました: " + text); if (roleRegistry.TryGet(DeviceKind.PortableRadar, out ItemRoleBinding binding)) { SendRoomMessage("携帯レーダー: トグル付きはON中、トグルなしは手持ち中に標準マップ表示を更新します。"); } if (roleRegistry.TryGet(DeviceKind.NoiseBeacon, out binding)) { SendRoomMessage("ノイズビーコン: 登録された既存アイテムのトグルをONにした間だけ敵を誘導します。"); } } else { SendRoomMessage("既存装置候補が見つからないため、装置効果は安全に縮退します。"); } } private static string DeviceDisplayName(DeviceKind kind) { return kind switch { DeviceKind.MedicalCore => "大型医療コア", DeviceKind.PortableRadar => "携帯レーダー", DeviceKind.NoiseBeacon => "ノイズビーコン", _ => kind.ToString(), }; } private void PrepareBatteries() { int num = RequiredUnits(); batteries.AddRange(roleRegistry.FindBatteryCandidates(num, new HashSet<int>(tokens.Keys))); recoveryAnchors.AddRange(FindKnownRecoveryAnchors(num)); if (batteries.Count < num || recoveryAnchors.Count < num) { stateMachine.Fail($"既存バッテリーまたは既知ランドマークが不足 (電池{batteries.Count}/{num}, 地点{recoveryAnchors.Count}/{num})"); } else { IEnumerable<string> values = batteries.Take(num).Select((ItemAttributes battery, int index) => "『" + GetItemDisplayName(battery) + "』→" + DescribeKnownAnchor(recoveryAnchors[index])); SendRoomMessage("復旧対象: " + string.Join(" / ", values) + "。各バッテリーを対応地点へ置いてください。"); } } private void PrepareDualPoints() { try { int num = RequiredUnits(); dualAnchors.AddRange(FindKnownDualAnchors(num)); if (dualAnchors.Count < num) { stateMachine.Fail("トラックと抽出地点など既知のランドマークを2か所確保できませんでした"); } else { SendRoomMessage(string.Format("二地点は {0} です。{1}人で同時に滞在してください。", string.Join(" / ", dualAnchors.Select(DescribeKnownAnchor)), num)); } } catch (Exception ex) { LogDebug("二地点アンカー発見失敗: " + ex.GetType().Name); stateMachine.Fail("二地点アンカーを発見できませんでした"); } } private void EvaluateActiveContract(float deltaSeconds) { if (activeKind.HasValue) { switch (activeKind.Value) { case ContractKind.CursedCargo: EvaluateCursedCargo(); break; case ContractKind.BountyEvidence: EvaluateBountyEvidence(); break; case ContractKind.BlackoutRecovery: EvaluateBlackoutRecovery(deltaSeconds); break; case ContractKind.ExtractionDefense: EvaluateExtractionDefense(deltaSeconds); break; case ContractKind.DualActivation: EvaluateDualActivation(deltaSeconds); break; } } } private void EvaluateCursedCargo() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if (cargo == null || (Object)(object)cargo.Valuable == (Object)null || (Object)(object)cargo.PhysGrabObject == (Object)null || cargo.PhysGrabObject.dead) { stateMachine.Fail("呪われた貨物が破損または消失しました"); return; } ExtractionPoint val = SemiFunc.ExtractionPointGetNearest(cargo.Transform.position); if (!((Object)(object)val == (Object)null)) { bool flag = (((Object)(object)cargo.Valuable.roomVolumeCheck != (Object)null) ? cargo.Valuable.roomVolumeCheck.inExtractionPoint : (Vector3.Distance(((Component)val).transform.position, cargo.Transform.position) <= 4f)); bool flag2 = SemiFunc.PhysGrabObjectIsGrabbed(cargo.PhysGrabObject); if (flag && !flag2) { stateMachine.AddProgress(1, "呪われた貨物を抽出地点へ安定配置"); } } } private void EvaluateBountyEvidence() { //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) if (!bountyAssigned) { if (!TryAssignBountyTargets() && Time.unscaledTime >= bountyDiscoveryDeadline) { stateMachine.Fail("賞金首または証拠品候補を発見できませんでした"); } return; } if ((Object)(object)bountyTarget == (Object)null || (Object)(object)bountyTarget.Enemy == (Object)null || (Object)(object)bountyTarget.Enemy.PhotonView == (Object)null || bountyTarget.Enemy.PhotonView.ViewID != bountyTargetViewId) { stateMachine.Fail("賞金首が討伐確認前に消失しました"); return; } if (bountyEvidence == null || (Object)(object)bountyEvidence.Valuable == (Object)null || bountyEvidence.ViewId != bountyEvidenceViewId || (Object)(object)bountyEvidence.PhysGrabObject == (Object)null || bountyEvidence.PhysGrabObject.dead) { stateMachine.Fail("証拠品が破損または消失しました"); return; } EnemyHealth health = bountyTarget.Enemy.Health; if ((Object)(object)health == (Object)null) { stateMachine.Fail("賞金首の体力状態を読み取れませんでした"); return; } if (!bountyDefeated && (Object)(object)health != (Object)null && health.dead) { bountyDefeated = true; stateMachine.AddProgress(1, "賞金首を討伐。証拠品を抽出地点へ運んでください"); SendRoomMessage("賞金首を確認しました。証拠品を抽出地点へ運んでください。"); } if (bountyDefeated) { ExtractionPoint val = SemiFunc.ExtractionPointGetNearest(bountyEvidence.Transform.position); if (!((Object)(object)val == (Object)null) && (((Object)(object)bountyEvidence.Valuable.roomVolumeCheck != (Object)null) ? bountyEvidence.Valuable.roomVolumeCheck.inExtractionPoint : (Vector3.Distance(((Component)val).transform.position, bountyEvidence.Transform.position) <= 4f)) && !SemiFunc.PhysGrabObjectIsGrabbed(bountyEvidence.PhysGrabObject)) { stateMachine.AddProgress(1, "証拠品を抽出地点へ回収"); } } } private void EvaluateBlackoutRecovery(float deltaSeconds) { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) int num = RequiredUnits(); for (int i = 0; i < batteries.Count && i < num; i++) { ItemAttributes val = batteries[i]; if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null || (Object)(object)val.physGrabObject == (Object)null || val.physGrabObject.dead) { stateMachine.Fail($"復旧バッテリー{i + 1}が破損または消失しました"); break; } int viewID = val.photonView.ViewID; if (viewID <= 0) { stateMachine.Fail($"復旧バッテリー{i + 1}のネットワーク個体を確認できません"); break; } if (completedBatteryViewIds.Contains(viewID)) { continue; } Transform val2 = recoveryAnchors[i]; if ((Object)(object)val2 == (Object)null) { stateMachine.Fail($"復旧地点{i + 1}が消失しました"); break; } bool flag = Vector3.Distance(((Component)val).transform.position, val2.position) <= 3f; bool flag2 = SemiFunc.PhysGrabObjectIsGrabbed(val.physGrabObject); if (!flag || flag2 || !IsPhysicallyStationary(val.physGrabObject)) { batteryHoldTimers.Remove(viewID); continue; } float value; float num2 = (batteryHoldTimers.TryGetValue(viewID, out value) ? (value + deltaSeconds) : deltaSeconds); batteryHoldTimers[viewID] = num2; if (!(num2 < 1.5f)) { completedBatteryViewIds.Add(viewID); stateMachine.AddProgress(1, $"復旧地点{i + 1}を起動"); } } } private void EvaluateExtractionDefense(float deltaSeconds) { //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)defensePoint == (Object)null) { stateMachine.Fail("防衛対象の抽出地点が消失しました"); return; } string text = ((object)Unsafe.As<State, State>(ref defensePoint.currentState)/*cast due to .constrained prefix*/).ToString(); if (string.Equals(text, "Complete", StringComparison.OrdinalIgnoreCase)) { stateMachine.Fail("通常の抽出完了へ移行したため、防衛契約だけを終了しました"); return; } if (!GetPlayers().Any((PlayerAvatar player) => IsNear(player, ((Component)defensePoint).transform.position, 7f))) { defenseTimer = 0f; defenseThreatSeen = false; defenseThreatDeadline = 0f; return; } if (!defenseStarted) { defenseStarted = true; defenseTimer = 0f; SendRoomMessage("抽出地点へ集合しました(状態:" + text + ")。周囲へ既存敵を誘導し、脅威接近後に防衛タイマーを開始します。"); } if (defenseThreatDeadline <= 0f) { defenseThreatDeadline = Time.unscaledTime + config.DefenseThreatWaitSeconds.Value; } if (Time.unscaledTime - lastDefenseInvestigationTime >= 4f) { lastDefenseInvestigationTime = Time.unscaledTime; EnemyDirector instance = EnemyDirector.instance; if (instance != null) { instance.SetInvestigate(((Component)defensePoint).transform.position, config.DefenseThreatRadius.Value, false); } } int num = CountLiveEnemiesNear(((Component)defensePoint).transform.position, config.DefenseThreatRadius.Value); if (!defenseThreatSeen) { if (num <= 0) { if (Time.unscaledTime >= defenseThreatDeadline) { stateMachine.Fail("既存敵が接近しなかったため、防衛契約だけを安全終了しました"); } return; } defenseThreatSeen = true; defenseTimer = 0f; SendRoomMessage($"脅威を{num}体検知。{config.DefenseSeconds.Value:0}秒の防衛を開始します。"); } defenseTimer += deltaSeconds; if (defenseTimer >= config.DefenseSeconds.Value) { stateMachine.AddProgress(1, "防衛時間を耐えました"); } } private void EvaluateDualActivation(float deltaSeconds) { if (dualAnchors.Count < 2) { return; } List<PlayerAvatar> players = GetPlayers(); PlayerAvatar first = ((IEnumerable<PlayerAvatar>)players).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar player) => IsNear(player, dualAnchors[0].position, config.DualRadius.Value))); PlayerAvatar val = ((IEnumerable<PlayerAvatar>)players).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar player) => (Object)(object)player != (Object)(object)first && IsNear(player, dualAnchors[1].position, config.DualRadius.Value))); if ((Object)(object)first == (Object)null || (Object)(object)val == (Object)null) { dualStableTimer = 0f; return; } dualStableTimer += deltaSeconds; if (dualStableTimer >= config.DualHoldSeconds.Value) { stateMachine.AddProgress(2, "二地点を同時起動"); } } private void UpdateDevices() { TryUpdateDevice(DeviceKind.MedicalCore, config.EnableMedicalCore.Value, UpdateMedicalCore); TryUpdateDevice(DeviceKind.PortableRadar, config.EnablePortableRadar.Value, UpdatePortableRadar); TryUpdateDevice(DeviceKind.NoiseBeacon, config.EnableNoiseBeacon.Value, UpdateNoiseBeacon); } private void TryUpdateDevice(DeviceKind kind, bool featureEnabled, Action<ItemRoleBinding> update) { if (!featureEnabled || !roleRegistry.TryGet(kind, out ItemRoleBinding binding)) { return; } try { update(binding); } catch (Exception ex) { ManualLogSource obj = logger; if (obj != null) { obj.LogWarning((object)("[ACD] " + DeviceDisplayName(kind) + "だけを安全に解除しました: " + ex.GetType().Name)); } deviceActivationState.Remove(binding.ViewId); roleRegistry.Release(kind); } } private void UpdateMedicalCore(ItemRoleBinding binding) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) bool flag = IsPhysicallyStationary(binding.PhysGrabObject); if (!(!binding.IsHeld && flag) || !GetPlayers().Any((PlayerAvatar player) => IsNear(player, binding.Transform.position, config.MedicalRadius.Value)) || Time.unscaledTime < nextMedicalTime) { return; } nextMedicalTime = Time.unscaledTime + Math.Max(0.5f, config.DeviceIntervalSeconds.Value); roleRegistry.MarkActive(DeviceKind.MedicalCore); foreach (PlayerAvatar player in GetPlayers()) { if (IsNear(player, binding.Transform.position, config.MedicalRadius.Value) && !((Object)(object)player.playerHealth == (Object)null) && !player.isDisabled && !player.deadSet && player.playerHealth.health > 0 && player.playerHealth.health < player.playerHealth.maxHealth) { player.playerHealth.HealOther(config.MedicalHealAmount.Value, true); } } } private void UpdatePortableRadar(ItemRoleBinding binding) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) ItemToggle toggle = binding.Toggle; bool flag = (((Object)(object)toggle != (Object)null) ? toggle.toggleState : binding.IsHeld); bool value; bool flag2 = deviceActivationState.TryGetValue(binding.ViewId, out value) && value; deviceActivationState[binding.ViewId] = flag; if (!flag || Time.unscaledTime < nextRadarTime) { return; } nextRadarTime = Time.unscaledTime + Math.Max(1f, config.DeviceIntervalSeconds.Value * 3f); roleRegistry.MarkActive(DeviceKind.PortableRadar); Vector3 position = binding.Transform.position; int num = 0; ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>(); foreach (ValuableObject val in array) { if (!((Object)(object)val == (Object)null) && !val.discovered && !((Object)(object)((Component)val).transform == (Object)null) && !(Vector3.Distance(position, ((Component)val).transform.position) > config.RadarRadius.Value) && GameInterop.TryDiscoverValuable(val)) { num++; } } if (num > 0) { SendRoomMessage($"携帯レーダー: 周囲の貴重品を{num}件マップへ表示しました。"); } else if (!flag2) { SendRoomMessage("携帯レーダー: 現在の範囲に未発見の貴重品はありません。"); } } private void UpdateNoiseBeacon(ItemRoleBinding binding) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) ItemToggle toggle = binding.Toggle; if ((Object)(object)toggle == (Object)null) { throw new InvalidOperationException("ノイズビーコンの既存トグルが消失しました"); } bool toggleState = toggle.toggleState; bool value; bool flag = deviceActivationState.TryGetValue(binding.ViewId, out value) && value; deviceActivationState[binding.ViewId] = toggleState; if (toggleState && !(Time.unscaledTime < nextNoiseTime)) { nextNoiseTime = Time.unscaledTime + Math.Max(2f, config.DeviceIntervalSeconds.Value * 4f); roleRegistry.MarkActive(DeviceKind.NoiseBeacon); EnemyDirector instance = EnemyDirector.instance; if (instance != null) { instance.SetInvestigate(binding.Transform.position, config.NoiseRadius.Value, false); } if (!flag) { SendRoomMessage("ノイズビーコン: 周囲の敵を別方向へ誘導しています。"); } } } private EnemyParent? FindBountyTarget() { return (from enemy in Object.FindObjectsOfType<EnemyParent>() where (Object)(object)enemy != (Object)null && enemy.SetupDone && enemy.Spawned && (Object)(object)enemy.Enemy != (Object)null && (Object)(object)enemy.Enemy.Health != (Object)null where !enemy.Enemy.Health.dead && (Object)(object)enemy.Enemy.PhotonView != (Object)null && enemy.Enemy.PhotonView.ViewID > 0 orderby enemy.Enemy.Health.healthCurrent select enemy).FirstOrDefault(); } private ExtractionPoint? FindDefensePoint() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) List<PlayerAvatar> players = GetPlayers(); Vector3 origin = ((players.Count > 0) ? players[0].playerTransform.position : Vector3.zero); return (from point in Object.FindObjectsOfType<ExtractionPoint>() where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy where !string.Equals(((object)Unsafe.As<State, State>(ref point.currentState)/*cast due to .constrained prefix*/).ToString(), "Complete", StringComparison.OrdinalIgnoreCase) orderby Vector3.Distance(((Component)point).transform.position, origin) select point).FirstOrDefault(); } private IReadOnlyList<Transform> FindKnownRecoveryAnchors(int required) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) List<Transform> list = new List<Transform>(); try { List<ExtractionPoint> list2 = (from point in Object.FindObjectsOfType<ExtractionPoint>() where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy where !string.Equals(((object)Unsafe.As<State, State>(ref point.currentState)/*cast due to .constrained prefix*/).ToString(), "Complete", StringComparison.OrdinalIgnoreCase) select point).ToList(); if (list2.Count > 0) { list.Add(((Component)list2[0]).transform); } LevelPoint val = ((IEnumerable<LevelPoint>)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func<LevelPoint, bool>)((LevelPoint point) => (Object)(object)point != (Object)null && point.Truck)); if (required > 1 && (Object)(object)val != (Object)null) { list.Add(((Component)val).transform); } int num = 1; while (list.Count < required && num < list2.Count) { if (!(Vector3.Distance(list[0].position, ((Component)list2[num]).transform.position) < config.DualRadius.Value * 4f)) { list.Add(((Component)list2[num]).transform); } num++; } return list.Take(required).ToList(); } catch { return list; } } private IReadOnlyList<Transform> FindKnownDualAnchors(int required) { List<Transform> list = new List<Transform>(); try { LevelPoint truck = ((IEnumerable<LevelPoint>)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func<LevelPoint, bool>)((LevelPoint val) => (Object)(object)val != (Object)null && val.Truck)); if ((Object)(object)truck != (Object)null) { list.Add(((Component)truck).transform); } List<ExtractionPoint> list2 = (from val in Object.FindObjectsOfType<ExtractionPoint>() where (Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy where !string.Equals(((object)Unsafe.As<State, State>(ref val.currentState)/*cast due to .constrained prefix*/).ToString(), "Complete", StringComparison.OrdinalIgnoreCase) orderby (!((Object)(object)truck == (Object)null)) ? Vector3.Distance(((Component)truck).transform.position, ((Component)val).transform.position) : 0f select val).ToList(); foreach (ExtractionPoint point in list2) { if (list.Count < required) { if (!list.Any((Transform existing) => Vector3.Distance(existing.position, ((Component)point).transform.position) < config.DualRadius.Value * 4f)) { list.Add(((Component)point).transform); } continue; } break; } return list.Take(required).ToList(); } catch { return list; } } private IReadOnlyList<ContractKind> GetEnabledKinds() { List<ContractKind> list = new List<ContractKind>(); foreach (ContractKind item in ContractCatalog.All.Select((ContractDefinition definition) => definition.Kind)) { if (IsEnabled(item) && IsAvailableNow(item)) { list.Add(item); } } return list; } private bool IsEnabled(ContractKind kind) { return kind switch { ContractKind.CursedCargo => config.EnableCursedCargo.Value, ContractKind.BountyEvidence => config.EnableBountyEvidence.Value, ContractKind.BlackoutRecovery => config.EnableBlackoutRecovery.Value, ContractKind.ExtractionDefense => config.EnableExtractionDefense.Value, ContractKind.DualActivation => config.EnableDualActivation.Value, _ => false, }; } private bool IsAvailableNow(ContractKind kind) { if (kind == ContractKind.DualActivation) { return GetPlayerCount() >= 2; } return true; } private ContractKind? ParseContract(string value) { if (string.IsNullOrWhiteSpace(value)) { return null; } switch (value.Trim().ToLowerInvariant()) { case "cursed": case "呪われた貨物": case "cargo": case "cursed_cargo": return ContractKind.CursedCargo; case "bounty": case "evidence": case "bounty_evidence": case "賞金首": return ContractKind.BountyEvidence; case "blackout": case "recovery": case "ブラックアウト": case "blackout_recovery": return ContractKind.BlackoutRecovery; case "defense": case "extraction_defense": case "防衛": return ContractKind.ExtractionDefense; case "dual_activation": case "二地点": case "dual": case "activation": return ContractKind.DualActivation; default: return null; } } private int RequiredUnits() { if (!activeKind.HasValue) { return 1; } ContractDefinition contractDefinition = ContractCatalog.Get(activeKind.Value); if (!config.PlayerScaling.Value) { return contractDefinition.BaseUnits; } return contractDefinition.ScaleUnits(GetPlayerCount()); } private List<PlayerAvatar> GetPlayers() { try { return (from player in SemiFunc.PlayerGetAll() where (Object)(object)player != (Object)null && (Object)(object)player.playerTransform != (Object)null select player).ToList(); } catch { return new List<PlayerAvatar>(); } } private int GetPlayerCount() { int count = GetPlayers().Count; return Math.Max(1, count); } private static bool IsNear(PlayerAvatar player, Vector3 position, float radius) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)null && (Object)(object)player.playerTransform != (Object)null) { return Vector3.Distance(player.playerTransform.position, position) <= radius; } return false; } private static bool IsPhysicallyStationary(PhysGrabObject physGrabObject) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)physGrabObject == (Object)null || physGrabObject.grabbed || (Object)(object)physGrabObject.rb == (Object)null) { return false; } Vector3 val = physGrabObject.rb.velocity; if (((Vector3)(ref val)).sqrMagnitude <= 0.25f) { val = physGrabObject.rb.angularVelocity; return ((Vector3)(ref val)).sqrMagnitude <= 0.25f; } return false; } private static string GetItemDisplayName(ItemAttributes item) { if ((Object)(object)item == (Object)null) { return "消失した既存アイテム"; } if (!string.IsNullOrWhiteSpace(item.itemName)) { return item.itemName; } if ((Object)(object)item.item != (Object)null && !string.IsNullOrWhiteSpace(item.item.itemName)) { return item.item.itemName; } if (!((Object)(object)item.photonView != (Object)null)) { return "既存アイテム"; } return $"既存アイテム#{item.photonView.ViewID}"; } private static string DescribeKnownAnchor(Transform anchor) { try { LevelPoint val = ((IEnumerable<LevelPoint>)SemiFunc.LevelPointsGetAll()).FirstOrDefault((Func<LevelPoint, bool>)((LevelPoint point) => (Object)(object)point != (Object)null && point.Truck && (Object)(object)((Component)point).transform == (Object)(object)anchor)); if ((Object)(object)val != (Object)null) { return "トラック"; } List<ExtractionPoint> list = (from point in Object.FindObjectsOfType<ExtractionPoint>() where (Object)(object)point != (Object)null && ((Component)point).gameObject.activeInHierarchy orderby ((Component)point).transform.position.x, ((Component)point).transform.position.z select point).ToList(); int num = list.FindIndex((ExtractionPoint point) => (Object)(object)((Component)point).transform == (Object)(object)anchor); return (num >= 0) ? $"抽出地点#{num + 1}" : "既知ランドマーク"; } catch { return "既知ランドマーク"; } } private static int CountLiveEnemiesNear(Vector3 position, float radius) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) int num = 0; EnemyParent[] array = Object.FindObjectsOfType<EnemyParent>(); foreach (EnemyParent val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Enemy == (Object)null) && !((Object)(object)val.Enemy.Health == (Object)null) && !val.Enemy.Health.dead && val.Spawned && Vector3.Distance(((Component)val).transform.position, position) <= radius) { num++; } } return num; } private bool IsHost() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return false; } } private bool IsLevelReady() { try { if (!SemiFunc.LevelGenDone()) { return false; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || instance.restarting || instance.waitToChangeScene) { return false; } return true; } catch { return false; } } private static PlayerAvatar? SafeLocalPlayer() { try { return SemiFunc.PlayerAvatarLocal(); } catch { return null; } } private void SendRoomMessage(string message) { PlayerAvatar val = SafeLocalPlayer(); if ((Object)(object)val != (Object)null) { try { val.ChatMessageSend("[契約局] " + message); return; } catch (Exception ex) { LogDebug("標準チャットRPC送信を縮退: " + ex.GetType().Name); } } try { ChatManager instance = ChatManager.instance; if (instance != null) { instance.ForceSendMessage("[契約局] " + message); } } catch { LogDebug("チャット通知を送信できませんでした"); } } private void OnStateChanged(ContractSnapshot snapshot) { if (snapshot.Phase == ContractPhase.Succeeded) { SendRoomMessage("【契約成功】" + snapshot.Reason); ApplySuccessReward(); } else if (snapshot.Phase == ContractPhase.Failed || snapshot.Phase == ContractPhase.TimedOut) { SendRoomMessage("【契約終了】" + snapshot.Reason); } LogDebug(string.Format("状態遷移: {0}, kind={1}, progress={2}/{3}, reason={4}", snapshot.Phase, snapshot.Kind?.ToString() ?? "none", snapshot.Progress, snapshot.RequiredUnits, snapshot.Reason)); } private void ApplySuccessReward() { //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) if (config == null || !config.EnableSuccessReward.Value || !IsHost() || !levelActive) { return; } try { int num = 0; if (config.SuccessHealAmount.Value > 0) { foreach (PlayerAvatar player in GetPlayers()) { if (!((Object)(object)player.playerHealth == (Object)null) && !player.isDisabled && !player.deadSet && player.playerHealth.health > 0 && player.playerHealth.health < player.playerHealth.maxHealth) { player.playerHealth.HealOther(config.SuccessHealAmount.Value, true); num++; } } } int num2 = 0; if (config.SuccessRevealCount.Value > 0) { PlayerAvatar val = SafeLocalPlayer(); Vector3 origin = (((Object)(object)val != (Object)null && (Object)(object)val.playerTransform != (Object)null) ? val.playerTransform.position : Vector3.zero); foreach (ValuableObject item in (from value in Object.FindObjectsOfType<ValuableObject>() where (Object)(object)value != (Object)null && !value.discovered && !value.inStartRoom orderby Vector3.Distance(origin, ((Component)value).transform.position) select value).Take(config.SuccessRevealCount.Value)) { if (GameInterop.TryDiscoverValuable(item)) { num2++; } } } SendRoomMessage($"成功報酬: HP回復 {num}人 / 標準マップ発見 {num2}件(通貨・抽出額の書換えなし)"); } catch (Exception ex) { ManualLogSource obj = logger; if (obj != null) { obj.LogWarning((object)("[ACD] 成功報酬だけを安全に縮退しました: " + ex.GetType().Name)); } } } private void LogDebug(string message) { if (config != null && config.DetailedLogging.Value) { ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)("[ACD] " + message)); } } } } internal sealed class DirectorConfig { internal ConfigEntry<bool> Enabled { get; } internal ConfigEntry<bool> DetailedLogging { get; } internal ConfigEntry<bool> PhysicalTokenSelection { get; } internal ConfigEntry<bool> FallbackRandom { get; } internal ConfigEntry<float> SelectionSeconds { get; } internal ConfigEntry<float> DiscoveryGraceSeconds { get; } internal ConfigEntry<float> PollIntervalSeconds { get; } internal ConfigEntry<bool> PlayerScaling { get; } internal ConfigEntry<bool> PreventConsecutive { get; } internal ConfigEntry<bool> EnableSuccessReward { get; } internal ConfigEntry<int> SuccessHealAmount { get; } internal ConfigEntry<int> SuccessRevealCount { get; } internal ConfigEntry<bool> EnableCursedCargo { get; } internal ConfigEntry<bool> EnableBountyEvidence { get; } internal ConfigEntry<bool> EnableBlackoutRecovery { get; } internal ConfigEntry<bool> EnableExtractionDefense { get; } internal ConfigEntry<bool> EnableDualActivation { get; } internal ConfigEntry<bool> EnableMedicalCore { get; } internal ConfigEntry<int> MedicalHealAmount { get; } internal ConfigEntry<float> MedicalRadius { get; } internal ConfigEntry<bool> EnablePortableRadar { get; } internal ConfigEntry<float> RadarRadius { get; } internal ConfigEntry<bool> EnableNoiseBeacon { get; } internal ConfigEntry<float> NoiseRadius { get; } internal ConfigEntry<float> DeviceIntervalSeconds { get; } internal ConfigEntry<float> DefenseSeconds { get; } internal ConfigEntry<float> DefenseThreatRadius { get; } internal ConfigEntry<float> DefenseThreatWaitSeconds { get; } internal ConfigEntry<float> DualRadius { get; } internal ConfigEntry<float> DualHoldSeconds { get; } internal ConfigEntry<bool> EnableAnomalyEvents { get; } internal
BepInEx/plugins/RepoHostSuite/YoneRai12.HostSkillManager.dll
Decompiled 12 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using YoneRai12.HostSkillManager.Models; using YoneRai12.HostSkillManager.Patches; using YoneRai12.HostSkillManager.Services; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("YoneRai12.HostSkillManager")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("YoneRai12.HostSkillManager")] [assembly: AssemblyTitle("YoneRai12.HostSkillManager")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace YoneRai12.HostSkillManager { [BepInPlugin("YoneRai12.HostSkillManager", "Host Skill Manager", "1.0.0")] public sealed class HostSkillManager : BaseUnityPlugin { internal const string PluginGuid = "YoneRai12.HostSkillManager"; internal const string PluginName = "Host Skill Manager"; internal const string PluginVersion = "1.0.0"; internal const string UpgradePrefix = "playerUpgrade"; internal static readonly HashSet<string> VanillaUpgradeKeys = new HashSet<string>(StringComparer.Ordinal) { "playerUpgradeCrouchRest", "playerUpgradeDeathHeadBattery", "playerUpgradeExtraJump", "playerUpgradeHealth", "playerUpgradeLaunch", "playerUpgradeMapPlayerCount", "playerUpgradeRange", "playerUpgradeSpeed", "playerUpgradeStamina", "playerUpgradeStrength", "playerUpgradeThrow", "playerUpgradeTumbleClimb", "playerUpgradeTumbleWings" }; private readonly Dictionary<string, SkillPolicy> skillPolicies = new Dictionary<string, SkillPolicy>(StringComparer.Ordinal); private readonly Dictionary<string, PlayerPolicy> playerPolicies = new Dictionary<string, PlayerPolicy>(StringComparer.Ordinal); private readonly HashSet<string> seenPlayers = new HashSet<string>(StringComparer.Ordinal); private readonly Random random = new Random(); private Harmony harmony; private ConfigEntry<bool> managerEnabled; private ConfigEntry<bool> purchaseSharingEnabled; private ConfigEntry<bool> syncNewPlayers; private ConfigEntry<bool> manageModdedUpgrades; private ConfigEntry<bool> realtimeConfigApply; private ConfigEntry<bool> verboseLogging; private int applicationDepth; private bool applyRequested = true; private bool repoLibPatched; private float nextScanTime; private float nextEnforcementTime; internal static HostSkillManager Instance { get; private set; } = null; internal bool IsApplyingPolicy => applicationDepth > 0; private void Awake() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown Instance = this; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; BindGeneralConfig(); foreach (string item in VanillaUpgradeKeys.OrderBy(SkillDisplayName)) { BindSkillPolicy(item); } harmony = new Harmony("YoneRai12.HostSkillManager"); harmony.PatchAll(Assembly.GetExecutingAssembly()); ((Component)this).gameObject.AddComponent<HostSkillNetworkCallbacks>(); TryPatchRepoLib(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Host Skill Manager v1.0.0 loaded. This manager acts only on the host."); } private void Update() { if (Time.unscaledTime < nextScanTime) { return; } nextScanTime = Time.unscaledTime + 0.5f; TryPatchRepoLib(); if (!managerEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return; } foreach (string item in UpgradeBridge.DiscoverUpgradeKeys()) { if (UpgradeBridge.IsVanilla(item) || manageModdedUpgrades.Value) { BindSkillPolicy(item); } } List<PlayerAvatar> currentPlayers = GetCurrentPlayers(); foreach (PlayerAvatar item2 in currentPlayers) { if (!string.IsNullOrWhiteSpace(item2.steamID)) { bool num = seenPlayers.Add(item2.steamID); BindPlayerPolicy(item2.steamID, item2.playerName); if (num) { ApplyPlayerPolicy(item2, isNewPlayer: true, currentPlayers); } } } if (applyRequested && realtimeConfigApply.Value) { applyRequested = false; ApplyAllFixedTargets(currentPlayers); } if (Time.unscaledTime >= nextEnforcementTime) { nextEnforcementTime = Time.unscaledTime + 3f; ApplyAllFixedTargets(currentPlayers); } } internal PurchaseSnapshot? CaptureVanillaPurchase(ItemUpgrade upgrade) { if (!CanDistributePurchase()) { return null; } ItemToggle itemToggle = upgrade.itemToggle; if (itemToggle == null || !itemToggle.toggleState) { return null; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(itemToggle.playerTogglePhotonID); if (!Object.op_Implicit((Object)(object)val) || string.IsNullOrWhiteSpace(val.steamID)) { return null; } return new PurchaseSnapshot(val.steamID, itemToggle.playerTogglePhotonID, UpgradeBridge.SnapshotVanilla(val.steamID)); } internal void FinishVanillaPurchase(PurchaseSnapshot snapshot) { if (!CanDistributePurchase()) { return; } foreach (string vanillaUpgradeKey in VanillaUpgradeKeys) { int value; int num = (snapshot.LevelsBefore.TryGetValue(vanillaUpgradeKey, out value) ? value : 0); int num2 = UpgradeBridge.ReadLevel(vanillaUpgradeKey, snapshot.SteamId); if (num2 > num) { HandlePurchasedIncrease(snapshot.SteamId, vanillaUpgradeKey, num2 - num); } } } internal void HandlePurchasedIncrease(string buyerSteamId, string upgradeKey, int purchasedLevels) { if (purchasedLevels <= 0 || !CanDistributePurchase() || (!UpgradeBridge.IsVanilla(upgradeKey) && !manageModdedUpgrades.Value)) { return; } SkillPolicy skillPolicy = BindSkillPolicy(upgradeKey); if (!skillPolicy.SharePurchases.Value || skillPolicy.ShareAmount.Value <= 0) { return; } List<PlayerAvatar> currentPlayers = GetCurrentPlayers(); int num = 0; foreach (PlayerAvatar item in currentPlayers) { if (!Object.op_Implicit((Object)(object)item) || string.IsNullOrWhiteSpace(item.steamID) || item.steamID == buyerSteamId) { continue; } BindPlayerPolicy(item.steamID, item.playerName); if (GetFixedTarget(item.steamID, skillPolicy).HasValue || (skillPolicy.ShareChance.Value < 100 && random.Next(0, 100) >= skillPolicy.ShareChance.Value)) { continue; } int num2 = UpgradeBridge.ReadLevel(upgradeKey, item.steamID); int num3 = Math.Min(100, num2 + purchasedLevels * skillPolicy.ShareAmount.Value); if (num3 == num2) { continue; } applicationDepth++; try { if (UpgradeBridge.SetExactLevel(upgradeKey, item.steamID, num3)) { num++; } } finally { applicationDepth--; } } LogDebug($"共有: {SkillDisplayName(upgradeKey)} +{purchasedLevels}, 配布先 {num}人"); } internal void ResetRoomState() { seenPlayers.Clear(); applyRequested = true; } internal void LogWarning(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } private bool CanDistributePurchase() { if (managerEnabled.Value && purchaseSharingEnabled.Value && !IsApplyingPolicy && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)StatsManager.instance != (Object)null) { return (Object)(object)PunManager.instance != (Object)null; } return false; } private void BindGeneralConfig() { managerEnabled = Bind("00 全般", "管理MODを有効", defaultValue: true, "ホストのスキル共有・人物別管理を有効にします。クライアントにはこのMODは不要です。"); purchaseSharingEnabled = Bind("00 全般", "購入スキルを共有", defaultValue: true, "誰かが購入したスキルを他のメンバーへ配布します。各スキルの項目で量と確率を変更できます。"); syncNewPlayers = Bind("00 全般", "新規参加者をチーム最大値へ同期", defaultValue: true, "途中参加者に、固定値が指定されていないスキルのチーム内最大レベルを適用します。"); manageModdedUpgrades = Bind("00 全般", "追加MODスキルも管理", defaultValue: true, "REPOLibに登録されたSelfReviveなどの追加スキルも同じ画面へ自動追加します。"); realtimeConfigApply = Bind("00 全般", "設定変更をリアルタイム反映", defaultValue: true, "REPOConfigで変更した全体・人物別の固定レベルをプレイ中すぐ反映します。"); verboseLogging = Bind("00 全般", "詳細ログ", defaultValue: false, "共有・固定値の処理内容をBepInExログへ詳しく記録します。"); Watch<bool>(managerEnabled); Watch<bool>(purchaseSharingEnabled); Watch<bool>(syncNewPlayers); Watch<bool>(manageModdedUpgrades); Watch<bool>(realtimeConfigApply); } private SkillPolicy BindSkillPolicy(string upgradeKey) { if (skillPolicies.TryGetValue(upgradeKey, out SkillPolicy value)) { return value; } string text = SkillDisplayName(upgradeKey); string section = (UpgradeBridge.IsVanilla(upgradeKey) ? "10 標準スキル" : "20 追加スキル") + " - " + text; SkillPolicy skillPolicy = new SkillPolicy(upgradeKey, Bind(section, "01 購入共有", defaultValue: true, text + "を購入した時、他のメンバーへ共有します。"), Bind(section, "02 1レベル購入時の配布量", 1, "購入者が1レベル得た時に他の各メンバーへ渡す" + text + "の量です。0で配布なし。", 0, 20), Bind(section, "03 共有確率パーセント", 100, "各メンバーへ配布する確率です。100で必ず共有します。", 0, 100), Bind(section, "04 全員を目標レベルへ固定", defaultValue: false, "ONにすると購入共有より下の目標レベルを優先し、全員を同じ値へ合わせます。"), Bind(section, "05 全員の目標レベル", 0, "固定をONにした時の" + text + "レベルです。0なら削除、最大100。", 0, 100)); Watch<bool>(skillPolicy.SharePurchases); Watch<int>(skillPolicy.ShareAmount); Watch<int>(skillPolicy.ShareChance); Watch<bool>(skillPolicy.ForceTeamTarget); Watch<int>(skillPolicy.TeamTarget); skillPolicies.Add(upgradeKey, skillPolicy); foreach (PlayerPolicy value2 in playerPolicies.Values) { BindPlayerSkillTarget(value2, skillPolicy); } LogDebug("設定へスキルを追加: " + upgradeKey); return skillPolicy; } private PlayerPolicy BindPlayerPolicy(string steamId, string playerName) { if (playerPolicies.TryGetValue(steamId, out PlayerPolicy value)) { if (!string.IsNullOrWhiteSpace(playerName) && value.DisplayName.Value != playerName) { value.DisplayName.Value = playerName; } { foreach (SkillPolicy value2 in skillPolicies.Values) { BindPlayerSkillTarget(value, value2); } return value; } } string section = "30 人物別 - " + steamId; ConfigEntry<string> displayName = Bind(section, "00 表示名", playerName ?? string.Empty, "このSteam IDで最後に確認したプレイヤー名です。識別用です。"); ConfigEntry<bool> val = Bind(section, "01 この人物の個別設定を有効", defaultValue: true, "各スキルの値が-1以外の時、その人物だけに個別目標を適用します。"); PlayerPolicy playerPolicy = new PlayerPolicy(steamId, displayName, val); playerPolicies.Add(steamId, playerPolicy); Watch<bool>(val); foreach (SkillPolicy value3 in skillPolicies.Values) { BindPlayerSkillTarget(playerPolicy, value3); } LogDebug("設定へ人物を追加: " + playerName + " (" + steamId + ")"); return playerPolicy; } private void BindPlayerSkillTarget(PlayerPolicy player, SkillPolicy skill) { if (!player.Targets.ContainsKey(skill.Key)) { string section = "30 人物別 - " + player.SteamId; ConfigEntry<int> val = Bind(section, SkillDisplayName(skill.Key) + " 目標レベル", -1, "-1は個別指定なし(全体設定を使用)、0はその人物から削除、1以上はその人物だけの目標レベルです。", -1, 100); player.Targets.Add(skill.Key, val); Watch<int>(val); } } private void ApplyAllFixedTargets(IReadOnlyList<PlayerAvatar> players) { if (!managerEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } foreach (PlayerAvatar player in players) { ApplyPlayerPolicy(player, isNewPlayer: false, players); } } private void ApplyPlayerPolicy(PlayerAvatar player, bool isNewPlayer, IReadOnlyList<PlayerAvatar> players) { if (!Object.op_Implicit((Object)(object)player) || string.IsNullOrWhiteSpace(player.steamID)) { return; } BindPlayerPolicy(player.steamID, player.playerName); foreach (SkillPolicy value in skillPolicies.Values) { if (!UpgradeBridge.IsVanilla(value.Key) && !manageModdedUpgrades.Value) { continue; } int? num = GetFixedTarget(player.steamID, value); if (!num.HasValue && isNewPlayer && syncNewPlayers.Value && value.SharePurchases.Value) { int num2 = 0; foreach (PlayerAvatar player2 in players) { if (Object.op_Implicit((Object)(object)player2) && !string.IsNullOrWhiteSpace(player2.steamID)) { num2 = Math.Max(num2, UpgradeBridge.ReadLevel(value.Key, player2.steamID)); } } if (num2 > UpgradeBridge.ReadLevel(value.Key, player.steamID)) { num = num2; } } if (!num.HasValue) { continue; } applicationDepth++; try { if (!UpgradeBridge.SetExactLevel(value.Key, player.steamID, num.Value)) { LogDebug($"適用待ち: {player.playerName} / {SkillDisplayName(value.Key)} = {num.Value}"); } } finally { applicationDepth--; } } } private int? GetFixedTarget(string steamId, SkillPolicy skill) { if (playerPolicies.TryGetValue(steamId, out PlayerPolicy value) && value.Enabled.Value && value.Targets.TryGetValue(skill.Key, out ConfigEntry<int> value2) && value2.Value >= 0) { return value2.Value; } if (!skill.ForceTeamTarget.Value) { return null; } return skill.TeamTarget.Value; } private List<PlayerAvatar> GetCurrentPlayers() { try { return (from player in SemiFunc.PlayerGetAll() where (Object)(object)player != (Object)null && !string.IsNullOrWhiteSpace(player.steamID) select player).ToList(); } catch { return new List<PlayerAvatar>(); } } private void TryPatchRepoLib() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0061: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_009b: Expected O, but got Unknown if (!repoLibPatched) { MethodInfo setLevelMethod = UpgradeBridge.SetLevelMethod; MethodInfo applyUpgradeMethod = UpgradeBridge.ApplyUpgradeMethod; if (!(setLevelMethod == null) && !(applyUpgradeMethod == null)) { harmony.Patch((MethodBase)setLevelMethod, new HarmonyMethod(typeof(RepoLibUpgradeHooks), "SetLevelPrefix", (Type[])null), new HarmonyMethod(typeof(RepoLibUpgradeHooks), "SetLevelPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)applyUpgradeMethod, new HarmonyMethod(typeof(RepoLibUpgradeHooks), "ApplyUpgradePrefix", (Type[])null), new HarmonyMethod(typeof(RepoLibUpgradeHooks), "ApplyUpgradePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); repoLibPatched = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"REPOLib upgrade synchronization detected and connected."); } } } private ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description) { return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description); } private ConfigEntry<int> Bind(string section, string key, int defaultValue, string description, int minimum, int maximum) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<int>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(minimum, maximum), Array.Empty<object>())); } private void Watch<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { applyRequested = true; }; } private void LogDebug(string message) { if (verboseLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } } internal static string SkillDisplayName(string upgradeKey) { string text; if (!upgradeKey.StartsWith("playerUpgrade", StringComparison.Ordinal)) { text = upgradeKey; } else { string text2 = upgradeKey; int length = "playerUpgrade".Length; text = text2.Substring(length, text2.Length - length); } string text3 = text; return text3 switch { "CrouchRest" => "しゃがみ回復", "DeathHeadBattery" => "デスヘッド電池", "ExtraJump" => "追加ジャンプ", "Health" => "体力", "Launch" => "タンブル射出", "MapPlayerCount" => "マップ表示人数", "Range" => "掴み距離", "Speed" => "移動速度", "Stamina" => "スタミナ", "Strength" => "掴み力", "Throw" => "投擲力", "TumbleClimb" => "タンブル登攀", "TumbleWings" => "タンブル飛行", "SelfRevive" => "自己蘇生", _ => text3, }; } } internal sealed class HostSkillNetworkCallbacks : MonoBehaviourPunCallbacks { public override void OnJoinedRoom() { HostSkillManager.Instance?.ResetRoomState(); } public override void OnLeftRoom() { HostSkillManager.Instance?.ResetRoomState(); } public override void OnMasterClientSwitched(Player newMasterClient) { HostSkillManager.Instance?.ResetRoomState(); } } } namespace YoneRai12.HostSkillManager.Services { internal static class UpgradeBridge { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static Type? playerUpgradeType; private static FieldInfo? upgradeIdField; private static FieldInfo? playerDictionaryField; private static FieldInfo? playerUpgradesField; private static PropertyInfo? playerUpgradesProperty; private static MethodInfo? setLevelMethod; private static MethodInfo? applyUpgradeMethod; internal static MethodInfo? SetLevelMethod { get { ResolveRepoLib(); return setLevelMethod; } } internal static MethodInfo? ApplyUpgradeMethod { get { ResolveRepoLib(); return applyUpgradeMethod; } } internal static bool IsVanilla(string upgradeKey) { return HostSkillManager.VanillaUpgradeKeys.Contains(upgradeKey); } internal static HashSet<string> DiscoverUpgradeKeys() { HashSet<string> hashSet = new HashSet<string>(HostSkillManager.VanillaUpgradeKeys, StringComparer.Ordinal); if (StatsManager.instance?.dictionaryOfDictionaries != null) { foreach (string key in StatsManager.instance.dictionaryOfDictionaries.Keys) { if (key.StartsWith("playerUpgrade", StringComparison.Ordinal)) { hashSet.Add(key); } } } ResolveRepoLib(); foreach (string item in EnumerateRepoUpgradeIds()) { hashSet.Add("playerUpgrade" + item); } return hashSet; } internal static int ReadLevel(string upgradeKey, string steamId) { if (!IsVanilla(upgradeKey) && TryGetRepoUpgrade(upgradeKey, out object playerUpgrade)) { return ReadRepoLevel(playerUpgrade, steamId); } if (StatsManager.instance?.dictionaryOfDictionaries != null && StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value) && value.TryGetValue(steamId, out var value2)) { return value2; } return 0; } internal static Dictionary<string, int> SnapshotVanilla(string steamId) { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); foreach (string vanillaUpgradeKey in HostSkillManager.VanillaUpgradeKeys) { dictionary[vanillaUpgradeKey] = ReadLevel(vanillaUpgradeKey, steamId); } return dictionary; } internal static bool SetExactLevel(string upgradeKey, string steamId, int requestedLevel) { int num = Math.Max(0, Math.Min(100, requestedLevel)); int num2 = ReadLevel(upgradeKey, steamId); if (num2 == num) { return true; } if (IsVanilla(upgradeKey)) { if ((Object)(object)PunManager.instance == (Object)null) { return false; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null) { return false; } object[] obj = new object[3] { steamId, null, null }; int length = "playerUpgrade".Length; obj[1] = upgradeKey.Substring(length, upgradeKey.Length - length); obj[2] = num - num2; component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, obj); return true; } if (!TryGetRepoUpgrade(upgradeKey, out object playerUpgrade) || setLevelMethod == null) { return false; } try { setLevelMethod.Invoke(playerUpgrade, new object[2] { steamId, num }); return true; } catch (Exception ex) { Exception ex2 = ((ex is TargetInvocationException ex3 && ex.InnerException != null) ? ex3.InnerException : ex); HostSkillManager.Instance.LogWarning("REPOLib SetLevel failed: " + upgradeKey + ", " + steamId + ", " + ex2.GetType().Name + ": " + ex2.Message); return false; } } internal static bool TryGetUpgradeKey(object playerUpgrade, out string upgradeKey) { upgradeKey = string.Empty; ResolveRepoLib(); if (!(upgradeIdField?.GetValue(playerUpgrade) is string text) || string.IsNullOrWhiteSpace(text)) { return false; } upgradeKey = "playerUpgrade" + text; return true; } internal static int ReadRepoLevel(object playerUpgrade, string steamId) { ResolveRepoLib(); if (!(playerDictionaryField?.GetValue(playerUpgrade) is IDictionary dictionary)) { return 0; } if (!dictionary.Contains(steamId)) { return 0; } object obj = dictionary[steamId]; if (obj is int) { return (int)obj; } return 0; } private static void ResolveRepoLib() { if (!(playerUpgradeType != null) || !(setLevelMethod != null)) { Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); Type type2 = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); if (!(type == null) && !(type2 == null)) { playerUpgradeType = type2; upgradeIdField = playerUpgradeType.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); playerDictionaryField = playerUpgradeType.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); playerUpgradesField = type.GetField("_playerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); playerUpgradesProperty = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); setLevelMethod = playerUpgradeType.GetMethod("SetLevel", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); applyUpgradeMethod = playerUpgradeType.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); } } } private static IEnumerable<string> EnumerateRepoUpgradeIds() { object obj = playerUpgradesProperty?.GetValue(null) ?? playerUpgradesField?.GetValue(null); if (obj is IDictionary dictionary) { foreach (DictionaryEntry item in dictionary) { if (item.Key is string text && !string.IsNullOrWhiteSpace(text)) { yield return text; } } } else { if (!(obj is IEnumerable enumerable) || upgradeIdField == null) { yield break; } foreach (object item2 in enumerable) { if (item2 != null && upgradeIdField.GetValue(item2) is string text2 && !string.IsNullOrWhiteSpace(text2)) { yield return text2; } } } } private static bool TryGetRepoUpgrade(string upgradeKey, out object? playerUpgrade) { playerUpgrade = null; ResolveRepoLib(); if (!upgradeKey.StartsWith("playerUpgrade", StringComparison.Ordinal)) { return false; } int length = "playerUpgrade".Length; string key = upgradeKey.Substring(length, upgradeKey.Length - length); if (!(playerUpgradesField?.GetValue(null) is IDictionary dictionary) || !dictionary.Contains(key)) { return false; } playerUpgrade = dictionary[key]; return playerUpgrade != null; } } } namespace YoneRai12.HostSkillManager.Patches { internal static class RepoLibUpgradeHooks { [ThreadStatic] private static int? stashedPreviousLevel; public static void SetLevelPrefix(object __instance, string steamId) { stashedPreviousLevel = UpgradeBridge.ReadRepoLevel(__instance, steamId); } public static void SetLevelPostfix() { stashedPreviousLevel = null; } public static void ApplyUpgradePrefix(object __instance, string steamId, out int __state) { if (stashedPreviousLevel.HasValue) { __state = stashedPreviousLevel.Value; stashedPreviousLevel = null; } else { __state = UpgradeBridge.ReadRepoLevel(__instance, steamId); } } public static void ApplyUpgradePostfix(object __instance, string steamId, int level, int __state) { if (level > __state && UpgradeBridge.TryGetUpgradeKey(__instance, out string upgradeKey)) { HostSkillManager.Instance?.HandlePurchasedIncrease(steamId, upgradeKey, level - __state); } } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] internal static class VanillaPurchasePatch { [HarmonyPrefix] private static void Prefix(ItemUpgrade __instance, out PurchaseSnapshot? __state) { __state = HostSkillManager.Instance?.CaptureVanillaPurchase(__instance); } [HarmonyPostfix] private static void Postfix(PurchaseSnapshot? __state) { if (__state != null) { HostSkillManager.Instance?.FinishVanillaPurchase(__state); } } } } namespace YoneRai12.HostSkillManager.Models { internal sealed class PlayerPolicy { internal string SteamId { get; } internal ConfigEntry<string> DisplayName { get; } internal ConfigEntry<bool> Enabled { get; } internal Dictionary<string, ConfigEntry<int>> Targets { get; } = new Dictionary<string, ConfigEntry<int>>(); internal PlayerPolicy(string steamId, ConfigEntry<string> displayName, ConfigEntry<bool> enabled) { SteamId = steamId; DisplayName = displayName; Enabled = enabled; } } internal sealed class PurchaseSnapshot { internal string SteamId { get; } internal int ViewId { get; } internal Dictionary<string, int> LevelsBefore { get; } internal PurchaseSnapshot(string steamId, int viewId, Dictionary<string, int> levelsBefore) { SteamId = steamId; ViewId = viewId; LevelsBefore = levelsBefore; } } internal sealed class SkillPolicy { internal string Key { get; } internal string CleanName { get { if (!Key.StartsWith("playerUpgrade")) { return Key; } string key = Key; int length = "playerUpgrade".Length; return key.Substring(length, key.Length - length); } } internal ConfigEntry<bool> SharePurchases { get; } internal ConfigEntry<int> ShareAmount { get; } internal ConfigEntry<int> ShareChance { get; } internal ConfigEntry<bool> ForceTeamTarget { get; } internal ConfigEntry<int> TeamTarget { get; } internal SkillPolicy(string key, ConfigEntry<bool> sharePurchases, ConfigEntry<int> shareAmount, ConfigEntry<int> shareChance, ConfigEntry<bool> forceTeamTarget, ConfigEntry<int> teamTarget) { Key = key; SharePurchases = sharePurchases; ShareAmount = shareAmount; ShareChance = shareChance; ForceTeamTarget = forceTeamTarget; TeamTarget = teamTarget; } } }