Decompiled source of Komeiji Koishi v1.0.2

plugins/KoishiEnemy.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
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 KoishiEnemy.NetcodePatcher;
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)]
[module: NetcodePatchedAssembly]
internal class <Module>
{
	static <Module>()
	{
	}
}
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;

		public AudioClip killVoice;

		public AudioClip missVoice;

		private Vector3 lungeDirection;

		private Vector3 lungeTarget;

		private Vector3 lungeTargetPlayerPos;

		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 Random koishiRandom;

		private float retargetCooldown;

		private Material koishiMaterial;

		private const float LUNGE_FADE_IN = 0.5f;

		private const float LUNGE_FADE_OUT = 0.5f;

		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;

		private float doorCheckTimer;

		private AudioSource walkieGhostSource;

		private int walkieGhostOwnerId = -1;

		public override void Start()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//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)
			((EnemyAI)this).Start();
			if (base.enemyBehaviourStates == null || base.enemyBehaviourStates.Length < 5)
			{
				EnemyBehaviourState[] enemyBehaviourStates = base.enemyBehaviourStates;
				base.enemyBehaviourStates = (EnemyBehaviourState[])(object)new EnemyBehaviourState[5];
				if (enemyBehaviourStates != null)
				{
					Array.Copy(enemyBehaviourStates, base.enemyBehaviourStates, Mathf.Min(enemyBehaviourStates.Length, 5));
				}
				for (int i = 0; i < 5; i++)
				{
					if (base.enemyBehaviourStates[i] == null)
					{
						base.enemyBehaviourStates[i] = new EnemyBehaviourState();
					}
				}
			}
			base.openDoorSpeedMultiplier = 2f;
			int num = LayerMask.NameToLayer("Enemies");
			if (num >= 0)
			{
				((Component)this).gameObject.layer = num;
				Plugin.Log.LogInfo((object)$"Koishi: set layer to Enemies ({num})");
			}
			else
			{
				Plugin.Log.LogWarning((object)"Koishi: Enemies layer not found!");
			}
			Rigidbody val = ((Component)this).GetComponent<Rigidbody>();
			if ((Object)(object)val == (Object)null)
			{
				val = ((Component)this).gameObject.AddComponent<Rigidbody>();
				Plugin.Log.LogInfo((object)"Koishi: added Rigidbody for door interaction");
			}
			val.isKinematic = true;
			val.useGravity = false;
			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();
			if (base.skinnedMeshRenderers != null && base.skinnedMeshRenderers.Length != 0)
			{
				koishiMaterial = ((Renderer)base.skinnedMeshRenderers[0]).material;
				for (int j = 1; j < base.skinnedMeshRenderers.Length; j++)
				{
					((Renderer)base.skinnedMeshRenderers[j]).sharedMaterial = koishiMaterial;
				}
				if (base.meshRenderers != null)
				{
					MeshRenderer[] meshRenderers = base.meshRenderers;
					for (int k = 0; k < meshRenderers.Length; k++)
					{
						((Renderer)meshRenderers[k]).sharedMaterial = koishiMaterial;
					}
				}
				koishiMaterial.SetFloat("_AlphaCutoff", 1f);
			}
			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, PlayerControllerB player = null)
		{
			float num = Mathf.Clamp01((float)fear / 180f);
			float num2 = 0.85f * num * num;
			if ((Object)(object)player != (Object)null)
			{
				for (int i = 0; i < player.ItemSlots.Length; i++)
				{
					GrabbableObject val = player.ItemSlots[i];
					if ((Object)(object)val != (Object)null && val is WalkieTalkie)
					{
						num2 += 0.2f;
						break;
					}
				}
			}
			return Mathf.Min(1f, num2);
		}

		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 || !val.isInsideFactory)
				{
					array[i] = 0;
					continue;
				}
				array[i] = CalculateFear(val);
				flag = true;
			}
			if (!flag)
			{
				base.targetPlayer = null;
				Plugin.Log.LogInfo((object)"Koishi: no indoor players to haunt");
			}
			else
			{
				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);
			PickRandomFarNode();
		}

		private void PickRandomFarNode()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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)
			if (base.allAINodes != null && base.allAINodes.Length != 0)
			{
				List<GameObject> list = new List<GameObject>(base.allAINodes);
				Vector3 myPos = ((Component)this).transform.position;
				list.Sort((GameObject a, GameObject b) => Vector3.Distance(b.transform.position, myPos).CompareTo(Vector3.Distance(a.transform.position, myPos)));
				int num = Mathf.Max(1, list.Count / 2);
				GameObject val = list[Random.Range(0, num)];
				((EnemyAI)this).SetDestinationToPosition(val.transform.position, false);
				Plugin.Log.LogInfo((object)("Koishi: roaming to far node " + ((Object)val).name));
			}
		}

		private void DoHaunting()
		{
			retargetCooldown -= Time.deltaTime;
			if (retargetCooldown <= 0f)
			{
				retargetCooldown = 5f;
				ChoosePlayerToHaunt();
			}
			base.agent.speed = 7f;
			if (!base.agent.pathPending && base.agent.remainingDistance < 1.5f)
			{
				PickRandomFarNode();
			}
		}

		private void UpdateHaunting()
		{
			hauntElapsed += Time.deltaTime;
			hauntCheckTimer -= Time.deltaTime;
			if (!(hauntCheckTimer <= 0f))
			{
				return;
			}
			hauntCheckTimer = 1f;
			ChoosePlayerToHaunt();
			if (!((Object)(object)base.targetPlayer == (Object)null))
			{
				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, base.targetPlayer);
					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, base.targetPlayer);
			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;
			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)
				{
					PlaySoundForTarget(val, 0.3f);
				}
				break;
			}
			case 1:
				alertPhaseTimer = (((Object)(object)chaseVoice != (Object)null) ? chaseVoice.length : 3f);
				if ((Object)(object)chaseVoice != (Object)null)
				{
					PlaySoundForTarget(chaseVoice, 0.8f);
				}
				break;
			}
		}

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

		private void UpdateAlert()
		{
			alertPhaseTimer -= Time.deltaTime;
			if (!(alertPhaseTimer > 0f))
			{
				if (alertPhase < GetMaxAlertPhase())
				{
					PlayAlertPhase(alertPhase + 1);
					return;
				}
				if (alertIsReal && (Object)(object)base.targetPlayer != (Object)null && !base.targetPlayer.isPlayerDead && base.targetPlayer.isInsideFactory)
				{
					SwitchToState(State.Chasing);
					return;
				}
				Plugin.Log.LogInfo((object)"Koishi: false alarm or target left facility, resuming haunt");
				hauntElapsed = 0f;
				SwitchToState(State.Haunting);
			}
		}

		private void EnterChasing()
		{
			hasHitPlayer = false;
			stuckTimer = 0f;
			SetInvisible();
			SetColliderEnabled(enabled: true);
			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 from current position!");
		}

		private void DoChasing()
		{
			if ((Object)(object)base.targetPlayer == (Object)null || base.targetPlayer.isPlayerDead)
			{
				StopChargeAudio();
				SwitchToState(State.Retarget);
			}
			else if (!base.targetPlayer.isInsideFactory)
			{
				Plugin.Log.LogInfo((object)"Koishi: target left facility, giving up chase");
				StopChargeAudio();
				SwitchToState(State.Retarget);
			}
			else
			{
				((EnemyAI)this).SetMovingTowardsTargetPlayer(base.targetPlayer);
			}
		}

		private void UpdateChasing()
		{
			//IL_0021: 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)
			//IL_006f: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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)
			if ((Object)(object)base.targetPlayer == (Object)null)
			{
				return;
			}
			CheckStuck();
			TryOpenNearbyDoors();
			float num = Vector3.Distance(((Component)this).transform.position, ((Component)base.targetPlayer).transform.position);
			if (num <= 10f)
			{
				float remainingDistance = base.agent.remainingDistance;
				float num2 = remainingDistance / Mathf.Max(num, 0.1f);
				bool num3 = num2 > 2f;
				Vector3 val = ((Component)this).transform.position + Vector3.up * 0.5f;
				Vector3 position = ((Component)base.targetPlayer.gameplayCamera).transform.position;
				bool flag = Physics.Linecast(val, position, StartOfRound.Instance.collidersAndRoomMaskAndDefault);
				if (!num3 && !flag)
				{
					StopChargeAudio();
					Plugin.Log.LogInfo((object)$"Koishi: LOS clear → lunge at dist={num:F1}m (path={remainingDistance:F1}m, ratio={num2:F2})");
					SwitchToState(State.Lunge);
				}
				else if (num <= 5f)
				{
					Plugin.Log.LogInfo((object)$"Koishi: blocked at dist={num:F1}m (pathRatio={num2:F2}, ray={flag})");
				}
			}
		}

		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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			lungeTimer = 1f;
			hasHitPlayer = false;
			SetRenderersEnabled(enabled: true);
			if ((Object)(object)koishiMaterial != (Object)null)
			{
				koishiMaterial.SetFloat("_AlphaCutoff", 1f);
			}
			SetColliderEnabled(enabled: true);
			lungeTargetPlayerPos = ((Component)base.targetPlayer).transform.position;
			Vector3 val = lungeTargetPlayerPos - ((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_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: 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_008d: 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_009d: 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_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: 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_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: 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_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: 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_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			lungeTimer -= Time.deltaTime;
			float num = 1f - lungeTimer;
			if ((Object)(object)koishiMaterial != (Object)null)
			{
				float num2 = ((!(num < 0.5f)) ? ((num - 0.5f) / 0.5f) : (1f - num / 0.5f));
				koishiMaterial.SetFloat("_AlphaCutoff", Mathf.Clamp01(num2));
			}
			float num3 = 25f * Time.deltaTime;
			Transform transform = ((Component)this).transform;
			transform.position += lungeDirection * num3;
			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))
			{
				return;
			}
			base.agent.updatePosition = true;
			Vector3 navMeshPosition = RoundManager.Instance.GetNavMeshPosition(((Component)this).transform.position, default(NavMeshHit), 10f, -1);
			if (!base.agent.Warp(navMeshPosition))
			{
				Plugin.Log.LogWarning((object)"Koishi: failed to warp to NavMesh, using nearest AI node");
				float num4 = float.MaxValue;
				Vector3 position = ((Component)this).transform.position;
				if (base.allAINodes != null)
				{
					GameObject[] allAINodes = base.allAINodes;
					foreach (GameObject val in allAINodes)
					{
						if (!((Object)(object)val == (Object)null))
						{
							float num5 = Vector3.Distance(val.transform.position, ((Component)this).transform.position);
							if (num5 < num4)
							{
								num4 = num5;
								position = val.transform.position;
							}
						}
					}
				}
				base.agent.Warp(position);
			}
			base.agent.isStopped = false;
			bool flag = (Object)(object)base.targetPlayer != (Object)null && base.targetPlayer.isPlayerDead;
			Plugin.Log.LogInfo((object)$"Koishi: lunge finished. hasHitPlayer={hasHitPlayer}, targetDead={flag}");
			if (flag)
			{
				if ((Object)(object)killVoice != (Object)null)
				{
					AudioSource.PlayClipAtPoint(killVoice, lungeTargetPlayerPos, 1f);
					Plugin.Log.LogInfo((object)"Koishi: target confirmed dead, playing kill voice");
				}
				else
				{
					Plugin.Log.LogWarning((object)"Koishi: target dead but killVoice is null!");
				}
			}
			else if ((Object)(object)missVoice != (Object)null)
			{
				AudioSource.PlayClipAtPoint(missVoice, lungeTargetPlayerPos, 1f);
				Plugin.Log.LogInfo((object)"Koishi: target alive, playing miss voice");
			}
			else
			{
				Plugin.Log.LogWarning((object)"Koishi: missed but missVoice is null!");
			}
			EndLungeDither();
			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();
				WarpAwayFromPlayer(20f);
				SwitchToState(State.Haunting);
			}
		}

		private void WarpAwayFromPlayer(float minDist)
		{
			//IL_002c: 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)
			//IL_005d: 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_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: 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_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: 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_014e: 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_00a5: 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_0100: 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)
			//IL_00d1: 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_00ef: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)base.targetPlayer == (Object)null || base.allAINodes == null || base.allAINodes.Length == 0)
			{
				return;
			}
			Vector3 position = ((Component)base.targetPlayer).transform.position;
			List<Vector3> list = new List<Vector3>();
			GameObject[] allAINodes = base.allAINodes;
			foreach (GameObject val in allAINodes)
			{
				if (!((Object)(object)val == (Object)null) && Vector3.Distance(val.transform.position, position) >= minDist)
				{
					list.Add(val.transform.position);
				}
			}
			if (list.Count == 0)
			{
				float num = 0f;
				Vector3 position2 = ((Component)this).transform.position;
				allAINodes = base.allAINodes;
				foreach (GameObject val2 in allAINodes)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						float num2 = Vector3.Distance(val2.transform.position, position);
						if (num2 > num)
						{
							num = num2;
							position2 = val2.transform.position;
						}
					}
				}
				list.Add(position2);
			}
			Vector3 val3 = list[Random.Range(0, list.Count)];
			Vector3 navMeshPosition = RoundManager.Instance.GetNavMeshPosition(val3, default(NavMeshHit), 5f, -1);
			base.agent.Warp(navMeshPosition);
			Plugin.Log.LogInfo((object)$"Koishi: retarget warped {Vector3.Distance(navMeshPosition, position):F0}m from player");
		}

		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_0027: 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_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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_0085: 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_0099: 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_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: 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_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)base.targetPlayer == (Object)null || base.allAINodes == null || base.allAINodes.Length == 0)
			{
				return;
			}
			float num = Vector3.Distance(((Component)this).transform.position, ((Component)base.targetPlayer).transform.position);
			Vector3 val = Vector3.zero;
			float num2 = float.MaxValue;
			GameObject[] allAINodes = base.allAINodes;
			foreach (GameObject val2 in allAINodes)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					Vector3 position = val2.transform.position;
					float num3 = Vector3.Distance(position, ((Component)base.targetPlayer).transform.position);
					float num4 = Vector3.Distance(position, ((Component)this).transform.position);
					if (num3 < num && num4 > 3f && num3 > 20f && num3 < num2)
					{
						num2 = num3;
						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={num2:F0}m");
			}
		}

		private void SetRenderersEnabled(bool enabled)
		{
			bool enabled2 = enabled;
			if (enabled)
			{
				enabled2 = (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 = enabled2;
				}
			}
			if (base.meshRenderers != null)
			{
				MeshRenderer[] meshRenderers = base.meshRenderers;
				for (int i = 0; i < meshRenderers.Length; i++)
				{
					((Renderer)meshRenderers[i]).enabled = enabled2;
				}
			}
		}

		private void SetVisible()
		{
			SetRenderersEnabled(enabled: true);
			if ((Object)(object)koishiMaterial != (Object)null)
			{
				koishiMaterial.SetFloat("_AlphaCutoff", 0f);
			}
		}

		private void SetInvisible()
		{
			if ((Object)(object)koishiMaterial != (Object)null)
			{
				koishiMaterial.SetFloat("_AlphaCutoff", 1f);
			}
			SetRenderersEnabled(enabled: false);
		}

		private void EndLungeDither()
		{
			if ((Object)(object)koishiMaterial != (Object)null)
			{
				koishiMaterial.SetFloat("_AlphaCutoff", 1f);
			}
			SetRenderersEnabled(enabled: false);
		}

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

		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 TryOpenNearbyDoors()
		{
			//IL_0031: 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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: 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_0055: 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_0070: 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)
			doorCheckTimer -= Time.deltaTime;
			if (doorCheckTimer > 0f)
			{
				return;
			}
			doorCheckTimer = 0.25f;
			RaycastHit[] array = Physics.RaycastAll(new Ray(((Component)this).transform.position + Vector3.up * 0.5f, ((Component)this).transform.forward), 4f);
			for (int i = 0; i < array.Length; i++)
			{
				RaycastHit val = array[i];
				if (TryOpenDoor(((RaycastHit)(ref val)).collider, "ahead"))
				{
					return;
				}
			}
			Collider[] array2 = Physics.OverlapSphere(((Component)this).transform.position, 2f);
			foreach (Collider col in array2)
			{
				if (TryOpenDoor(col, "nearby"))
				{
					break;
				}
			}
		}

		private bool TryOpenDoor(Collider col, string label)
		{
			DoorLock val = ((Component)col).GetComponent<DoorLock>() ?? ((Component)col).GetComponentInParent<DoorLock>() ?? ((Component)col).GetComponentInChildren<DoorLock>();
			if ((Object)(object)val == (Object)null || val.isDoorOpened)
			{
				return false;
			}
			if (val.isLocked)
			{
				val.UnlockDoorSyncWithServer();
			}
			val.OpenDoorAsEnemyServerRpc();
			AnimatedObjectTrigger val2 = ((Component)val).GetComponentInChildren<AnimatedObjectTrigger>();
			if ((Object)(object)val2 == (Object)null)
			{
				val2 = ((Component)val).GetComponentInParent<AnimatedObjectTrigger>();
			}
			if ((Object)(object)val2 != (Object)null)
			{
				val2.TriggerAnimationNonPlayer(true, true, false);
				Plugin.Log.LogInfo((object)("Koishi: opened " + label + " door (AnimatedObjectTrigger)"));
			}
			else
			{
				Plugin.Log.LogInfo((object)("Koishi: opened " + label + " door (ServerRpc only, no AnimTrigger found)"));
			}
			val.isDoorOpened = true;
			return true;
		}

		private void PlaySoundForTarget(AudioClip clip, float volume)
		{
			if (!((Object)(object)clip == (Object)null) && (Object)(object)base.targetPlayer != (Object)null && (Object)(object)base.targetPlayer == (Object)(object)GameNetworkManager.Instance.localPlayerController)
			{
				GrabbableObject walkieItem = GetWalkieItem(base.targetPlayer);
				if ((Object)(object)walkieItem != (Object)null)
				{
					GetOrCreateGhostSource(walkieItem).PlayOneShot(clip, volume);
					Plugin.Log.LogInfo((object)("Koishi: playing " + ((Object)clip).name + " through ghost walkie source"));
				}
				else
				{
					HUDManager.Instance.UIAudio.PlayOneShot(clip, volume);
				}
			}
		}

		private GrabbableObject GetWalkieItem(PlayerControllerB player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			for (int i = 0; i < player.ItemSlots.Length; i++)
			{
				GrabbableObject val = player.ItemSlots[i];
				if ((Object)(object)val != (Object)null && val is WalkieTalkie)
				{
					return val;
				}
			}
			return null;
		}

		private AudioSource GetOrCreateGhostSource(GrabbableObject walkie)
		{
			int instanceID = ((Object)walkie).GetInstanceID();
			if ((Object)(object)walkieGhostSource != (Object)null && walkieGhostOwnerId == instanceID)
			{
				return walkieGhostSource;
			}
			AudioSource val = ((Component)walkie).gameObject.AddComponent<AudioSource>();
			val.spatialBlend = 1f;
			val.maxDistance = 15f;
			val.rolloffMode = (AudioRolloffMode)1;
			val.minDistance = 1f;
			val.playOnAwake = false;
			walkieGhostSource = val;
			walkieGhostOwnerId = instanceID;
			return val;
		}

		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");
			}
			killVoice = LoadOggFromFile(Path.Combine(path, "kill_voice.ogg"), "KoishiKillVoice");
			missVoice = LoadOggFromFile(Path.Combine(path, "miss_voice.ogg"), "KoishiMissVoice");
			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");
			}
			if ((Object)(object)killVoice != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded kill voice: {killVoice.length:F2}s");
			}
			if ((Object)(object)missVoice != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Loaded miss voice: {missVoice.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;
		}

		protected override void __initializeVariables()
		{
			((EnemyAI)this).__initializeVariables();
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		protected internal override string __getTypeName()
		{
			return "KoishiAI";
		}
	}
	[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.");
			InitializeNetworkBehaviours();
			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)
			{
				KoishiEnemyType.numberSpawned = 0;
				if (KoishiEnemyType.spawnInGroupsOf <= 0)
				{
					KoishiEnemyType.spawnInGroupsOf = 1;
					Log.LogInfo((object)"Koishi: fixed spawnInGroupsOf from 0 to 1");
				}
				Log.LogInfo((object)$"Koishi EnemyType: name={KoishiEnemyType.enemyName}, MaxCount={KoishiEnemyType.MaxCount}, numberSpawned={KoishiEnemyType.numberSpawned}, isOutside={KoishiEnemyType.isOutsideEnemy}, PowerLevel={KoishiEnemyType.PowerLevel}");
				NetworkObject component = KoishiPrefab.GetComponent<NetworkObject>();
				if ((Object)(object)component != (Object)null)
				{
					NetworkManager singleton = NetworkManager.Singleton;
					if (singleton != null)
					{
						singleton.AddNetworkPrefab(KoishiPrefab);
					}
					Log.LogInfo((object)$"Koishi NetworkPrefab manually registered (hash={component.PrefabIdHash})");
				}
				Enemies.RegisterEnemy(KoishiEnemyType, 10, (LevelTypes)(-1), (SpawnType)0, (TerminalNode)null, (TerminalKeyword)null);
				Log.LogInfo((object)"Koishi registered with base rarity 10. Per-level rarity set via Harmony patch.");
			}
			else
			{
				Log.LogError((object)"EnemyType not found on prefab!");
			}
			NetworkPrefabs.RegisterNetworkPrefab(KoishiPrefab);
			Log.LogInfo((object)"Koishi prefab registered via LethalLib NetworkPrefabs.");
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "com.suwako.KoishiEnemy");
			Log.LogInfo((object)"Koishi Enemy v1.1.0 loaded!");
		}

		private static void InitializeNetworkBehaviours()
		{
			int num = 0;
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			foreach (Type type in types)
			{
				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), inherit: false).Length != 0)
					{
						Log.LogInfo((object)("[NetcodeInit] Invoking " + type.Name + "." + methodInfo.Name));
						methodInfo.Invoke(null, null);
						num++;
					}
				}
			}
			Log.LogInfo((object)$"[NetcodeInit] Total methods invoked: {num}");
		}

		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), "Start")]
	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;
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(RoundManager))]
	internal class SpawnDebugPatch
	{
		[HarmonyPatch("AdvanceHourAndSpawnNewBatchOfEnemies")]
		[HarmonyPostfix]
		private static void AfterAdvanceHour(RoundManager __instance)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Plugin.KoishiEnemyType == (Object)null)
			{
				return;
			}
			Plugin.Log.LogInfo((object)$"[SpawnDebug] AdvanceHour: currentEnemyPower={__instance.currentEnemyPower}, Koishi numberSpawned={Plugin.KoishiEnemyType.numberSpawned}");
			KoishiAI[] array = Object.FindObjectsOfType<KoishiAI>();
			Plugin.Log.LogInfo((object)$"[SpawnDebug] KoishiAI instances in scene: {array.Length}");
			EnemyVent[] array2 = Object.FindObjectsOfType<EnemyVent>();
			int num = 0;
			EnemyVent[] array3 = array2;
			foreach (EnemyVent val in array3)
			{
				if ((Object)(object)val.enemyType == (Object)(object)Plugin.KoishiEnemyType)
				{
					num++;
					Plugin.Log.LogInfo((object)$"[SpawnDebug] Vent has Koishi! occupied={val.occupied}, pos={((Component)val).transform.position}");
				}
			}
			Plugin.Log.LogInfo((object)$"[SpawnDebug] Total vents: {array2.Length}, vents with Koishi: {num}");
			SelectableLevel currentLevel = __instance.currentLevel;
			if (!((Object)(object)currentLevel != (Object)null))
			{
				return;
			}
			bool flag = false;
			Plugin.Log.LogInfo((object)$"[SpawnDebug] Current level: {currentLevel.PlanetName}, Enemies count: {currentLevel.Enemies.Count}");
			foreach (SpawnableEnemyWithRarity enemy in currentLevel.Enemies)
			{
				if ((Object)(object)enemy.enemyType == (Object)(object)Plugin.KoishiEnemyType)
				{
					flag = true;
					Plugin.Log.LogInfo((object)$"[SpawnDebug] >>> KOISHI IN POOL: rarity={enemy.rarity}, ref match={(Object)(object)enemy.enemyType == (Object)(object)Plugin.KoishiEnemyType}");
				}
			}
			if (flag)
			{
				return;
			}
			Plugin.Log.LogWarning((object)"[SpawnDebug] !!! KOISHI NOT IN currentLevel.Enemies !!!");
			foreach (SpawnableEnemyWithRarity enemy2 in currentLevel.Enemies)
			{
				if ((Object)(object)enemy2.enemyType != (Object)null && enemy2.enemyType.enemyName == "Koishi")
				{
					Plugin.Log.LogWarning((object)$"[SpawnDebug] Found Koishi by name but different EnemyType reference! rarity={enemy2.rarity}");
				}
			}
			List<SpawnableEnemyWithRarity> list = new List<SpawnableEnemyWithRarity>(currentLevel.Enemies);
			list.Sort((SpawnableEnemyWithRarity a, SpawnableEnemyWithRarity b) => b.rarity.CompareTo(a.rarity));
			for (int j = 0; j < Math.Min(5, list.Count); j++)
			{
				SpawnableEnemyWithRarity val2 = list[j];
				Plugin.Log.LogInfo((object)string.Format("[SpawnDebug] Top enemy #{0}: {1} rarity={2}", j + 1, val2.enemyType?.enemyName ?? "NULL", val2.rarity));
			}
		}
	}
	[HarmonyPatch(typeof(RoundManager), "SpawnEnemyFromVent")]
	internal class VentSpawnDebugPatch
	{
		[HarmonyPrefix]
		private static void BeforeVentSpawn(EnemyVent vent)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)vent == (Object)null)
			{
				return;
			}
			EnemyType enemyType = vent.enemyType;
			if ((Object)(object)enemyType != (Object)null)
			{
				string text = (((Object)(object)enemyType == (Object)(object)Plugin.KoishiEnemyType) ? " <<<KOISHI>>>" : "");
				Plugin.Log.LogInfo((object)string.Format("[SpawnDebug] SpawnEnemyFromVent: {0}, prefab={1}, ventPos={2}{3}", enemyType.enemyName, ((Object)(object)enemyType.enemyPrefab != (Object)null) ? ((Object)enemyType.enemyPrefab).name : "NULL", ((Component)vent).transform.position, text));
				if ((Object)(object)enemyType == (Object)(object)Plugin.KoishiEnemyType && (Object)(object)enemyType.enemyPrefab != (Object)null)
				{
					NetworkObject component = enemyType.enemyPrefab.GetComponent<NetworkObject>();
					Plugin.Log.LogInfo((object)("[SpawnDebug] Koishi prefab NetworkObject: " + (((Object)(object)component != (Object)null) ? $"hash={component.PrefabIdHash}" : "MISSING")));
					Plugin.Log.LogInfo((object)$"[SpawnDebug] Koishi prefab active: {enemyType.enemyPrefab.activeSelf}, components: {enemyType.enemyPrefab.GetComponents<Component>().Length}");
				}
			}
		}

		[HarmonyPostfix]
		private static void AfterVentSpawn(EnemyVent vent)
		{
			if ((Object)(object)vent?.enemyType == (Object)(object)Plugin.KoishiEnemyType)
			{
				KoishiAI[] array = Object.FindObjectsOfType<KoishiAI>();
				Plugin.Log.LogInfo((object)$"[SpawnDebug] After Koishi vent spawn: KoishiAI instances={array.Length}");
			}
		}
	}
	internal static class PluginInfo
	{
		public const string GUID = "com.suwako.KoishiEnemy";

		public const string NAME = "Koishi Enemy";

		public const string VERSION = "1.1.0";
	}
}
namespace KoishiEnemy.NetcodePatcher
{
	[AttributeUsage(AttributeTargets.Module)]
	internal class NetcodePatchedAssemblyAttribute : Attribute
	{
	}
}