using System.Collections.Generic;
using MegaKoop.Game.Combat;
using MegaKoop.Game.Networking;
using MegaKoop.Game.WeaponSystem;
using UnityEngine;
using UnityEngine.AI;
namespace MegaKoop.Game.Enemy
{
///
/// Host-driven enemy brain that chases the closest hero, fires using the shared weapon system,
/// and mirrors movement state to peers through SteamCoopNetworkManager.
///
[DisallowMultipleComponent]
[RequireComponent(typeof(NetworkIdentity))]
[RequireComponent(typeof(Health))]
[RequireComponent(typeof(SteamNetworkTransform))]
[RequireComponent(typeof(WeaponController))]
public class SteamEnemyController : MonoBehaviour
{
private static readonly List SharedHealthBuffer = new(32);
[Header("Movement")]
[SerializeField] private float moveSpeed = 3.5f;
[SerializeField] private float turnSpeed = 6f;
[SerializeField] private float stoppingDistance = 4f;
[SerializeField] private float detectionRadius = 30f;
[SerializeField] private float leashDistance = 60f;
[SerializeField] private float retargetInterval = 0.4f;
[SerializeField] private float repathThreshold = 1.5f;
[SerializeField] private bool returnToSpawnWhenIdle = true;
[Header("References")]
[SerializeField] private NavMeshAgent navMeshAgent;
[SerializeField] private WeaponController weaponController;
[SerializeField] private Health health;
[SerializeField] private NetworkIdentity identity;
[SerializeField] private SteamNetworkTransform networkTransform;
private SteamCoopNetworkManager networkManager;
private Transform currentTargetTransform;
private NetworkIdentity currentTargetIdentity;
private Health currentTargetHealth;
private Vector3 lastTargetPosition;
private Vector3 lastKnownTargetPosition;
private float retargetTimer;
private Vector3 spawnPosition;
private bool cachedNavAgentState;
private void Awake()
{
spawnPosition = transform.position;
if (navMeshAgent == null)
{
navMeshAgent = GetComponent();
}
if (weaponController == null)
{
weaponController = GetComponent();
}
if (health == null)
{
health = GetComponent();
}
if (identity == null)
{
identity = GetComponent();
}
if (networkTransform == null)
{
networkTransform = GetComponent();
}
if (navMeshAgent != null)
{
navMeshAgent.updateRotation = false;
navMeshAgent.stoppingDistance = stoppingDistance;
navMeshAgent.speed = moveSpeed;
cachedNavAgentState = navMeshAgent.enabled;
}
lastTargetPosition = Vector3.positiveInfinity;
lastKnownTargetPosition = spawnPosition;
retargetTimer = Random.Range(0f, Mathf.Max(0.05f, retargetInterval));
}
private void OnEnable()
{
networkManager = SteamCoopNetworkManager.Instance;
SyncNavMeshAgentState(ShouldSimulate());
}
private void Update()
{
networkManager ??= SteamCoopNetworkManager.Instance;
bool simulate = ShouldSimulate();
SyncNavMeshAgentState(simulate);
if (weaponController != null)
{
weaponController.enabled = simulate;
}
if (!simulate)
{
return;
}
if (health != null && !health.IsAlive)
{
StopAgentMovement();
return;
}
retargetTimer -= Time.deltaTime;
if (!IsTargetValid() || retargetTimer <= 0f)
{
AcquireTarget();
}
if (!TryGetTargetPosition(out _))
{
HandleIdle();
return;
}
TickMovement(Time.deltaTime);
FaceTarget(Time.deltaTime);
}
private bool ShouldSimulate()
{
if (networkManager == null)
{
return true;
}
if (!networkManager.IsConnected)
{
return true;
}
return networkManager.IsHost;
}
private void SyncNavMeshAgentState(bool simulate)
{
if (navMeshAgent == null)
{
return;
}
if (cachedNavAgentState == simulate)
{
return;
}
navMeshAgent.enabled = simulate;
cachedNavAgentState = simulate;
}
private bool IsTargetValid()
{
if (currentTargetTransform == null && currentTargetIdentity == null && currentTargetHealth == null)
{
return false;
}
if (currentTargetHealth != null && !currentTargetHealth.IsAlive)
{
return false;
}
if (!TryGetTargetPosition(out Vector3 targetPosition))
{
return false;
}
float sqrDistance = (targetPosition - transform.position).sqrMagnitude;
if (detectionRadius > 0f && sqrDistance > detectionRadius * detectionRadius)
{
return false;
}
if (leashDistance > 0f)
{
float sqrLeash = (targetPosition - spawnPosition).sqrMagnitude;
if (sqrLeash > leashDistance * leashDistance)
{
return false;
}
}
return true;
}
private void AcquireTarget()
{
retargetTimer = retargetInterval;
ClearTarget();
float bestScore = float.MaxValue;
SharedHealthBuffer.Clear();
SharedHealthBuffer.AddRange(FindObjectsOfType(false));
foreach (Health candidate in SharedHealthBuffer)
{
if (candidate == null || candidate == health)
{
continue;
}
if (!candidate.IsAlive || candidate.Team != Team.Heroes)
{
continue;
}
Transform candidateTransform = candidate.transform;
NetworkIdentity candidateIdentity = candidate.GetComponent();
Vector3 candidatePosition;
if (candidateIdentity != null && SteamCharacterStateCache.TryGetState(candidateIdentity.NetworkId, out var state))
{
candidatePosition = state.Position;
}
else
{
candidatePosition = candidateTransform.position;
}
float sqrDistance = (candidatePosition - transform.position).sqrMagnitude;
if (detectionRadius > 0f && sqrDistance > detectionRadius * detectionRadius)
{
continue;
}
if (sqrDistance < bestScore)
{
bestScore = sqrDistance;
currentTargetTransform = candidateTransform;
currentTargetIdentity = candidateIdentity;
currentTargetHealth = candidate;
lastKnownTargetPosition = candidatePosition;
lastTargetPosition = Vector3.positiveInfinity;
}
}
if (currentTargetTransform == null && currentTargetIdentity == null)
{
lastTargetPosition = Vector3.positiveInfinity;
}
SharedHealthBuffer.Clear();
}
private void TickMovement(float deltaTime)
{
if (!TryGetTargetPosition(out Vector3 targetPosition))
{
ClearTarget();
return;
}
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
{
navMeshAgent.speed = moveSpeed;
navMeshAgent.stoppingDistance = stoppingDistance;
if (!navMeshAgent.hasPath || (targetPosition - lastTargetPosition).sqrMagnitude >= repathThreshold * repathThreshold)
{
navMeshAgent.SetDestination(targetPosition);
lastTargetPosition = targetPosition;
}
}
else
{
ManualMove(targetPosition, deltaTime);
lastTargetPosition = targetPosition;
}
}
private void ManualMove(Vector3 targetPosition, float deltaTime)
{
Vector3 currentPosition = transform.position;
Vector3 direction = targetPosition - currentPosition;
direction.y = 0f;
float distance = direction.magnitude;
if (distance <= Mathf.Max(0.1f, stoppingDistance))
{
return;
}
Vector3 normalized = direction / distance;
float step = moveSpeed * deltaTime;
Vector3 newPosition = currentPosition + normalized * Mathf.Min(step, distance - stoppingDistance);
transform.position = newPosition;
}
private void FaceTarget(float deltaTime)
{
if (!TryGetTargetPosition(out Vector3 targetPosition))
{
return;
}
Vector3 toTarget = targetPosition - transform.position;
toTarget.y = 0f;
if (toTarget.sqrMagnitude < 0.0001f)
{
return;
}
Quaternion desiredRotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, Mathf.Clamp01(turnSpeed * deltaTime));
}
private void HandleIdle()
{
StopAgentMovement();
if (!returnToSpawnWhenIdle)
{
return;
}
if ((transform.position - spawnPosition).sqrMagnitude <= 0.25f)
{
return;
}
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
{
navMeshAgent.SetDestination(spawnPosition);
}
else
{
ManualMove(spawnPosition, Time.deltaTime);
FacePoint(spawnPosition, Time.deltaTime);
}
}
private bool TryGetTargetPosition(out Vector3 position)
{
if (currentTargetIdentity != null && SteamCharacterStateCache.TryGetState(currentTargetIdentity.NetworkId, out var state))
{
position = state.Position;
lastKnownTargetPosition = position;
return true;
}
if (currentTargetTransform != null)
{
position = currentTargetTransform.position;
lastKnownTargetPosition = position;
return true;
}
if (currentTargetHealth != null)
{
position = currentTargetHealth.transform.position;
lastKnownTargetPosition = position;
return true;
}
if (lastKnownTargetPosition != Vector3.positiveInfinity)
{
position = lastKnownTargetPosition;
return true;
}
position = Vector3.zero;
return false;
}
private void ClearTarget()
{
currentTargetTransform = null;
currentTargetIdentity = null;
currentTargetHealth = null;
lastKnownTargetPosition = Vector3.positiveInfinity;
}
private void FacePoint(Vector3 point, float deltaTime)
{
Vector3 toPoint = point - transform.position;
toPoint.y = 0f;
if (toPoint.sqrMagnitude < 0.0001f)
{
return;
}
Quaternion desiredRotation = Quaternion.LookRotation(toPoint.normalized, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, Mathf.Clamp01(turnSpeed * deltaTime));
}
private void StopAgentMovement()
{
if (navMeshAgent != null && navMeshAgent.enabled)
{
navMeshAgent.ResetPath();
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1f, 0.25f, 0f, 0.2f);
Gizmos.DrawWireSphere(transform.position, detectionRadius);
if (leashDistance > 0f)
{
Gizmos.color = new Color(0.2f, 0.4f, 1f, 0.2f);
Vector3 leashCenter = Application.isPlaying ? spawnPosition : transform.position;
Gizmos.DrawWireSphere(leashCenter, leashDistance);
}
}
private void OnValidate()
{
moveSpeed = Mathf.Max(0f, moveSpeed);
turnSpeed = Mathf.Max(0f, turnSpeed);
stoppingDistance = Mathf.Max(0f, stoppingDistance);
detectionRadius = Mathf.Max(0f, detectionRadius);
leashDistance = Mathf.Max(0f, leashDistance);
retargetInterval = Mathf.Max(0.05f, retargetInterval);
repathThreshold = Mathf.Max(0.1f, repathThreshold);
}
}
}