using System.Collections; using UnityEngine; using MLAPI; namespace BossRoom.Server { /// /// Component responsible for spawning prefab clones in waves on the server. /// [RequireComponent(typeof(Collider))] public class ServerWaveSpawner : NetworkedBehaviour { [SerializeField] NetworkSpawnerState m_NetworkSpawnerState; // amount of hits it takes to break any spawner const int k_MaxHealth = 3; // networked object that will be spawned in waves [SerializeField] NetworkedObject m_NetworkedPrefab; // cache reference to our own transform Transform m_Transform; // track wave index and reset once all waves are complete int m_WaveIndex; // keep reference to our wave spawning coroutine Coroutine m_WaveSpawning; // cache array of RaycastHit as it will be reused for player visibility RaycastHit[] m_Hit; int m_PlayerLayerMask; // cache Collider array of OverlapSphere results for player proximity Collider[] m_Colliders; [Tooltip("Select which layers will block visibility.")] [SerializeField] LayerMask m_BlockingMask; [Tooltip("Time between player distance & visibility scans, in seconds.")] [SerializeField] float m_PlayerProximityValidationTimestep; [Header("Wave parameters")] [Tooltip("Total number of waves.")] [SerializeField] int m_NumberOfWaves; [Tooltip("Number of spawns per wave.")] [SerializeField] int m_SpawnsPerWave; [Tooltip("Time between individual spawns, in seconds.")] [SerializeField] float m_TimeBetweenSpawns; [Tooltip("Time between waves, in seconds.")] [SerializeField] float m_TimeBetweenWaves; [Tooltip("Once last wave is spawned, the spawner waits this long to restart wave spawns, in seconds.")] [SerializeField] float m_RestartDelay; [Tooltip("A player must be withing this distance to commence first wave spawn.")] [SerializeField] float m_ProximityDistance; [Tooltip("After being broken, the spawner waits this long to restart wave spawns, in seconds.")] [SerializeField] float m_DormantCooldown; void Awake() { m_Transform = transform; m_PlayerLayerMask = LayerMask.GetMask("PCs"); } public override void NetworkStart() { base.NetworkStart(); if (!IsServer) { enabled = false; return; } ReviveSpawner(); m_Hit = new RaycastHit[1]; m_Colliders = new Collider[8]; StartCoroutine(ValidatePlayersProximity(StartWaveSpawning)); } /// /// Coroutine for continually validating proximity to players and invoking an action when any is near. /// /// /// IEnumerator ValidatePlayersProximity(System.Action validationAction) { while (true) { if (m_NetworkSpawnerState.Broken.Value) { yield return new WaitForSeconds(m_DormantCooldown); ReviveSpawner(); } if (m_WaveSpawning == null) { if (IsAnyPlayerNearbyAndVisible()) { validationAction(); } } else { // do nothing, a wave spawning routine is currently underway } yield return new WaitForSeconds(m_PlayerProximityValidationTimestep); } } void StartWaveSpawning() { StopWaveSpawning(); m_WaveSpawning = StartCoroutine(SpawnWaves()); } void StopWaveSpawning() { if (m_WaveSpawning != null) { StopCoroutine(m_WaveSpawning); } m_WaveSpawning = null; } /// /// Coroutine for spawning prefabs clones in waves, waiting a duration before spawning a new wave. /// Once all waves are completed, it waits a restart time before termination. /// /// IEnumerator SpawnWaves() { m_WaveIndex = 0; while (m_WaveIndex < m_NumberOfWaves) { yield return SpawnWave(); yield return new WaitForSeconds(m_TimeBetweenWaves); } yield return new WaitForSeconds(m_RestartDelay); m_WaveSpawning = null; } /// /// Coroutine that spawns a wave of prefab clones, with some time between spawns. /// /// IEnumerator SpawnWave() { for (int i = 0; i < m_SpawnsPerWave; i++) { SpawnPrefab(); yield return new WaitForSeconds(m_TimeBetweenSpawns); } m_WaveIndex++; } /// /// Spawn a NetworkedObject prefab clone. /// void SpawnPrefab() { if (m_NetworkedPrefab == null) { throw new System.ArgumentNullException("m_NetworkedPrefab"); } // spawn clone right in front of spawner var spawnPosition = m_Transform.position + m_Transform.forward; var clone = Instantiate(m_NetworkedPrefab, spawnPosition, Quaternion.identity); if (!clone.IsSpawned) { clone.Spawn(); } } /// /// Determines whether any player is within range & visible through RaycastNonAlloc check. /// /// True if visible and within range, else false. bool IsAnyPlayerNearbyAndVisible() { var spawnerPosition = m_Transform.position; var ray = new Ray(); int hits = Physics.OverlapSphereNonAlloc(spawnerPosition, m_ProximityDistance, m_Colliders, m_PlayerLayerMask); if (hits == 0) { return false; } // iterate through players and only return true if a player is in range // and is not occluded by a blocking collider. foreach (var playerCollider in m_Colliders) { var playerPosition = playerCollider.transform.position; var direction = playerPosition - spawnerPosition; ray.origin = spawnerPosition; ray.direction = direction; var hit = Physics.RaycastNonAlloc(ray, m_Hit, Mathf.Min(direction.magnitude, m_ProximityDistance),m_BlockingMask); if (hit == 0) { return true; } } return false; } void ReviveSpawner() { m_NetworkSpawnerState.HitPoints.Value = k_MaxHealth; m_NetworkSpawnerState.Broken.Value = false; } // TODO: David will create interface hookup for receiving hits on non-NPC/PC objects (GOMPS-ID TBD) void ReceiveHP(ServerCharacter inflicter, int HP) { if (!IsServer) { return; } m_NetworkSpawnerState.HitPoints.Value += HP; if (m_NetworkSpawnerState.HitPoints.Value <= 0) { m_NetworkSpawnerState.Broken.Value = true; StopWaveSpawning(); } } } }