public static void OnSpawnEnemy(ScenarioWave.Enemy enemy, int waveIndex, int enemyIndex)
 {
     if (s_instance != null)
     {
         s_instance.OnSpawnEnemyInternal(enemy, waveIndex, enemyIndex);
     }
 }
    public void OnSpawnEnemyInternal(ScenarioWave.Enemy enemy, int waveIndex, int enemyIndex)
    {
        //Debug.Log(DebugUtilities.AddTimestampPrefix("Wave " + waveIndex + " spawning enemy " + enemyIndex + " at time " + RoundTime));

        Vector3    startPosition  = Vector3.Lerp(new Vector3(m_gameArea.xMin, m_gameArea.yMax, 0f), new Vector3(m_gameArea.xMax, m_gameArea.yMax, 0f), Random.value);
        Vector3    targetPosition = Vector3.Lerp(new Vector3(m_gameArea.xMin, 0f, 0f), new Vector3(m_gameArea.xMax, 0f, 0f), Random.value);
        Quaternion rotation       = Quaternion.LookRotation(targetPosition - startPosition, Vector3.back);

        Entity entity = OnSpawnEntityInternal(enemy.m_entityPrefab, startPosition, rotation);

        if (entity != null)
        {
            entity.name = enemy.m_entityPrefab.name + "_" + waveIndex + "-" + enemyIndex;

            if (entity is Projectile)
            {
                Projectile p     = entity as Projectile;
                float      speed = enemy.m_speed * (ScenarioManager.Scenario != null ? ScenarioManager.Scenario.m_globalSpeedMultiplier : 1f);
                p.Initialize(p.ID, startPosition, targetPosition, speed, enemy.m_modelPrefab);
            }
        }

        //DebugUtilities.DrawArrow(source, target, Vector3.back, Color.red, 0, 0.5f);
    }
    public static ScenarioWave[] GenerateWaves(int roundIndex, ScenarioPreset preset)
    {
        float t         = preset.m_waveCountCurve.Evaluate(roundIndex / (preset.m_waveCountRoundMax - 1f));
        int   waveCount = t < 1f ? Mathf.FloorToInt(Mathf.Lerp(preset.m_waveCountMin, preset.m_waveCountMax, t)) : preset.m_waveCountMax;

        ScenarioWave wave;

        ScenarioWave[] waves               = new ScenarioWave[waveCount];
        float[]        wavePickRolls       = new float[waveCount];
        float[]        waveEnemyCountRolls = new float[waveCount];

        t = preset.m_waveIntervalCurve.Evaluate(roundIndex / (preset.m_waveIntervalRoundMax - 1f));
        float waveInterval = Mathf.Lerp(preset.m_waveIntervalMin, preset.m_waveIntervalMax, t);

        Debug.Log(DebugUtilities.AddTimestampPrefix("Round " + (roundIndex + 1) + " will have " + waveCount + " waves in it, interval=" + waveInterval));

        float      enemyWaveCountWeightTotal, enemySpeed;
        int        i, p, waveCountToSpawnEnemyIn, enemyCountTotal, remainingEnemyCount;
        List <int> pickedWaveIndices;

        int[] pickedWaveEnemyCounts;
        ScenarioPreset.Enemy enemyPreset;

        ScenarioWave.Enemy[] waveEnemies;

        // TODO: Optimize this, it's stupid now
        for (int e = 0; e < preset.m_enemyPresets.Length; e++)
        {
            enemyPreset = preset.m_enemyPresets[e];

            if (roundIndex < enemyPreset.m_firstRoundToSpawn - 1)
            {
                continue;
            }

            // Determine how many enemies to spawn in total
            if (roundIndex == enemyPreset.m_firstRoundToSpawn - 1)
            {
                enemyCountTotal = enemyPreset.m_spawnCountMin;
            }
            else
            {
                t = enemyPreset.m_spawnCountCurve.Evaluate((roundIndex - (enemyPreset.m_firstRoundToSpawn - 1f)) / (enemyPreset.m_spawnCountRoundMax - enemyPreset.m_firstRoundToSpawn));
                enemyCountTotal = t < 1f ? Mathf.FloorToInt(Mathf.Lerp(enemyPreset.m_spawnCountMin, enemyPreset.m_spawnCountMax, t)) : enemyPreset.m_spawnCountMax;
            }

            Debug.Log(DebugUtilities.AddTimestampPrefix("Round " + (roundIndex + 1) + " will spawn a total of " + enemyCountTotal + " instances of enemy " + enemyPreset.m_enemyPrefab.name));

            // Roll for each wave after first to determine which ones to pick and the weight value for the enemy count
            for (i = 0; i < waveCount; i++)
            {
                // First wave is always picked
                wavePickRolls[i]       = i == 0 ? 2f : Random.value * enemyPreset.m_waveProbabilityCurve.Evaluate((float)i / waveCount);
                waveEnemyCountRolls[i] = Random.value;
                //Debug.Log(DebugUtilities.AddTimestampPrefix("Round " + (roundIndex + 1) + " Wave[" + i + "] pick roll: " + wavePickRolls[i] + ", enemy count roll: " + waveEnemyCountRolls[i]));
            }

            waveCountToSpawnEnemyIn = Mathf.FloorToInt(enemyPreset.m_waveCountMultiplier * waveCount);
            pickedWaveIndices       = new List <int>(waveCountToSpawnEnemyIn);
            pickedWaveEnemyCounts   = new int[waveCountToSpawnEnemyIn];

            enemyWaveCountWeightTotal = 0f;

            // Gather the highest rolled indices of the picked waves
            for (i = 0; i < waveCountToSpawnEnemyIn; i++)
            {
                pickedWaveIndices.Add(0);

                t = 0f;
                for (p = 0; p < waveCount; p++)
                {
                    if (wavePickRolls[p] > t)
                    {
                        t = wavePickRolls[p];
                        pickedWaveIndices[i] = p;
                    }
                }

                //Debug.Log(DebugUtilities.AddTimestampPrefix("Wave[" + pickedWaveIndices[i] + "] picked, roll: " + wavePickRolls[pickedWaveIndices[i]]));

                // Add to enemy count weight total so the amount can be distributed properly
                enemyWaveCountWeightTotal += waveEnemyCountRolls[pickedWaveIndices[i]];

                // Strike down the best pick from the rolls so it won't be considered again
                wavePickRolls[pickedWaveIndices[i]] = -1f;
            }

            pickedWaveIndices.Sort(SortWaves);

            remainingEnemyCount = enemyCountTotal;

            Debug.Log(DebugUtilities.AddTimestampPrefix("Round " + (roundIndex + 1) + " will spawn these enemies in " + waveCountToSpawnEnemyIn + " waves"));

            // Calculate how many enemies to spawn in each chosen wave
            for (i = 0; i < waveCountToSpawnEnemyIn; i++)
            {
                p = pickedWaveIndices[i];

                // Get the wave in question or create it
                if (waves[p] == null)
                {
                    wave     = new ScenarioWave(i, p * waveInterval);
                    waves[p] = wave;
                }
                else
                {
                    wave = waves[p];
                }

                // Last wave should have all the remaining enemies, otherwise floor to int from roll values
                if (i == waveCountToSpawnEnemyIn - 1)
                {
                    pickedWaveEnemyCounts[i] = remainingEnemyCount;
                }
                else
                {
                    pickedWaveEnemyCounts[i] = Mathf.Max(Mathf.FloorToInt(enemyCountTotal * waveEnemyCountRolls[p] / enemyWaveCountWeightTotal), 1);
                    remainingEnemyCount     -= pickedWaveEnemyCounts[i];
                }

                t = Mathf.Pow(enemyPreset.m_speedMultiplierGain, roundIndex);
                if (enemyPreset.m_speedMultiplierMax > 1f)
                {
                    t = Mathf.Clamp(t, 1f, enemyPreset.m_speedMultiplierMax);
                }

                Debug.Log(DebugUtilities.AddTimestampPrefix("Wave[" + p + "] will spawn " + pickedWaveEnemyCounts[i] + " enemies of type " + enemyPreset.m_enemyPrefab.name + " with speed multiplier of " + t + " at time " + wave.WaveTime));

                // Generate the enemies in this wave
                waveEnemies = new ScenarioWave.Enemy[pickedWaveEnemyCounts[i]];

                for (p = 0; p < pickedWaveEnemyCounts[i]; p++)
                {
                    // Calculate the speed for each enemy individually
                    enemySpeed     = t * Mathf.Lerp(enemyPreset.m_speedMin, enemyPreset.m_speedMax, enemyPreset.m_speedCurve.Evaluate(Random.value));
                    waveEnemies[p] = new ScenarioWave.Enemy(enemyPreset.m_enemyPrefab, enemyPreset.m_modelPrefab, enemySpeed);
                }

                wave.AddEnemies(waveEnemies);
            }
        }

        return(waves);
    }