Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of KoishiEnemy v1.1.0
plugins/KoishiEnemy.dll
Decompiled 7 months agousing 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"; } }