EnemySpawner.cs
using System;
using Sandbox;
using Sandbox.Diagnostics;
interface IEnemySpawnerEvents : ISceneEvent<IEnemySpawnerEvents>
{
void FinishedWave(int count) { }
}
public class EnemyPrefabOption
{
public int Chance { get; set; } = 1;
public GameObject Prefab { get; set; }
public override string ToString()
{
return $"{Chance}x for {Prefab.Name}";
}
}
public class Sector
{
public int Min { get; set; }
public int Max { get; set; }
public Sector()
{ }
public Sector( int min, int max )
{
Min = min;
Max = max;
}
public override string ToString()
{
return $"{Min}..{Max}";
}
}
public class WaveQueueItem
{
public int WaitAfter { get; set; }
public IWaveEvent Event { get; set; }
public WaveQueueItem( int waitAfter, IWaveEvent @event )
{
WaitAfter = waitAfter;
Event = @event;
}
public override string ToString()
{
var waitTime = WaitAfter > 0 ? $" then wait {WaitAfter} seconds." : ".";
return $"{Event}{waitTime}";
}
}
public interface IWaveEvent
{
void Invoke( EnemySpawner spawner );
}
public class SpawnEnemies : IWaveEvent
{
public int EnemyCount { get; set; }
public SpawnEnemies( int count )
{
EnemyCount = count;
}
void IWaveEvent.Invoke( EnemySpawner spawner )
{
spawner.LastSpawn = -0.2f;
spawner.SpawnQueue += EnemyCount;
}
public override string ToString()
{
return $"Spawn {EnemyCount} enemies";
}
}
public class ActivateSectors : IWaveEvent
{
public List<Sector> NewSectors { get; set; }
public ActivateSectors( Sector sector )
{
NewSectors = [sector];
}
public ActivateSectors( List<Sector> sectors )
{
NewSectors = sectors;
}
void IWaveEvent.Invoke( EnemySpawner spawner )
{
spawner.ActiveSectors = NewSectors;
}
public override string ToString()
{
return $"Activate sectors {String.Join( ", ", NewSectors )}";
}
}
public class SetRareEnemyChance : IWaveEvent
{
public float NewChance { get; set; }
public SetRareEnemyChance( float newChance )
{
NewChance = newChance;
}
void IWaveEvent.Invoke( EnemySpawner spawner )
{
spawner.RareEnemyChance = NewChance;
}
public override string ToString()
{
return $"Set rare enemy chance to {NewChance:P0}";
}
}
public class SetMaxRareEnemies : IWaveEvent
{
public int NewCap { get; set; }
public SetMaxRareEnemies( int newCap )
{
NewCap = newCap;
}
void IWaveEvent.Invoke( EnemySpawner spawner )
{
spawner.MaxRareEnemies = NewCap;
}
public override string ToString()
{
return $"Set max rare enemies to {NewCap}";
}
}
public sealed class EnemySpawner : Component
{
[Property]
public List<EnemyPrefabOption> EnemyPrefabs { get; set; }
[Property]
public float SpawnRange { get; set; } = 1000f;
[Property]
public List<Sector> ActiveSectors { get; set; } = [new Sector( 0, 90 )];
[Property]
public List<WaveQueueItem> WaveQueue { get; set; } = [];
[Property]
public int WaveQueueIndex { get; set; }
[Property]
public int WaitToAct { get; set; } = 5;
[Property]
public TimeSince LastAction { get; private set; }
[Property]
public int WaveCounter { get; set; }
[Property]
public float RareEnemyChance { get; set; }
[Property]
public int MaxRareEnemies { get; set; }
[Property]
public bool WaitingForEnemiesToDie { get; set; }
public TimeSince LastSpawn;
public int SpawnQueue;
protected override void OnEnabled()
{
base.OnEnabled();
LastAction = 0;
LastSpawn = 0;
SpawnQueue = 0;
}
protected override void OnFixedUpdate()
{
if (LastSpawn > 0 && SpawnQueue > 0)
{
SpawnOne();
LastSpawn = Game.Random.Float( -0.2f, -0.05f );
SpawnQueue--;
}
if ( WaitingForEnemiesToDie )
{
if ( !Scene.GetAll<BulletEnemy>().Any() )
{
WaitingForEnemiesToDie = false;
WaitToAct = 15;
LastAction = 0;
}
return;
}
if ( WaveQueue.Count == 0 && LastAction >= WaitToAct )
{
if ( WaveCounter > 0 )
{
IEnemySpawnerEvents.Post( x => x.FinishedWave( WaveCounter ) );
Scene.Get<ShopSystem>().OpenShop();
}
GenerateWave();
return;
}
if ( WaveQueue.Count > 0 && WaveQueueIndex >= WaveQueue.Count )
{
if (SpawnQueue == 0)
{
WaveQueue.Clear();
WaitingForEnemiesToDie = true;
}
return;
}
else
{
if ( LastAction < WaitToAct ) return;
var todo = WaveQueue[WaveQueueIndex++];
todo.Event.Invoke( this );
WaitToAct = todo.WaitAfter;
}
LastAction = 0;
}
void GenerateWave()
{
WaveCounter++;
Log.Info( $"Wave {WaveCounter}" );
WaveQueueIndex = 0;
if ( WaveCounter == 1 )
{
GenerateFirstWave();
return;
}
if ( WaveCounter == 2 )
{
GenerateSecondWave();
return;
}
if ( WaveCounter < 8 )
{
GenerateTier1Wave();
return;
}
if (WaveCounter == 8)
{
GenerateCrossoverWave();
return;
}
GenerateTier2Wave();
}
Sector OneRandomSector()
{
return Game.Random.FromList( AllSectors() );
}
void GenerateFirstWave()
{
WaveQueue = [
new WaveQueueItem(0, new ActivateSectors(OneRandomSector())),
new WaveQueueItem(15, new SpawnEnemies(3)),
new WaveQueueItem(0, new SpawnEnemies(2)),
];
}
List<Sector> TwoOpposingSectors()
{
bool coin = Game.Random.Int( 1 ) == 1;
if ( coin )
{
return [
new Sector(100, 170),
new Sector(-80, -10)
];
}
else
{
return [
new Sector(10, 80),
new Sector(-170, -100)
];
}
}
void GenerateSecondWave()
{
WaveQueue = [
new WaveQueueItem(0, new ActivateSectors(TwoOpposingSectors())),
new WaveQueueItem(0, new SetMaxRareEnemies(1)),
new WaveQueueItem(0, new SetRareEnemyChance(0.2f)),
new WaveQueueItem(40, new SpawnEnemies(7)),
new WaveQueueItem(0, new SpawnEnemies(8)),
];
}
// 3 to 7
void GenerateTier1Wave()
{
var enemiesToSpawn = 15 + (WaveCounter) * 3;
enemiesToSpawn += Game.Random.Int( WaveCounter ) - 2;
WaveQueue = [
new WaveQueueItem(0, new ActivateSectors(TwoOpposingSectors())),
new WaveQueueItem(0, new SetMaxRareEnemies(WaveCounter - 1)),
new WaveQueueItem(0, new SetRareEnemyChance(0.2f)),
new WaveQueueItem(40, new SpawnEnemies((int)(enemiesToSpawn * 0.6))),
new WaveQueueItem(0, new SpawnEnemies((int)(enemiesToSpawn * 0.4))),
];
}
List<Sector> AllSectors()
{
return [
new Sector(10, 80),
new Sector(100, 170),
new Sector(-80, -10),
new Sector(-170, -100)
];
}
// half of the wave should be 2 sides, then 4
void GenerateCrossoverWave()
{
var enemiesToSpawn = 15 + (WaveCounter) * 3;
enemiesToSpawn += Game.Random.Int( WaveCounter ) - 2;
WaveQueue = [
new WaveQueueItem(0, new ActivateSectors(TwoOpposingSectors())),
new WaveQueueItem(0, new SetMaxRareEnemies(WaveCounter - 1)),
new WaveQueueItem(0, new SetRareEnemyChance(0.2f)),
new WaveQueueItem(40, new SpawnEnemies((int)(enemiesToSpawn * 0.6))),
new WaveQueueItem(0, new ActivateSectors(AllSectors())),
new WaveQueueItem(0, new SetMaxRareEnemies(2)),
new WaveQueueItem(0, new SetRareEnemyChance(0.2f)),
new WaveQueueItem(0, new SpawnEnemies((int)(enemiesToSpawn * 0.4))),
];
}
// 9 to ???
void GenerateTier2Wave()
{
var cnt = WaveCounter - 8;
var enemiesToSpawn = 20 + (WaveCounter) * 3;
enemiesToSpawn += (Game.Random.Int( WaveCounter ) - 2) * 2;
WaveQueue = [
new WaveQueueItem(0, new ActivateSectors(AllSectors())),
new WaveQueueItem(0, new SetMaxRareEnemies((cnt+1)*5)),
new WaveQueueItem(0, new SetRareEnemyChance(0.2f)),
];
while (enemiesToSpawn > 0)
{
var enemiesInSubWave = Game.Random.Int( 20, 36 );
WaveQueue.Add( new WaveQueueItem( Game.Random.Int( 40, 50 ) - cnt, new SpawnEnemies( enemiesInSubWave ) ) );
enemiesToSpawn -= enemiesInSubWave;
}
}
protected override void DrawGizmos()
{
base.DrawGizmos();
Gizmo.Draw.Color = Color.Orange;
Gizmo.Draw.LineCircle( Vector3.Zero, Vector3.Up, Vector3.Forward, SpawnRange, 0, 360, 64 );
}
GameObject GetRandomEnemyPrefab()
{
List<GameObject> list = [];
foreach ( var option in EnemyPrefabs )
{
for ( var i = 0; i < option.Chance; i++ )
{
list.Add( option.Prefab );
}
}
return Game.Random.FromList( list );
}
public void SpawnOne()
{
var sector = Game.Random.FromList( ActiveSectors );
var degrees = Game.Random.Int( sector.Min, sector.Max );
var rot = Rotation.FromYaw( degrees );
var position = new Vector3( SpawnRange, 0, 0 ).RotateAround( Vector3.Zero, rot );
var prefab = GetRandomEnemyPrefab();
Assert.IsValid( prefab );
var instance = prefab.Clone( position );
if ( Game.Random.Float() < RareEnemyChance )
{
var rare = instance.GetComponent<IRareEnemy>();
if ( rare is not null )
{
rare.MakeRare();
if ( MaxRareEnemies > 0 )
{
MaxRareEnemies--;
if ( MaxRareEnemies == 0 )
{
RareEnemyChance = 0;
}
}
}
}
}
}