LadyAliceMargatroid f37cded831 Added more radio music, based on dungeon variant.
Redid vent code to work better and like work in general.
Knight animation now pauses for a second.
Knights no longer trigger the default scarlet vent animation.
Revenants now spawn at a vent if no knights exist.
2025-01-15 20:41:48 -08:00

200 lines
6.3 KiB
C#

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;
using ScarletMansion.Configs;
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 KnightSpawnPoint spawnPoint;
public bool activated;
public override void Start(){
base.Start();
maxChaseSpeed = ConfigMain.Instance.revEnemyValue.speed;
if (IsOwner && KnightSpawnManager.Instance) {
var index = KnightSpawnManager.Instance.GetSpawnPointIndex();
if (index == -1) {
// grab position instead
var allVents = RoundManager.Instance.allEnemyVents;
var anyEnemyVent = allVents[UnityEngine.Random.Range(0, allVents.Length)];
SyncKnightAtPositionClientRpc(anyEnemyVent.floorNode.position, Quaternion.Euler(0f, anyEnemyVent.floorNode.eulerAngles.y, 0f));
return;
}
SyncKnightReplacementClientRpc(index);
}
}
public override void DoAIInterval() {
base.DoAIInterval();
movingTowardsTargetPlayer = true;
if (targetPlayer == null || !targetPlayer.IsOwner) return;
if (isEnemyDead) return;
if (timeSinceSpawn < 1f) return;
var targetOutOfMap = !PlayerIsTargetable(targetPlayer);
if (targetOutOfMap){
CallDisappear();
return;
}
// whoops i didn't copy from knight code properly lmaoz
var targetLookingAtMe = targetPlayer.HasLineOfSightToPosition(transform.position + Vector3.up * 1.6f, 68f, 60);
var newBehaviourState = targetLookingAtMe ? 1 : 0;
if (targetLookingAtMe) {
targetPlayer.JumpToFearLevel(0.75f, true);
}
if (newBehaviourState != currentBehaviourStateIndex) {
SwitchToBehaviourState(newBehaviourState);
}
}
public override void Update() {
base.Update();
if (isEnemyDead) return;
if (timeSinceSpawn < 1f) return;
if (!activated) {
activated = true;
creatureAnimator.SetBool("Activate", true);
}
var trueSpeed = currentBehaviourStateIndex == 0 ? maxChaseSpeed : slowChaseSpeed;
if (IsOwner) {
agent.speed = Mathf.MoveTowards(agent.speed, trueSpeed, (maxChaseSpeed - slowChaseSpeed) * 4f * 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);
if (timeSinceSpawn < 1f) return;
var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false);
if (playerControllerB != null && playerControllerB.IsOwner && playerControllerB == targetPlayer) {
var damage = ScarletNetworkManagerUtility.GetMarkedDamageToPlayer(playerControllerB);
playerControllerB.DamagePlayer(damage, true, true, CauseOfDeath.Mauling, 1, false, default(Vector3));
playerControllerB.JumpToFearLevel(1f, true);
ScarletNetworkManagerUtility.MarkPlayerForDeath(playerControllerB);
if (playerControllerB.isPlayerDead) {
Assets.onPlayerDeath.Call(new ModPatch.CoronerParameters(playerControllerB, ModPatch.CoronerDeathEnum.GhostKnight));
}
RoundManager.PlayRandomClip(creatureVoice, ramAudioClips, false, 1f, 1);
CallDisappear();
}
}
[ClientRpc]
public void SyncKnightReplacementClientRpc(int index){
Plugin.logger.LogDebug($"Spawning ghost knight at {index}");
try {
spawnPoint = KnightSpawnManager.Instance.GetSpawnPoint(index);
KnightSpawnManager.Instance.SpawnEnemyOnSpawnPoint(this, spawnPoint);
} catch (Exception e){
Plugin.logger.LogError($"Tried to ghost knight spawn at {index}, but completely failed");
Plugin.logger.LogError(e);
}
}
[ClientRpc]
public void SyncKnightAtPositionClientRpc(Vector3 position, Quaternion rotation){
Plugin.logger.LogDebug($"Spawning ghost knight at {position}");
try {
KnightSpawnManager.Instance.SpawnEnemyOnSpawnPoint(this, position, rotation);
} catch (Exception e){
Plugin.logger.LogError($"Tried to ghost knight spawn at {position}, 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<PlayerControllerB>(out var player)){
targetPlayer = player;
Plugin.logger.LogDebug($"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.LogDebug("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);
}
}
}