using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using Unity.Netcode; using UnityEngine.AI; using GameNetcodeStuff; using ScarletMansion.GamePatch.Components; namespace ScarletMansion { public class KnightGhostVariant : EnemyAI { public float maxChaseSpeed = 14.5f; public float slowChaseSpeed = 6f; private float currentAnimSpeed = 1f; public Collider mainCollider; public MeshRenderer[] knightMeshRenderers; public AudioClip[] ramAudioClips; public override void Start(){ base.Start(); if (IsOwner && KnightSpawnManager.Instance) { var index = KnightSpawnManager.Instance.GetSpawnPointIndex(); if (index == -1) return; SyncKnightReplacementClientRpc(index); } } public override void DoAIInterval() { base.DoAIInterval(); movingTowardsTargetPlayer = true; if (targetPlayer == null || !targetPlayer.IsOwner) return; if (isEnemyDead) return; var targetOutOfMap = !PlayerIsTargetable(targetPlayer); if (targetOutOfMap){ CallDisappear(); return; } var targetLookingAtMe = !Physics.Linecast(transform.position + Vector3.up * 0.5f, targetPlayer.gameplayCamera.transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault) && Vector3.Distance(base.transform.position, targetPlayer.transform.position) < 30f; var newBehaviourState = targetLookingAtMe ? 1 : 0; if (newBehaviourState != currentBehaviourStateIndex) { SwitchToBehaviourState(newBehaviourState); } } public override void Update() { base.Update(); if (isEnemyDead) return; var trueSpeed = currentBehaviourStateIndex == 0 ? maxChaseSpeed : slowChaseSpeed; if (IsOwner) { agent.speed = Mathf.MoveTowards(agent.speed, trueSpeed, 4.5f * Time.deltaTime); } currentAnimSpeed = Mathf.Lerp(currentAnimSpeed, trueSpeed * 0.4597f, 4f * Time.deltaTime); creatureAnimator.SetFloat("walkSpeed", currentAnimSpeed); } public override void OnCollideWithPlayer(Collider other) { base.OnCollideWithPlayer(other); var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false); if (playerControllerB != null && playerControllerB.IsOwner && playerControllerB == targetPlayer) { var damage = ScarletNetworkManagerUtility.GetCriticalDamageToPlayer(playerControllerB, false); playerControllerB.DamagePlayer(damage, true, true, CauseOfDeath.Mauling, 1, false, default(Vector3)); playerControllerB.JumpToFearLevel(1f, true); RoundManager.PlayRandomClip(creatureVoice, ramAudioClips, false, 1f, 0); CallDisappear(); } } [ClientRpc] public void SyncKnightReplacementClientRpc(int index){ Plugin.logger.LogInfo($"Spawning ghost knight at {index}"); try { var target = KnightSpawnManager.Instance.GetSpawnPointTransform(index); target.gameObject.SetActive(false); transform.position = target.position; transform.rotation = target.rotation; serverPosition = target.position; if (agent == null) agent = GetComponentInChildren(); agent.Warp(target.position); if (IsOwner) { SyncPositionToClients(); } } catch (Exception e){ Plugin.logger.LogError($"Tried to ghost knight spawn at {index}, but completely failed"); Plugin.logger.LogError(e); } } public void FindAndTunnelPlayer(Vector3 searchPosition){ var validPlayers = StartOfRound.Instance.allPlayerScripts.Where(p => p != null && PlayerIsTargetable(p)); if (validPlayers.Count() == 0) { Plugin.logger.LogWarning("Could not find valid target to tunnel"); return; } var closestPlayer = validPlayers .OrderBy(p => Vector3.SqrMagnitude(p.transform.position - searchPosition)) .FirstOrDefault(); if (closestPlayer != null) { ChangeOwnershipOfEnemy(closestPlayer.actualClientId); TunnelPlayerClientRpc(closestPlayer); } } [ClientRpc] public void TunnelPlayerClientRpc(NetworkBehaviourReference playerRef){ if (playerRef.TryGet(out var player)){ targetPlayer = player; Plugin.logger.LogInfo($"Targeting {player.playerUsername} for death"); if (targetPlayer.IsOwner) return; // other clients will only see eyes of death foreach(var m in knightMeshRenderers) { m.enabled = false; } } else { Plugin.logger.LogWarning("Could not find target player through reference"); } } private bool calledDisappear = false; public void CallDisappear(){ if (calledDisappear) return; Plugin.logger.LogInfo("Killing ghost knight"); calledDisappear = true; mainCollider.enabled = false; mainCollider.gameObject.SetActive(false); ScarletNetworkManager.Instance.CreateSpawnAudioPrefab(transform.position, targetPlayer.actualClientId); DisappearServerRpc(); } [ServerRpc(RequireOwnership = false)] public void DisappearServerRpc(){ KillEnemy(true); } } }