Please disclose if your mod was created primarily 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 Komeiji Koishi v1.0.2
plugins/KoishiEnemy.dll
Decompiled 2 weeks agousing 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 { } }