using GatherAndDefend.Events; using System.Threading.Tasks; using System.Collections.Generic; using UnityEngine; public class WaveObserver : Singleton { private const int MAXTOUGHNESS = 10; private List _subjects = new List(); private List _aliveEnemyCount = new List(); private List _groupSpawnTimers; private List _copyConstantSpawn; private List _intervalTiming = new List(); private List> _copyGroupSpawn; //Contains count of enemies per group private List _monsterCoreGroups; private WaveConfig _levelConfig; private int _spawnerTiming = 0; private int _currentGroupIndex = 0; private bool _once = true; public void Init(WaveConfig config) { _subjects.Clear(); _aliveEnemyCount.Clear(); _intervalTiming.Clear(); _levelConfig = Object.Instantiate(config); _copyConstantSpawn = new List(); _copyGroupSpawn = new List>(); _groupSpawnTimers = new List(); _spawnerTiming = 0; _currentGroupIndex = 0; _once = true; if (!_levelConfig) { Debug.LogError("level config was null"); return; } foreach (EnemyType enemy in _levelConfig.ConstantSpawn) { _copyConstantSpawn.Add(enemy.Count); } for (int index = 0; index < _levelConfig.NestedGroupSpawn.Count; index++) { _copyGroupSpawn.Add(new List()); for (int nestedIndex = 0; nestedIndex < _levelConfig.NestedGroupSpawn[index].groupSpawn.Count; nestedIndex++) { _copyGroupSpawn[index].Add(_levelConfig.NestedGroupSpawn[index].groupSpawn[nestedIndex].Count); } _groupSpawnTimers.Add(_levelConfig.NestedGroupSpawn[index].triggerTime); _monsterCoreGroups.Add(_levelConfig.NestedGroupSpawn[index].dropsMonsterCore); } // Start game timer, at the end, player wins. GameTimer(); } private async void GameTimer() { await Task.Delay((int) _levelConfig.GameDuration * 60 * 1000); EventAggregator.Instance.GetEvent().Invoke(); } /** * Called by spawner at the start of the game * Assigns enemy to spawn and registers them */ public void Attach(SpawnerTile spawnerSubject) { if (_subjects.Contains(spawnerSubject)) return; spawnerSubject.Prefab = _levelConfig.GetRandomSpawn().GetEnemyObject(); _subjects.Add(spawnerSubject); _aliveEnemyCount.Add(0); _intervalTiming.Add(++_spawnerTiming); // Ensures that only one spawner keeps track of the grouped spawn timer. if (_once) { _once = false; spawnerSubject.SetGroupSpawnTimers(_groupSpawnTimers); } } /** * Called by spawner when making enemies * Assigns a new interval */ public void NotifySpawned(SpawnerTile spawnerSubject) { GameObject paramPrefab = spawnerSubject.Prefab; spawnerSubject.ChangeSpawnSpeed(_levelConfig.Interval * _spawnerTiming); for (int spawnIndex = 0; spawnIndex < _copyConstantSpawn.Count; spawnIndex++) { if (_copyConstantSpawn[spawnIndex] == 0) //Doesn't compare this iteration because it's already empty { continue; } if (paramPrefab.Equals(_levelConfig.ConstantSpawn[spawnIndex].GetEnemyObject())) { _copyConstantSpawn[spawnIndex]--; if (_copyConstantSpawn[spawnIndex] == 0) { foreach (SpawnerTile spawner in _subjects) { if (spawner.Prefab.Equals(paramPrefab)) { EnemyType randomEnemy = _levelConfig.GetRandomSpawn(paramPrefab); if (randomEnemy?.GetEnemyObject() == spawner.Prefab || randomEnemy == null) { spawner.StopSpawn(); } else { spawner.Prefab = randomEnemy.GetEnemyObject(); } } } } break; } } } /** * Called by enemy when they spawn * Keeps track of their row */ public int NotifyEnemy(float yPosition, float toughness) { int index = FindEnemyIndex(yPosition); _aliveEnemyCount[index] += toughness; if (_aliveEnemyCount[index] >= MAXTOUGHNESS) { _subjects[index].StopSpawn(); } return index; } /** * Called when an enemy dies * Reactivates spawning on that row if disabled before */ public void NotifyDies(int position, float toughness, GameObject paramPrefab) { _aliveEnemyCount[position] -= toughness; if (_aliveEnemyCount[position] < MAXTOUGHNESS) { for (int i = 0; i < _copyConstantSpawn.Count; i++) { if (_levelConfig.ConstantSpawn[i].GetEnemyObject() == paramPrefab) { // Checks if there are more of the same type to create if (_copyConstantSpawn[i] > 0) { _subjects[position].StartSpawn(); break; } for (int j = i; j < _copyConstantSpawn.Count; j++) { // Checks if there are other types to create if (_copyConstantSpawn[_copyConstantSpawn.Count - j] > 0) { _subjects[position].Prefab = _levelConfig.ConstantSpawn[j].GetEnemyObject(); _subjects[position].StartSpawn(); break; } } break; } } } } /** * Called when an enemy is spawned automatically at the start of the game * Adjusts the intervall between spawns */ public void NotifyOnStart(SpawnerTile spawnerSubject) { float interval = _levelConfig.GetUpdatedInterval(); foreach (var subject in _subjects) { subject.ChangeSpawnSpeed(interval); } NotifySpawned(spawnerSubject); } // To find which spawner an ennemy came from. private int FindEnemyIndex(float yPosition) { for (int i = 0; i < _subjects.Count; i++) { if (_subjects[i].Position.y == yPosition) { return i; } } return -1; } /** * Spawners wait 1 second to have time to register them all * Then gets assigned a random spawn interval */ public void NotifyEndCooldown(SpawnerTile spawnerTile) { System.Random rand = new System.Random(); int index; do { index = rand.Next(_subjects.Count); } while (_intervalTiming.Count <= index); spawnerTile.ChangeSpawnSpeed(_levelConfig.Interval * _intervalTiming[index]); _intervalTiming.Remove(index); } /** * Called when it is time to spawn a group * Assigns a random element of the group to a random spawner */ public bool NotifyGroupSpawn() { List usedRows = new List(); List currentGroup = _levelConfig.NestedGroupSpawn[_currentGroupIndex].groupSpawn; for (int groupIndex = 0; groupIndex < currentGroup.Count; groupIndex++) //Loops through enemy groups { if (_copyGroupSpawn[_currentGroupIndex][groupIndex] != 0) { CycleRows(usedRows, currentGroup, groupIndex); // If group is done OR max rows reached while group is not done if (_copyGroupSpawn[_currentGroupIndex][groupIndex] > 0 || (usedRows.Count == _subjects.Count && _copyGroupSpawn[_currentGroupIndex][groupIndex] > 0)) { return false; } } } _currentGroupIndex++; return true; } /** * Called to go through every row randomly without duplicate rows */ private void CycleRows(List usedRows, List currentGroup, int groupIndex) { System.Random rand = new System.Random(); bool dropsCore = _monsterCoreGroups != null && _monsterCoreGroups.Count > _currentGroupIndex && _monsterCoreGroups[_currentGroupIndex]; int totalToSpawn = _copyGroupSpawn[_currentGroupIndex][groupIndex]; int monsterCoreTarget = -1; if (dropsCore && totalToSpawn > 0) { monsterCoreTarget = rand.Next(0, totalToSpawn); } int spawnCount = 0; while (usedRows.Count < _subjects.Count) { int currentRow = rand.Next(_subjects.Count); if (!usedRows.Contains(currentRow)) //If picked row has laready been used { GameObject spawnedInstance = _subjects[currentRow].TriggerSpawn(currentGroup[groupIndex].GetEnemyObject()); // Si ce spawn doit être le porteur du MonsterCore, on remplace/ajoute le comportement if (monsterCoreTarget >= 0 && spawnCount == monsterCoreTarget) { if (spawnedInstance != null) { // tentative de remplacer le composant Opponent par MonsterCoreDrop var existingOpponent = spawnedInstance.GetComponent(); if (existingOpponent != null) { // we remove the existing Opponent-derived behaviour to avoid duplicate logic Object.Destroy(existingOpponent); } // add MonsterCoreDrop component and configure son prefab (si disponible dans la Database) MonsterCoreDrop mc = spawnedInstance.AddComponent(); mc.Init(); // essaye de récupérer le prefab "MonsterCore" dans la database (adapter le nom si nécessaire) //GameObject corePrefab = null; //try //{ // corePrefab = Database.Instance.Prefabs["MonsterCore"]; //} //catch { corePrefab = null; } //if (corePrefab != null) //{ // mc.SetCorePrefab(corePrefab); //} } } _copyGroupSpawn[_currentGroupIndex][groupIndex]--; usedRows.Add(currentRow); spawnCount++; } if (_copyGroupSpawn[_currentGroupIndex][groupIndex] == 0) //If current ennemy has reached count of 0 { break; } } } }