Decompiled source of KoishiEnemy v1.1.0

plugins/KoishiEnemy.dll

Decompiled 19 hours ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using LethalLib.Modules;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("KoishiEnemy")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("KoishiEnemy")]
[assembly: AssemblyTitle("KoishiEnemy")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace KoishiEnemy
{
	public class KoishiAI : EnemyAI
	{
		private enum State
		{
			Haunting,
			Alert,
			Chasing,
			Lunge,
			Retarget
		}

		[Header("Koishi Settings")]
		public AudioClip alertSound;

		public AudioClip alertEnhancedSound;

		public AudioClip chargeFootsteps;

		public AudioClip attackSound;

		public AudioClip chaseVoice;

		private Vector3 lungeDirection;

		private Vector3 lungeTarget;

		private int alertPhase;

		private float alertPhaseTimer;

		private bool enhancedAlert;

		private float lungeTimer;

		private float hauntCheckTimer;

		private float hauntElapsed;

		private bool hasHitPlayer;

		private float retargetTimer;

		private bool alertIsReal;

		private float chaseTimer;

		private const float CHASE_TIMEOUT = 20f;

		private Random koishiRandom;

		private Vector3 lastPosition;

		private float stuckTimer;

		private const float STUCK_CHECK_SPEED = 0.5f;

		private const float STUCK_WARP_TIME = 3f;

		private const float HAUNT_CHECK_INTERVAL = 1f;

		private const float LUNGE_TRIGGER_DISTANCE = 10f;

		private const float LUNGE_DISTANCE = 25f;

		private const float LUNGE_DURATION = 1f;

		public override void Start()
		{
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			((EnemyAI)this).Start();
			if (base.skinnedMeshRenderers == null || base.skinnedMeshRenderers.Length == 0)
			{
				base.skinnedMeshRenderers = ((Component)this).GetComponentsInChildren<SkinnedMeshRenderer>();
			}
			if (base.meshRenderers == null || base.meshRenderers.Length == 0)
			{
				base.meshRenderers = ((Component)this).GetComponentsInChildren<MeshRenderer>();
			}
			LoadAudio();
			if ((Object)(object)base.creatureAnimator != (Object)null)
			{
				base.creatureAnimator.speed = 2f;
			}
			if ((Object)(object)base.creatureSFX != (Object)null)
			{
				base.creatureSFX.spatialBlend = 1f;
				base.creatureSFX.maxDistance = 50f;
				base.creatureSFX.rolloffMode = (AudioRolloffMode)1;
				base.creatureSFX.minDistance = 1f;
			}
			SetInvisible();
			koishiRandom = new Random(StartOfRound.Instance.randomMapSeed + 314);
			lastPosition = ((Component)this).transform.position;
			stuckTimer = 0f;
			ChoosePlayerToHaunt();
			SwitchToState(State.Haunting);
		}

		public override void DoAIInterval()
		{
			((EnemyAI)this).DoAIInterval();
			if (!base.isEnemyDead && !StartOfRound.Instance.allPlayersDead)
			{
				switch ((State)base.currentBehaviourStateIndex)
				{
				case State.Haunting:
					DoHaunting();
					break;
				case State.Chasing:
					DoChasing();
					break;
				case State.Retarget:
					DoRetarget();
					break;
				case State.Alert:
				case State.Lunge:
					break;
				}
			}
		}

		public override void Update()
		{
			((EnemyAI)this).Update();
			if (!base.isEnemyDead && !StartOfRound.Instance.allPlayersDead)
			{
				switch ((State)base.currentBehaviourStateIndex)
				{
				case State.Haunting:
					UpdateHaunting();
					break;
				case State.Alert:
					UpdateAlert();
					break;
				case State.Chasing:
					UpdateChasing();
					break;
				case State.Lunge:
					UpdateLunge();
					break;
				case State.Retarget:
					retargetTimer -= Time.deltaTime;
					break;
				}
			}
		}

		private int CalculateFear(PlayerControllerB player)
		{
			int num = 80;
			float num2 = 0f;
			for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++)
			{
				PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[i];
				if (val.isPlayerControlled && val.insanityLevel > num2)
				{
					num2 = val.insanityLevel;
				}
			}
			if (num2 > 0f)
			{
				float num3 = Mathf.Clamp01(player.insanityLevel / Mathf.Max(num2, 1f));
				num += Mathf.RoundToInt(num3 * 50f);
			}
			int num4 = 0;
			int num5 = -1;
			for (int j = 0; j < StartOfRound.Instance.allPlayerScripts.Length; j++)
			{
				if ((Object)(object)StartOfRound.Instance.allPlayerScripts[j] == (Object)(object)player)
				{
					num5 = j;
				}
				if (StartOfRound.Instance.gameStats.allPlayerStats[j].turnAmount > num4)
				{
					num4 = StartOfRound.Instance.gameStats.allPlayerStats[j].turnAmount;
				}
			}
			if (num5 >= 0 && num4 > 0)
			{
				float num6 = (float)StartOfRound.Instance.gameStats.allPlayerStats[num5].turnAmount / (float)num4;
				num += Mathf.RoundToInt(num6 * 30f);
			}
			if (player.hasBeenCriticallyInjured)
			{
				num += 20;
			}
			return num;
		}

		private float FearToChaseChance(int fear)
		{
			float num = Mathf.Clamp01((float)fear / 180f);
			return 0.85f * num * num;
		}

		private void ChoosePlayerToHaunt()
		{
			int[] array = new int[StartOfRound.Instance.allPlayerScripts.Length];
			bool flag = false;
			for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++)
			{
				PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[i];
				if (!val.isPlayerControlled || val.isPlayerDead)
				{
					array[i] = 0;
					continue;
				}
				array[i] = CalculateFear(val);
				flag = true;
			}
			if (flag)
			{
				base.targetPlayer = StartOfRound.Instance.allPlayerScripts[RoundManager.Instance.GetRandomWeightedIndex(array, koishiRandom)];
				Plugin.Log.LogInfo((object)$"Koishi now haunting: {base.targetPlayer.playerUsername} (FEAR={CalculateFear(base.targetPlayer)})");
			}
		}

		private void EnterHaunting()
		{
			hauntCheckTimer = 1f;
			hauntElapsed = 0f;
			base.agent.isStopped = false;
			base.agent.speed = 7f;
			SetInvisible();
			SetColliderEnabled(enabled: false);
			if ((Object)(object)base.targetPlayer != (Object)null)
			{
				((EnemyAI)this).SetMovingTowardsTargetPlayer(base.targetPlayer);
				base.moveTowardsDestination = true;
			}
		}

		private void DoHaunting()
		{
			if ((Object)(object)base.targetPlayer == (Object)null || base.targetPlayer.isPlayerDead)
			{
				ChoosePlayerToHaunt();
				return;
			}
			base.agent.speed = 7f;
			((EnemyAI)this).SetMovingTowardsTargetPlayer(base.targetPlayer);
		}

		private void UpdateHaunting()
		{
			hauntElapsed += Time.deltaTime;
			hauntCheckTimer -= Time.deltaTime;
			CheckStuck();
			if (!(hauntCheckTimer <= 0f))
			{
				return;
			}
			hauntCheckTimer = 1f;
			if ((Object)(object)base.targetPlayer != (Object)null && !base.targetPlayer.isPlayerDead)
			{
				float num = 0.045f * Mathf.Log(1f + hauntElapsed / 60f) / Mathf.Log(4f);
				if (Random.value < num)
				{
					int num2 = CalculateFear(base.targetPlayer);
					float num3 = FearToChaseChance(num2);
					alertIsReal = Random.value < num3;
					Plugin.Log.LogInfo((object)$"Koishi alert! elapsed={hauntElapsed:F0}s, alertChance={num:P0}, FEAR={num2}, chaseChance={num3:P0}, real={alertIsReal}");
					SwitchToState(State.Alert);
				}
			}
		}

		private void EnterAlert()
		{
			base.agent.speed = 0f;
			base.agent.isStopped = true;
			alertPhase = 0;
			int fear = (((Object)(object)base.targetPlayer != (Object)null) ? CalculateFear(base.targetPlayer) : 80);
			float num = FearToChaseChance(fear);
			enhancedAlert = alertIsReal && num > 0.6f;
			Plugin.Log.LogInfo((object)$"Koishi alert: enhanced={enhancedAlert}, chaseChance={num:P0}, real={alertIsReal}");
			PlayAlertPhase(0);
		}

		private void PlayAlertPhase(int phase)
		{
			alertPhase = phase;
			bool flag = (Object)(object)base.targetPlayer != (Object)null && (Object)(object)base.targetPlayer == (Object)(object)GameNetworkManager.Instance.localPlayerController;
			switch (phase)
			{
			case 0:
			{
				AudioClip val = (enhancedAlert ? alertEnhancedSound : alertSound);
				alertPhaseTimer = (((Object)(object)val != (Object)null) ? val.length : 8.5f);
				if ((Object)(object)val != (Object)null && flag)
				{
					HUDManager.Instance.UIAudio.PlayOneShot(val, 0.3f);
				}
				break;
			}
			case 1:
				alertPhaseTimer = (((Object)(object)chaseVoice != (Object)null) ? chaseVoice.length : 3f);
				if ((Object)(object)chaseVoice != (Object)null && flag)
				{
					HUDManager.Instance.UIAudio.PlayOneShot(chaseVoice, 0.8f);
				}
				break;
			}
		}

		private int GetMaxAlertPhase()
		{
			return alertIsReal ? 1 : 0;
		}

		private void UpdateAlert()
		{
			//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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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)
			alertPhaseTimer -= Time.deltaTime;
			if (alertPhaseTimer > 0f)
			{
				return;
			}
			if (alertPhase < GetMaxAlertPhase())
			{
				PlayAlertPhase(alertPhase + 1);
			}
			else if (alertIsReal)
			{
				if ((Object)(object)base.targetPlayer != (Object)null)
				{
					Vector3 val = FindWarpPositionWithLOS(base.targetPlayer);
					if (val != Vector3.zero)
					{
						base.agent.Warp(val);
						SwitchToState(State.Chasing);
					}
					else
					{
						Plugin.Log.LogInfo((object)"Koishi: warp failed, no valid LOS node. Treating as false alarm.");
						hauntElapsed = 0f;
						SwitchToState(State.Haunting);
					}
				}
				else
				{
					SwitchToState(State.Haunting);
				}
			}
			else
			{
				Plugin.Log.LogInfo((object)"Koishi: false alarm, resuming haunt");
				hauntElapsed = 0f;
				SwitchToState(State.Haunting);
			}
		}

		private void EnterChasing()
		{
			hasHitPlayer = false;
			chaseTimer = 0f;
			SetInvisible();
			SetColliderEnabled(enabled: false);
			base.agent.isStopped = false;
			base.agent.speed = 20f;
			if ((Object)(object)chargeFootsteps != (Object)null && (Object)(object)base.creatureSFX != (Object)null)
			{
				base.creatureSFX.clip = chargeFootsteps;
				base.creatureSFX.loop = true;
				base.creatureSFX.volume = 1.5f;
				base.creatureSFX.Play();
			}
			Plugin.Log.LogInfo((object)"Koishi: chase started immediately after alert audio!");
		}

		private void DoChasing()
		{
			if ((Object)(object)base.targetPlayer == (Object)null || base.targetPlayer.isPlayerDead)
			{
				StopChargeAudio();
				SwitchToState(State.Retarget);
			}
			else
			{
				((EnemyAI)this).SetMovingTowardsTargetPlayer(base.targetPlayer);
			}
		}

		private void UpdateChasing()
		{
			//IL_002d: 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_0056: 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_0065: 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_007f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)base.targetPlayer == (Object)null)
			{
				return;
			}
			chaseTimer += Time.deltaTime;
			CheckStuck();
			float num = Vector3.Distance(((Component)this).transform.position, ((Component)base.targetPlayer).transform.position);
			if (num <= 10f)
			{
				if (!Physics.Linecast(((Component)this).transform.position + Vector3.up * 0.5f, ((Component)base.targetPlayer.gameplayCamera).transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault))
				{
					StopChargeAudio();
					SwitchToState(State.Lunge);
				}
			}
			else if (chaseTimer >= 20f)
			{
				Plugin.Log.LogInfo((object)$"Koishi: chase timeout ({20f}s), dist={num:F0}m. Giving up.");
				StopChargeAudio();
				SwitchToState(State.Retarget);
			}
		}

		private void StopChargeAudio()
		{
			if ((Object)(object)base.creatureSFX != (Object)null)
			{
				base.creatureSFX.loop = false;
				base.creatureSFX.Stop();
				base.creatureSFX.clip = null;
			}
		}

		private void EnterLunge()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			lungeTimer = 1f;
			hasHitPlayer = false;
			SetVisible();
			SetColliderEnabled(enabled: true);
			Vector3 val = ((Component)base.targetPlayer).transform.position - ((Component)this).transform.position;
			lungeDirection = ((Vector3)(ref val)).normalized;
			lungeTarget = ((Component)this).transform.position + lungeDirection * 25f;
			base.agent.isStopped = true;
			base.agent.updatePosition = false;
			base.creatureAnimator.SetTrigger("Attack");
			if ((Object)(object)attackSound != (Object)null && (Object)(object)base.creatureSFX != (Object)null)
			{
				base.creatureSFX.PlayOneShot(attackSound);
			}
			Plugin.Log.LogInfo((object)"Koishi: LUNGE!");
		}

		private void UpdateLunge()
		{
			//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_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_003e: 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_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: 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_0093: 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_00a0: 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)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: 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)
			lungeTimer -= Time.deltaTime;
			float num = 25f * Time.deltaTime;
			if (Physics.Raycast(((Component)this).transform.position + Vector3.up * 0.5f, lungeDirection, num + 0.3f, StartOfRound.Instance.collidersAndRoomMaskAndDefault))
			{
				Plugin.Log.LogInfo((object)"Koishi: lunge hit wall, stopping early");
				base.agent.updatePosition = true;
				base.agent.isStopped = false;
				base.agent.Warp(RoundManager.Instance.GetNavMeshPosition(((Component)this).transform.position, default(NavMeshHit), 5f, -1));
				SetInvisible();
				SwitchToState(State.Retarget);
				return;
			}
			Transform transform = ((Component)this).transform;
			transform.position += lungeDirection * num;
			if (lungeDirection != Vector3.zero)
			{
				((Component)this).transform.rotation = Quaternion.LookRotation(lungeDirection);
			}
			if (!hasHitPlayer && (Object)(object)base.targetPlayer != (Object)null && !base.targetPlayer.isPlayerDead && Vector3.Distance(((Component)this).transform.position, ((Component)base.targetPlayer).transform.position) < 1.2f)
			{
				HitPlayer(base.targetPlayer);
			}
			if (lungeTimer <= 0f)
			{
				base.agent.updatePosition = true;
				base.agent.isStopped = false;
				base.agent.Warp(RoundManager.Instance.GetNavMeshPosition(((Component)this).transform.position, default(NavMeshHit), 5f, -1));
				SetInvisible();
				SwitchToState(State.Retarget);
			}
		}

		private void HitPlayer(PlayerControllerB player)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			hasHitPlayer = true;
			player.KillPlayer(lungeDirection * 5f, true, (CauseOfDeath)6, 0, default(Vector3));
			Plugin.Log.LogInfo((object)("Koishi killed player " + player.playerUsername));
		}

		private void EnterRetarget()
		{
			retargetTimer = 3f;
			base.agent.isStopped = true;
			SetInvisible();
			SetColliderEnabled(enabled: false);
		}

		private void DoRetarget()
		{
			if (retargetTimer <= 0f)
			{
				ChoosePlayerToHaunt();
				SwitchToState(State.Haunting);
			}
		}

		private Vector3 FindWarpPositionWithLOS(PlayerControllerB player)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: 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_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: 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_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02da: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			GameObject[] allAINodes = base.allAINodes;
			GameObject[] array = GameObject.FindGameObjectsWithTag("OutsideAINode");
			int num = ((allAINodes != null) ? allAINodes.Length : 0);
			int num2 = ((array != null) ? array.Length : 0);
			int num3 = num + num2;
			if (num3 == 0)
			{
				return Vector3.zero;
			}
			GameObject[] array2 = (GameObject[])(object)new GameObject[num3];
			if (num > 0)
			{
				Array.Copy(allAINodes, 0, array2, 0, num);
			}
			if (num2 > 0)
			{
				Array.Copy(array, 0, array2, num, num2);
			}
			int[] array3 = new int[num3];
			for (int i = 0; i < array3.Length; i++)
			{
				array3[i] = i;
			}
			for (int num4 = array3.Length - 1; num4 > 0; num4--)
			{
				int num5 = Random.Range(0, num4 + 1);
				int num6 = array3[num4];
				array3[num4] = array3[num5];
				array3[num5] = num6;
			}
			float num7 = 20f;
			float num8 = 50f;
			int[] array4 = array3;
			foreach (int num9 in array4)
			{
				Vector3 position = array2[num9].transform.position;
				float num10 = Vector3.Distance(position, ((Component)player).transform.position);
				if (!(num10 < num7) && !(num10 > num8) && !Physics.Linecast(position + Vector3.up * 0.4f, ((Component)player.gameplayCamera).transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault))
				{
					return RoundManager.Instance.GetNavMeshPosition(position, default(NavMeshHit), 5f, -1);
				}
			}
			float num11 = Mathf.Max(num7, 20f);
			float num12 = num8 * 2f;
			array4 = array3;
			foreach (int num13 in array4)
			{
				Vector3 position2 = array2[num13].transform.position;
				float num14 = Vector3.Distance(position2, ((Component)player).transform.position);
				if (!(num14 < num11) && !(num14 > num12) && !Physics.Linecast(position2 + Vector3.up * 0.4f, ((Component)player.gameplayCamera).transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault))
				{
					return RoundManager.Instance.GetNavMeshPosition(position2, default(NavMeshHit), 5f, -1);
				}
			}
			Vector3 val = Vector3.zero;
			float num15 = float.MaxValue;
			array4 = array3;
			foreach (int num16 in array4)
			{
				Vector3 position3 = array2[num16].transform.position;
				float num17 = Vector3.Distance(position3, ((Component)player).transform.position);
				if (!(num17 < num11) && !(num17 > num12))
				{
					float num18 = (num11 + num12) * 0.5f;
					float num19 = Mathf.Abs(num17 - num18);
					if (num19 < num15)
					{
						num15 = num19;
						val = position3;
					}
				}
			}
			if (val != Vector3.zero)
			{
				Plugin.Log.LogInfo((object)$"Koishi: warp fallback (no LOS), dist={Vector3.Distance(val, ((Component)player).transform.position):F0}m");
				return RoundManager.Instance.GetNavMeshPosition(val, default(NavMeshHit), 5f, -1);
			}
			return Vector3.zero;
		}

		private void CheckStuck()
		{
			//IL_0006: 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_0074: 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)
			if (Vector3.Distance(((Component)this).transform.position, lastPosition) / Time.deltaTime < 0.5f && !base.agent.isStopped)
			{
				stuckTimer += Time.deltaTime;
				if (stuckTimer >= 3f)
				{
					WarpPastObstacle();
					stuckTimer = 0f;
				}
			}
			else
			{
				stuckTimer = 0f;
			}
			lastPosition = ((Component)this).transform.position;
		}

		private void WarpPastObstacle()
		{
			//IL_006e: 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_008a: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: 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_00be: 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_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)base.targetPlayer == (Object)null)
			{
				return;
			}
			GameObject[] array = GameObject.FindGameObjectsWithTag("OutsideAINode");
			int num = ((base.allAINodes != null) ? base.allAINodes.Length : 0);
			int num2 = ((array != null) ? array.Length : 0);
			if (num + num2 == 0)
			{
				return;
			}
			GameObject[] array2 = (GameObject[])(object)new GameObject[num + num2];
			if (num > 0)
			{
				Array.Copy(base.allAINodes, 0, array2, 0, num);
			}
			if (num2 > 0)
			{
				Array.Copy(array, 0, array2, num, num2);
			}
			float num3 = Vector3.Distance(((Component)this).transform.position, ((Component)base.targetPlayer).transform.position);
			Vector3 val = Vector3.zero;
			float num4 = float.MaxValue;
			GameObject[] array3 = array2;
			for (int i = 0; i < array3.Length; i++)
			{
				Vector3 position = array3[i].transform.position;
				float num5 = Vector3.Distance(position, ((Component)base.targetPlayer).transform.position);
				float num6 = Vector3.Distance(position, ((Component)this).transform.position);
				if (num5 < num3 && num6 > 3f && num5 < num4)
				{
					num4 = num5;
					val = position;
				}
			}
			if (val != Vector3.zero)
			{
				Vector3 navMeshPosition = RoundManager.Instance.GetNavMeshPosition(val, default(NavMeshHit), 5f, -1);
				base.agent.Warp(navMeshPosition);
				Plugin.Log.LogInfo((object)$"Koishi: warped past obstacle, new dist to player={num4:F0}m");
			}
		}

		private void SetVisible()
		{
			bool flag = (Object)(object)base.targetPlayer != (Object)null && (Object)(object)base.targetPlayer == (Object)(object)GameNetworkManager.Instance.localPlayerController;
			if (base.skinnedMeshRenderers != null)
			{
				SkinnedMeshRenderer[] skinnedMeshRenderers = base.skinnedMeshRenderers;
				for (int i = 0; i < skinnedMeshRenderers.Length; i++)
				{
					((Renderer)skinnedMeshRenderers[i]).enabled = flag;
				}
			}
			if (base.meshRenderers != null)
			{
				MeshRenderer[] meshRenderers = base.meshRenderers;
				for (int i = 0; i < meshRenderers.Length; i++)
				{
					((Renderer)meshRenderers[i]).enabled = flag;
				}
			}
			ManualLogSource log = Plugin.Log;
			object arg = flag;
			SkinnedMeshRenderer[] skinnedMeshRenderers2 = base.skinnedMeshRenderers;
			object arg2 = ((skinnedMeshRenderers2 != null) ? skinnedMeshRenderers2.Length : 0);
			MeshRenderer[] meshRenderers2 = base.meshRenderers;
			log.LogInfo((object)$"SetVisible: isLocalTarget={arg}, skinned={arg2}, mesh={((meshRenderers2 != null) ? meshRenderers2.Length : 0)}");
		}

		private void SetInvisible()
		{
			if (base.skinnedMeshRenderers != null)
			{
				SkinnedMeshRenderer[] skinnedMeshRenderers = base.skinnedMeshRenderers;
				for (int i = 0; i < skinnedMeshRenderers.Length; i++)
				{
					((Renderer)skinnedMeshRenderers[i]).enabled = false;
				}
			}
			if (base.meshRenderers != null)
			{
				MeshRenderer[] meshRenderers = base.meshRenderers;
				for (int i = 0; i < meshRenderers.Length; i++)
				{
					((Renderer)meshRenderers[i]).enabled = false;
				}
			}
		}

		private void SetColliderEnabled(bool enabled)
		{
			Collider component = ((Component)this).GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				component.enabled = enabled;
			}
		}

		private void SwitchToState(State newState)
		{
			if (newState != 0 && newState != State.Retarget)
			{
				((EnemyAI)this).StopSearch(base.currentSearch, true);
			}
			((EnemyAI)this).SwitchToBehaviourClientRpc((int)newState);
			switch (newState)
			{
			case State.Haunting:
				EnterHaunting();
				break;
			case State.Alert:
				EnterAlert();
				break;
			case State.Chasing:
				EnterChasing();
				break;
			case State.Lunge:
				EnterLunge();
				break;
			case State.Retarget:
				EnterRetarget();
				break;
			}
		}

		public override void OnCollideWithPlayer(Collider other)
		{
			((EnemyAI)this).OnCollideWithPlayer(other);
			if (base.currentBehaviourStateIndex == 3 && !hasHitPlayer)
			{
				PlayerControllerB component = ((Component)other).GetComponent<PlayerControllerB>();
				if ((Object)(object)component != (Object)null && !component.isPlayerDead && (Object)(object)component == (Object)(object)base.targetPlayer)
				{
					HitPlayer(component);
				}
			}
		}

		private void LoadAudio()
		{
			string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Audio");
			alertSound = LoadOggFromFile(Path.Combine(path, "alert.ogg"), "KoishiAlert");
			alertEnhancedSound = LoadOggFromFile(Path.Combine(path, "alert_enhanced.ogg"), "KoishiAlertEnhanced");
			chargeFootsteps = LoadOggFromFile(Path.Combine(path, "charge.ogg"), "KoishiCharge");
			attackSound = LoadOggFromFile(Path.Combine(path, "attack.ogg"), "KoishiAttack");
			chaseVoice = LoadOggFromFile(Path.Combine(path, "koishi_part2.ogg"), "KoishiChaseVoice");
			if ((Object)(object)alertSound != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded alert sound: {alertSound.length:F2}s");
			}
			if ((Object)(object)alertEnhancedSound != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded alert enhanced: {alertEnhancedSound.length:F2}s");
			}
			if ((Object)(object)chargeFootsteps != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded charge sound: {chargeFootsteps.length:F2}s");
			}
			if ((Object)(object)attackSound != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded attack sound: {attackSound.length:F2}s");
			}
			if ((Object)(object)chaseVoice != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded chase voice: {chaseVoice.length:F2}s");
			}
		}

		private static AudioClip LoadOggFromFile(string filePath, string clipName)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Invalid comparison between Unknown and I4
			if (!File.Exists(filePath))
			{
				Plugin.Log.LogWarning((object)("Audio file not found: " + filePath));
				return null;
			}
			UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip("file://" + filePath, (AudioType)14);
			audioClip.SendWebRequest();
			while (!audioClip.isDone)
			{
			}
			if ((int)audioClip.result != 1)
			{
				Plugin.Log.LogError((object)("Failed to load audio " + clipName + ": " + audioClip.error));
				return null;
			}
			AudioClip content = DownloadHandlerAudioClip.GetContent(audioClip);
			((Object)content).name = clipName;
			Plugin.Log.LogInfo((object)("Loaded " + clipName + " from " + filePath));
			return content;
		}
	}
	[BepInPlugin("com.suwako.KoishiEnemy", "Koishi Enemy", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal const float ChargeSpeedValue = 20f;

		internal const float RoamSpeedValue = 7f;

		internal const float WarpMinDistanceValue = 20f;

		internal const float WarpMaxDistanceValue = 50f;

		internal static ConfigEntry<int> RarityD;

		internal static ConfigEntry<int> RarityC;

		internal static ConfigEntry<int> RarityB;

		internal static ConfigEntry<int> RarityA;

		internal static ConfigEntry<int> RarityS;

		internal static ConfigEntry<int> RaritySPlus;

		internal static ConfigEntry<int> RaritySSS;

		internal static ConfigEntry<int> RarityOther;

		internal static EnemyType KoishiEnemyType;

		internal static AssetBundle KoishiBundle;

		internal static GameObject KoishiPrefab;

		public static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			RarityD = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level D", 3, "Spawn rarity for moons with a Risk Level of 'D'.");
			RarityC = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level C", 4, "Spawn rarity for moons with a Risk Level of 'C'.");
			RarityB = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level B", 6, "Spawn rarity for moons with a Risk Level of 'B'.");
			RarityA = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level A", 12, "Spawn rarity for moons with a Risk Level of 'A'.");
			RarityS = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level S", 24, "Spawn rarity for moons with a Risk Level of 'S'.");
			RaritySPlus = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level S+", 30, "Spawn rarity for moons with a Risk Level of 'S+'.");
			RaritySSS = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level SSS", 40, "Spawn rarity for modded moons with a Risk Level of 'SSS'.");
			RarityOther = ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Rarity", "Risk Level Other", 18, "Spawn rarity for moons with an unknown Risk Level or any other Risk Level.");
			KoishiBundle = AssetBundle.LoadFromFile(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "koishi_assets"));
			if ((Object)(object)KoishiBundle != (Object)null)
			{
				KoishiPrefab = KoishiBundle.LoadAsset<GameObject>("KoishiEnemy");
			}
			if ((Object)(object)KoishiPrefab == (Object)null)
			{
				Log.LogWarning((object)"No asset bundle found — building debug prefab (invisible placeholder).");
				KoishiPrefab = BuildDebugPrefab();
			}
			KoishiEnemyType = KoishiPrefab.GetComponent<EnemyAI>()?.enemyType;
			if ((Object)(object)KoishiEnemyType != (Object)null)
			{
				Enemies.RegisterEnemy(KoishiEnemyType, 0, (LevelTypes)(-1), (SpawnType)0, (TerminalNode)null, (TerminalKeyword)null);
				Log.LogInfo((object)"Koishi registered (terminal entry). Spawn rarity set per risk level.");
			}
			else
			{
				Log.LogError((object)"EnemyType not found on prefab!");
			}
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "com.suwako.KoishiEnemy");
			Log.LogInfo((object)"Koishi Enemy v1.1.0 loaded!");
		}

		internal static int GetRarityForRiskLevel(string riskLevel)
		{
			if (string.IsNullOrEmpty(riskLevel))
			{
				return RarityOther.Value;
			}
			return riskLevel.Trim().ToUpper() switch
			{
				"D" => RarityD.Value, 
				"C" => RarityC.Value, 
				"B" => RarityB.Value, 
				"A" => RarityA.Value, 
				"S" => RarityS.Value, 
				"S+" => RaritySPlus.Value, 
				"S++" => RaritySPlus.Value, 
				"SSS" => RaritySSS.Value, 
				_ => RarityOther.Value, 
			};
		}

		private static GameObject BuildDebugPrefab()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Expected O, but got Unknown
			GameObject val = new GameObject("KoishiEnemy");
			val.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)val);
			NavMeshAgent obj = val.AddComponent<NavMeshAgent>();
			obj.speed = 3.5f;
			obj.angularSpeed = 300f;
			obj.acceleration = 20f;
			obj.stoppingDistance = 0f;
			obj.radius = 0.4f;
			obj.height = 1.8f;
			BoxCollider obj2 = val.AddComponent<BoxCollider>();
			obj2.size = new Vector3(1f, 2f, 1f);
			((Collider)obj2).isTrigger = true;
			EnemyType val2 = ScriptableObject.CreateInstance<EnemyType>();
			val2.enemyName = "Koishi";
			val2.enemyPrefab = val;
			val2.isDaytimeEnemy = false;
			val2.isOutsideEnemy = false;
			val2.canDie = false;
			val2.PowerLevel = 2f;
			val2.MaxCount = 1;
			KoishiAI koishiAI = val.AddComponent<KoishiAI>();
			((EnemyAI)koishiAI).enemyType = val2;
			GameObject val3 = new GameObject("Eye");
			val3.transform.SetParent(val.transform);
			val3.transform.localPosition = new Vector3(0f, 1.6f, 0f);
			((EnemyAI)koishiAI).eye = val3.transform;
			GameObject val4 = new GameObject("CreatureSFX");
			val4.transform.SetParent(val.transform);
			AudioSource val5 = val4.AddComponent<AudioSource>();
			val5.spatialBlend = 1f;
			val5.maxDistance = 30f;
			val5.rolloffMode = (AudioRolloffMode)1;
			((EnemyAI)koishiAI).creatureSFX = val5;
			GameObject val6 = new GameObject("AnimContainer");
			val6.transform.SetParent(val.transform);
			((EnemyAI)koishiAI).creatureAnimator = val6.AddComponent<Animator>();
			((EnemyAI)koishiAI).skinnedMeshRenderers = (SkinnedMeshRenderer[])(object)new SkinnedMeshRenderer[0];
			val.AddComponent<NetworkObject>();
			val.SetActive(true);
			return val;
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Awake")]
	internal class StartOfRoundPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(200)]
		private static void SetKoishiRarityPerLevel(StartOfRound __instance)
		{
			if ((Object)(object)Plugin.KoishiEnemyType == (Object)null)
			{
				return;
			}
			SelectableLevel[] levels = __instance.levels;
			foreach (SelectableLevel val in levels)
			{
				foreach (SpawnableEnemyWithRarity enemy in val.Enemies)
				{
					if ((Object)(object)enemy.enemyType == (Object)(object)Plugin.KoishiEnemyType)
					{
						int num = (enemy.rarity = Plugin.GetRarityForRiskLevel(val.riskLevel));
						Plugin.Log.LogInfo((object)$"Koishi rarity on {val.PlanetName} (risk={val.riskLevel}): {num}");
						break;
					}
				}
			}
		}
	}
	internal static class PluginInfo
	{
		public const string GUID = "com.suwako.KoishiEnemy";

		public const string NAME = "Koishi Enemy";

		public const string VERSION = "1.1.0";
	}
}