Manager.cs
using Sandbox.Audio;
using SS1;
public enum FloaterType { Damage, Heal, Xp }
public sealed class Manager : Component, Component.INetworkListener
{
public static Manager Instance { get; private set; }
[Property] public GameObject PlayerPrefab { get; set; }
[Property] public GameObject ShadowPrefab { get; set; }
[Property] public GameObject CoinPrefab { get; set; }
[Property] public GameObject MagnetPrefab { get; set; }
[Property] public GameObject BloodSplatterPrefab { get; set; }
[Property] public GameObject LavaPuddlePrefab { get; set; }
[Property] public GameObject LavaBlobPrefab { get; set; }
[Property] public GameObject CloudPrefab { get; set; }
[Property] public GameObject BurningVfxPrefab { get; set; }
[Property] public GameObject FrozenVfxPrefab { get; set; }
[Property] public GameObject FearVfxPrefab { get; set; }
[Property] public GameObject CharmVfxPrefab { get; set; }
[Property] public GameObject GrenadePrefab { get; set; }
[Property] public GameObject ExplosionEffectPrefab { get; set; }
[Property] public GameObject RingEffectPrefab { get; set; }
[Property] public GameObject ReviveSoulPrefab { get; set; }
[Property] public GameObject HealthPackPrefab { get; set; }
[Property] public GameObject RerollPickupPrefab { get; set; }
[Property] public GameObject EnemyBulletPrefab { get; set; }
[Property] public GameObject EnemySpikePrefab { get; set; }
[Property] public GameObject EnemySpikeBgPrefab { get; set; }
[Property] public GameObject EnemySpikeElitePrefab { get; set; }
[Property] public GameObject EnemySpikeSpecialPrefab { get; set; }
[Property] public GameObject FirePrefab { get; set; }
[Property] public GameObject ShieldVfxPrefab { get; set; }
[Property] public GameObject CratePrefab { get; set; }
[Property] public GameObject ZombiePrefab { get; set; }
[Property] public GameObject ZombieElitePrefab { get; set; }
[Property] public GameObject ExploderPrefab { get; set; }
[Property] public GameObject ExploderElitePrefab { get; set; }
[Property] public GameObject ExploderSpecialPrefab { get; set; }
[Property] public GameObject SpitterPrefab { get; set; }
[Property] public GameObject SpitterElitePrefab { get; set; }
[Property] public GameObject SpitterSpecialPrefab { get; set; }
[Property] public GameObject SpitterEliteSpecialPrefab { get; set; }
[Property] public GameObject SpikerPrefab { get; set; }
[Property] public GameObject SpikerElitePrefab { get; set; }
[Property] public GameObject SpikerSpecialPrefab { get; set; }
[Property] public GameObject ChargerPrefab { get; set; }
[Property] public GameObject ChargerElitePrefab { get; set; }
[Property] public GameObject ChargerSpecialPrefab { get; set; }
[Property] public GameObject RunnerPrefab { get; set; }
[Property] public GameObject RunnerElitePrefab { get; set; }
[Property] public GameObject RunnerEliteSpecialPrefab { get; set; }
[Property] public GameObject BossPrefab { get; set; }
[Property] public GameObject CrownPrefab { get; set; }
[Property] public GameObject WarningPrefab { get; set; }
[Property] public GameObject SatelliteLaserPrefab { get; set; }
[Property] public GameObject FloaterParticleTextPrefab { get; set; }
[Property] public CameraComponent Camera { get; private set; }
[Property] public Camera2D Camera2D { get; set; }
[Property] public MusicManager MusicManager { get; set; }
public Mixer SfxMixer { get; set; }
public int EnemyCount { get; private set; }
public const float MAX_ENEMY_COUNT = 350;
public int CharmedEnemyCount { get; set; }
public int CrateCount { get; private set; }
public const float MAX_CRATE_COUNT = 7;
public int CoinCount { get; private set; }
public const float MAX_COIN_COUNT = 200;
private int _coinDebt; // coin value not spawned because too many coins exist
public record struct GridSquare( int x, int y );
public Dictionary<GridSquare, List<Thing>> ThingGridPositions = new Dictionary<GridSquare, List<Thing>>();
public float GRID_SIZE = 1f;
public Vector2 BOUNDS_MIN;
public Vector2 BOUNDS_MAX;
public Vector2 BOUNDS_MIN_SPAWN;
public Vector2 BOUNDS_MAX_SPAWN;
private TimeSince _timeSinceEnemySpawn;
[Sync] public TimeSince ElapsedTime { get; set; }
[Sync] public RealTimeSince TimeSinceRunStart { get; set; }
[Sync] public float FinalRunTime { get; set; }
public bool ShouldUpdateThings => !(IsGameOver && ShowFinalPanel);
public bool ShouldUpdatePlayer => !IsGameOver;
[Sync] public bool IsGameOver { get; private set; }
[Sync] public bool IsVictory { get; private set; }
public bool IsWaitingForFinalPanel { get; private set; }
public bool ShowFinalPanel { get; private set; }
private RealTimeSince _realTimeSinceFinalPanelWait;
public const float FINAL_PANEL_WAIT_TIME = 1.7f;
[Sync] public bool IsPauseMenuOpen { get; set; }
public Vector2 MouseWorldPos { get; private set; }
public bool HasSpawnedBoss { get; private set; }
public bool IsBossDead { get; set; }
public Boss Boss { get; set; }
public Boss OtherBoss { get; set; }
public int NumBossesKilled { get; set; }
public bool IsBoss0Dead { get; set; }
public bool IsBoss1Dead { get; set; }
public TimeSince TimeSinceMagnet { get; set; }
public List<BloodSplatter> _bloodSplatters = new();
public List<LavaPuddle> _lavaPuddles = new();
public List<LavaBlob> _lavaBlobs = new();
public List<Cloud> _clouds = new();
public List<ExplosionEffect> _explosions = new();
public List<RingEffect> _ringEffects = new();
public List<Warning> _warnings = new();
public List<SatelliteLaser> _satelliteLasers = new();
public Status HoveredStatus { get; set; }
private int _numEnemyDeathSfxs;
public int NumPlayers { get; private set; }
public List<Player> Players { get; private set; } = new();
public Player Player { get; private set; }
public bool UnlockedBulletDmgAchievement { get; set; }
public int Difficulty { get; private set; }
public static int MinDifficulty = -1;
public static int MaxDifficulty = 15;
private int _currEnemyIdNum;
protected override void OnAwake()
{
base.OnAwake();
Instance = this;
var difficultyTracker = Scene.GetAllComponents<DifficultyTracker>().FirstOrDefault();
Difficulty = difficultyTracker?.Difficulty ?? 0;
//Difficulty = 4;
//Log.Info( $"Difficulty: {Difficulty}" );
if ( difficultyTracker != null )
difficultyTracker.GameObject.Flags &= ~GameObjectFlags.DontDestroyOnLoad;
BOUNDS_MIN = new Vector2( -16f, -12.35f );
BOUNDS_MAX = new Vector2( 16f, 12f );
BOUNDS_MIN_SPAWN = new Vector2( -15.5f, -11.75f );
BOUNDS_MAX_SPAWN = new Vector2( 15.5f, 11.5f );
ElapsedTime = 0f;
//ElapsedTime = 15 * 60f - 20;
TimeSinceRunStart = 0f;
for ( float x = BOUNDS_MIN.x; x < BOUNDS_MAX.x; x += GRID_SIZE )
{
for ( float y = BOUNDS_MIN.y; y < BOUNDS_MAX.y; y += GRID_SIZE )
{
ThingGridPositions.Add( GetGridSquareForPos( new Vector2( x, y ) ), new List<Thing>() );
}
}
SfxMixer = Mixer.FindMixerByName( "Game" );
SfxMixer.Volume = 0.75f;
//MusicSoundPointDrums = Scene.Directory.FindByName( "MusicDrums" ).First().Components.Get<SoundPointComponent>();
//Music.Seek( MathF.Max( CurrentTime, 0f ) );
}
protected override void OnStart()
{
//if ( !Networking.IsActive )
//{
// if ( MainMenu.IsSingleplayerGame )
// CreatePlayer( Connection.Local );
// else
// Networking.CreateLobby();
//}
CreatePlayer( Connection.Local );
if ( IsProxy )
return;
SpawnStartingThings();
}
public void SpawnStartingThings()
{
for ( int i = 0; i < 3; i++ )
{
var pos = new Vector2( Game.Random.Float( BOUNDS_MIN_SPAWN.x, BOUNDS_MAX_SPAWN.x ), Game.Random.Float( BOUNDS_MIN_SPAWN.y, BOUNDS_MAX_SPAWN.y ) );
SpawnEnemy( TypeLibrary.GetType( typeof( Crate ) ), pos );
}
for ( int i = 0; i < 150; i++ )
{
//var zombie = SpawnEnemy( TypeLibrary.GetType( typeof( Zombie ) ), new Vector2( Game.Random.Float( -10, 10f ), Game.Random.Float( -10, 10f ) ), forceSpawn: true ) as Zombie;
//zombie.Target = Player;
//zombie.IsWandering = false;
}
//float amount = 2f;
//for ( int i = 0; i < 10; i++ )
//{
// //SpawnEnemy( TypeLibrary.GetType( typeof( Zombie ) ), new Vector2( Game.Random.Float( -amount, amount ), Game.Random.Float( -amount, amount ) ), forceSpawn: true );
// //SpawnEnemy( TypeLibrary.GetType( typeof( ZombieElite ) ), new Vector2( Game.Random.Float( -amount, amount ), Game.Random.Float( -amount, amount ) ), forceSpawn: true );
// SpawnEnemy( TypeLibrary.GetType( typeof( Exploder ) ), new Vector2( Game.Random.Float( -amount, amount ), Game.Random.Float( -amount, amount ) ), forceSpawn: true );
// //SpawnEnemy( TypeLibrary.GetType( typeof( ExploderElite ) ), new Vector2( Game.Random.Float( -amount, amount ), Game.Random.Float( -amount, amount ) ), forceSpawn: true );
//}
//var enemy = SpawnEnemy( TypeLibrary.GetType( typeof( Exploder ) ), new Vector2( Game.Random.Float( -2f, 2f ), Game.Random.Float( -2f, 2f ) ), forceSpawn: true );
//enemy.Charm();
//enemy.Flash( 0f );
//SpawnBoss( new Vector2( 3f, 3f ) );
//HasSpawnedBoss = true;
//SpawnCrown( new Vector2( 4f, 4f ) );
//SpawnRerollPickup( 0.6f, 0.6f );
//SpawnHealthPack( new Vector2( 3f, 3f ), Vector2.Zero );
}
public void SetPaused( bool paused )
{
IsPauseMenuOpen = paused;
MusicManager.SetPaused( paused );
}
protected override void OnUpdate()
{
//for ( float x = BOUNDS_MIN.x; x < BOUNDS_MAX.x; x += GRID_SIZE )
//{
// for ( float y = BOUNDS_MIN.y; y < BOUNDS_MAX.y; y += GRID_SIZE )
// {
// var pos = new Vector2( x, y );
// Gizmo.Draw.LineBBox( new BBox( pos, new Vector2( x + GRID_SIZE, y + GRID_SIZE ) ) );
// //var gridSquare = GetGridSquareForPos( pos );
// //Gizmo.Draw.Text( (new Vector2( gridSquare.x, gridSquare.y )).ToString(), new global::Transform( new Vector3( x + 7f, y + 7f, 0f ) ) );
// }
//}
if ( Input.EscapePressed )
{
SetPaused( !IsPauseMenuOpen );
Input.EscapePressed = false;
}
NumPlayers = Players.Count();
if ( IsGameOver )
{
if ( IsWaitingForFinalPanel )
{
if ( _realTimeSinceFinalPanelWait > FINAL_PANEL_WAIT_TIME )
{
IsWaitingForFinalPanel = false;
ShowFinalPanel = true;
Scene.TimeScale = 1f;
if ( !IsVictory && !IsBossDead )
{
foreach ( var enemy in Scene.GetAll<Enemy>() )
{
if ( !enemy.IsDying && !enemy.IsCharmed )
enemy.Celebrate();
}
}
}
else
{
Scene.TimeScale = Utils.Map( _realTimeSinceFinalPanelWait, 0f, FINAL_PANEL_WAIT_TIME, 0.0001f, 0.25f, EasingType.SineIn );
float zoom = 1f + Player.Stats[PlayerStat.ZoomAmount];
Camera.OrthographicHeight = Utils.Map( _realTimeSinceFinalPanelWait, 0f, 0.5f, 10f * zoom, 8f, EasingType.SineInOut );
}
}
return;
}
var tr = Scene.Trace.Ray( Camera.ScreenPixelToRay( Mouse.Position ), 1500f ).Run();
if ( tr.Hit )
{
MouseWorldPos = (Vector2)tr.HitPosition;
}
if ( _numEnemyDeathSfxs > 0 )
_numEnemyDeathSfxs--;
if ( IsPauseMenuOpen )
{
Scene.TimeScale = 0f;
}
else
{
if ( Player.Stats[PlayerStat.PauseWhileChoosing] > 0f )
{
if ( Player.IsChoosingLevelUpReward )
{
Scene.TimeScale = Utils.Map( Player.TimeSinceLevelUp, 0f, 0.5f, 0.7f, 0f, EasingType.QuadOut );
}
else
{
Scene.TimeScale = Player.Statuses.Count > 1
? Utils.Map( Player.RealTimeSinceChoseUpgrade, 0f, 0.5f, 0f, 1f, EasingType.QuadOut )
: 1f;
}
}
else
{
Scene.TimeScale = 1f;
}
}
HandleEnemySpawn();
if ( !HasSpawnedBoss && !IsGameOver && ElapsedTime > Player.Stats[PlayerStat.BossArrivalTime] )
{
SpawnBoss( new Vector2( 0f, 0f ) );
HasSpawnedBoss = true;
}
}
public void OnActive( Connection channel )
{
Log.Info( $"Player '{channel.DisplayName}' is becoming active (local = {channel == Connection.Local}) (host = {channel.IsHost})" );
CreatePlayer( channel );
}
void CreatePlayer( Connection channel )
{
var playerObj = PlayerPrefab.Clone( new Vector3( Game.Random.Float( -3f, 3f ), Game.Random.Float( -3f, 3f ), Globals.GetZPos( 0f ) ) );
var player = playerObj.Components.Get<Player>();
Player = player;
playerObj.NetworkSpawn( channel );
}
void HandleEnemySpawn()
{
var t = ElapsedTime;
var spawnTime = Utils.Map( EnemyCount, 0, MAX_ENEMY_COUNT, 0.05f, 0.3f, EasingType.QuadOut )
* Utils.Map( t, 0f, 80f, 1.5f, 1f )
* Utils.Map( t, 0f, 250f, 3f, 1f )
* Utils.Map( t, 0f, 800f, 1.2f, 1f );
if ( HasSpawnedBoss )
{
if ( Difficulty == -1 )
spawnTime *= 4f;
else if ( Difficulty == 0 )
spawnTime *= 3.5f;
else if ( Difficulty == 1 )
spawnTime *= 2.5f;
else if ( Difficulty == 2 )
spawnTime *= 2f;
else if ( Difficulty == 3 )
spawnTime *= 1.5f;
else if ( Difficulty == 4 )
spawnTime *= 1.25f;
}
//if ( Difficulty < 0 )
// spawnTime *= 1.1f;
if ( _timeSinceEnemySpawn > spawnTime )
{
SpawnRandomEnemy();
//if ( Difficulty == 0 )
// SpawnRandomEnemy();
//else
// SpawnRandomEnemyNew( ElapsedTime );
_timeSinceEnemySpawn = 0f;
}
}
void SpawnRandomEnemy()
{
if ( EnemyCount >= MAX_ENEMY_COUNT )
return;
var pos = new Vector2( Game.Random.Float( BOUNDS_MIN_SPAWN.x, BOUNDS_MAX_SPAWN.x ), Game.Random.Float( BOUNDS_MIN_SPAWN.y, BOUNDS_MAX_SPAWN.y ) );
//// ZOMBIE (DEFAULT)
TypeDescription type = TypeLibrary.GetType( typeof( Zombie ) );
var t = ElapsedTime;
if ( Difficulty < 0 )
t *= 0.9f;
else if ( Difficulty == 1 )
t *= 1.15f;
else if ( Difficulty == 2 )
t *= 1.25f;
else if ( Difficulty == 3 )
t *= 1.35f;
else if ( Difficulty == 4 )
t *= 1.45f;
else if ( Difficulty == 5 )
t *= 1.55f;
// CRATE
if ( CrateCount < MAX_CRATE_COUNT )
{
float crateChance = t < 20f ? 0f : Utils.Map( t, 20f, 200f, 0.005f, 0.01f );
if ( Difficulty == -1 )
crateChance *= 1.2f;
//float additionalCrateChance = 0f;
//foreach ( Player player in GetPlayers() )
//{
// if ( player.Stats[PlayerStat.CrateChanceAdditional] > 0f )
// additionalCrateChance += player.Stats[PlayerStat.CrateChanceAdditional];
//}
//crateChance *= (1f + additionalCrateChance);
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < crateChance )
type = TypeLibrary.GetType( typeof( Crate ) );
}
// EXPLODER
float exploderChance = t < 35f ? 0f : Utils.Map( t, 35f, 1000f, 0.022f, 0.06f );
if ( Difficulty == -1 )
exploderChance *= 0.9f;
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < exploderChance )
{
float eliteChance = t < 510f ? 0f : Utils.Map( t, 510f, 1600f, 0.02f, 0.95f, EasingType.SineIn );
//if ( Difficulty == -1 )
// eliteChance *= 0.5f;
//else if ( Difficulty > 0 )
// eliteChance *= 1.2f;
type = Game.Random.Float( 0f, 1f ) < eliteChance ? TypeLibrary.GetType( typeof( ExploderElite ) ) : TypeLibrary.GetType( typeof( Exploder ) );
}
if ( Difficulty >= 2 && type == TypeLibrary.GetType( typeof( Zombie ) ) && t > 30f && Game.Random.Float( 0f, 1f ) < Utils.Map( t, 30f, 1700f, 0.0008f, 0.07f ) * Utils.Map( Difficulty, 2, 5, 0.95f, 1.25f, EasingType.Linear ) )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( Scene.GetAll<ExploderSpecial>().Count(), 0, 2 + Math.Min( Manager.Instance.Difficulty, 5 ), 1f, 0f, EasingType.ExpoOut ) )
type = TypeLibrary.GetType( typeof( ExploderSpecial ) );
}
// SPITTER
float spitterChance = t < 100f ? 0f : Utils.Map( t, 100f, 1100f, 0.013f, 0.1f );
if ( Difficulty == -1 )
spitterChance *= 0.7f;
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < spitterChance )
{
float eliteChance = t < 540f ? 0f : Utils.Map( t, 540f, 1350f, 0.025f, 1f, EasingType.QuadIn );
//if ( Difficulty == -1 )
// eliteChance *= 0.5f;
//else if ( Difficulty > 0 )
// eliteChance *= 1.1f;
type = Game.Random.Float( 0f, 1f ) < eliteChance ? TypeLibrary.GetType( typeof( SpitterElite ) ) : TypeLibrary.GetType( typeof( Spitter ) );
}
if ( Difficulty >= 2 && type == TypeLibrary.GetType( typeof( Zombie ) ) && t > 120f && Game.Random.Float( 0f, 1f ) < Utils.Map( t, 120f, 2600f, 0.000085f, 0.06f ) * Utils.Map( Difficulty, 2, 5, 0.9f, 1.25f, EasingType.Linear ) )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( Scene.GetAll<SpitterSpecial>().Count(), 0, Math.Min( Manager.Instance.Difficulty, 5 ) - 1, 1f, 0f, EasingType.ExpoOut ) )
type = TypeLibrary.GetType( typeof( SpitterSpecial ) );
}
if ( Difficulty >= 2 && type == TypeLibrary.GetType( typeof( Zombie ) ) && t > 150f && Game.Random.Float( 0f, 1f ) < Utils.Map( t, 150f, 2700f, 0.000125f, 0.06f ) * Utils.Map( Difficulty, 2, 5, 0.9f, 1.25f, EasingType.Linear ) )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( Scene.GetAll<SpitterEliteSpecial>().Count(), 0, Math.Min( Manager.Instance.Difficulty, 5 ) - 1, 1f, 0f, EasingType.ExpoOut ) )
type = TypeLibrary.GetType( typeof( SpitterEliteSpecial ) );
}
// SPIKER
float spikerChance = t < 320f ? 0f : Utils.Map( t, 320f, 120f, 0.018f, 0.07f, EasingType.SineIn );
if ( Player.TimeSinceInputMove > 20f )
spikerChance = Math.Max( spikerChance, Utils.Map( Player.TimeSinceInputMove, 20f, 400f, 0.02f, 0.25f ) ); // todo
//if ( Difficulty == -1 )
// spikerChance *= 0.65f;
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < spikerChance )
{
float eliteChance = t < 580f ? 0f : Utils.Map( t, 580f, 1500f, 0.008f, 1f, EasingType.SineIn );
if ( Player.TimeSinceInputMove > 30f )
eliteChance = Math.Max( eliteChance, Utils.Map( Player.TimeSinceInputMove, 30f, 500f, 0.02f, 0.25f ) ); // todo
//if ( Difficulty == -1 )
// eliteChance *= 0.5f;
//else if ( Difficulty > 0 )
// eliteChance *= 1.2f;
type = Game.Random.Float( 0f, 1f ) < eliteChance ? TypeLibrary.GetType( typeof( SpikerElite ) ) : TypeLibrary.GetType( typeof( Spiker ) );
}
if ( Difficulty >= 2 && type == TypeLibrary.GetType( typeof( Zombie ) ) && t > 150f && Game.Random.Float( 0f, 1f ) < Utils.Map( t, 150f, 2500f, 0.000325f, 0.045f ) * Utils.Map( Difficulty, 2, 5, 0.9f, 1.25f, EasingType.Linear ) )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( Scene.GetAll<SpikerSpecial>().Count(), 0, Math.Min( Manager.Instance.Difficulty, 5 ) - 1, 1f, 0f, EasingType.ExpoOut ) )
type = TypeLibrary.GetType( typeof( SpikerSpecial ) );
}
// CHARGER
float chargerChance = t < 420f ? 0f : Utils.Map( t, 420f, 1200f, 0.022f, 0.075f );
//if ( Difficulty == -1 )
// chargerChance *= 0.6f;
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < chargerChance )
{
float eliteChance = t < 660f ? 0f : Utils.Map( t, 660f, 1700f, 0.008f, 1f, EasingType.SineIn );
//if ( Difficulty == -1 )
// eliteChance *= 0.5f;
//else if ( Difficulty > 0 )
// eliteChance *= 1.2f;
type = Game.Random.Float( 0f, 1f ) < eliteChance ? TypeLibrary.GetType( typeof( ChargerElite ) ) : TypeLibrary.GetType( typeof( Charger ) );
}
if ( Difficulty >= 2 && type == TypeLibrary.GetType( typeof( Zombie ) ) && t > 220f && Game.Random.Float( 0f, 1f ) < Utils.Map( t, 220f, 3300f, 0.0005f, 0.04f ) * Utils.Map( Difficulty, 2, 5, 0.9f, 1.25f, EasingType.Linear ) )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( Scene.GetAll<ChargerSpecial>().Count(), 0, Math.Min( Manager.Instance.Difficulty, 5 ) - 1, 1f, 0f, EasingType.ExpoOut ) )
type = TypeLibrary.GetType( typeof( ChargerSpecial ) );
}
// RUNNER
float runnerChance = t < 500f ? 0f : Utils.Map( t, 500f, 1200f, 0.035f, 0.15f, EasingType.QuadIn );
//if ( Difficulty == -1 )
// runnerChance *= 0.65f;
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < runnerChance )
{
float eliteChance = t < 720f ? 0f : Utils.Map( t, 720f, 1700f, 0.01f, 1f, EasingType.QuadIn );
//if ( Difficulty == -1 )
// eliteChance *= 0.5f;
//else if ( Difficulty > 0 )
// eliteChance *= 1.1f;
type = Game.Random.Float( 0f, 1f ) < eliteChance ? TypeLibrary.GetType( typeof( RunnerElite ) ) : TypeLibrary.GetType( typeof( Runner ) );
}
if ( Difficulty >= 2 && type == TypeLibrary.GetType( typeof( Zombie ) ) && t > 220f && Game.Random.Float( 0f, 1f ) < Utils.Map( t, 220f, 2800f, 0.0005f, 0.05f ) * Utils.Map( Difficulty, 2, 5, 0.9f, 1.25f, EasingType.Linear ) )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( Scene.GetAll<RunnerEliteSpecial>().Count(), 0, Math.Min( Manager.Instance.Difficulty, 5 ) - 1, 1f, 0f, EasingType.ExpoOut ) )
type = TypeLibrary.GetType( typeof( RunnerEliteSpecial ) );
}
// ZOMBIE ELITE
var zombieEliteChance = t < 400f ? 0f : Utils.Map( t, 400f, 1600f, 0.0175f, (Difficulty >= 4 ? 0.33f : 1f), EasingType.SineIn );
if ( type == TypeLibrary.GetType( typeof( Zombie ) ) && Game.Random.Float( 0f, 1f ) < zombieEliteChance )
{
type = TypeLibrary.GetType( typeof( ZombieElite ) );
}
SpawnEnemy( type, pos );
}
//void SpawnRandomEnemyNew(float t)
//{
// if ( EnemyCount >= MAX_ENEMY_COUNT )
// return;
// List<(TypeDescription Type, float Weight)> chances = new();
// if ( CrateCount < MAX_CRATE_COUNT )
// chances.Add( (TypeLibrary.GetType( typeof( Crate ) ), t < 20f ? 0f : Utils.Map( t, 20f, 200f, 0.005f, 0.01f ) ) );
// float zombieChance = 0f;
// float zombieEliteChance = 0f;
// float exploderChance = 0f;
// float exploderEliteChance = 0f;
// float spitterChance = 0f;
// float spitterEliteChance = 0f;
// float spikerChance = 0f;
// float spikerEliteChance = 0f;
// float chargerChance = 0f;
// float chargerEliteChance = 0f;
// float runnerChance = 0f;
// float runnerEliteChance = 0f;
// switch(Difficulty)
// {
// case -1:
// if ( t < 15 * 60f )
// {
// zombieChance = 1f;
// zombieEliteChance = t < 400f ? 0f : Utils.Map( t, 400f, 1200f, 0.0175f, 1f, EasingType.SineIn );
// exploderChance = t < 35f ? 0f : Utils.Map( t, 35f, 700f, 0.022f, 0.08f );
// exploderEliteChance = t < 480f ? 0f : Utils.Map( t, 480f, 1200f, 0.025f, 1f, EasingType.SineIn );
// spitterChance = t < 100f ? 0f : Utils.Map( t, 100f, 800f, 0.015f, 0.1f );
// spitterEliteChance = t < 540f ? 0f : Utils.Map( t, 540f, 1200f, 0.025f, 1f, EasingType.QuadIn );
// spikerChance = t < 320f ? 0f : Utils.Map( t, 320f, 800f, 0.018f, 0.1f, EasingType.SineIn );
// spikerEliteChance = t < 580f ? 0f : Utils.Map( t, 580f, 1300f, 0.008f, 1f, EasingType.SineIn );
// chargerChance = t < 420f ? 0f : Utils.Map( t, 420f, 800f, 0.022f, 0.075f );
// chargerEliteChance = t < 660f ? 0f : Utils.Map( t, 660f, 1400f, 0.008f, 1f, EasingType.SineIn );
// runnerChance = t < 500f ? 0f : Utils.Map( t, 500f, 900f, 0.035f, 0.15f, EasingType.QuadIn );
// runnerEliteChance = t < 720f ? 0f : Utils.Map( t, 720f, 1500f, 0.01f, 1f, EasingType.QuadIn );
// }
// else
// {
// zombieChance = Utils.Map( t, 15 * 60f, 20 * 60f, 1f, 0f );
// zombieEliteChance = t < 400f ? 0f : Utils.Map( t, 400f, 1200f, 0.0175f, 1f, EasingType.SineIn );
// exploderChance = t < 35f ? 0f : Utils.Map( t, 35f, 700f, 0.022f, 0.08f );
// exploderEliteChance = t < 480f ? 0f : Utils.Map( t, 480f, 1200f, 0.025f, 1f, EasingType.SineIn );
// spitterChance = t < 100f ? 0f : Utils.Map( t, 100f, 800f, 0.015f, 0.1f );
// spitterEliteChance = t < 540f ? 0f : Utils.Map( t, 540f, 1200f, 0.025f, 1f, EasingType.QuadIn );
// spikerChance = t < 320f ? 0f : Utils.Map( t, 320f, 800f, 0.018f, 0.1f, EasingType.SineIn );
// spikerEliteChance = t < 580f ? 0f : Utils.Map( t, 580f, 1300f, 0.008f, 1f, EasingType.SineIn );
// chargerChance = t < 420f ? 0f : Utils.Map( t, 420f, 800f, 0.022f, 0.075f );
// chargerEliteChance = t < 660f ? 0f : Utils.Map( t, 660f, 1400f, 0.008f, 1f, EasingType.SineIn );
// runnerChance = t < 500f ? 0f : Utils.Map( t, 500f, 900f, 0.035f, 0.15f, EasingType.QuadIn );
// runnerEliteChance = t < 720f ? 0f : Utils.Map( t, 720f, 1500f, 0.01f, 1f, EasingType.QuadIn );
// }
// break;
// case 0: // NORMAL
// if ( t < 15 * 60f )
// {
// //zombieChance = 1f;
// //zombieEliteChance = t < 400f ? 0f : Utils.Map( t, 400f, 1200f, 0.0175f, 1f, EasingType.SineIn );
// //exploderChance = t < 35f ? 0f : Utils.Map( t, 35f, 700f, 0.022f, 0.08f );
// //exploderEliteChance = t < 480f ? 0f : Utils.Map( t, 480f, 1300f, 0.022f, 1f, EasingType.SineIn );
// //spitterChance = t < 100f ? 0f : Utils.Map( t, 100f, 800f, 0.015f, 0.1f );
// //spitterEliteChance = t < 540f ? 0f : Utils.Map( t, 540f, 1200f, 0.025f, 1f, EasingType.QuadIn );
// //spikerChance = t < 320f ? 0f : Utils.Map( t, 320f, 800f, 0.018f, 0.1f, EasingType.SineIn );
// //spikerEliteChance = t < 580f ? 0f : Utils.Map( t, 580f, 1300f, 0.008f, 1f, EasingType.SineIn );
// //chargerChance = t < 420f ? 0f : Utils.Map( t, 420f, 800f, 0.022f, 0.075f );
// //chargerEliteChance = t < 660f ? 0f : Utils.Map( t, 660f, 1400f, 0.008f, 1f, EasingType.SineIn );
// //runnerChance = t < 500f ? 0f : Utils.Map( t, 500f, 900f, 0.035f, 0.15f, EasingType.QuadIn );
// //runnerEliteChance = t < 720f ? 0f : Utils.Map( t, 720f, 1500f, 0.01f, 1f, EasingType.QuadIn );
// }
// else
// {
// zombieChance = Utils.Map( t, 20 * 60f, 30 * 60f, 5f, 0f );
// zombieEliteChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.15f, 0f );
// exploderChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.08f, 0f );
// exploderEliteChance = Utils.Map( t, 20 * 60f, 40 * 60f, 0.8f, 1f );
// spitterChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.1f, 0f );
// spitterEliteChance = Utils.Map( t, 20 * 60f, 30 * 60f, 0.08f, 0.25f );
// spikerChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.1f, 0f );
// spikerEliteChance = Utils.Map( t, 20 * 60f, 40 * 60f, 0.7f, 5f );
// chargerChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.15f, 0.02f );
// chargerEliteChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.6f, 4f );
// runnerChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.15f, 0.01f );
// runnerEliteChance = Utils.Map( t, 20 * 60f, 35 * 60f, 0.6f, 0.8f );
// }
// break;
// case 1:
// if ( t < 15f * 60f )
// {
// zombieChance = 1f;
// zombieEliteChance = t < (6.6f * 60) ? 0f : Utils.Map( t, (6.6f * 60), (20f * 60), 0.0175f, 1f, EasingType.SineIn );
// exploderChance = t < (0.5f * 60) ? 0f : Utils.Map( t, (0.5f * 60), (11.5f * 60), 0.02f, 0.08f );
// exploderEliteChance = t < (8.5f * 60) ? 0f : Utils.Map( t, (8.5f * 60), (20f * 60), 0.02f, 1f, EasingType.SineIn );
// spitterChance = t < (1.4f * 60) ? 0f : Utils.Map( t, (1.4f * 60), (12f * 60), 0.015f, 0.1f );
// spitterEliteChance = t < (8f * 60) ? 0f : Utils.Map( t, (8f * 60), (18f * 60), 0.021f, 1f, EasingType.QuadIn );
// spikerChance = t < (4.5f * 60) ? 0f : Utils.Map( t, (4.5f * 60), (13f * 60), 0.016f, 0.1f, EasingType.SineIn );
// spikerEliteChance = t < (9f * 60) ? 0f : Utils.Map( t, (9f * 60), (19f * 60), 0.008f, 1f, EasingType.SineIn );
// chargerChance = t < (6f * 60) ? 0f : Utils.Map( t, (6f * 60), (14f * 60), 0.02f, 0.075f );
// chargerEliteChance = t < (10f * 60) ? 0f : Utils.Map( t, (10f * 60), (18f * 60), 0.007f, 1f, EasingType.SineIn );
// runnerChance = t < (7.5f * 60) ? 0f : Utils.Map( t, (7.5f * 60), (14f * 60), 0.035f, 0.14f, EasingType.QuadIn );
// runnerEliteChance = t < (10.5f * 60) ? 0f : Utils.Map( t, (10.5f * 60), (22f * 60), 0.01f, 1f, EasingType.QuadIn );
// }
// else
// {
// zombieChance = Utils.Map( t, (15f * 60), (25f * 60), 1f, 0f, EasingType.Linear );
// zombieEliteChance = Utils.Map( t, (15f * 60), (35f * 60), 0.8f, 0f, EasingType.Linear );
// exploderChance = Utils.Map( t, (15f * 60), (35f * 60), 0.08f, 0f, EasingType.Linear );
// exploderEliteChance = Utils.Map( t, (15f * 60), (33f * 60), 0.8f, 8f, EasingType.Linear );
// spitterChance = Utils.Map( t, (15f * 60), (20f * 60), 0.1f, 0f, EasingType.Linear );
// spitterEliteChance = 1f;
// spikerChance = Utils.Map( t, (15f * 60), (19f * 60), 0.1f, 0f, EasingType.Linear );
// spikerEliteChance = Utils.Map( t, (15f * 60), (40f * 60), 0.7f, 10f, EasingType.Linear );
// chargerChance = Utils.Map( t, (15f * 60), (25f * 60), 0.075f, 0f, EasingType.Linear );
// chargerEliteChance = Utils.Map( t, (15f * 60), (37f * 60), 0.8f, 6f, EasingType.Linear );
// runnerChance = Utils.Map( t, (15f * 60), (23f * 60), 0.15f, 0f, EasingType.Linear );
// runnerEliteChance = Utils.Map( t, (15f * 60), (33f * 60), 0.6f, 0.05f, EasingType.Linear );
// }
// break;
// }
// chances.Add( (TypeLibrary.GetType( typeof( Zombie ) ), zombieChance ) );
// chances.Add( (TypeLibrary.GetType( typeof( ZombieElite ) ), zombieEliteChance) );
// chances.Add( (TypeLibrary.GetType( typeof( Exploder ) ), exploderChance) );
// chances.Add( (TypeLibrary.GetType( typeof( ExploderElite ) ), exploderEliteChance) );
// chances.Add( (TypeLibrary.GetType( typeof( Spitter ) ), spitterChance) );
// chances.Add( (TypeLibrary.GetType( typeof( SpitterElite ) ), spitterEliteChance) );
// chances.Add( (TypeLibrary.GetType( typeof( Spiker ) ), spikerChance) );
// chances.Add( (TypeLibrary.GetType( typeof( SpikerElite ) ), spikerEliteChance) );
// chances.Add( (TypeLibrary.GetType( typeof( Charger ) ), chargerChance) );
// chances.Add( (TypeLibrary.GetType( typeof( ChargerElite ) ), chargerEliteChance) );
// chances.Add( (TypeLibrary.GetType( typeof( Runner ) ), runnerChance) );
// chances.Add( (TypeLibrary.GetType( typeof( RunnerElite ) ), runnerEliteChance) );
// TypeDescription chosenEnemyType = null;
// float totalWeight = chances.Sum( x => x.Weight );
// var rand = Game.Random.Float( 0f, totalWeight );
// for ( int i = chances.Count - 1; i >= 0; i-- )
// {
// var (type, weight) = chances[i];
// rand -= weight;
// if ( rand < 0f )
// {
// chosenEnemyType = type;
// break;
// }
// }
// if(chosenEnemyType != null)
// {
// var pos = new Vector2( Game.Random.Float( BOUNDS_MIN_SPAWN.x, BOUNDS_MAX_SPAWN.x ), Game.Random.Float( BOUNDS_MIN_SPAWN.y, BOUNDS_MAX_SPAWN.y ) );
// SpawnEnemy( chosenEnemyType, pos );
// }
//}
[ConCmd( "spawn_enemy" )]
public static void GiveStatus( string name )
{
// Cheat only works in the editor
if ( !Game.IsEditor )
return;
var type = TypeLibrary.GetType( name );
if ( type == null )
{
Log.Info( $"No enemy with name '{name}' found!" );
return;
}
Manager.Instance?.SpawnEnemy( type, Utils.GetRandomVector() * 1f );
}
public Enemy SpawnEnemy( TypeDescription type, Vector2 pos, bool forceSpawn = false )
{
if ( EnemyCount >= MAX_ENEMY_COUNT && !forceSpawn )
return null;
GameObject enemyObj;
Enemy enemy;
var pos3 = new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) );
if ( type == TypeLibrary.GetType( typeof( Crate ) ) )
{
enemyObj = CratePrefab.Clone( pos3 );
CrateCount++;
}
else if ( type == TypeLibrary.GetType( typeof( Zombie ) ) ) { enemyObj = ZombiePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( ZombieElite ) ) ) { enemyObj = ZombieElitePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( Exploder ) ) ) { enemyObj = ExploderPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( ExploderElite ) ) ) { enemyObj = ExploderElitePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( ExploderSpecial ) ) ) { enemyObj = ExploderSpecialPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( Spitter ) ) ) { enemyObj = SpitterPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( SpitterElite ) ) ) { enemyObj = SpitterElitePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( SpitterSpecial ) ) ) { enemyObj = SpitterSpecialPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( SpitterEliteSpecial ) ) ) { enemyObj = SpitterEliteSpecialPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( Spiker ) ) ) { enemyObj = SpikerPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( SpikerSpecial ) ) ) { enemyObj = SpikerSpecialPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( SpikerElite ) ) ) { enemyObj = SpikerElitePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( Charger ) ) ) { enemyObj = ChargerPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( ChargerElite ) ) ) { enemyObj = ChargerElitePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( ChargerSpecial ) ) ) { enemyObj = ChargerSpecialPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( Runner ) ) ) { enemyObj = RunnerPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( RunnerElite ) ) ) { enemyObj = RunnerElitePrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( RunnerEliteSpecial ) ) ) { enemyObj = RunnerEliteSpecialPrefab.Clone( pos3 ); }
else if ( type == TypeLibrary.GetType( typeof( Boss ) ) ) { enemyObj = BossPrefab.Clone( pos3 ); }
else
{
Log.Info( $"Enemy {type} not implemented yet!" );
return null;
}
enemy = enemyObj.Components.Get<Enemy>();
var closestPlayer = GetClosestPlayer( pos );
if ( closestPlayer?.Position2D.x > pos.x && type != TypeLibrary.GetType( typeof( Crate ) ) )
{
enemy.FlipX = true;
enemy.Sprite.SpriteFlags = SpriteFlags.HorizontalFlip;
}
enemyObj.Name = type.ToString();
//enemyObj.NetworkSpawn( null );
enemy.Target = Player;
AddThing( enemy );
EnemyCount++;
PlaySfxNearby( "zombie.dirt", pos, pitch: Game.Random.Float( 0.6f, 0.8f ), volume: 0.7f, maxDist: 7.5f );
enemy.EnemyIdNum = _currEnemyIdNum++;
//if ( Difficulty >= 7 && type != TypeLibrary.GetType( typeof( Boss ) ) && type != TypeLibrary.GetType( typeof( Crate ) ) )
//{
// Log.Info( $"enemy.Health: {enemy.Health} Globals.DIFFICULTY_4_HP_BOOST: {Globals.DIFFICULTY_ENEMY_HP_BOOST}" );
// enemy.Health = enemy.Health * Globals.DIFFICULTY_ENEMY_HP_BOOST;
// enemy.MaxHealth = enemy.Health;
//}
return enemy;
}
public void SpawnCoin( Vector2 pos, Vector2 vel, int value = 1, bool force = false )
{
if ( CoinCount > 60 && !force )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( CoinCount, 60, 200, 0f, 1f, EasingType.QuadOut ) )
{
_coinDebt += value;
return;
}
}
//if ( CoinCount >= MAX_COIN_COUNT )
// return;
var coinObj = CoinPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var coin = coinObj.Components.Get<Coin>();
coin.Velocity = vel;
coin.SetValue( value + _coinDebt );
_coinDebt = 0;
//coinObj.NetworkSpawn( null );
AddThing( coin );
CoinCount++;
return;
}
public Magnet SpawnMagnet( Vector2 pos, Vector2 vel )
{
var magnetObj = MagnetPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var magnet = magnetObj.Components.Get<Magnet>();
magnet.Velocity = vel;
//magnetObj.NetworkSpawn( null );
TimeSinceMagnet = 0f;
AddThing( magnet );
return magnet;
}
public void SpawnReviveSoul( Vector2 pos, Vector2 vel )
{
var reviveObj = ReviveSoulPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var revive = reviveObj.Components.Get<ReviveSoul>();
revive.Velocity = vel;
//reviveObj.NetworkSpawn( null );
AddThing( revive );
}
public void SpawnHealthPack( Vector2 pos, Vector2 vel )
{
var healthPackObj = HealthPackPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var healthPack = healthPackObj.Components.Get<HealthPack>();
healthPack.Velocity = vel;
//healthPackObj.NetworkSpawn( null );
AddThing( healthPack );
}
public void SpawnRerollPickup( Vector2 pos, Vector2 vel )
{
var rerollPickupObj = RerollPickupPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var rerollPickup = rerollPickupObj.Components.Get<RerollPickup>();
rerollPickup.Velocity = vel;
AddThing( rerollPickup );
}
public EnemyBullet SpawnEnemyBullet( Vector2 pos, Vector2 dir, float speed )
{
var enemyBulletObj = EnemyBulletPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var enemyBullet = enemyBulletObj.Components.Get<EnemyBullet>();
enemyBullet.Direction = dir;
enemyBullet.Speed = speed;
if ( dir.x < 0f )
enemyBullet.Sprite.SpriteFlags = SpriteFlags.HorizontalFlip;
//enemyBulletObj.NetworkSpawn( null );
AddThing( enemyBullet );
return enemyBullet;
}
public EnemySpike SpawnEnemySpike( Vector2 pos, bool special = false )
{
var spikeObj = (special ? EnemySpikeSpecialPrefab : EnemySpikePrefab).Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var spike = spikeObj.Components.Get<EnemySpike>();
//spikeObj.NetworkSpawn( null );
AddThing( spike );
return spike;
}
public EnemySpikeElite SpawnEnemySpikeElite( Vector2 pos, Thing target = null )
{
var spikeObj = EnemySpikeElitePrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var spike = spikeObj.Components.Get<EnemySpikeElite>();
spike.Target = target;
//spikeObj.NetworkSpawn( null );
AddThing( spike );
return spike;
}
public void SpawnFire( Vector2 pos, Guid playerId )
{
var playerObj = Scene.Directory.FindByGuid( playerId );
Player player = playerObj?.Components.Get<Player>() ?? null;
if ( player == null )
return;
var fireObj = FirePrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var fire = fireObj.Components.Get<Fire>();
fire.Shooter = player;
fire.Lifetime = player.Stats[PlayerStat.FireLifetime];
//fireObj.NetworkSpawn( null );
AddThing( fire );
}
public void SpawnBoss( Vector2 pos )
{
Boss = SpawnEnemy( TypeLibrary.GetType( typeof( Boss ) ), pos, forceSpawn: true ) as Boss;
Boss.BossNum = 0;
if ( Difficulty >= 5 )
{
OtherBoss = SpawnEnemy( TypeLibrary.GetType( typeof( Boss ) ), new Vector2( Game.Random.Float( -2f, 2f ), Game.Random.Float( -2f, 2f ) ), forceSpawn: true ) as Boss;
OtherBoss.BossNum = 1;
OtherBoss.Sprite.Sprite = OtherBoss.OtherBossSprite;
}
PlaySfxNearby( "boss.fanfare", pos, pitch: 1.0f, volume: 1.3f, maxDist: 30f );
Player.ShakeCam( 0.025f, 2f, EasingType.SineIn );
}
public Crown SpawnCrown( Vector2 pos )
{
var crownObj = CrownPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ) );
var crown = crownObj.Components.Get<Crown>();
AddThing( crown );
return crown;
}
//private T GetClosest<T>( IEnumerable<T> enumerable, Vector3 pos, float maxRange, bool ignoreZ, T except )
// where T : Thing
//{
// var dists = ignoreZ
// ? enumerable.Select( x => (Thing: x, DistSq: (x.WorldPosition - pos).WithZ( 0f ).LengthSquared) )
// : enumerable.Select( x => (Thing: x, DistSq: (x.WorldPosition - pos).LengthSquared) );
// return dists.OrderBy( x => x.DistSq )
// //.FirstOrDefault( x => x.DistSq <= maxRange * maxRange && x.Thing != except && (!ignoreZ || x.Thing.Parent == null) )
// .FirstOrDefault( x => x.DistSq <= maxRange * maxRange && x.Thing != except )
// .Thing;
//}
public GridSquare GetGridSquareForPos( Vector2 pos )
{
return new GridSquare( (int)MathF.Floor( pos.x ), (int)MathF.Floor( pos.y ) );
}
public List<Thing> GetThingsInGridSquare( GridSquare gridSquare )
{
if ( ThingGridPositions.ContainsKey( gridSquare ) )
{
return ThingGridPositions[gridSquare];
}
return null;
}
public bool IsGridSquareInArena( GridSquare gridSquare )
{
return ThingGridPositions.ContainsKey( gridSquare );
}
public void RegisterThingGridSquare( Thing thing, GridSquare gridSquare )
{
if ( IsGridSquareInArena( gridSquare ) )
ThingGridPositions[gridSquare].Add( thing );
}
public void DeregisterThingGridSquare( Thing thing, GridSquare gridSquare )
{
if ( ThingGridPositions.ContainsKey( gridSquare ) && ThingGridPositions[gridSquare].Contains( thing ) )
{
ThingGridPositions[gridSquare].Remove( thing );
}
}
public void AddThing( Thing thing )
{
//_things.Add( thing );
thing.GridPos = GetGridSquareForPos( thing.Position2D );
RegisterThingGridSquare( thing, thing.GridPos );
}
public void RemoveThing( Thing thing )
{
if ( ThingGridPositions.ContainsKey( thing.GridPos ) )
ThingGridPositions[thing.GridPos].Remove( thing );
if ( thing is Enemy enemy ) // counts Crate too
{
EnemyCount--;
//if ( enemy.IsCharmed )
// CharmedEnemyCount--;
if ( thing is Crate )
CrateCount--;
}
else if ( thing is Coin )
{
CoinCount--;
}
}
public void HandleThingCollisionForGridSquare( Thing thing, GridSquare gridSquare, float dt )
{
if ( thing.IsProxy )
return;
if ( !ThingGridPositions.ContainsKey( gridSquare ) )
return;
var things = ThingGridPositions[gridSquare];
if ( things.Count == 0 )
return;
for ( int i = things.Count - 1; i >= 0; i-- )
{
if ( i >= things.Count )
continue;
if ( thing == null || !thing.IsValid() || thing.IsRemoved )
return;
var other = things[i];
if ( other == thing || other.IsRemoved || !other.IsValid() )
continue;
bool isValidType = false;
foreach ( var t in thing.CollideWith )
{
if ( t.IsAssignableFrom( other.GetType() ) )
{
isValidType = true;
break;
}
}
if ( !isValidType )
continue;
if ( other is Enemy enemy && (enemy.IsDying || enemy.IgnoreCollision) )
continue;
var dist_sqr = (thing.Position2D - other.Position2D).LengthSquared;
var total_radius_sqr = MathF.Pow( thing.Radius + other.Radius, 2f );
if ( dist_sqr < total_radius_sqr )
{
float percent = Utils.Map( dist_sqr, total_radius_sqr, 0f, 0f, 1f );
thing.Colliding( other, percent, dt * thing.TimeScale );
}
}
}
public void AddThingsInGridSquare( GridSquare gridSquare, List<Thing> things )
{
if ( !ThingGridPositions.ContainsKey( gridSquare ) )
return;
things.AddRange( ThingGridPositions[gridSquare] );
}
public void PlayerDied( Player player )
{
//int numPlayersAlive = GetPlayers(alive: true).Count();
//if ( numPlayersAlive == 0 )
GameOver();
}
public void GameOver()
{
if ( IsGameOver )
return;
IsGameOver = true;
IsVictory = false;
IsWaitingForFinalPanel = true;
_realTimeSinceFinalPanelWait = 0f;
ShowFinalPanel = false;
FinalRunTime = TimeSinceRunStart.Relative;
BroadcastLoss();
GameOverAsync();
}
async void GameOverAsync()
{
var enemies = Scene.GetAll<Enemy>().ToList();
enemies.Shuffle();
var currNum = 0;
while ( currNum < 20 && currNum < enemies.Count )
{
var enemy = enemies[currNum];
if ( IsGameOver && enemy.IsValid() && !enemy.IsDying )
{
PlaySfxNearby( "enemy_cheer", enemy.Position2D, Game.Random.Float( 0.7f, 0.8f ), 1f, 6f );
}
currNum++;
await Task.Delay( Game.Random.Int( 30, 110 ) );
}
}
void BroadcastLoss()
{
//Sandbox.Services.Stats.SetValue( GetStatName( Difficulty, survival: true ), ElapsedTime );
}
public void Victory()
{
if ( IsGameOver )
return;
IsGameOver = true;
IsVictory = true;
IsWaitingForFinalPanel = true;
_realTimeSinceFinalPanelWait = 0f;
ShowFinalPanel = false;
FinalRunTime = TimeSinceRunStart.Relative;
BroadcastVictory();
}
void BroadcastVictory()
{
Sandbox.Services.Stats.SetValue( GetStatName( Difficulty ), TimeSinceRunStart );
Log.Info( $"submitting_score: {GetStatName( Difficulty )}" );
//Sandbox.Services.Stats.SetValue( GetStatName( Difficulty, survival: true ), ElapsedTime );
if ( Difficulty >= 0 )
{
Sandbox.Services.Achievements.Unlock( "winner" );
bool isJackOfAllTradesValid = true;
foreach ( KeyValuePair<int, Status> pair in Player.Statuses )
{
Status status = pair.Value;
if ( status.Level > 1 )
{
isJackOfAllTradesValid = false;
break;
}
}
if ( isJackOfAllTradesValid )
Sandbox.Services.Achievements.Unlock( "jack_trades" );
}
//Sandbox.Services.Stats.SetValue( "victory_time_2", TimeSinceRunStart );
//Sandbox.Services.Stats.SetValue( "victory_time_add", TimeSinceRunStart );
//Sandbox.Services.Stats.Increment( "v_int_test", Game.Random.Int(1, 99) );
}
public BloodSplatter SpawnBloodSplatter( Vector2 pos )
{
if ( _bloodSplatters.Count > 30 )
{
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( _bloodSplatters.Count, 30, 80, 0f, 1f ) )
return null;
}
var bloodObj = BloodSplatterPrefab.Clone( new Vector3( pos.x, pos.y, Globals.BLOOD_DEPTH ) );
var bloodSplatter = bloodObj.Components.Get<BloodSplatter>();
bloodSplatter.Lifetime = Utils.Map( _lavaPuddles.Count, 0, 100, 10f, 1f ) * Game.Random.Float( 0.8f, 1.2f );
_bloodSplatters.Add( bloodSplatter );
return bloodSplatter;
}
public void RemoveBloodSplatter( BloodSplatter blood )
{
if ( _bloodSplatters.Contains( blood ) )
_bloodSplatters.Remove( blood );
}
public bool GetLavaBlobEndPos( Vector2 startPos, out Vector2 endPos )
{
endPos = ClampToBounds( startPos + Utils.GetRandomVector() * Game.Random.Float( 0f, 5f ), buffer: 1f );
int NUM_TRIES = 40;
int count = 0;
while ( IsInLava( endPos ) && count < NUM_TRIES )
{
endPos = ClampToBounds( startPos + Utils.GetRandomVector() * Game.Random.Float( 0f, 5f ), buffer: 1f );
count++;
}
count = 0;
while ( IsInLava( endPos ) && count < NUM_TRIES )
{
endPos = ClampToBounds( startPos + Utils.GetRandomVector() * Game.Random.Float( 5f, 6.5f ), buffer: 1f );
count++;
}
count = 0;
while ( IsInLava( endPos ) && count < NUM_TRIES )
{
endPos = ClampToBounds( startPos + Utils.GetRandomVector() * Game.Random.Float( 6.5f, 8f ), buffer: 1f );
count++;
}
return !IsInLava( endPos );
}
public LavaBlob SpawnLavaBlob( Vector2 startPos, Vector2 endPos )
{
var lavaBlobObj = LavaBlobPrefab.Clone( new Vector3( startPos.x, startPos.y, 0f ) );
var lavaBlob = lavaBlobObj.Components.Get<LavaBlob>();
lavaBlob.Init( startPos, endPos );
_lavaBlobs.Add( lavaBlob );
return lavaBlob;
}
public void RemoveLavaBlob( LavaBlob lavaBlob )
{
if ( _lavaBlobs.Contains( lavaBlob ) )
_lavaBlobs.Remove( lavaBlob );
}
bool IsInLava( Vector2 pos )
{
foreach ( var lavaPuddle in Scene.GetAllComponents<LavaPuddle>() )
{
if ( lavaPuddle.TimeSinceSpawn > lavaPuddle.Lifetime - 0.7f )
continue;
if ( (lavaPuddle.Position2D - pos).LengthSquared < MathF.Pow( lavaPuddle.Radius * 1.2f, 2f ) )
return true;
}
foreach ( var lavaBlob in Scene.GetAllComponents<LavaBlob>() )
{
if ( (lavaBlob.EndPos - pos).LengthSquared < MathF.Pow( 2f * 1.2f, 2f ) )
return true;
}
return false;
}
public LavaPuddle SpawnLavaPuddle( Vector2 pos, float lifetime, Color colorA, Color colorB, float damage, float radius )
{
var lavaPuddleObj = LavaPuddlePrefab.Clone( new Vector3( pos.x, pos.y, Globals.LAVA_PUDDLE_DEPTH ) );
var lavaPuddle = lavaPuddleObj.Components.Get<LavaPuddle>();
lavaPuddle.Lifetime = lifetime;
lavaPuddle.ColorA = colorA;
lavaPuddle.ColorB = colorB;
lavaPuddle.DamageToPlayer = damage;
lavaPuddle.FullRadius = radius;
_lavaPuddles.Add( lavaPuddle );
return lavaPuddle;
}
public void RemoveLavaPuddle( LavaPuddle lavaPuddle )
{
if ( _lavaPuddles.Contains( lavaPuddle ) )
_lavaPuddles.Remove( lavaPuddle );
}
public Cloud SpawnCloud( Vector2 pos )
{
var cloudObj = CloudPrefab.Clone( new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) ), new Angles( 0f, -90f, 0f ) );
var cloud = cloudObj.Components.Get<Cloud>();
cloud.Lifetime = 0.7f * Game.Random.Float( 0.8f, 1.2f );
_clouds.Add( cloud );
return cloud;
}
public void RemoveCloud( Cloud cloud )
{
if ( _clouds.Contains( cloud ) )
_clouds.Remove( cloud );
}
public ExplosionEffect SpawnExplosionEffect( Vector2 pos, Color colorA, Color colorB, float opacity = 0.8f, float scaleModifier = 1f )
{
var explosionObj = ExplosionEffectPrefab.Clone( new Vector3( pos.x, pos.y, 100f ) );
var explosion = explosionObj.Components.Get<ExplosionEffect>();
explosion.Lifetime = 0.5f;
explosion.LocalScale *= scaleModifier;
explosion.Init( colorA, colorB, opacity );
_explosions.Add( explosion );
return explosion;
}
public void RemoveExplosionEffect( ExplosionEffect explosion )
{
if ( _explosions.Contains( explosion ) )
_explosions.Remove( explosion );
}
public RingEffect SpawnRingEffect( Vector2 pos, Color colorA, Color colorB, float damage = 0f, float opacity = 0.8f, float scaleMin = 1f, float scaleMax = 1f, float depth = 100f )
{
var ringObj = RingEffectPrefab.Clone( new Vector3( pos.x, pos.y, depth ) );
var ring = ringObj.Components.Get<RingEffect>();
ring.Lifetime = 0.5f;
ring.ScaleMin = scaleMin;
ring.ScaleMax = scaleMax;
ring.Init( colorA, colorB, opacity );
ring.Position2D = pos;
ring.Damage = damage;
_ringEffects.Add( ring );
return ring;
}
public void RemoveRingEffect( RingEffect ring )
{
if ( _ringEffects.Contains( ring ) )
_ringEffects.Remove( ring );
}
public Warning SpawnWarning( Vector2 pos, Color colorA, Color colorB )
{
var warningObj = WarningPrefab.Clone( new Vector3( pos.x, pos.y, Globals.WARNING_DEPTH ), new Angles( 0f, -90f, 0f ) );
var warning = warningObj.Components.Get<Warning>();
warning.ColorA = colorA;
warning.ColorB = colorB;
_warnings.Add( warning );
return warning;
}
public void RemoveWarning( Warning warning )
{
if ( _warnings.Contains( warning ) )
_warnings.Remove( warning );
}
public SatelliteLaser SpawnSatelliteLaser( Vector2 pos )
{
var laserObj = SatelliteLaserPrefab.Clone( new Vector3( pos.x, pos.y, Globals.WARNING_DEPTH ) );
var laser = laserObj.Components.Get<SatelliteLaser>();
laser.Init();
_satelliteLasers.Add( laser );
return laser;
}
public void RemoveSatelliteLaser( SatelliteLaser laser )
{
if ( _satelliteLasers.Contains( laser ) )
_satelliteLasers.Remove( laser );
}
public void Restart()
{
MusicManager.Restart();
foreach ( var blood in _bloodSplatters )
blood.GameObject.Destroy();
_bloodSplatters.Clear();
foreach ( var lavaPuddle in _lavaPuddles )
lavaPuddle.GameObject.Destroy();
_lavaPuddles.Clear();
foreach ( var lavaBlob in _lavaBlobs )
lavaBlob.GameObject.Destroy();
_lavaBlobs.Clear();
foreach ( var cloud in _clouds )
cloud.GameObject.Destroy();
_clouds.Clear();
foreach ( var explosion in _explosions )
explosion.GameObject.Destroy();
_explosions.Clear();
foreach ( var ring in _ringEffects )
ring.GameObject.Destroy();
_ringEffects.Clear();
foreach ( var warning in _warnings )
warning.GameObject.Destroy();
_warnings.Clear();
foreach ( var laser in _satelliteLasers )
laser.GameObject.Destroy();
_satelliteLasers.Clear();
foreach ( KeyValuePair<GridSquare, List<Thing>> pair in ThingGridPositions )
pair.Value.Clear();
EnemyCount = 0;
CharmedEnemyCount = 0;
CrateCount = 0;
CoinCount = 0;
_coinDebt = 0;
_timeSinceEnemySpawn = 0f;
ElapsedTime = 0f;
TimeSinceRunStart = 0f;
IsGameOver = false;
IsWaitingForFinalPanel = false;
HasSpawnedBoss = false;
IsBossDead = false;
NumBossesKilled = 0;
IsBoss0Dead = false;
IsBoss1Dead = false;
Boss = null;
TimeSinceMagnet = 0f;
Hud.Instance?.FadeIn();
Camera.OrthographicHeight = 10f;
//Components.Get<PauseMenu>().IsOpen = false;
IsPauseMenuOpen = false;
if ( IsProxy )
return;
foreach ( Thing thing in Scene.GetAllComponents<Thing>() )
{
if ( thing is Player player )
player.Restart();
else
thing.GameObject.Destroy();
}
//foreach ( var number in Scene.GetAllComponents<LegacyParticleSystem>() )
// number.GameObject.Destroy();
foreach ( var number in Scene.GetAllComponents<ParticleEffect>() )
number.GameObject.Destroy();
_currEnemyIdNum = 0;
SpawnStartingThings();
}
public void PlayEnemyDeathSfxLocal( Vector3 worldPos )
{
if ( _numEnemyDeathSfxs >= 3 )
return;
PlaySfxNearby( "enemy.die", worldPos, pitch: Game.Random.Float( 0.85f, 1.15f ), volume: 1f, maxDist: 5.5f );
_numEnemyDeathSfxs++;
}
public void PlaySfxNearby( string name, Vector2 worldPos, float pitch, float volume, float maxDist )
{
maxDist *= Globals.SFX_DIST_MODIFIER;
var player = GetLocalPlayer();
if ( player == null )
return;
var playerPos = player.Position2D;
var distSqr = (player.Position2D - worldPos).LengthSquared;
if ( distSqr < maxDist * maxDist )
{
var dist = (player.Position2D - worldPos).Length;
var falloff = Utils.Map( dist, 0f, maxDist, 1f, 0f, EasingType.SineIn );
var pos = playerPos + (worldPos - playerPos) * 0.1f;
player.PlaySfx( name, pos, pitch * Globals.SFX_PITCH_MODIFIER, volume * falloff );
}
}
public Player GetLocalPlayer()
{
foreach ( var player in Players )
{
if ( player.Network.IsOwner )
return player;
}
return null;
}
public void AddPlayer( Player player )
{
Players.Add( player );
}
public void RemovePlayer( Player player )
{
Players.Remove( player );
}
private List<Player> _players = new();
public List<Player> GetPlayers( bool alive = true )
{
_players.Clear();
foreach ( var player in Players )
{
if ( alive != player.IsDead )
_players.Add( player );
}
return _players;
}
public Player GetClosestPlayer( Vector2 pos, bool alive = true )
{
Player closestPlayer = null;
float closestDistSqr = float.MaxValue;
foreach ( var player in Players )
{
if ( alive != player.IsDead )
{
var distSqr = (player.Position2D - pos).LengthSquared;
if ( distSqr < closestDistSqr )
{
closestDistSqr = distSqr;
closestPlayer = player;
}
}
}
return closestPlayer;
}
public Vector2 ClampToBounds( Vector2 pos, float buffer = 0.3f )
{
return new Vector2( MathX.Clamp( pos.x, Manager.Instance.BOUNDS_MIN.x + buffer, Manager.Instance.BOUNDS_MAX.x - buffer ), MathX.Clamp( pos.y, Manager.Instance.BOUNDS_MIN.y + buffer, Manager.Instance.BOUNDS_MAX.y - buffer ) );
}
public void ShakePlayerCam( Vector2 pos, float radius, float maxStrength, float time )
{
if ( (Player.Position2D - pos).LengthSquared < MathF.Pow( radius, 2f ) )
{
Player.ShakeCam( Utils.Map( (Player.Position2D - pos).Length, 0f, radius, maxStrength, 0f, EasingType.Linear ), time, EasingType.Linear );
}
}
public static string GetStatName( int difficulty, bool survival = false )
{
//if ( survival )
//{
// switch ( difficulty )
// {
// case -1: return "end_time_easy";
// case 0: return "end_time";
// default: return $"end_time_diff_{difficulty}";
// }
//}
//else
//{
switch ( difficulty )
{
case -1: return "victory_elapsed_time_easy_2";
case 0: return "victory_elapsed_time";
case 5: return "victory_elapsed_time_diff_5_c";
case 6: return "victory_elapsed_time_diff_6_b";
default: return $"victory_elapsed_time_diff_{difficulty}";
}
//}
}
public static string GetNameForDifficulty( int difficulty )
{
switch ( difficulty )
{
case -1: return "Easy Difficulty";
case 0: return "Normal Difficulty";
default: return $"Difficulty +{difficulty}";
}
}
public static string GetDescriptionForDifficulty( int difficulty )
{
switch ( difficulty )
{
case -1: return "achievements disabled";
case 0: default: return "";
case 1: return "new enemy behavior";
case 2: return "special enemies appear";
case 3: return "less resources";
case 4: return "basic zombies are more dangerous";
case 5: return "twin bosses";
case 6: return "cursed every 10 levels";
case 7: return "cursed every 9 levels";
case 8: return "cursed every 8 levels";
case 9: return "cursed every 7 levels";
case 10: return "cursed every 6 levels";
case 11: return "cursed every 5 levels";
case 12: return "cursed every 4 levels";
case 13: return "cursed every 3 levels";
case 14: return "cursed every other level";
case 15: return "cursed 2/3 levels";
}
}
public static Color GetDifficultyLabelColor( int difficulty )
{
switch ( difficulty )
{
case -1: return new Color( 1f, 0.5f, 1f, 0.6f );
case 0: return new Color( 1f, 1f, 1f, 0.5f );
default: return Color.Lerp( new Color( 1f, 0.5f, 0.5f, 0.5f ), new Color( 1f, 0f, 0f, 0.7f ), Utils.Map( difficulty, 0, Manager.MaxDifficulty, 0f, 1f, EasingType.Linear ) );
}
}
public void SpawnDamageNumber( Vector3 pos, float damage, Color color, float size = 1f, FloaterType floaterType = FloaterType.Damage )
{
int amount = GetFloaterNumber( damage );
SpawnFloaterParticle( pos, amount.ToString(), color, size, floaterType );
}
public int GetFloaterNumber( float amount )
{
if ( amount < 1f )
{
amount = MathF.Ceiling( amount );
}
else
{
float fractional = amount - MathF.Floor( amount );
if ( fractional > 0f && Game.Random.Float( 0f, 1f ) > fractional ) // the higher the fractional, the lower the chance of rolling higher than it, so Floor when doing so
amount = MathF.Floor( amount );
else
amount = MathF.Ceiling( amount );
}
return (int)amount;
}
public void SpawnFloaterParticle( Vector3 pos, string message, Color color, float size = 1f, FloaterType floaterType = FloaterType.Damage )
{
if ( color == default )
color = Color.White;
pos = pos.WithZ( 200f );
size *= 8f;
var particle = FloaterParticleTextPrefab.Clone( new CloneConfig()
{
Transform = new Transform( pos, new Angles( 0, 90, 0 ) ),
StartEnabled = true
} );
var particleEffect = particle.Components.Get<ParticleEffect>();
if ( floaterType == FloaterType.Xp )
message += " XP";
var textRenderer = particle.Components.Get<ParticleTextRenderer>();
if ( textRenderer != null )
{
var text = new TextRendering.Scope( message, color, 44, weight: 800 );
text.FontName = "Mini Pixel-7";
text.Outline.Enabled = true;
text.Outline.Color = Color.Black;
text.Outline.Size = 8;
//textRenderer.Scale = 1.5f;
//text.FontItalic = true;
textRenderer.Text = text;
textRenderer.Scale = size;
}
if ( floaterType == FloaterType.Heal )
{
particleEffect.StartVelocity = Game.Random.Float( 0f, 0.1f );
particleEffect.ForceDirection = new Vector3( 0f, Game.Random.Float( 10f, 20f ), 0f );
particleEffect.Damping = 0f;
particleEffect.Stretch = 0f;
}
else if ( floaterType == FloaterType.Xp )
{
particleEffect.StartVelocity = Game.Random.Float( 0.3f, 0.4f );
particleEffect.ForceDirection = new Vector3( 0f, Game.Random.Float( 40f, 50f ), 0f );
particleEffect.Damping = 0.03f;
particleEffect.Stretch = 0f;
particleEffect.Lifetime = 0.5f;
}
}
public void BossDied( int bossNum )
{
if ( Difficulty >= 5 )
{
if ( bossNum == 0 )
IsBoss0Dead = true;
else
IsBoss1Dead = true;
NumBossesKilled++;
if ( NumBossesKilled >= 2 )
IsBossDead = true;
}
else
{
IsBossDead = true;
}
}
}