448 lines
16 KiB
C#

using OctoberStudio.Bossfight;
using OctoberStudio.Extensions;
using OctoberStudio.Pool;
using OctoberStudio.Timeline;
using OctoberStudio.UI;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Playables;
namespace OctoberStudio
{
[DefaultExecutionOrder(-10)]
public class EnemiesSpawner : MonoBehaviour
{
protected List<EnemyBehavior> enemies = new List<EnemyBehavior>();
[SerializeField] protected EnemiesDatabase database;
[SerializeField] protected BossfightDatabase bossfightDatabase;
[Space]
[SerializeField] protected ScalingLabelBehavior enemiesDiedLabel;
[Space]
[Tooltip("Maximum amount of alive enemies at a time. No more enemies will be spawned until some of existing aren't defeated")]
[SerializeField] protected int enemiesCap = 2000;
[Header("Offscreen Teleport")]
[Tooltip("When enabled, enemies that are behaind of the player will teleport to the front")]
[SerializeField] protected bool isOffscreenTeleportEnabled = true;
[Tooltip("Distance from the player to enemy, where 1 is a camera diagonal length")]
[SerializeField] protected float diagonalDistanceMultiplier = 1.3f;
[SerializeField, Range(0, 1f)] protected float teleportConeSize = 0.8f;
[Tooltip("Enemy will stop teleporting if there are more than this amount of enemies")]
[SerializeField] protected int enemiesTeleportCap = 100;
protected int enemiesDiedCounter;
protected Dictionary<EnemyType, PoolComponent<EnemyBehavior>> enemyPools;
protected Dictionary<EnemyType, EnemyData> enemyDataDictionary;
protected StageSave stageSave;
public bool IsBossfightActive { get; set; }
protected Camera mainCamera;
protected virtual void Awake()
{
mainCamera = Camera.main;
}
// Were creating pools only for the enemies that are present in the Stage Timeline
public virtual void Init(PlayableDirector director)
{
enemyDataDictionary = database.GetEnemyDataDictionary();
stageSave = GameController.SaveManager.GetSave<StageSave>("Stage");
Dictionary<EnemyType, int> enemiesOnLevel = new Dictionary<EnemyType, int>();
var waves = director.GetAssets<WaveTrack, WaveAsset>();
for(int i = 0; i < waves.Count; i++)
{
var wave = waves[i];
var enemyType = wave.EnemyType;
var enemiesCount = wave.EnemiesCount;
if (enemiesOnLevel.ContainsKey(enemyType))
{
if (enemiesOnLevel[enemyType] < enemiesCount)
{
enemiesOnLevel[enemyType] = enemiesCount;
}
}
else
{
enemiesOnLevel.Add(enemyType, enemiesCount);
}
}
var trackEnemies = new List<EnemyType>();
foreach (var output in director.playableAsset.outputs)
{
if(output.sourceObject is WaveTrack waveTrack)
{
if (!trackEnemies.Contains(waveTrack.EnemyType))
{
trackEnemies.Add(waveTrack.EnemyType);
}
}
}
enemyPools = new Dictionary<EnemyType, PoolComponent<EnemyBehavior>>();
foreach(var enemyType in enemiesOnLevel.Keys)
{
var data = database.GetEnemyData(enemyType);
var amount = enemiesOnLevel[enemyType];
if (amount > 100) amount = 100;
if (amount < 0) amount = 1;
var pool = new PoolComponent<EnemyBehavior>($"Enemy {enemyType}", data.Prefab, amount);
enemyPools.Add(data.Type, pool);
}
foreach(var enemyType in trackEnemies)
{
if (!enemyPools.ContainsKey(enemyType))
{
var data = database.GetEnemyData(enemyType);
var pool = new PoolComponent<EnemyBehavior>($"Enemy {enemyType}", data.Prefab, 1);
enemyPools.Add(data.Type, pool);
}
}
enemiesDiedCounter = 0;
if (!stageSave.ResetStageData)
{
enemiesDiedCounter = stageSave.EnemiesKilled;
}
enemiesDiedLabel.SetAmount(enemiesDiedCounter);
}
protected virtual void Update()
{
if (!isOffscreenTeleportEnabled || IsBossfightActive || enemies.Count > enemiesTeleportCap) return;
var diagonalSqr = (CameraManager.HalfWidth * CameraManager.HalfWidth + CameraManager.HalfHeight * CameraManager.HalfHeight) * diagonalDistanceMultiplier;
var diagonal = Mathf.Sqrt(diagonalSqr);
var dotValue = teleportConeSize - 1;
var modValue = Mathf.Clamp(enemies.Count / 20, 1, 100);
int frame = Time.frameCount % modValue;
for(int i = frame; i < enemies.Count; i += modValue)
{
var enemy = enemies[i];
if (enemy.WaveOverride != null && enemy.WaveOverride.DisableOffscreenTeleport) continue;
var enemyToPlayer = enemy.transform.position - PlayerBehavior.Player.transform.position;
var direction = enemyToPlayer.normalized;
var dot = Vector2.Dot(direction, PlayerBehavior.Player.LookDirection);
if(diagonalSqr < enemyToPlayer.sqrMagnitude && dot < dotValue)
{
var teleportPosition = PlayerBehavior.Player.transform.position + Quaternion.Euler(0, 0, Random.Range(-45, 45)) * PlayerBehavior.Player.LookDirection * diagonal;
enemy.transform.position = teleportPosition;
}
}
}
public virtual EnemyBehavior GetClosestEnemy(Vector2 point)
{
EnemyBehavior closestEnemy = null;
float closestDistance = float.MaxValue;
for(int i = 0; i < enemies.Count; i++)
{
if (enemies[i] == null)
{
enemies.RemoveAt(i);
i--;
continue;
}
float distance = (point - enemies[i].transform.position.XY()).sqrMagnitude;
if(distance < closestDistance)
{
closestEnemy = enemies[i];
closestDistance = distance;
}
}
return closestEnemy;
}
public virtual EnemyBehavior Spawn(EnemyType enemyType, Vector2 position, UnityAction<EnemyBehavior> onEnemyDiedCallback = null)
{
if (enemies.Count >= enemiesCap) return null;
var enemyData = enemyDataDictionary[enemyType];
if (!enemyPools.ContainsKey(enemyType))
{
var pool = new PoolComponent<EnemyBehavior>($"Enemy {enemyType}", enemyData.Prefab, 10);
enemyPools.Add(enemyData.Type, pool);
}
var enemy = enemyPools[enemyType].GetEntity();
enemy.SetData(enemyData);
enemy.transform.position = position;
enemy.onEnemyDied += OnEnemyDied;
if (onEnemyDiedCallback != null) enemy.onEnemyDied += onEnemyDiedCallback;
enemy.Play();
enemies.Add(enemy);
return enemy;
}
public virtual void Spawn(EnemyType type, WaveOverride waveOverride, bool circularSpawn = false, int amount = 1, UnityAction<EnemyBehavior> onEnemyDiedCallback = null)
{
var cameraHeight = mainCamera.orthographicSize;
var cameraWidth = cameraHeight * mainCamera.aspect;
var cameraDiagonal = Mathf.Sqrt(cameraWidth * cameraWidth + cameraHeight * cameraHeight);
for (int i = 0; i < amount; i++)
{
if (enemies.Count >= enemiesCap) return;
var enemy = enemyPools[type].GetEntity();
enemy.SetData(enemyDataDictionary[type]);
enemy.SetWaveOverride(waveOverride);
var triesCount = 0;
var maxTriesCount = 10;
var position = Vector3.zero;
var foundPosition = false;
while(triesCount < maxTriesCount)
{
triesCount++;
// when spawning a lot of enemies at once we want to offset them to not overload physics computations
var additionalDistance = amount > 100 ? Mathf.Sqrt(enemies.Count) * 0.1f : 0;
if (circularSpawn)
{
position = PlayerBehavior.Player.transform.position + Random.onUnitSphere.SetZ(0).normalized * (cameraDiagonal * 1.05f + Random.value * 0.2f + additionalDistance);
} else
{
position = CameraManager.GetRandomPointOutsideCamera(0.5f + Random.value * 0.2f + additionalDistance);
}
if(StageController.FieldManager.ValidatePosition(position, Vector2.zero))
{
foundPosition = true;
break;
}
}
if (!foundPosition)
{
for (int j = 1; j < 10; j++)
{
var middlePosition = Vector3.Lerp(position, PlayerBehavior.Player.transform.position, 1 - j / 10f);
if (StageController.FieldManager.ValidatePosition(middlePosition, Vector2.zero))
{
foundPosition = true;
position = middlePosition;
break;
}
}
}
if (!foundPosition)
{
position = StageController.FieldManager.GetRandomPositionOnBorder();
}
enemy.transform.position = position;
enemy.onEnemyDied += OnEnemyDied;
if (onEnemyDiedCallback != null) enemy.onEnemyDied += onEnemyDiedCallback;
enemy.Play();
enemies.Add(enemy);
}
}
public virtual EnemyBehavior GetRandomVisibleEnemy()
{
if (enemies.Count == 0) return null;
// Trying to find random visible enemy 10 times
for(int i = 0; i < 10; i++)
{
var randomIndex = Random.Range(0, enemies.Count);
var enemy = enemies[randomIndex];
if (enemy.IsVisible) return enemy;
}
for(int i = 0; i < enemies.Count; i++)
{
var enemy = enemies[i];
if (enemy.IsVisible) return enemy;
}
return null;
}
public virtual List<EnemyBehavior> GetEnemiesInRadius(Vector2 position, float radius)
{
var result = new List<EnemyBehavior>();
float radiusSqr = radius * radius;
for(int i = 0; i < enemies.Count; i++)
{
if ((enemies[i].transform.position.XY() - position).sqrMagnitude <= radiusSqr)
{
result.Add(enemies[i]);
}
}
return result;
}
public virtual void KillEveryEnemy()
{
foreach(var enemy in enemies)
{
enemy.onEnemyDied -= OnEnemyDied;
enemy.Kill();
}
enemiesDiedCounter += enemies.Count;
stageSave.EnemiesKilled = enemiesDiedCounter;
enemiesDiedLabel.SetAmount(enemiesDiedCounter);
enemies.Clear();
}
public virtual void DealDamageToAllEnemies(float damage)
{
var aliveEnemies = new List<EnemyBehavior>();
foreach(var enemy in enemies)
{
if(enemy.HP <= damage)
{
// if enemy is not a boss
if(enemy.Data != null)
{
enemy.onEnemyDied -= OnEnemyDied;
enemy.Kill();
foreach (var dropData in enemy.GetDropData())
{
if (dropData.Chance == 0) continue;
if (Random.value * 100 <= dropData.Chance && StageController.DropManager.CheckDropCooldown(dropData.DropType))
{
StageController.DropManager.Drop(dropData.DropType, enemy.transform.position.XY() + Random.insideUnitCircle * 0.2f);
}
}
} else
{
aliveEnemies.Add(enemy);
}
}
else
{
// if enemy is not a boss
if (enemy.Data != null)
{
enemy.TakeDamage(damage);
}
aliveEnemies.Add(enemy);
}
}
enemiesDiedCounter += enemies.Count - aliveEnemies.Count;
stageSave.EnemiesKilled = enemiesDiedCounter;
enemiesDiedLabel.SetAmount(enemiesDiedCounter);
enemies.Clear();
enemies.AddRange(aliveEnemies);
}
protected virtual void OnEnemyDied(EnemyBehavior enemy)
{
enemies.RemoveSwapBack(enemy);
enemy.onEnemyDied -= OnEnemyDied;
foreach(var dropData in enemy.GetDropData())
{
if(dropData.Chance == 0) continue;
if(Random.value * 100 <= dropData.Chance && StageController.DropManager.CheckDropCooldown(dropData.DropType))
{
StageController.DropManager.Drop(dropData.DropType, enemy.transform.position.XY() + Random.insideUnitCircle * 0.2f);
}
}
enemiesDiedCounter++;
stageSave.EnemiesKilled = enemiesDiedCounter;
enemiesDiedLabel.SetAmount(enemiesDiedCounter);
}
protected virtual void OnBossDied(EnemyBehavior boss)
{
enemies.RemoveSwapBack(boss);
boss.onEnemyDied -= OnBossDied;
if (boss.ShouldSpawnChestOnDeath && StageController.AbilityManager.HasAvailableAbilities()) StageController.DropManager.Drop(DropType.Chest, boss.transform.position.XY() + Random.insideUnitCircle);
StageController.DropManager.Drop(DropType.Magnet, boss.transform.position.XY() + Random.insideUnitCircle);
StageController.DropManager.Drop(DropType.Food, boss.transform.position.XY() + Random.insideUnitCircle);
enemiesDiedCounter++;
stageSave.EnemiesKilled = enemiesDiedCounter;
enemiesDiedLabel.SetAmount(enemiesDiedCounter);
}
public virtual EnemyBehavior SpawnBoss(BossType bossType, Vector2 spawnPosition, UnityAction<EnemyBehavior> onBossDied = null)
{
var bossData = bossfightDatabase.GetBossfight(bossType);
var boss = Instantiate(bossData.BossPrefab).GetComponent<EnemyBehavior>();
boss.transform.position = spawnPosition;
boss.Play();
boss.onEnemyDied += OnBossDied;
boss.onEnemyDied += onBossDied;
enemies.Add(boss);
return boss;
}
public virtual BossfightData GetBossData(BossType bossType)
{
return bossfightDatabase.GetBossfight(bossType);
}
}
}