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 Scare Crow v1.0.0
TCF.dll
Decompiled 5 hours agousing System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using GameNetcodeStuff; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] namespace TCF; [BepInPlugin("com.tonysmoons.tcf", "TCF", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.tonysmoons.tcf"; public const string PluginName = "TCF"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"TCF loaded."); } } public class ScarecrowAI : EnemyAI { private const int StateRoaming = 0; private const int StateInvestigating = 1; private const int StateChasing = 2; private const int StateChokeHold = 3; private const int StateFleeing = 4; private const int StateWatching = 5; private const int StateDead = 6; [Header("Detection")] [Tooltip("Max distance (units) a player can be spotted at. Line-of-sight is always required on top of this - see the class summary.")] [Min(0f)] public float detectionRange = 35f; [Tooltip("Field-of-view cone width (degrees) used for spotting players, same meaning as EnemyAI.CheckLineOfSightForPlayer's Width parameter.")] [Min(0f)] public float detectionWidth = 65f; [Tooltip("AISearchRoutine search width for the random outdoor wander used while Roaming/Watching.")] [Min(0f)] public float wanderSearchWidth = 50f; [Header("Movement Speeds")] [Tooltip("NavMeshAgent speed while Roaming.")] [Min(0f)] public float roamSpeed = 3f; [Tooltip("NavMeshAgent speed while Investigating a lost target's last-known position.")] [Min(0f)] public float investigateSpeed = 4.5f; [Tooltip("NavMeshAgent speed while Chasing a clearly-seen player.")] [Min(0f)] public float chaseSpeed = 7f; [Tooltip("Roam speed used once Enraged (after the first successful kill).")] [Min(0f)] public float enragedRoamSpeed = 5f; [Tooltip("Chase speed used once Enraged.")] [Min(0f)] public float enragedChaseSpeed = 11f; [Header("Facing")] [Tooltip("Visual model transform rotated to face the current target while Chasing/Choke Holding. Falls back to this object's own transform if left unset.")] public Transform modelRoot; [Tooltip("Degrees/sec turned to face the target.")] [Min(0f)] public float turnSpeed = 260f; [Header("Investigating")] [Tooltip("Seconds spent moving toward a lost target's last-known position before giving up and resuming Roaming/Watching.")] [Min(0f)] public float investigateTimeout = 8f; [Header("Choke Hold Attack")] [Tooltip("Distance (units) at which the Scarecrow stops chasing and grabs its target.")] [Min(0f)] public float chokeHoldRange = 2.5f; [Tooltip("Seconds the choke hold sequence lasts before the kill. Not used once Enraged - the kill becomes instant instead.")] [Min(0f)] public float chokeHoldDuration = 15f; [Tooltip("REQUIRED for the hold to visually lock the victim in place. Exact position/rotation the victim's body is pinned to for the whole choke hold - assign a child Transform positioned in the Scarecrow's grip.")] public Transform chokeHoldVictimPosition; [Tooltip("Dedicated Animator driving the choke-hold attack animation. Assign any Animator (with its own Controller) here to swap the attack animation without touching code - can also point at the same Animator as Creature Animator if you'd rather drive it from one Controller.")] public Animator chokeHoldAnimator; [Tooltip("Bool parameter set on Choke Hold Animator for the duration of the hold (true = playing, false = idle/released). Change this if your Animator Controller names the parameter differently.")] public string chokeHoldAnimatorBoolParam = "ChokeHold"; [Tooltip("Cause of death reported for the choke-hold / enraged instant kill.")] public CauseOfDeath killCauseOfDeath = (CauseOfDeath)17; [Tooltip("Death ragdoll animation index passed to KillPlayer (base game's playerRagdolls array index) - pick whichever built-in ragdoll looks best for a decapitation.")] [Min(0f)] public int killDeathAnimation; [Header("Enraged (after the first kill)")] [Tooltip("Trigger fired once on Creature Animator the moment the Scarecrow becomes Enraged.")] public string enragedTriggerParam = "Enraged"; [Header("Fleeing / Watching (after being stunned)")] [Tooltip("NavMeshAgent speed while Fleeing from whoever just stunned it.")] [Min(0f)] public float fleeSpeed = 9f; [Tooltip("Seconds spent Fleeing before settling into Watching.")] [Min(0f)] public float fleeDuration = 5f; [Tooltip("While Watching, the minimum distance the Scarecrow tries to keep from any player who has stunned it before.")] [Min(0f)] public float watchKeepDistance = 20f; [Header("Audio")] [Tooltip("Possible sounds played once (for everyone) the moment this instance spawns - one is picked at random.")] public List<AudioClip> spawnSFX = new List<AudioClip>(); [Tooltip("AudioSource the spawn sound plays on. Falls back to creatureSFX if left unset.")] public AudioSource spawnAudioSource; [Tooltip("Played once (for everyone) the moment a choke hold begins.")] public AudioClip chokeHoldStartSFX; [Tooltip("Played once (for everyone) the moment a kill (choke hold or enraged instant kill) lands.")] public AudioClip killSFX; private AISearchRoutine wanderSearch; private int lockedTargetId = -1; private Vector3 investigatePosition; private float investigateTimer; private bool isChokeHolding; private bool chokeHoldKillTriggered; private float chokeHoldTimer; private PlayerControllerB chokeHoldVictim; private bool isEnraged; private bool hasBeenStunned; private float fleeTimer; private readonly HashSet<int> blacklistedPlayerIds = new HashSet<int>(); private Transform FacingTransform { get { if (!((Object)(object)modelRoot != (Object)null)) { return ((Component)this).transform; } return modelRoot; } } public override void Start() { //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_003f: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) ((EnemyAI)this).Start(); if (!base.isOutside) { Debug.LogWarning((object)(base.enemyType.enemyName + ": Scarecrow's EnemyType asset must have \"Is Outside Enemy\" checked - it is designed to never operate inside the facility.")); } wanderSearch = new AISearchRoutine { searchWidth = wanderSearchWidth }; base.agent.speed = roamSpeed; PlaySpawnSound(); if (((NetworkBehaviour)this).IsOwner) { ((EnemyAI)this).StartSearch(((Component)this).transform.position, wanderSearch); } } private void PlaySpawnSound() { AudioSource val = (((Object)(object)spawnAudioSource != (Object)null) ? spawnAudioSource : base.creatureSFX); if (spawnSFX != null && spawnSFX.Count != 0 && !((Object)(object)val == (Object)null)) { AudioClip val2 = spawnSFX[Random.Range(0, spawnSFX.Count)]; val.PlayOneShot(val2); WalkieTalkie.TransmitOneShotAudio(val, val2, 1f); } } public override void Update() { ((EnemyAI)this).Update(); if (base.isEnemyDead) { return; } if (isChokeHolding) { HoldVictimInPlace(); if (((NetworkBehaviour)this).IsOwner && !chokeHoldKillTriggered && (Object)(object)chokeHoldVictim != (Object)null) { chokeHoldTimer -= Time.deltaTime; if (chokeHoldTimer <= 0f) { chokeHoldKillTriggered = true; PerformKillServerRpc((int)chokeHoldVictim.playerClientId); } } } if (base.currentBehaviourStateIndex == 2 || base.currentBehaviourStateIndex == 3) { FaceTarget(base.targetPlayer); } } private void HoldVictimInPlace() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)chokeHoldVictim == (Object)null) && !((Object)(object)chokeHoldVictimPosition == (Object)null)) { if ((Object)(object)chokeHoldVictim.thisController != (Object)null) { ((Collider)chokeHoldVictim.thisController).enabled = false; } ((Component)chokeHoldVictim).transform.position = chokeHoldVictimPosition.position; ((Component)chokeHoldVictim).transform.rotation = chokeHoldVictimPosition.rotation; } } private void FaceTarget(PlayerControllerB target) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0027: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_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_005e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null)) { Transform facingTransform = FacingTransform; Vector3 val = ((Component)target).transform.position - facingTransform.position; val.y = 0f; if (!(((Vector3)(ref val)).sqrMagnitude < 0.0001f)) { Quaternion val2 = Quaternion.LookRotation(val); facingTransform.rotation = Quaternion.RotateTowards(facingTransform.rotation, val2, turnSpeed * Time.deltaTime); } } } public override void DoAIInterval() { ((EnemyAI)this).DoAIInterval(); if (base.isEnemyDead || !((NetworkBehaviour)this).IsOwner) { return; } switch (base.currentBehaviourStateIndex) { case 0: DoRoamingInterval(); break; case 1: DoInvestigatingInterval(); break; case 2: DoChasingInterval(); break; case 3: if ((Object)(object)chokeHoldVictim == (Object)null || chokeHoldVictim.isPlayerDead) { ReleaseChokeHoldLocal(); ReturnToDefaultState(); } break; case 4: DoFleeingInterval(); break; case 5: DoWatchingInterval(); break; } } private void ReturnToDefaultState() { ((EnemyAI)this).SwitchToBehaviourState(hasBeenStunned ? 5 : 0); } private void DoRoamingInterval() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) base.agent.speed = (isEnraged ? enragedRoamSpeed : roamSpeed); PlayerControllerB val = FindVisiblePlayer(detectionRange, detectionWidth); if ((Object)(object)val != (Object)null) { BeginChase(val); } else if (!wanderSearch.inProgress) { ((EnemyAI)this).StartSearch(((Component)this).transform.position, wanderSearch); } } private void DoInvestigatingInterval() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) base.agent.speed = investigateSpeed; if ((Object)(object)base.targetPlayer != (Object)null && HasLineOfSightTo(base.targetPlayer) && ((EnemyAI)this).PlayerIsTargetable(base.targetPlayer, false, false, true)) { BeginChase(base.targetPlayer); return; } investigateTimer -= base.AIIntervalTime; if (investigateTimer <= 0f || Vector3.Distance(((Component)this).transform.position, investigatePosition) < 1.5f) { SetTargetPlayer(-1); ReturnToDefaultState(); return; } PlayerControllerB val = FindVisiblePlayer(detectionRange, detectionWidth); if ((Object)(object)val != (Object)null) { BeginChase(val); } } private void DoChasingInterval() { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009e: 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_0075: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)base.targetPlayer == (Object)null || base.targetPlayer.isPlayerDead || !((EnemyAI)this).PlayerIsTargetable(base.targetPlayer, false, false, true)) { SetTargetPlayer(-1); ReturnToDefaultState(); return; } base.agent.speed = (isEnraged ? enragedChaseSpeed : chaseSpeed); if (!HasLineOfSightTo(base.targetPlayer)) { BeginInvestigate(((Component)base.targetPlayer).transform.position); return; } ((EnemyAI)this).SetDestinationToPosition(((Component)base.targetPlayer).transform.position, true); if (Vector3.Distance(((Component)this).transform.position, ((Component)base.targetPlayer).transform.position) <= chokeHoldRange) { if (isEnraged) { TriggerInstantKill(base.targetPlayer); } else { BeginChokeHold(base.targetPlayer); } } } private void DoFleeingInterval() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_008f: Unknown result type (might be due to invalid IL or missing references) base.agent.speed = fleeSpeed; fleeTimer -= base.AIIntervalTime; if (fleeTimer <= 0f) { ((EnemyAI)this).SwitchToBehaviourState(5); return; } PlayerControllerB val = FindNearestBlacklistedPlayer(); if ((Object)(object)val != (Object)null && Vector3.Distance(((Component)this).transform.position, ((Component)val).transform.position) < watchKeepDistance) { Transform val2 = ((EnemyAI)this).ChooseFarthestNodeFromPosition(((Component)val).transform.position, true, 0, false, 50, -1); if ((Object)(object)val2 != (Object)null) { ((EnemyAI)this).SetDestinationToPosition(val2.position, true); } } } private void DoWatchingInterval() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) base.agent.speed = (isEnraged ? enragedRoamSpeed : roamSpeed); PlayerControllerB val = FindNearestBlacklistedPlayer(); if ((Object)(object)val != (Object)null && Vector3.Distance(((Component)this).transform.position, ((Component)val).transform.position) < watchKeepDistance) { Transform val2 = ((EnemyAI)this).ChooseFarthestNodeFromPosition(((Component)val).transform.position, true, 0, false, 50, -1); if ((Object)(object)val2 != (Object)null) { ((EnemyAI)this).SetDestinationToPosition(val2.position, true); } } else if (!wanderSearch.inProgress) { ((EnemyAI)this).StartSearch(((Component)this).transform.position, wanderSearch); } PlayerControllerB val3 = FindVisiblePlayer(detectionRange, detectionWidth); if ((Object)(object)val3 != (Object)null) { BeginChase(val3); } } private PlayerControllerB FindNearestBlacklistedPlayer() { //IL_0044: 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) PlayerControllerB result = null; float num = float.MaxValue; for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++) { PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[i]; if (!((Object)(object)val == (Object)null) && !val.isPlayerDead && blacklistedPlayerIds.Contains((int)val.playerClientId)) { float num2 = Vector3.Distance(((Component)this).transform.position, ((Component)val).transform.position); if (num2 < num) { num = num2; result = val; } } } return result; } private PlayerControllerB FindVisiblePlayer(float range, float width) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB result = null; float num = float.MaxValue; for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++) { PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[i]; if ((Object)(object)val == (Object)null || !((EnemyAI)this).PlayerIsTargetable(val, false, false, true) || blacklistedPlayerIds.Contains((int)val.playerClientId)) { continue; } Vector3 position = base.eye.position; Vector3 val2 = (((Object)(object)val.gameplayCamera != (Object)null) ? ((Component)val.gameplayCamera).transform.position : ((Component)val).transform.position); float num2 = Vector3.Distance(position, val2); if (!(num2 > range)) { Vector3 val3 = val2 - position; if (!(Vector3.Angle(base.eye.forward, val3) > width) && !Physics.Linecast(position, val2, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1) && num2 < num) { num = num2; result = val; } } } return result; } private bool HasLineOfSightTo(PlayerControllerB player) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_0042: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return false; } Vector3 position = base.eye.position; Vector3 val = (((Object)(object)player.gameplayCamera != (Object)null) ? ((Component)player.gameplayCamera).transform.position : ((Component)player).transform.position); return !Physics.Linecast(position, val, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1); } private void BeginChase(PlayerControllerB player) { ((EnemyAI)this).StopSearch(wanderSearch, false); ((EnemyAI)this).SetMovingTowardsTargetPlayer(player); SetTargetPlayer((int)player.playerClientId); ((EnemyAI)this).SwitchToBehaviourState(2); } private void BeginInvestigate(Vector3 lastKnownPosition) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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) base.movingTowardsTargetPlayer = false; investigatePosition = lastKnownPosition; investigateTimer = investigateTimeout; ((EnemyAI)this).SetDestinationToPosition(investigatePosition, true); ((EnemyAI)this).SwitchToBehaviourState(1); } private void SetTargetPlayer(int playerObjectId) { if (lockedTargetId != playerObjectId) { SetTargetPlayerServerRpc(playerObjectId); } } [ServerRpc(RequireOwnership = false)] private void SetTargetPlayerServerRpc(int playerObjectId) { SetTargetPlayerClientRpc(playerObjectId); } [ClientRpc] private void SetTargetPlayerClientRpc(int playerObjectId) { lockedTargetId = playerObjectId; base.targetPlayer = ((playerObjectId >= 0 && (Object)(object)StartOfRound.Instance != (Object)null && playerObjectId < StartOfRound.Instance.allPlayerScripts.Length) ? StartOfRound.Instance.allPlayerScripts[playerObjectId] : null); } private void BeginChokeHold(PlayerControllerB target) { StartChokeHoldServerRpc((int)target.playerClientId); } [ServerRpc(RequireOwnership = false)] private void StartChokeHoldServerRpc(int playerObjectId) { StartChokeHoldClientRpc(playerObjectId); } [ClientRpc] private void StartChokeHoldClientRpc(int playerObjectId) { if ((Object)(object)StartOfRound.Instance == (Object)null || playerObjectId < 0 || playerObjectId >= StartOfRound.Instance.allPlayerScripts.Length) { return; } PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[playerObjectId]; if (!((Object)(object)val == (Object)null) && !val.isPlayerDead) { chokeHoldVictim = val; chokeHoldTimer = chokeHoldDuration; chokeHoldKillTriggered = false; isChokeHolding = true; base.inSpecialAnimation = true; base.inSpecialAnimationWithPlayer = val; val.inAnimationWithEnemy = (EnemyAI)(object)this; val.inSpecialInteractAnimation = true; val.snapToServerPosition = true; if ((Object)(object)chokeHoldAnimator != (Object)null && !string.IsNullOrEmpty(chokeHoldAnimatorBoolParam)) { chokeHoldAnimator.SetBool(chokeHoldAnimatorBoolParam, true); } if ((Object)(object)chokeHoldStartSFX != (Object)null && (Object)(object)base.creatureSFX != (Object)null) { base.creatureSFX.PlayOneShot(chokeHoldStartSFX); WalkieTalkie.TransmitOneShotAudio(base.creatureSFX, chokeHoldStartSFX, 1f); } if (((NetworkBehaviour)this).IsOwner) { base.movingTowardsTargetPlayer = false; base.moveTowardsDestination = false; base.agent.speed = 0f; } ((EnemyAI)this).SwitchToBehaviourStateOnLocalClient(3); } } private void TriggerInstantKill(PlayerControllerB target) { PerformKillServerRpc((int)target.playerClientId); } [ServerRpc(RequireOwnership = false)] private void PerformKillServerRpc(int playerObjectId) { PerformKillClientRpc(playerObjectId); } [ClientRpc] private void PerformKillClientRpc(int playerObjectId) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) ReleaseChokeHoldLocal(); if ((Object)(object)StartOfRound.Instance == (Object)null || playerObjectId < 0 || playerObjectId >= StartOfRound.Instance.allPlayerScripts.Length) { return; } PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[playerObjectId]; if ((Object)(object)val == (Object)null || val.isPlayerDead) { return; } if ((Object)(object)killSFX != (Object)null && (Object)(object)base.creatureSFX != (Object)null) { base.creatureSFX.PlayOneShot(killSFX); WalkieTalkie.TransmitOneShotAudio(base.creatureSFX, killSFX, 1f); } val.KillPlayer(Vector3.zero, true, killCauseOfDeath, killDeathAnimation, default(Vector3), false); if (!isEnraged) { isEnraged = true; if ((Object)(object)base.creatureAnimator != (Object)null && !string.IsNullOrEmpty(enragedTriggerParam)) { base.creatureAnimator.SetTrigger(enragedTriggerParam); } } if (((NetworkBehaviour)this).IsOwner) { SetTargetPlayer(-1); base.movingTowardsTargetPlayer = false; base.agent.speed = 0f; ReturnToDefaultState(); } } private void ReleaseChokeHoldLocal() { if (!isChokeHolding) { return; } isChokeHolding = false; if ((Object)(object)chokeHoldAnimator != (Object)null && !string.IsNullOrEmpty(chokeHoldAnimatorBoolParam)) { chokeHoldAnimator.SetBool(chokeHoldAnimatorBoolParam, false); } if ((Object)(object)chokeHoldVictim != (Object)null) { if ((Object)(object)chokeHoldVictim.thisController != (Object)null) { ((Collider)chokeHoldVictim.thisController).enabled = true; } chokeHoldVictim.inSpecialInteractAnimation = false; chokeHoldVictim.snapToServerPosition = false; if ((Object)(object)chokeHoldVictim.inAnimationWithEnemy == (Object)(object)this) { chokeHoldVictim.inAnimationWithEnemy = null; } } if ((Object)(object)base.inSpecialAnimationWithPlayer == (Object)(object)chokeHoldVictim) { base.inSpecialAnimationWithPlayer = null; } base.inSpecialAnimation = false; chokeHoldVictim = null; } public override void SetEnemyStunned(bool setToStunned, float setToStunTime = 1f, PlayerControllerB setStunnedByPlayer = null) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0096: Unknown result type (might be due to invalid IL or missing references) ((EnemyAI)this).SetEnemyStunned(setToStunned, setToStunTime, setStunnedByPlayer); if (!setToStunned || base.isEnemyDead) { return; } hasBeenStunned = true; if ((Object)(object)setStunnedByPlayer != (Object)null) { blacklistedPlayerIds.Add((int)setStunnedByPlayer.playerClientId); } ReleaseChokeHoldLocal(); if (((NetworkBehaviour)this).IsOwner) { SetTargetPlayer(-1); base.movingTowardsTargetPlayer = false; base.moveTowardsDestination = false; Vector3 val = (((Object)(object)setStunnedByPlayer != (Object)null) ? ((Component)setStunnedByPlayer).transform.position : ((Component)this).transform.position); Transform val2 = ((EnemyAI)this).ChooseFarthestNodeFromPosition(val, true, 0, false, 50, -1); if ((Object)(object)val2 != (Object)null) { ((EnemyAI)this).SetDestinationToPosition(val2.position, true); } fleeTimer = fleeDuration; ((EnemyAI)this).SwitchToBehaviourState(4); } } public override void HitEnemy(int force = 1, PlayerControllerB playerWhoHit = null, bool playHitSFX = false, int hitID = -1) { ((EnemyAI)this).HitEnemy(force, playerWhoHit, playHitSFX, hitID); if (!base.isEnemyDead) { base.enemyHP -= force; if (base.enemyHP <= 0 && ((NetworkBehaviour)this).IsOwner) { ((EnemyAI)this).KillEnemyOnOwnerClient(false); } } } public override void KillEnemy(bool destroy = false) { ReleaseChokeHoldLocal(); ((EnemyAI)this).KillEnemy(destroy); base.agent.speed = 0f; ((EnemyAI)this).SwitchToBehaviourStateOnLocalClient(6); } public override void OnDestroy() { ReleaseChokeHoldLocal(); ((EnemyAI)this).OnDestroy(); } } public class SludgeFiendAI : EnemyAI { [Header("Scrap Collection")] [Tooltip("Body sockets carried scrap is parented onto, one per carried item slot (index 0 = first item picked up). Needs at least Scrap Needed To Attack entries - extra items beyond the array length re-use the last socket.")] public Transform[] scrapAttachPoints; [Tooltip("Distance (units) to a targeted scrap item at which it's actually picked up.")] [Min(0f)] public float scrapPickupRange = 1.5f; [Tooltip("Max distance (units) away a piece of scrap can be for this instance to notice and go for it.")] [Min(0f)] public float scrapDetectRange = 25f; [Tooltip("How many scrap items need to be carried before it stops collecting and starts hunting for a player to launch them at.")] [Min(1f)] public int scrapNeededToAttack = 5; [Tooltip("Possible sounds played once (for everyone) each time a piece of scrap is picked up.")] public List<AudioClip> pickupSFX = new List<AudioClip>(); [Tooltip("AudioSource the pickup sound plays on. Falls back to creatureSFX if left unset.")] public AudioSource pickupAudioSource; [Header("Detection / Fleeing")] [Tooltip("How wide a cone (degrees) counts as \"seeing\"/\"being seen by\" a player - same meaning as CheckLineOfSightForPlayer's own Width parameter.")] [Min(0f)] public float playerDetectWidth = 60f; [Tooltip("Max distance (units) a player can be spotted at while Searching, triggering an immediate flee.")] [Min(0f)] public float playerDetectRange = 25f; [Tooltip("NavMeshAgent speed while running away, either from being spotted or right after launching scrap.")] [Min(0f)] public float fleeSpeed = 8f; [Tooltip("Seconds spent fleeing before it settles back down and resumes searching for scrap.")] [Min(0f)] public float fleeDuration = 6f; [Header("Wandering")] [Tooltip("NavMeshAgent speed while searching for scrap.")] [Min(0f)] public float wanderSpeed = 4f; [Tooltip("Search width passed to the wander AISearchRoutine when no scrap is currently known about.")] [Min(0f)] public float wanderSearchWidth = 60f; [Header("Attack")] [Tooltip("Max distance (units) to a player at which it will stop and launch its carried scrap.")] [Min(0f)] public float launchRange = 15f; [Tooltip("Seconds spent standing still and facing the target before the scrap actually launches.")] [Min(0f)] public float launchWindupTime = 1f; [Tooltip("Damage dealt if the launch connects.")] [Min(0f)] public int launchDamage = 95; [Tooltip("Speed (units/sec) the thrown scrap meshes fly toward the target - cosmetic only, the hit/miss result is decided separately at launch time.")] [Min(0f)] public float launchThrowSpeed = 20f; [Tooltip("Extra upward velocity added to each thrown piece so they arc instead of flying flat.")] [Min(0f)] public float launchArcForce = 4f; [Tooltip("Cause of death reported if a launch brings the player's health to 0.")] public CauseOfDeath launchCauseOfDeath; [Tooltip("Possible sounds played once (for everyone) the moment scrap is launched.")] public List<AudioClip> launchSFX = new List<AudioClip>(); [Tooltip("AudioSource the launch sound plays on. Falls back to creatureSFX if left unset.")] public AudioSource launchAudioSource; [Header("Facing")] [Tooltip("Visual model transform rotated to face the target while winding up an attack. Falls back to this object's own transform if left unset.")] public Transform modelRoot; [Tooltip("Degrees/sec turned to face the target player during the attack windup.")] [Min(0f)] public float turnSpeed = 240f; [Header("Stun Death (explode)")] [Tooltip("VFX prefab instantiated at this instance's position the moment it's stunned/tased and explodes. Purely cosmetic, not networked - every client independently spawns its own local copy the same way R2AI's spawn lightning does.")] public GameObject explosionEffectPrefab; [Tooltip("Seconds before the spawned explosion VFX instance is destroyed again.")] [Min(0f)] public float explosionEffectLifetime = 3f; [Tooltip("Played once (for everyone) the moment it explodes.")] public AudioClip explosionSFX; [Tooltip("AudioSource the explosion sound plays on. Falls back to creatureSFX if left unset.")] public AudioSource explosionAudioSource; [Header("Spawn")] [Tooltip("Possible sounds played once (for everyone) the moment this instance spawns.")] public List<AudioClip> spawnSFX = new List<AudioClip>(); [Tooltip("AudioSource the spawn sound plays on. Falls back to creatureSFX if left unset.")] public AudioSource spawnAudioSource; private static readonly List<GameObject> grabbableObjectsInMap = new List<GameObject>(); private readonly List<GrabbableObject> carriedScrap = new List<GrabbableObject>(); private AISearchRoutine searchForItems; private GrabbableObject targetItem; private float fleeTimer; private PlayerControllerB attackTargetPlayer; private bool isWindingUpLaunch; private float launchTimer; private bool sendingCollectRpc; private bool hasExploded; public override void Start() { //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_001d: Expected O, but got Unknown ((EnemyAI)this).Start(); searchForItems = new AISearchRoutine { searchWidth = wanderSearchWidth }; RefreshGrabbableObjectsInMap(); AudioSource val = (((Object)(object)spawnAudioSource != (Object)null) ? spawnAudioSource : base.creatureSFX); if (spawnSFX != null && spawnSFX.Count > 0 && (Object)(object)val != (Object)null) { AudioClip val2 = spawnSFX[Random.Range(0, spawnSFX.Count)]; val.PlayOneShot(val2); WalkieTalkie.TransmitOneShotAudio(val, val2, 1f); } } private static void RefreshGrabbableObjectsInMap() { grabbableObjectsInMap.Clear(); GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); for (int i = 0; i < array.Length; i++) { if (array[i].itemProperties.isScrap && array[i].grabbableToEnemies && !array[i].deactivated) { grabbableObjectsInMap.Add(((Component)array[i]).gameObject); } } } public override void Update() { ((EnemyAI)this).Update(); if (base.isEnemyDead) { return; } switch (base.currentBehaviourStateIndex) { case 0: base.agent.speed = wanderSpeed; break; case 1: base.agent.speed = fleeSpeed; if (((NetworkBehaviour)this).IsOwner) { fleeTimer -= Time.deltaTime; } break; case 2: base.agent.speed = 0f; FaceTargetPlayer(attackTargetPlayer); if (((NetworkBehaviour)this).IsOwner && isWindingUpLaunch) { launchTimer -= Time.deltaTime; if (launchTimer <= 0f) { isWindingUpLaunch = false; LaunchScrapAtTarget(attackTargetPlayer); } } break; } } public override void DoAIInterval() { ((EnemyAI)this).DoAIInterval(); if (!base.isEnemyDead && ((NetworkBehaviour)this).IsOwner) { switch (base.currentBehaviourStateIndex) { case 0: DoSearchingInterval(); break; case 1: DoFleeingInterval(); break; } } } private void DoSearchingInterval() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = ((EnemyAI)this).CheckLineOfSightForPlayer(playerDetectWidth, (int)playerDetectRange, -1); if ((Object)(object)val != (Object)null) { BeginFlee(((Component)val).transform.position); return; } if (carriedScrap.Count >= scrapNeededToAttack) { PlayerControllerB val2 = ((EnemyAI)this).CheckLineOfSightForPlayer(playerDetectWidth, (int)launchRange, -1); if ((Object)(object)val2 != (Object)null) { BeginAttack(val2); return; } } if ((Object)(object)targetItem != (Object)null) { if (!targetItem.isHeld && !targetItem.deactivated && grabbableObjectsInMap.Contains(((Component)targetItem).gameObject)) { if (Vector3.Distance(((Component)this).transform.position, ((Component)targetItem).transform.position) <= scrapPickupRange) { CollectScrap(targetItem); targetItem = null; } else { ((EnemyAI)this).SetDestinationToPosition(((Component)targetItem).transform.position, true); } return; } targetItem = null; } GameObject val3 = FindClosestScrap(); if ((Object)(object)val3 != (Object)null) { targetItem = val3.GetComponent<GrabbableObject>(); ((EnemyAI)this).StopSearch(searchForItems, false); ((EnemyAI)this).SetDestinationToPosition(((Component)targetItem).transform.position, true); } else if (!searchForItems.inProgress) { ((EnemyAI)this).StartSearch(((Component)this).transform.position, searchForItems); } } private GameObject FindClosestScrap() { //IL_006d: 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) GameObject result = null; float num = scrapDetectRange; for (int num2 = grabbableObjectsInMap.Count - 1; num2 >= 0; num2--) { GameObject val = grabbableObjectsInMap[num2]; if ((Object)(object)val == (Object)null) { grabbableObjectsInMap.RemoveAt(num2); } else { GrabbableObject component = val.GetComponent<GrabbableObject>(); if (!((Object)(object)component == (Object)null) && !component.isHeld && !component.deactivated && component.grabbableToEnemies) { float num3 = Vector3.Distance(((Component)this).transform.position, val.transform.position); if (num3 < num) { num = num3; result = val; } } } } return result; } private void CollectScrap(GrabbableObject item) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) NetworkObject component = ((Component)item).GetComponent<NetworkObject>(); AttachScrapLocally(item); sendingCollectRpc = true; CollectScrapServerRpc(NetworkObjectReference.op_Implicit(component)); } [ServerRpc(RequireOwnership = false)] private void CollectScrapServerRpc(NetworkObjectReference itemRef) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) CollectScrapClientRpc(itemRef); } [ClientRpc] private void CollectScrapClientRpc(NetworkObjectReference itemRef) { NetworkObject val = default(NetworkObject); if (sendingCollectRpc) { sendingCollectRpc = false; } else if (((NetworkObjectReference)(ref itemRef)).TryGet(ref val, (NetworkManager)null)) { GrabbableObject component = ((Component)val).GetComponent<GrabbableObject>(); if ((Object)(object)component != (Object)null) { AttachScrapLocally(component); } } } private void AttachScrapLocally(GrabbableObject item) { if (!((Object)(object)item == (Object)null) && !carriedScrap.Contains(item) && scrapAttachPoints != null && scrapAttachPoints.Length != 0) { int num = Mathf.Min(carriedScrap.Count, scrapAttachPoints.Length - 1); Transform parentObject = scrapAttachPoints[num]; item.parentObject = parentObject; item.hasHitGround = false; item.GrabItemFromEnemy((EnemyAI)(object)this); item.EnablePhysics(false); carriedScrap.Add(item); grabbableObjectsInMap.Remove(((Component)item).gameObject); AudioSource val = (((Object)(object)pickupAudioSource != (Object)null) ? pickupAudioSource : base.creatureSFX); if (pickupSFX != null && pickupSFX.Count > 0 && (Object)(object)val != (Object)null) { AudioClip val2 = pickupSFX[Random.Range(0, pickupSFX.Count)]; val.PlayOneShot(val2); WalkieTalkie.TransmitOneShotAudio(val, val2, 1f); } } } private void BeginFlee(Vector3 threatPosition) { //IL_0015: 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) targetItem = null; ((EnemyAI)this).StopSearch(searchForItems, false); Transform val = ((EnemyAI)this).ChooseFarthestNodeFromPosition(threatPosition, true, 0, false, 50, -1); if ((Object)(object)val != (Object)null) { ((EnemyAI)this).SetDestinationToPosition(val.position, true); } fleeTimer = fleeDuration; ((EnemyAI)this).SwitchToBehaviourState(1); } private void DoFleeingInterval() { if (fleeTimer <= 0f) { ((EnemyAI)this).SwitchToBehaviourState(0); } } private void BeginAttack(PlayerControllerB target) { targetItem = null; ((EnemyAI)this).StopSearch(searchForItems, false); attackTargetPlayer = target; isWindingUpLaunch = true; launchTimer = launchWindupTime; ((EnemyAI)this).SwitchToBehaviourState(2); } private void FaceTargetPlayer(PlayerControllerB target) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0062: 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_0074: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null)) { Transform val = (((Object)(object)modelRoot != (Object)null) ? modelRoot : ((Component)this).transform); Vector3 val2 = ((Component)target).transform.position - val.position; val2.y = 0f; if (!(((Vector3)(ref val2)).sqrMagnitude < 0.0001f)) { Quaternion val3 = Quaternion.LookRotation(val2); val.rotation = Quaternion.RotateTowards(val.rotation, val3, turnSpeed * Time.deltaTime); } } } private void LaunchScrapAtTarget(PlayerControllerB target) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target != (Object)null && carriedScrap.Count > 0) { float num = Vector3.Distance(((Component)this).transform.position, ((Component)target).transform.position); bool flag = !Physics.Linecast(base.eye.position, ((Component)target.gameplayCamera).transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1); bool willHit = num <= launchRange && flag; LaunchScrapServerRpc((int)target.playerClientId, willHit); } Vector3 threatPosition = (((Object)(object)target != (Object)null) ? ((Component)target).transform.position : ((Component)this).transform.position); attackTargetPlayer = null; BeginFlee(threatPosition); } [ServerRpc(RequireOwnership = false)] private void LaunchScrapServerRpc(int targetPlayerId, bool willHit) { LaunchScrapClientRpc(targetPlayerId, willHit); } [ClientRpc] private void LaunchScrapClientRpc(int targetPlayerId, bool willHit) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = null; if ((Object)(object)StartOfRound.Instance != (Object)null && targetPlayerId >= 0 && targetPlayerId < StartOfRound.Instance.allPlayerScripts.Length) { val = StartOfRound.Instance.allPlayerScripts[targetPlayerId]; } ThrowCarriedScrapVisual(val); AudioSource val2 = (((Object)(object)launchAudioSource != (Object)null) ? launchAudioSource : base.creatureSFX); if (launchSFX != null && launchSFX.Count > 0 && (Object)(object)val2 != (Object)null) { AudioClip val3 = launchSFX[Random.Range(0, launchSFX.Count)]; val2.PlayOneShot(val3); WalkieTalkie.TransmitOneShotAudio(val2, val3, 1f); } if (willHit && (Object)(object)val != (Object)null && !val.isPlayerDead) { val.DamagePlayer(launchDamage, true, true, launchCauseOfDeath, 0, false, default(Vector3)); } } private void ThrowCarriedScrapVisual(PlayerControllerB target) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) //IL_002a: 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) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) Vector3 val = (((Object)(object)target != (Object)null) ? (((Component)target).transform.position + Vector3.up) : (((Component)this).transform.position + ((Component)this).transform.forward * launchRange)); foreach (GrabbableObject item in carriedScrap) { if (!((Object)(object)item == (Object)null)) { item.parentObject = null; item.EnablePhysics(true); item.hasHitGround = false; if ((Object)(object)item.propBody != (Object)null) { Vector3 val2 = val - ((Component)item).transform.position; Vector3 normalized = ((Vector3)(ref val2)).normalized; item.propBody.velocity = normalized * launchThrowSpeed + Vector3.up * launchArcForce; } item.DiscardItemFromEnemy(); grabbableObjectsInMap.Add(((Component)item).gameObject); } } carriedScrap.Clear(); } public override void HitEnemy(int force = 1, PlayerControllerB playerWhoHit = null, bool playHitSFX = false, int hitID = -1) { ((EnemyAI)this).HitEnemy(force, playerWhoHit, playHitSFX, hitID); if (!base.isEnemyDead) { base.enemyHP -= force; if (base.enemyHP <= 0 && ((NetworkBehaviour)this).IsOwner) { ((EnemyAI)this).KillEnemyOnOwnerClient(false); } } } public override void SetEnemyStunned(bool setToStunned, float setToStunTime = 1f, PlayerControllerB setStunnedByPlayer = null) { ((EnemyAI)this).SetEnemyStunned(setToStunned, setToStunTime, setStunnedByPlayer); if (setToStunned && !base.isEnemyDead && !hasExploded) { hasExploded = true; PlayExplosionEffect(); if (((NetworkBehaviour)this).IsOwner) { ((EnemyAI)this).KillEnemyOnOwnerClient(true); } } } private void PlayExplosionEffect() { //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) if ((Object)(object)explosionEffectPrefab != (Object)null) { Object.Destroy((Object)(object)Object.Instantiate<GameObject>(explosionEffectPrefab, ((Component)this).transform.position, Quaternion.identity), explosionEffectLifetime); } AudioSource val = (((Object)(object)explosionAudioSource != (Object)null) ? explosionAudioSource : base.creatureSFX); if ((Object)(object)explosionSFX != (Object)null && (Object)(object)val != (Object)null) { val.PlayOneShot(explosionSFX); WalkieTalkie.TransmitOneShotAudio(val, explosionSFX, 1f); } } public override void KillEnemy(bool destroy = false) { ((EnemyAI)this).KillEnemy(false); base.agent.speed = 0f; DropAllCarriedScrap(); } private void DropAllCarriedScrap() { foreach (GrabbableObject item in carriedScrap) { if (!((Object)(object)item == (Object)null)) { item.parentObject = null; item.EnablePhysics(true); item.hasHitGround = false; item.DiscardItemFromEnemy(); grabbableObjectsInMap.Add(((Component)item).gameObject); } } carriedScrap.Clear(); } }