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 PandoraEnemyPatch v1.0.14
BepInEx\plugins\BeneathTwo-PandoraEnemyPatch\PandoraEnemyPatch.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using CodeRebirthLib.Util.INetworkSerializables; using GameNetcodeStuff; using Microsoft.CodeAnalysis; using MonoMod.RuntimeDetour; using PandoraEnemy; using PandoraEnemy.Content.Enemies; using PandoraEnemyPatch.Attacks; using PandoraEnemyPatch.Chasing; using PandoraEnemyPatch.Hooks; using PandoraEnemyPatch.Logging; using PandoraEnemyPatch.Networking; using PandoraEnemyPatch.Runtime; using PandoraEnemyPatch.Swapping; using PandoraEnemyPatch.Targeting; using PandoraEnemyPatch.UI; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.AI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("com.github.xuuxiaolan.pandoraenemy")] [assembly: AssemblyCompany("PandoraEnemyPatch")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.14.0")] [assembly: AssemblyInformationalVersion("1.0.14+c4c2a7ce235c3aea691e90e0cbb65ac5a59a0e50")] [assembly: AssemblyProduct("PandoraEnemyPatch")] [assembly: AssemblyTitle("PandoraEnemyPatch")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.14.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 PandoraEnemyPatch { [BepInPlugin("PandoraEnemyPatch", "PandoraEnemyPatch", "1.0.14")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private static bool _registered; internal static Plugin Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } private void Awake() { Instance = this; Logger = ((BaseUnityPlugin)this).Logger; NetworkEventLog.EnsureInitialized(); if (!_registered) { PandoraHookRegistrar.Register(); _registered = true; } NetworkEventLog.Info("PandoraEnemyPatch v1.0.14 has loaded!"); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "PandoraEnemyPatch"; public const string PLUGIN_NAME = "PandoraEnemyPatch"; public const string PLUGIN_VERSION = "1.0.14"; } } namespace PandoraEnemyPatch.UI { internal static class GroundPoundEscapeCue { private const string LogCategory = "GroundPoundCue"; private const string EscapeHeader = ""; private const string EscapeTipText = "JUMP! BREAK FREE!"; private const int InitialPulseTextIndex = 0; private const int FirstFollowUpPulseIndex = 1; private const int FallbackPulseIndex = 0; private static readonly string[] PulseTexts = new string[3] { "JUMP!", "BREAK FREE!", "JUMP!" }; private static readonly HashSet<int> ActiveEnemyIds = new HashSet<int>(); private static readonly Dictionary<int, int> PulseIndexByEnemyId = new Dictionary<int, int>(); public static void ShowForLocalPlayer(PandoraEnemyAI self, PlayerControllerB? player) { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)player == (Object)null || (Object)(object)val == (Object)null || (Object)(object)player != (Object)(object)val) { Log(self, "skipped cue because target/local player did not match.", relayToOtherClients: false); return; } if ((Object)(object)HUDManager.Instance == (Object)null) { Log(self, "skipped cue because HUDManager.Instance was null.", relayToOtherClients: false); return; } int instanceID = ((Object)self).GetInstanceID(); ActiveEnemyIds.Add(instanceID); PulseIndexByEnemyId[instanceID] = 1; Log(self, $"showing escape cue for {val.playerUsername} ({val.playerClientId}).", relayToOtherClients: false); HUDManager.Instance.DisplayTip("", "JUMP! BREAK FREE!", true, false, "LC_Tip1"); HUDManager.Instance.DisplayStatusEffect(PulseTexts[0]); } public static void Cancel(PandoraEnemyAI self) { StopTracking(self); Log(self, "cancelled active ground-pound cue sequence.", relayToOtherClients: false); } public static void PulseFromHeartbeat(HeartbeatAudioHandler heartbeat) { PandoraEnemyAI val = ((Component)heartbeat).GetComponent<PandoraEnemyAI>() ?? ((Component)heartbeat).GetComponentInParent<PandoraEnemyAI>(); if ((Object)(object)val == (Object)null) { return; } int instanceID = ((Object)val).GetInstanceID(); if (!ActiveEnemyIds.Contains(instanceID)) { return; } if (!IsCueStillValid(val)) { StopTracking(val); return; } if ((Object)(object)HUDManager.Instance == (Object)null) { Log(val, "skipped heartbeat pulse because HUDManager.Instance was null.", relayToOtherClients: false); return; } if (!PulseIndexByEnemyId.TryGetValue(instanceID, out var value)) { value = 0; } string text = PulseTexts[value]; Log(val, "pulsing heartbeat-synced escape cue '" + text + "'.", relayToOtherClients: false); HUDManager.Instance.DisplayStatusEffect(text); PulseIndexByEnemyId[instanceID] = (value + 1) % PulseTexts.Length; } public static bool IsActiveFor(PandoraEnemyAI self) { return ActiveEnemyIds.Contains(((Object)self).GetInstanceID()); } private static bool IsCueStillValid(PandoraEnemyAI self) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)self._nextAttack > 0) { Log(self, "stopped heartbeat pulse because Pandora is no longer performing GroundPound.", relayToOtherClients: false); return false; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null || val.isPlayerDead || !val.disableMoveInput) { Log(self, "stopped heartbeat pulse because the player was already released or invalid.", relayToOtherClients: false); return false; } return true; } private static void StopTracking(PandoraEnemyAI self) { int instanceID = ((Object)self).GetInstanceID(); ActiveEnemyIds.Remove(instanceID); PulseIndexByEnemyId.Remove(instanceID); } private static void Log(PandoraEnemyAI self, string message, bool relayToOtherClients) { NetworkEventLog.Write("[GroundPoundCue] [" + EnemyLogLabel.For(self) + "] " + message, relayToOtherClients); } } internal static class GroundPoundHeartbeatIntensity { private const float DangerHeartbeatMaxInterval = 1.2f; private const float DangerHeartbeatMinInterval = 0.35f; private const float DangerHeartbeatMinVolume = 0.85f; private const float DangerHeartbeatMaxVolume = 1.3f; public static void ApplyIfActive(HeartbeatAudioHandler heartbeat, AudioSource source) { PandoraEnemyAI val = ResolvePandora(heartbeat); if ((Object)(object)val == (Object)null || !GroundPoundEscapeCue.IsActiveFor(val)) { return; } if (!TryGetPinnedLocalPlayer(val, out PlayerControllerB localPlayer)) { GroundPoundEscapeCue.Cancel(val); return; } PandoraAdditionalPlayerData orCreate = PandoraAdditionalPlayerData.GetOrCreate(localPlayer); if ((Object)(object)orCreate.inAnimationWithPandora != (Object)(object)val) { GroundPoundEscapeCue.Cancel(val); return; } float num = Mathf.Clamp01(orCreate.OverrideSinkingValue); float num2 = Mathf.SmoothStep(0f, 1f, num); heartbeat.Focus(); heartbeat.SetInterval(Mathf.Lerp(1.2f, 0.35f, num2)); source.volume = Mathf.Max(source.volume, Mathf.Lerp(0.85f, 1.3f, num2)); source.pitch = Mathf.Lerp(1f, 1.1f, num2); } private static bool TryGetPinnedLocalPlayer(PandoraEnemyAI self, out PlayerControllerB localPlayer) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 localPlayer = GameNetworkManager.Instance?.localPlayerController; return (Object)(object)localPlayer != (Object)null && !localPlayer.isPlayerDead && localPlayer.disableMoveInput && (int)self._nextAttack == 0; } private static PandoraEnemyAI? ResolvePandora(HeartbeatAudioHandler heartbeat) { return ((Component)heartbeat).GetComponent<PandoraEnemyAI>() ?? ((Component)heartbeat).GetComponentInParent<PandoraEnemyAI>(); } } } namespace PandoraEnemyPatch.Targeting { internal readonly struct TargetObservation { public PlayerControllerB Player { get; } public Vector3 TargetPoint { get; } public float Distance { get; } public TargetObservation(PlayerControllerB player, Vector3 targetPoint, float distance) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Player = player; TargetPoint = targetPoint; Distance = distance; } } internal sealed class PandoraTargetingService { private const float TargetAnchorHeight = 1.6f; private const int PlayerSightMask = 60; private const float PlayerSightProximity = -1f; internal const float SharedVisibilityRange = 40f; public bool TryGetClosestVisibleTarget(PandoraEnemyAI self, out PlayerControllerB? player) { player = null; TargetObservation? targetObservation = null; PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB player2 in allPlayerScripts) { if (TryObserve(self, player2, out var observation) && (!targetObservation.HasValue || observation.Distance < targetObservation.Value.Distance)) { targetObservation = observation; } } if (!targetObservation.HasValue) { return false; } player = targetObservation.Value.Player; return true; } public bool CanAnyEligiblePlayerSeePandora(PandoraEnemyAI self) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (IsEligible(val) && val.HasLineOfSightToPosition(((EnemyAI)self).eye.position, 40f, 60, -1f)) { return true; } } return false; } internal static bool TryObserve(PandoraEnemyAI self, PlayerControllerB? player, out TargetObservation observation) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) observation = default(TargetObservation); if (!IsEligible(player)) { return false; } Vector3 targetPoint = GetTargetPoint(player); if (!HasLineOfSight(self, targetPoint, out var distance)) { return false; } observation = new TargetObservation(player, targetPoint, distance); return true; } private static bool IsEligible(PlayerControllerB? player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return (Object)(object)player != (Object)null && (Object)player != (Object)null && !player.isPlayerDead && player.isPlayerControlled; } private static Vector3 GetTargetPoint(PlayerControllerB player) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player.gameplayCamera != (Object)null) { return ((Component)player.gameplayCamera).transform.position; } return ((Component)player).transform.position + Vector3.up * 1.6f; } private static bool HasLineOfSight(PandoraEnemyAI self, Vector3 targetPoint, out float distance) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((EnemyAI)self).eye.position; distance = Vector3.Distance(position, targetPoint); if (distance > 40f) { return false; } Vector3 val = targetPoint - position; Vector3 normalized = ((Vector3)(ref val)).normalized; return !Physics.Raycast(position, normalized, distance, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1); } } } namespace PandoraEnemyPatch.Swapping { internal sealed class SwapPlanner { internal readonly struct SwapResolution { public bool Succeeded { get; } public SwapTeleportPlan? Plan { get; } public IReadOnlyList<SwapTeleportStep> FallbackSteps { get; } public string FailureReason { get; } public SwapResolution(SwapTeleportPlan plan) { Succeeded = true; Plan = plan; FallbackSteps = Array.Empty<SwapTeleportStep>(); FailureReason = string.Empty; } public SwapResolution(string failureReason, IReadOnlyList<SwapTeleportStep> fallbackSteps) { Succeeded = false; Plan = null; FallbackSteps = fallbackSteps; FailureReason = failureReason; } } private const string LogCategory = "Swap"; public bool CanBuildAnySwap(PandoraEnemyAI self) { List<PlayerControllerB> eligibleSeenPlayers = GetEligibleSeenPlayers(self); for (int i = 0; i < eligibleSeenPlayers.Count; i++) { for (int j = i + 1; j < eligibleSeenPlayers.Count; j++) { if (CanSwap(eligibleSeenPlayers[i], eligibleSeenPlayers[j], self._minTeleportSeparation)) { return true; } } } return false; } public SwapResolution Resolve(PandoraEnemyAI self, IReadOnlyList<PlayerControllerB> trappedPlayers) { //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) List<PlayerControllerB> list = (from player in trappedPlayers.Where(IsEligible).Distinct() orderby player.actualClientId select player).ToList(); if (list.Count == 0) { return CreateFailedResolution(self, list, "no trapped players were still valid when the swap resolved"); } List<PlayerControllerB> eligibleSeenPlayers = GetEligibleSeenPlayers(self); Log(self, "planner snapshot trapped=" + FormatPlayers(list) + " seenEligible=" + FormatPlayers(eligibleSeenPlayers)); if (eligibleSeenPlayers.Count < 2) { return CreateFailedResolution(self, list, $"not enough eligible players were seen to build a swap. eligible={eligibleSeenPlayers.Count}"); } HashSet<ulong> hashSet = new HashSet<ulong>(); List<SwapTeleportStep> list2 = new List<SwapTeleportStep>(list.Count * 2); List<PlannedSwapPair> list3 = new List<PlannedSwapPair>(list.Count); foreach (PlayerControllerB item in list) { if (!hashSet.Contains(item.actualClientId)) { PlayerControllerB partner = FindPartner(item, eligibleSeenPlayers, hashSet, self, self._minTeleportSeparation); if ((Object)(object)partner == (Object)null) { return CreateFailedResolution(self, list, BuildNoPartnerReason(item, eligibleSeenPlayers, hashSet, self._minTeleportSeparation)); } hashSet.Add(item.actualClientId); hashSet.Add(partner.actualClientId); list2.Add(new SwapTeleportStep(item, ((Component)partner).transform.position, partner.targetYRot)); list3.Add(new PlannedSwapPair(item, partner)); if (!list.Any((PlayerControllerB player) => player.actualClientId == partner.actualClientId)) { list2.Add(new SwapTeleportStep(partner, ((Component)item).transform.position, item.targetYRot)); list3.Add(new PlannedSwapPair(partner, item)); } else { list2.Add(new SwapTeleportStep(partner, ((Component)item).transform.position, item.targetYRot)); } } } return new SwapResolution(new SwapTeleportPlan(list2, list3)); } public void Execute(PandoraEnemyAI self, SwapTeleportPlan plan) { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) Log(self, $"executing plan with {plan.Pairs.Count} swap assignment(s)"); foreach (PlannedSwapPair pair in plan.Pairs) { Log(self, $"{pair.Source.playerUsername} ({pair.Source.playerClientId}) -> " + $"{pair.DestinationSource.playerUsername} ({pair.DestinationSource.playerClientId})"); } foreach (SwapTeleportStep step in plan.Steps) { PlayerControllerReference val = PlayerControllerReference.op_Implicit(step.Player); self.TeleportPlayerServerRPC(val, step.Destination, step.YRot); } } public void Execute(PandoraEnemyAI self, SwapResolution resolution) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!resolution.Succeeded) { Log(self, "cancelled swap attack because " + resolution.FailureReason + "."); { foreach (SwapTeleportStep fallbackStep in resolution.FallbackSteps) { Log(self, $"falling back to random teleport for {fallbackStep.Player.playerUsername} ({fallbackStep.Player.playerClientId})."); PlayerControllerReference val = PlayerControllerReference.op_Implicit(fallbackStep.Player); self.TeleportPlayerServerRPC(val, fallbackStep.Destination, fallbackStep.YRot); } return; } } Execute(self, resolution.Plan); } private List<PlayerControllerB> GetEligibleSeenPlayers(PandoraEnemyAI self) { return (from player in self._playersSeenOverLifetime.Where(IsEligible).Distinct() orderby player.actualClientId select player).ToList(); } private PlayerControllerB? FindPartner(PlayerControllerB trappedPlayer, IReadOnlyList<PlayerControllerB> eligiblePlayers, HashSet<ulong> reservedClientIds, PandoraEnemyAI self, float minSeparation) { HashSet<ulong> reservedClientIds2 = reservedClientIds; PlayerControllerB trappedPlayer2 = trappedPlayer; List<PlayerControllerB> list = eligiblePlayers.Where((PlayerControllerB candidate) => !reservedClientIds2.Contains(candidate.actualClientId) && CanSwap(trappedPlayer2, candidate, minSeparation)).ToList(); if (list.Count == 0) { return null; } return list[((PandoraBaseEnemyAI)self)._enemyRandom.Next(list.Count)]; } private static string BuildNoPartnerReason(PlayerControllerB trappedPlayer, IReadOnlyList<PlayerControllerB> eligiblePlayers, HashSet<ulong> reservedClientIds, float minSeparation) { HashSet<ulong> reservedClientIds2 = reservedClientIds; PlayerControllerB trappedPlayer2 = trappedPlayer; List<PlayerControllerB> list = eligiblePlayers.Where((PlayerControllerB candidate) => !reservedClientIds2.Contains(candidate.actualClientId) && candidate != trappedPlayer2).ToList(); if (list.Count == 0) { return "no partner remained for " + FormatPlayer(trappedPlayer2) + " after other assignments were reserved"; } float num = list.Select((PlayerControllerB candidate) => Vector3.Distance(((Component)trappedPlayer2).transform.position, ((Component)candidate).transform.position)).Min(); return $"no partner for {FormatPlayer(trappedPlayer2)} satisfied the minimum separation of {minSeparation:0.##}. " + $"nearest available distance was {num:0.##}"; } private static SwapResolution CreateFailedResolution(PandoraEnemyAI self, IReadOnlyList<PlayerControllerB> trapped, string failureReason) { return new SwapResolution(failureReason, BuildRandomTeleportFallbacks(self, trapped)); } private static IReadOnlyList<SwapTeleportStep> BuildRandomTeleportFallbacks(PandoraEnemyAI self, IReadOnlyList<PlayerControllerB> trapped) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) List<SwapTeleportStep> list = new List<SwapTeleportStep>(trapped.Count); foreach (PlayerControllerB item in trapped) { if (IsEligible(item) && TryPickFarTeleport(self, item, out var destination, out var yRot)) { list.Add(new SwapTeleportStep(item, destination, yRot)); } } return list; } private static bool TryPickFarTeleport(PandoraEnemyAI self, PlayerControllerB player, out Vector3 destination, out float yRot) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) destination = default(Vector3); yRot = player.targetYRot; Vector3 position = ((Component)player).transform.position; for (int i = 0; i < self._teleportAttempts; i++) { Vector3 position2 = RoundManager.Instance.insideAINodes[((PandoraBaseEnemyAI)self)._enemyRandom.Next(0, RoundManager.Instance.insideAINodes.Length)].transform.position; Vector3 randomNavMeshPositionInBoxPredictable = RoundManager.Instance.GetRandomNavMeshPositionInBoxPredictable(position2, 10f, default(NavMeshHit), ((PandoraBaseEnemyAI)self)._enemyRandom, -1, 1f); if (!(Vector3.Distance(randomNavMeshPositionInBoxPredictable, position) < self._minTeleportSeparation) && !(Vector3.Distance(randomNavMeshPositionInBoxPredictable, ((Component)self).transform.position) < 10f)) { destination = randomNavMeshPositionInBoxPredictable; return true; } } destination = RoundManager.Instance.GetRandomNavMeshPositionInRadiusSpherical(position, 60f, default(NavMeshHit)); return true; } private static bool IsEligible(PlayerControllerB? player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return (Object)(object)player != (Object)null && (Object)player != (Object)null && !player.isPlayerDead && player.isPlayerControlled; } private static bool CanSwap(PlayerControllerB source, PlayerControllerB candidate, float minSeparation) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) return IsEligible(source) && IsEligible(candidate) && source != candidate && Vector3.Distance(((Component)source).transform.position, ((Component)candidate).transform.position) >= minSeparation; } private static string FormatPlayers(IReadOnlyList<PlayerControllerB> players) { if (players.Count == 0) { return "[]"; } return "[" + string.Join(", ", players.Select((PlayerControllerB player) => FormatPlayer(player))) + "]"; } private static string FormatPlayer(PlayerControllerB player) { return $"{player.playerUsername} ({player.playerClientId})"; } private static void Log(PandoraEnemyAI self, string message, bool relayToOtherClients = true) { NetworkEventLog.Write("[Swap] [" + EnemyLogLabel.For(self) + "] " + message, relayToOtherClients); } } internal readonly struct SwapTeleportStep { public PlayerControllerB Player { get; } public Vector3 Destination { get; } public float YRot { get; } public SwapTeleportStep(PlayerControllerB player, Vector3 destination, float yRot) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Player = player; Destination = destination; YRot = yRot; } } internal readonly struct PlannedSwapPair { public PlayerControllerB Source { get; } public PlayerControllerB DestinationSource { get; } public PlannedSwapPair(PlayerControllerB source, PlayerControllerB destinationSource) { Source = source; DestinationSource = destinationSource; } } internal sealed class SwapTeleportPlan { public IReadOnlyList<SwapTeleportStep> Steps { get; } public IReadOnlyList<PlannedSwapPair> Pairs { get; } public SwapTeleportPlan(IReadOnlyList<SwapTeleportStep> steps, IReadOnlyList<PlannedSwapPair> pairs) { Steps = steps; Pairs = pairs; } } } namespace PandoraEnemyPatch.Runtime { internal sealed class PandoraAiIntervalCoordinator { private static readonly FieldInfo StartHeartbeatDistanceField = typeof(PandoraEnemyAI).GetField("_startHeartbeatDistance", BindingFlags.Instance | BindingFlags.NonPublic); public void Execute(PandoraEnemyAI self) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected I4, but got Unknown if (!StartOfRound.Instance.allPlayersDead && !((EnemyAI)self).isEnemyDead) { SyncHeartbeatRange(self); UpdateHostAnimation(self); self._timeSinceLastAttack += ((EnemyAI)self).AIIntervalTime; TrackNewlySeenPlayers(self); PandoraState value = self._currentState.Value; PandoraState val = value; switch ((int)val) { case 0: self.DoIdle(); break; case 1: self.DoAttacking(); break; case 2: self.DoChasing(); break; case 3: self.DoDeath(); break; } } } private static void SyncHeartbeatRange(PandoraEnemyAI self) { float num2 = ((StartHeartbeatDistanceField.GetValue(self) is float num) ? num : 0f); if (!Mathf.Approximately(num2, 40f)) { StartHeartbeatDistanceField.SetValue(self, 40f); } } private static void UpdateHostAnimation(PandoraEnemyAI self) { //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 (((NetworkBehaviour)self).IsHost) { Animator creatureAnimator = ((EnemyAI)self).creatureAnimator; Vector3 velocity = ((EnemyAI)self).agent.velocity; creatureAnimator.SetFloat(PandoraEnemyAI.RunSpeedAnimation, ((Vector3)(ref velocity)).magnitude / 3f); } } private static void TrackNewlySeenPlayers(PandoraEnemyAI self) { PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && !val.isPlayerDead && val.isPlayerControlled && !self._playersSeenOverLifetime.Contains(val) && self.CanSeePlayer(val)) { self._playersSeenOverLifetime.Add(val); } } } } internal static class PandoraAttackRecovery { private const string GroundPoundLogCategory = "GroundPound"; public static List<PlayerControllerB> CapturePlayers(PandoraEnemyAI self) { List<PlayerControllerB> list = new List<PlayerControllerB>(); if ((Object)(object)((EnemyAI)self).targetPlayer != (Object)null) { list.Add(((EnemyAI)self).targetPlayer); } foreach (PlayerControllerB item in self._playersTrappedThisAttack) { if (!list.Contains(item)) { list.Add(item); } } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val != (Object)null) { PandoraAdditionalPlayerData orCreate = PandoraAdditionalPlayerData.GetOrCreate(val); if ((Object)(object)orCreate.inAnimationWithPandora == (Object)(object)self && !list.Contains(val)) { list.Add(val); } } return list; } public static void ReleasePlayers(PandoraEnemyAI self, IReadOnlyList<PlayerControllerB> players, NextAttack completedAttack) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if ((int)completedAttack == 0) { GroundPoundEscapeCue.Cancel(self); } foreach (PlayerControllerB player in players) { if (!((Object)(object)player == (Object)null) && !((Object)player == (Object)null)) { player.disableMoveInput = false; PandoraAdditionalPlayerData orCreate = PandoraAdditionalPlayerData.GetOrCreate(player); if ((Object)(object)orCreate.inAnimationWithPandora == (Object)(object)self) { orCreate.OverrideSinkingValue = 0f; orCreate.inAnimationWithPandora = null; } } } } public static void ReleaseGroundPoundPlayer(PandoraEnemyAI self, PlayerControllerB? player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!((Object)(object)player == (Object)null) && !((Object)player == (Object)null)) { GroundPoundEscapeCue.Cancel(self); player.disableMoveInput = false; NetworkEventLog.Write(string.Format("[{0}] [{1}] released {2} ({3}) after sinking resolved.", "GroundPound", EnemyLogLabel.For(self), player.playerUsername, player.playerClientId), relayToOtherClients: false); } } } internal static class PandoraStareTimerTuning { private const float OriginalStareDecayPerSecond = 1f; private const float DesiredStareDecayPerSecond = 0.5f; private const float CompensationPerSecond = 0.5f; private const float StareCheckRange = 30f; private const int StareCheckMask = 60; private const float StareCheckProximity = -1f; private static readonly FieldInfo StareTimerField = typeof(PandoraEnemyAI).GetField("_stareTimer", BindingFlags.Instance | BindingFlags.NonPublic); public static void Apply(PandoraEnemyAI self) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if (!((Object)(object)val == (Object)null) && !val.isPlayerDead && !val.HasLineOfSightToPosition(((EnemyAI)self).eye.position, 30f, 60, -1f)) { float currentStareTimer = GetCurrentStareTimer(self); if (!(currentStareTimer <= 0f)) { float num = currentStareTimer + 0.5f * Time.deltaTime; StareTimerField.SetValue(self, num); } } } internal static float GetCurrentStareTimer(PandoraEnemyAI self) { return (float)(StareTimerField.GetValue(self) ?? ((object)0f)); } } } namespace PandoraEnemyPatch.Networking { internal readonly struct ResolvedPlayerReference { public object RawReference { get; } public PlayerControllerB? Player { get; } public bool HasPlayer => (Object)(object)Player != (Object)null; public ResolvedPlayerReference(object rawReference, PlayerControllerB? player) { RawReference = rawReference; Player = player; } public string FormatLogDetails() { return "refType=" + (RawReference?.GetType().FullName ?? "<null>") + ", player=" + (((Object)(object)Player != (Object)null) ? Player.playerUsername : "<null>") + ", clientId=" + (((Object)(object)Player != (Object)null) ? Player.actualClientId.ToString() : "<null>"); } public static ResolvedPlayerReference From(object rawReference) { PlayerControllerReference val = (PlayerControllerReference)((rawReference is PlayerControllerReference) ? rawReference : null); if (val != null) { PlayerControllerB player = PlayerControllerReference.op_Implicit(val); return new ResolvedPlayerReference(rawReference, player); } PlayerControllerB val2 = (PlayerControllerB)((rawReference is PlayerControllerB) ? rawReference : null); if (val2 != null) { return new ResolvedPlayerReference(rawReference, val2); } return new ResolvedPlayerReference(rawReference, null); } } } namespace PandoraEnemyPatch.Logging { internal static class EnemyLogLabel { public static string For(PandoraEnemyAI self) { NetworkObject networkObject = ((NetworkBehaviour)self).NetworkObject; if ((Object)(object)networkObject != (Object)null) { return $"{((EnemyAI)self).enemyType.enemyName}(net::{networkObject.NetworkObjectId})"; } return ((EnemyAI)self).enemyType.enemyName + "(unspawned)"; } } internal static class NetworkEventLog { [CompilerGenerated] private static class <>O { public static Action<NetworkManager> <0>__OnNetworkManagerInstantiated; public static Action<NetworkManager> <1>__OnNetworkManagerDestroying; public static Action <2>__OnNetworkStarted; public static Action<bool> <3>__OnNetworkStopped; public static HandleNamedMessageDelegate <4>__ReceiveRelayedLog; } private const string RelayMessageName = "PandoraEnemyPatch.NetworkEventLog"; private const int RelayMessageBufferCapacity = 2048; private static bool _initialized; private static NetworkManager? _attachedNetworkManager; private static CustomMessagingManager? _registeredManager; public static void Write(string message, bool relayToOtherClients = true) { Plugin.Logger.LogDebug((object)message); if (relayToOtherClients) { TrySendToOtherClients(message); } } public static void Info(string message, bool relayToOtherClients = true) { Plugin.Logger.LogInfo((object)message); if (relayToOtherClients) { TrySendToOtherClients(message); } } public static void EnsureInitialized() { if (!_initialized) { _initialized = true; NetworkManager.OnInstantiated += OnNetworkManagerInstantiated; NetworkManager.OnDestroying += OnNetworkManagerDestroying; if ((Object)(object)NetworkManager.Singleton != (Object)null) { AttachToNetworkManager(NetworkManager.Singleton); } } } private static void TrySendToOtherClients(string message) { //IL_0098: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || !singleton.IsServer) { return; } RegisterHandlerIfNeeded(); List<ulong> list = singleton.ConnectedClientsIds.Where((ulong clientId) => clientId != 0).ToList(); if (list.Count == 0) { return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(2048, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(message, false); singleton.CustomMessagingManager.SendNamedMessage("PandoraEnemyPatch.NetworkEventLog", (IReadOnlyList<ulong>)list, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } private static void AttachToNetworkManager(NetworkManager manager) { if (_attachedNetworkManager != manager) { DetachFromNetworkManager(_attachedNetworkManager); _attachedNetworkManager = manager; manager.OnServerStarted += OnNetworkStarted; manager.OnClientStarted += OnNetworkStarted; manager.OnServerStopped += OnNetworkStopped; manager.OnClientStopped += OnNetworkStopped; if (manager.IsListening) { RegisterHandlerIfNeeded(); } } } private static void DetachFromNetworkManager(NetworkManager? manager) { if (!((Object)(object)manager == (Object)null)) { manager.OnServerStarted -= OnNetworkStarted; manager.OnClientStarted -= OnNetworkStarted; manager.OnServerStopped -= OnNetworkStopped; manager.OnClientStopped -= OnNetworkStopped; UnregisterHandler(); if (_attachedNetworkManager == manager) { _attachedNetworkManager = null; } } } private static void RegisterHandlerIfNeeded() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { return; } CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager; if (_registeredManager != customMessagingManager) { UnregisterHandler(); object obj = <>O.<4>__ReceiveRelayedLog; if (obj == null) { HandleNamedMessageDelegate val = ReceiveRelayedLog; <>O.<4>__ReceiveRelayedLog = val; obj = (object)val; } customMessagingManager.RegisterNamedMessageHandler("PandoraEnemyPatch.NetworkEventLog", (HandleNamedMessageDelegate)obj); _registeredManager = customMessagingManager; } } private static void UnregisterHandler() { if (_registeredManager != null) { _registeredManager.UnregisterNamedMessageHandler("PandoraEnemyPatch.NetworkEventLog"); _registeredManager = null; } } private static void OnNetworkManagerInstantiated(NetworkManager manager) { AttachToNetworkManager(manager); } private static void OnNetworkManagerDestroying(NetworkManager manager) { DetachFromNetworkManager(manager); } private static void OnNetworkStarted() { RegisterHandlerIfNeeded(); } private static void OnNetworkStopped(bool _) { UnregisterHandler(); } private static void ReceiveRelayedLog(ulong senderClientId, FastBufferReader payload) { string arg = default(string); ((FastBufferReader)(ref payload)).ReadValueSafe(ref arg, false); Plugin.Logger.LogDebug((object)$"[Remote:{senderClientId}] {arg}"); } } } namespace PandoraEnemyPatch.Hooks { internal static class PandoraHookRegistrar { private delegate void EnemyAIIntervalInvoker(EnemyAI self); private delegate bool CanSeePlayerOrig(PandoraEnemyAI self, PlayerControllerB player); private delegate bool CanSeePlayerHook(CanSeePlayerOrig orig, PandoraEnemyAI self, PlayerControllerB player); private delegate void PandoraEnemyAIOrig(PandoraEnemyAI self); private delegate void PandoraEnemyAIHook(PandoraEnemyAIOrig orig, PandoraEnemyAI self); private delegate void TeleportPlayerClientRPCOrig(PandoraEnemyAI self, PlayerControllerReference playerRef, Vector3 position, float yRot); private delegate void TeleportPlayerClientRPCHook(TeleportPlayerClientRPCOrig orig, PandoraEnemyAI self, PlayerControllerReference playerRef, Vector3 position, float yRot); private delegate void SetTeleportTrappedClientRPCOrig(PandoraEnemyAI self, PlayerControllerReference playerRef, bool trapped); private delegate void SetTeleportTrappedClientRPCHook(SetTeleportTrappedClientRPCOrig orig, PandoraEnemyAI self, PlayerControllerReference playerRef, bool trapped); private delegate void BeginSinkingClientRPCOrig(PandoraEnemyAI self, PlayerControllerReference playerRef); private delegate void BeginSinkingClientRPCHook(BeginSinkingClientRPCOrig orig, PandoraEnemyAI self, PlayerControllerReference playerRef); private delegate IEnumerator DoGroundPoundSinkingOrig(PandoraEnemyAI self, PlayerControllerB player, float duration, float waitDuration); private delegate IEnumerator DoGroundPoundSinkingHook(DoGroundPoundSinkingOrig orig, PandoraEnemyAI self, PlayerControllerB player, float duration, float waitDuration); private delegate bool TeleportAndSwapIsValidOrig(PandoraEnemyAI self); private delegate bool TeleportAndSwapIsValidHook(TeleportAndSwapIsValidOrig orig, PandoraEnemyAI self); private delegate void PerformSwapTeleportOrig(PandoraEnemyAI self, List<PlayerControllerB> players); private delegate void PerformSwapTeleportHook(PerformSwapTeleportOrig orig, PandoraEnemyAI self, List<PlayerControllerB> players); private delegate void KillEnemyOrig(PandoraEnemyAI self, bool destroy); private delegate void KillEnemyHook(KillEnemyOrig orig, PandoraEnemyAI self, bool destroy); private delegate void HeartbeatAudioHandlerOrig(HeartbeatAudioHandler self); private delegate void HeartbeatAudioHandlerHook(HeartbeatAudioHandlerOrig orig, HeartbeatAudioHandler self); private delegate void PandoraEnemyAIUpdateOrig(PandoraEnemyAI self); private delegate void PandoraEnemyAIUpdateHook(PandoraEnemyAIUpdateOrig orig, PandoraEnemyAI self); private readonly struct RpcLogContext { public PlayerControllerReference PlayerReference { get; } public ResolvedPlayerReference Resolved { get; } public bool ShouldLogExecution { get; } public RpcLogContext(PlayerControllerReference playerReference, ResolvedPlayerReference resolved, bool shouldLogExecution) { PlayerReference = playerReference; Resolved = resolved; ShouldLogExecution = shouldLogExecution; } public string FormatPlayerReferenceDetails() { return Resolved.FormatLogDetails(); } } [CompilerGenerated] private sealed class <PandoraEnemyAI_DoGroundPoundSinking>d__48 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public DoGroundPoundSinkingOrig orig; public PandoraEnemyAI self; public PlayerControllerB player; public float duration; public float waitDuration; private IEnumerator <routine>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <PandoraEnemyAI_DoGroundPoundSinking>d__48(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <routine>5__1 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <routine>5__1 = orig(self, player, duration, waitDuration); break; case 1: <>1__state = -1; break; } if (<routine>5__1.MoveNext()) { <>2__current = <routine>5__1.Current; <>1__state = 1; return true; } PandoraAttackRecovery.ReleaseGroundPoundPlayer(self, player); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const int AvailableAttackCapacity = 3; private const int ClientRpcExecutionStage = 1; private const string HooksLogCategory = "Hooks"; private const string VisionLogCategory = "Vision"; private const string RpcLogCategory = "RPC"; private static readonly FieldInfo RpcExecStageField = typeof(NetworkBehaviour).GetField("__rpc_exec_stage", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly EnemyAIIntervalInvoker InvokeEnemyAIBaseDoAIInterval = CreateEnemyAIBaseDoAIIntervalInvoker(); private static readonly List<Hook> ActiveHooks = new List<Hook>(); private static bool _registered; private static readonly PandoraTargetingService TargetingService = new PandoraTargetingService(); private static readonly PandoraChaseController ChaseController = new PandoraChaseController(TargetingService); private static readonly PandoraAiIntervalCoordinator AiIntervalCoordinator = new PandoraAiIntervalCoordinator(); private static readonly SwapPlanner SwapPlanner = new SwapPlanner(); private static readonly PandoraAttackSelector AttackSelector = new PandoraAttackSelector(); private static bool _loggedCanSeeHook; internal static void Register() { if (!_registered) { RegisterTargetingAndChaseHooks(); RegisterAttackHooks(); RegisterRpcHooks(); RegisterGroundPoundHooks(); RegisterHeartbeatHooks(); _registered = true; Log("Hooks", "Registered Pandora hooks"); } } private static bool PandoraEnemyAI_CanSeePlayer(CanSeePlayerOrig orig, PandoraEnemyAI self, PlayerControllerB player) { if (!_loggedCanSeeHook) { _loggedCanSeeHook = true; Log("Vision", "PandoraEnemyAI.CanSeePlayer hook is running."); } TargetObservation observation; return PandoraTargetingService.TryObserve(self, player, out observation); } private static void PandoraEnemyAI_DoAIInterval(PandoraEnemyAIOrig orig, PandoraEnemyAI self) { InvokeEnemyAIBaseDoAIInterval((EnemyAI)(object)self); AiIntervalCoordinator.Execute(self); } private static void PandoraEnemyAI_DoChasing(PandoraEnemyAIOrig orig, PandoraEnemyAI self) { ChaseController.Execute(self); } private static void PandoraEnemyAI_DoIdle(PandoraEnemyAIOrig orig, PandoraEnemyAI self) { ChaseController.ExecuteIdle(self); } private static void PandoraEnemyAI_ChooseNextAttack(PandoraEnemyAIOrig orig, PandoraEnemyAI self) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) List<NextAttack> list = new List<NextAttack>(3) { (NextAttack)1, (NextAttack)0 }; if (self.TeleportAndSwapIsValid()) { list.Add((NextAttack)2); } self._nextAttack = AttackSelector.ChooseNextAttack(self, list); } private static void PandoraEnemyAI_Update(PandoraEnemyAIUpdateOrig orig, PandoraEnemyAI self) { orig(self); PandoraStareTimerTuning.Apply(self); } private static void PandoraEnemyAI_TeleportPlayerClientRPC(TeleportPlayerClientRPCOrig orig, PandoraEnemyAI self, PlayerControllerReference playerRef, Vector3 position, float yRot) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) RpcLogContext context = CreateRpcLogContext(self, playerRef); LogRpcIfNeeded(context, $"TeleportPlayerClientRPC {context.FormatPlayerReferenceDetails()}, pos={position}, yRot={yRot}"); orig(self, playerRef, position, yRot); } private static void PandoraEnemyAI_SetTeleportTrappedClientRPC(SetTeleportTrappedClientRPCOrig orig, PandoraEnemyAI self, PlayerControllerReference playerRef, bool trapped) { RpcLogContext context = CreateRpcLogContext(self, playerRef); LogRpcIfNeeded(context, $"SetTeleportTrappedClientRPC {context.FormatPlayerReferenceDetails()}, trapped={trapped}"); orig(self, playerRef, trapped); } private static void PandoraEnemyAI_BeginSinkingClientRPC(BeginSinkingClientRPCOrig orig, PandoraEnemyAI self, PlayerControllerReference playerRef) { RpcLogContext context = CreateRpcLogContext(self, playerRef); LogRpcIfNeeded(context, "BeginSinkingClientRPC " + context.FormatPlayerReferenceDetails()); orig(self, playerRef); if (context.ShouldLogExecution) { GroundPoundEscapeCue.ShowForLocalPlayer(self, context.Resolved.Player); } } [IteratorStateMachine(typeof(<PandoraEnemyAI_DoGroundPoundSinking>d__48))] private static IEnumerator PandoraEnemyAI_DoGroundPoundSinking(DoGroundPoundSinkingOrig orig, PandoraEnemyAI self, PlayerControllerB player, float duration, float waitDuration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <PandoraEnemyAI_DoGroundPoundSinking>d__48(0) { orig = orig, self = self, player = player, duration = duration, waitDuration = waitDuration }; } private static bool PandoraEnemyAI_TeleportAndSwapIsValid(TeleportAndSwapIsValidOrig orig, PandoraEnemyAI self) { return SwapPlanner.CanBuildAnySwap(self); } private static void PandoraEnemyAI_PerformSwapTeleport(PerformSwapTeleportOrig orig, PandoraEnemyAI self, List<PlayerControllerB> players) { SwapPlanner.SwapResolution resolution = SwapPlanner.Resolve(self, players); SwapPlanner.Execute(self, resolution); } private static void PandoraEnemyAI_FinishAttack(PandoraEnemyAIOrig orig, PandoraEnemyAI self) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) List<PlayerControllerB> players = PandoraAttackRecovery.CapturePlayers(self); NextAttack nextAttack = self._nextAttack; orig(self); PandoraAttackRecovery.ReleasePlayers(self, players, nextAttack); } private static void PandoraEnemyAI_KillEnemy(KillEnemyOrig orig, PandoraEnemyAI self, bool destroy) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) List<PlayerControllerB> players = PandoraAttackRecovery.CapturePlayers(self); orig(self, destroy); PandoraAttackRecovery.ReleasePlayers(self, players, self._nextAttack); } private static bool ShouldLogClientRpcExecution(NetworkBehaviour behaviour) { object value = RpcExecStageField.GetValue(behaviour); if (value == null) { return true; } return (int)value == 1; } private static RpcLogContext CreateRpcLogContext(PandoraEnemyAI self, PlayerControllerReference playerRef) { return new RpcLogContext(playerRef, ResolvedPlayerReference.From(playerRef), ShouldLogClientRpcExecution((NetworkBehaviour)(object)self)); } private static void LogRpcIfNeeded(RpcLogContext context, string message) { if (context.ShouldLogExecution) { Log("RPC", message); } } private static EnemyAIIntervalInvoker CreateEnemyAIBaseDoAIIntervalInvoker() { MethodInfo method = typeof(EnemyAI).GetMethod("DoAIInterval", BindingFlags.Instance | BindingFlags.Public); DynamicMethod dynamicMethod = new DynamicMethod("PandoraEnemyPatch_InvokeEnemyAIBaseDoAIInterval", typeof(void), new Type[1] { typeof(EnemyAI) }, typeof(PandoraHookRegistrar).Module, skipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Call, method); iLGenerator.Emit(OpCodes.Ret); return (EnemyAIIntervalInvoker)dynamicMethod.CreateDelegate(typeof(EnemyAIIntervalInvoker)); } private static void HeartbeatAudioHandler_Update(HeartbeatAudioHandlerOrig orig, HeartbeatAudioHandler self) { AudioSource component = ((Component)self).GetComponent<AudioSource>(); float currentTime = self._currentTime; orig(self); if (!((Object)(object)component == (Object)null)) { GroundPoundHeartbeatIntensity.ApplyIfActive(self, component); float currentTime2 = self._currentTime; if (currentTime2 < currentTime) { GroundPoundEscapeCue.PulseFromHeartbeat(self); } } } private static void RegisterTargetingAndChaseHooks() { RegisterPandoraHook("CanSeePlayer", new Type[1] { typeof(PlayerControllerB) }, new CanSeePlayerHook(PandoraEnemyAI_CanSeePlayer)); RegisterPandoraHook("Update", Type.EmptyTypes, new PandoraEnemyAIUpdateHook(PandoraEnemyAI_Update)); RegisterPandoraHook("DoAIInterval", Type.EmptyTypes, new PandoraEnemyAIHook(PandoraEnemyAI_DoAIInterval)); RegisterPandoraHook("DoIdle", Type.EmptyTypes, new PandoraEnemyAIHook(PandoraEnemyAI_DoIdle)); RegisterPandoraHook("DoChasing", Type.EmptyTypes, new PandoraEnemyAIHook(PandoraEnemyAI_DoChasing)); } private static void RegisterAttackHooks() { RegisterPandoraHook("ChooseNextAttack", Type.EmptyTypes, new PandoraEnemyAIHook(PandoraEnemyAI_ChooseNextAttack)); RegisterPandoraHook("FinishAttack", Type.EmptyTypes, new PandoraEnemyAIHook(PandoraEnemyAI_FinishAttack)); RegisterPandoraHook("TeleportAndSwapIsValid", Type.EmptyTypes, new TeleportAndSwapIsValidHook(PandoraEnemyAI_TeleportAndSwapIsValid)); RegisterPandoraHook("PerformSwapTeleport", new Type[1] { typeof(List<PlayerControllerB>) }, new PerformSwapTeleportHook(PandoraEnemyAI_PerformSwapTeleport)); RegisterPandoraHook("KillEnemy", new Type[1] { typeof(bool) }, new KillEnemyHook(PandoraEnemyAI_KillEnemy)); } private static void RegisterRpcHooks() { RegisterPandoraHook("TeleportPlayerClientRPC", new Type[3] { typeof(PlayerControllerReference), typeof(Vector3), typeof(float) }, new TeleportPlayerClientRPCHook(PandoraEnemyAI_TeleportPlayerClientRPC)); RegisterPandoraHook("SetTeleportTrappedClientRPC", new Type[2] { typeof(PlayerControllerReference), typeof(bool) }, new SetTeleportTrappedClientRPCHook(PandoraEnemyAI_SetTeleportTrappedClientRPC)); RegisterPandoraHook("BeginSinkingClientRPC", new Type[1] { typeof(PlayerControllerReference) }, new BeginSinkingClientRPCHook(PandoraEnemyAI_BeginSinkingClientRPC)); } private static void RegisterGroundPoundHooks() { RegisterPandoraHook("DoGroundPoundSinking", new Type[3] { typeof(PlayerControllerB), typeof(float), typeof(float) }, new DoGroundPoundSinkingHook(PandoraEnemyAI_DoGroundPoundSinking)); } private static void RegisterHeartbeatHooks() { RegisterHook(typeof(HeartbeatAudioHandler), "Update", Type.EmptyTypes, new HeartbeatAudioHandlerHook(HeartbeatAudioHandler_Update)); } private static void RegisterPandoraHook(string methodName, Type[] parameterTypes, Delegate hookDelegate) { RegisterHook(typeof(PandoraEnemyAI), methodName, parameterTypes, hookDelegate); } private static void RegisterHook(Type declaringType, string methodName, Type[] parameterTypes, Delegate hookDelegate) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown MethodInfo method = declaringType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null); if (method == null) { string text = string.Join(", ", parameterTypes.Select((Type type) => type.Name)); throw new MissingMethodException(declaringType.FullName, methodName + "(" + text + ")"); } ActiveHooks.Add(new Hook((MethodBase)method, hookDelegate)); } private static void Log(string category, string message) { NetworkEventLog.Write("[" + category + "] " + message); } private static void Log(PandoraEnemyAI self, string category, string message, bool relayToOtherClients = false) { NetworkEventLog.Write("[" + category + "] [" + EnemyLogLabel.For(self) + "] " + message, relayToOtherClients); } } } namespace PandoraEnemyPatch.Chasing { internal sealed class PandoraChaseController { private const string LogCategory = "Chase"; private const double RareLostTargetDisappearChance = 0.2; private readonly PandoraTargetingService _targetingService; private const float MinGiveUpSeconds = 25f; private const float MaxGiveUpSeconds = 35f; private const float AttackCooldownSeconds = 30f; private const float GroundPoundAttackRange = 2f; private const float TeleportAttackRange = 4f; private int? _lastLoggedTargetClientId; private bool _hasRolledCurrentMutualSightLoss; public PandoraChaseController(PandoraTargetingService targetingService) { _targetingService = targetingService; } public void Execute(PandoraEnemyAI self) { //IL_01b8: Unknown result type (might be due to invalid IL or missing references) self._giveUpTimer -= ((EnemyAI)self).AIIntervalTime; if (HasGivenUp(self)) { Log(self, $"giving up chase after timer expired (remaining={self._giveUpTimer:0.##})."); ReturnToIdle(self); return; } PlayerControllerB target; bool flag = TryRefreshTarget(self, out target); bool flag2 = _targetingService.CanAnyEligiblePlayerSeePandora(self); if (flag || flag2) { _hasRolledCurrentMutualSightLoss = false; } if (!flag && !flag2 && !_hasRolledCurrentMutualSightLoss) { _hasRolledCurrentMutualSightLoss = true; Log(self, $"running vanish check after mutual sight loss (remaining={self._giveUpTimer:0.##}, chancePercent={FormatPercent(0.2)}, pandoraCanSeePlayer={flag}, playerCanSeePandora={flag2})."); if (ShouldDisappearAfterLosingAllTargets(self, out var roll)) { Log(self, "vanishing after mutual sight loss (rollPercent=" + FormatPercent(roll) + ", chancePercent=" + FormatPercent(0.2) + ")."); ReturnToIdle(self); return; } Log(self, $"kept chasing after mutual sight loss (rollPercent={FormatPercent(roll)}, chancePercent={FormatPercent(0.2)}, remaining={self._giveUpTimer:0.##})."); } if ((Object)(object)target == (Object)null) { if (flag) { } return; } if (PandoraTargetingService.TryObserve(self, target, out var _)) { self._giveUpTimer = NextGiveUpTime(self); } ((PandoraBaseEnemyAI)self).smartAgentNavigator.DoPathingToDestination(((Component)target).transform.position); if (CanBeginAttack(self, target)) { BeginAttack(self, target); } } public void ExecuteIdle(PandoraEnemyAI self) { self._giveUpTimer -= ((EnemyAI)self).AIIntervalTime; PlayerControllerB val = default(PlayerControllerB); if (self._giveUpTimer < 0f) { self.TeleportAndResetSearchRoutine(); self._giveUpTimer = NextGiveUpTime(self); } else if (self.TryGetClosestChaseTarget(ref val) && !((Object)(object)val == (Object)null)) { ((EnemyAI)self).targetPlayer = val; self._currentState.Value = (PandoraState)2; self._timeSinceLastAttack = 30f; self.ChooseNextAttack(); ((PandoraBaseEnemyAI)self).smartAgentNavigator.StopSearchRoutine(); ((PandoraBaseEnemyAI)self).smartAgentNavigator.StopAgent(); self._giveUpTimer = NextGiveUpTime(self); Log(self, $"target -> {val.playerUsername} ({val.playerClientId})"); } } private bool TryRefreshTarget(PandoraEnemyAI self, out PlayerControllerB? target) { PlayerControllerB targetPlayer = ((EnemyAI)self).targetPlayer; if (_targetingService.TryGetClosestVisibleTarget(self, out PlayerControllerB player) && (Object)(object)player != (Object)null) { if (targetPlayer != player) { ((EnemyAI)self).targetPlayer = player; self._giveUpTimer = NextGiveUpTime(self); LogTargetChange(self, player); } target = player; return true; } if ((Object)(object)targetPlayer != (Object)null && !targetPlayer.isPlayerDead && targetPlayer.isPlayerControlled && targetPlayer.isInsideFactory) { target = targetPlayer; return false; } ((EnemyAI)self).targetPlayer = null; LogTargetChange(self, null); target = null; return false; } private void LogTargetChange(PandoraEnemyAI self, PlayerControllerB? newTarget) { int? num = (((Object)(object)newTarget != (Object)null) ? new int?((int)newTarget.playerClientId) : null); if (_lastLoggedTargetClientId != num) { _lastLoggedTargetClientId = num; if ((Object)(object)newTarget == (Object)null) { Log(self, "target cleared"); } else { Log(self, $"target -> {newTarget.playerUsername} ({newTarget.playerClientId})"); } } } private static bool HasGivenUp(PandoraEnemyAI self) { return self._giveUpTimer < 0f; } private static bool ShouldDisappearAfterLosingAllTargets(PandoraEnemyAI self, out double roll) { roll = ((PandoraBaseEnemyAI)self)._enemyRandom.NextDouble(); return roll < 0.2; } private static string FormatPercent(double value) { return $"{value * 100.0:0.00}%"; } private static void ReturnToIdle(PandoraEnemyAI self) { self._currentState.Value = (PandoraState)0; self.TeleportAndResetSearchRoutine(); self._giveUpTimer = NextGiveUpTime(self); } private static float NextGiveUpTime(PandoraEnemyAI self) { return 25f + (float)((PandoraBaseEnemyAI)self)._enemyRandom.NextDouble() * 10f; } private static bool CanBeginAttack(PandoraEnemyAI self, PlayerControllerB target) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) float num = Vector3.Distance(((Component)self).transform.position, ((Component)target).transform.position); float num2 = (((int)self._nextAttack == 0) ? 2f : 4f); return num < num2 && self._timeSinceLastAttack >= 30f; } private static void BeginAttack(PandoraEnemyAI self, PlayerControllerB target) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Invalid comparison between Unknown and I4 self._currentState.Value = (PandoraState)1; self._playersTrappedThisAttack.Clear(); ((PandoraBaseEnemyAI)self).smartAgentNavigator.StopAgent(); ((PandoraBaseEnemyAI)self).creatureNetworkAnimator.SetTrigger(Animator.StringToHash("armsRaised"), true); Log(self, $"starting attack '{self._nextAttack}' on {target.playerUsername} ({target.playerClientId})"); if ((int)self._nextAttack == 0) { self.BeginSinkingClientRPC(PlayerControllerReference.op_Implicit(target)); return; } List<PlayerControllerB> playersNearby = self.GetPlayersNearby(10f); foreach (PlayerControllerB item in playersNearby) { if (!self._playersTrappedThisAttack.Contains(item)) { self._playersTrappedThisAttack.Add(item); } } self.TrapPlayersForTeleportAttack(playersNearby); } private static void Log(PandoraEnemyAI self, string message) { NetworkEventLog.Write("[Chase] [" + EnemyLogLabel.For(self) + "] " + message); } } } namespace PandoraEnemyPatch.Attacks { internal sealed class PandoraAttackSelector { private sealed class AttackHistory { private const int RecentWindowSize = 3; private const int BaseAttackWeight = 10; private const int RecentMatchPenalty = 2; private const int RepeatStreakPenalty = 3; private const int MinimumAttackWeight = 1; private readonly Queue<NextAttack> _recentAttacks = new Queue<NextAttack>(); public int GetWeight(NextAttack attack) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; foreach (NextAttack recentAttack in _recentAttacks) { if (recentAttack == attack) { num++; } } foreach (NextAttack item in _recentAttacks.Reverse()) { if (item != attack) { break; } num2++; } int num3 = 10; num3 -= num * 2; num3 -= num2 * 3; return (num3 < 1) ? 1 : num3; } public void Record(NextAttack attack) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) _recentAttacks.Enqueue(attack); while (_recentAttacks.Count > 3) { _recentAttacks.Dequeue(); } } } private const string LogCategory = "Attack"; private readonly Dictionary<int, AttackHistory> _historyByEnemyId = new Dictionary<int, AttackHistory>(); public NextAttack ChooseNextAttack(PandoraEnemyAI self, IReadOnlyList<NextAttack> availableAttacks) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_0143: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) AttackHistory history = GetHistory(self); List<(NextAttack, int)> list = new List<(NextAttack, int)>(availableAttacks.Count); int num = 0; foreach (NextAttack availableAttack in availableAttacks) { int weight = history.GetWeight(availableAttack); list.Add((availableAttack, weight)); num += weight; } int num2 = ((PandoraBaseEnemyAI)self)._enemyRandom.Next(0, num); NextAttack val = list[0].Item1; foreach (var (val2, num3) in list) { if (num2 < num3) { val = val2; break; } num2 -= num3; } history.Record(val); Log(self, "available=[" + string.Join(", ", availableAttacks) + "] weights=[" + string.Join(", ", list.Select<(NextAttack, int), string>(((NextAttack Attack, int Weight) candidate) => $"{candidate.Attack}:{candidate.Weight}")) + "] " + $"chosen={val}"); return val; } private static void Log(PandoraEnemyAI self, string message) { NetworkEventLog.Write("[Attack] [" + EnemyLogLabel.For(self) + "] " + message); } private AttackHistory GetHistory(PandoraEnemyAI self) { int instanceID = ((Object)self).GetInstanceID(); if (_historyByEnemyId.TryGetValue(instanceID, out AttackHistory value)) { return value; } value = new AttackHistory(); _historyByEnemyId[instanceID] = value; return value; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }