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";
}
}