public class EnemyManager { private final int ENEMY_POOL_LENGTH = 999; private final int PATTERN_NONE = 0; private final int PATTERN_VERTILINE = 1; private final int PATTERN_HORILINE = 2; private final int PATTERN_STAGGEREDLINE = 3; private final int PATTERN_DIAGLINE = 4; private final int PATTERN_ARROW = 5; private final int PATTERN_ARROWREVERSE = 6; private final int PATTERN_USHAPE = 7; private final int PATTERN_CIRCLE = 8; //Enemy management private EnemyBase[] enemyPool; private ArrayList activeEnemies; //Timer variables private int lastSpawn; private int milliSeconds; private int extraDelayDuration; //Wave System private int waveEnemyCount; private int waveCompletionPauseDuration = 500; private boolean waveSpawnBoss = false; private int currentWaveEnemyCount; private int waveCompletionTime; private boolean waveIsActive; public ArrayList getActiveEnemies() { return activeEnemies; } public EnemyManager() { activeEnemies = new ArrayList(); enemyPool = new EnemyBase[ENEMY_POOL_LENGTH]; for (int i = 0; i < ENEMY_POOL_LENGTH; i++) { enemyPool[i] = new EnemyBase(); } lastSpawn = millis(); extraDelayDuration = 0; } /** Draws all the enemies on the screen. */ public void draw() { for (int i = 0; i < activeEnemies.size (); ++i) { EnemyBase currEnemy = activeEnemies.get(i); currEnemy.draw(); } } /** Starts a new wave. Copies wave-settings from Difficulty and resets some values. */ public void startNewWave() { difficulty.onNewWaveStart(); waveEnemyCount = difficulty.waveEnemyCount; waveSpawnBoss = difficulty.waveSpawnBoss; currentWaveEnemyCount = 0; waveIsActive = true; } /** Gets called when the wave ends; sets up the wave-pause timer. */ public void onWaveEnd() { waveIsActive = false; waveCompletionTime = millis(); } public void update() { milliSeconds = millis(); //Check if the all the enemies of the current wave have spawned if (waveIsActive && spawnedAllEnemies() && activeEnemies.size() == 0) { if (waveSpawnBoss) spawnBoss(); else onWaveEnd(); } //Depending on whether the wave is active, check a different set of timers if (waveIsActive) checkSpawnTimer(); else checkWavePauseTimer(); updateEnemies(); } /** Spawns a boss. (CURRENTLY NOT IMPLEMENTED) */ public void spawnBoss() { //temporary, no boss spawns println("BOSS SPAWN PLS"); onWaveEnd(); } /** Checks during the 'wave-break' if its time to start the new wave. */ public void checkWavePauseTimer() { if (milliSeconds >= waveCompletionTime+waveCompletionPauseDuration+extraDelayDuration) startNewWave(); } /** Checks if its time to spawn a new enemy and spawns one if it is. */ public void checkSpawnTimer() { if (!spawnedAllEnemies() && milliSeconds >= lastSpawn+difficulty.enemySpawnDelay+extraDelayDuration) { lastSpawn = milliSeconds; extraDelayDuration = 0; spawnEnemy(); } } public void updateEnemies() { //Update all the active enemies for (int i = 0; i < activeEnemies.size (); ++i) { EnemyBase currEnemy = activeEnemies.get(i); if ( checkOutOfBounds(currEnemy) ) { if (data.specialCharge < 50) { data.specialCharge = max(0, data.specialCharge - 1); popUpManager.createPopUp(width * 0.12, data.heightPoint + 6, "-1", color(123, 247, 255), 6, 3, 24); } removeEnemy(currEnemy); continue; } currEnemy.update(); //And check if any of them is colliding with the active bullets for (int j = 0; j < bulletManager.getActiveBullets ().size(); j++) { BulletBase bullet = bulletManager.getActiveBullets().get(j); checkForBulletCollision(currEnemy); } } } private void checkForBulletCollision(EnemyBase curEnemy) { for (int i = 0; i < bulletManager.getActiveBullets ().size(); i++) { BulletBase bullet = bulletManager.getActiveBullets().get(i); if (curEnemy.isDead) continue; if (bullet.owner == bulletManager.OWNER_PLAYER && hasBoxCollision(curEnemy.x, curEnemy.y, curEnemy.w, curEnemy.h, bullet.x, bullet.y, bullet.w, bullet.h)) { if (bullet.type == elements.TYPE_WIND) { if (!bullet.hasHitted) { curEnemy.onBulletHit(bullet); bullet.aoeCollisionCheck(curEnemy); } } else if (bullet.type == elements.TYPE_WATER) { if (!bullet.hasHitted) curEnemy.onBulletHit(bullet); bullet.aoeCollisionCheck(); } else if (bullet.explosiveShot) { bullet.aoeCollisionCheck(); } else { curEnemy.onBulletHit(bullet); if (!bullet.piercingShot) bulletManager.removeBullet(bullet); data.score += 1; } } } } /** Gets called when the game exits the pause menu. Used to delay all the timers so the game doesn't break. */ public void unpause() { extraDelayDuration += timer.getLatestPauseDuration(); for (int i = 0; i < activeEnemies.size (); ++i) { EnemyBase currEnemy = activeEnemies.get(i); currEnemy.unpause(); } } /** Removes an enemy from the active enemies list */ public void removeEnemy(EnemyBase enemy) { enemy.isActive = false; activeEnemies.remove(enemy); } /** Removes all the active enemies. */ public void removeAll() { activeEnemies.clear(); } /* private final int PATTERN_NONE = 0; private final int PATTERN_VERTILINE = 1; private final int PATTERN_HORILINE = 2; private final int PATTERN_STAGGEREDLINE = 3; private final int PATTERN_DIAGLINE = 4; private final int PATTERN_ARROW = 5; private final int PATTERN_ARROWREVERSE = 6; private final int PATTERN_USHAPE = 7; private final int PATTERN_CIRCLE = 8;*/ /** Spawn an enemy by */ public void spawnEnemy() { int pattern = (int)random(5) + 1; pattern = checkPattern(pattern); int enemyCount = 0, distance = 0, newY = -35, addYForEach = 0, newX = 0, difference = 0, spacingY = 0; int startX = (int)random(data.GAME_WIDTH - 40) + 40; int startY = 0; switch (pattern) { //Default case 0: enemyCount = ceil(random(min(4, enemiesLeft()) * difficulty.patternSizeMultipler, min(16, enemiesLeft()) * difficulty.patternSizeMultipler)); for (int i = 0; i < enemyCount; ++i) { EnemyBase createdEnemy = createEnemy(startX, -50); startX = (int)random(data.GAME_WIDTH - createdEnemy.w) + (int)createdEnemy.w; } break; //Vertical Line case 1: enemyCount = ceil(random(min(4, enemiesLeft()) * difficulty.patternSizeMultipler, min(16, enemiesLeft()) * difficulty.patternSizeMultipler)); distance = (int) (600 * difficulty.patternSizeMultipler); addYForEach = distance / enemyCount; for (int i = 0; i < enemyCount; ++i) { createEnemy(startX, newY); newY -= addYForEach; } break; //Horizontal Line case 2: enemyCount = ceil(random(min(4, enemiesLeft()) * difficulty.patternSizeMultipler, min(6, enemiesLeft()) * difficulty.patternSizeMultipler)); spacingY = 150; difference = 50; distance = (difference * enemyCount > (int)data.GAME_WIDTH ? ((int)data.GAME_WIDTH / difference) : enemyCount); do { newX = (int)random(data.GAME_WIDTH - 10) + 10; } while (newX + distance > data.GAME_WIDTH - 10); startX = newX; for (int i = 0; i < enemyCount; ++i) { EnemyBase createdEnemy = createEnemy(newX, newY); newX += difference; if (newX + createdEnemy.w > (int)data.GAME_WIDTH) { newX = startX; newY -= spacingY; } } break; //Staggered (Vertical) Line case 3: enemyCount = ceil(random(min(5, enemiesLeft()) * difficulty.patternSizeMultipler, min(16, enemiesLeft()) * difficulty.patternSizeMultipler)); startX += 75; distance = 600; int differenceInX = 100; addYForEach = distance / enemyCount; for (int i = 0; i < enemyCount; ++i) { createEnemy(startX, newY); if (i % 2 != 0) startX += differenceInX; else startX -= differenceInX; newY -= addYForEach; } break; //Diagonal Line Right case 4: enemyCount = ceil(random(min(4, enemiesLeft()) * difficulty.patternSizeMultipler, min(16, enemiesLeft()) * difficulty.patternSizeMultipler)); spacingY = 300; difference = 50; distance = 300; do { newX = (int)random(data.GAME_WIDTH-250); } while (newX + distance < data.GAME_WIDTH-250); startX = newX; startY = newY; for (int i = 0; i < enemyCount; ++i) { EnemyBase createdEnemy = createEnemy(newX, newY); newX += difference; newY -= difference; if (newX + createdEnemy.w > data.GAME_WIDTH) { newX = startX; startY -= spacingY; newY = startY; } } break; //Arrow Shape case 5: do { //Safety check! Check if the remaining is an even number. otherwise continue. if (enemiesLeft() % 2 == 0) return; enemyCount = ceil(random(min(5, enemiesLeft()) * difficulty.patternSizeMultipler, min(15, enemiesLeft()) * difficulty.patternSizeMultipler)); } while (enemyCount % 2 == 0); spacingY = 250; difference = 50; boolean reachedMiddle = false; int maxArrowCount = (difference * enemyCount > (int)data.GAME_WIDTH ? ((int)data.GAME_WIDTH / difference) : enemyCount); int arrowCount = maxArrowCount; //Calculate the arrow starting position if (maxArrowCount == (int)data.GAME_WIDTH / difference) { newX = 5; } else { do { newX = (int)random(data.GAME_WIDTH - 10) + 10; } while (newX * maxArrowCount > data.GAME_WIDTH - 10); } newY = -((int)(maxArrowCount / 2) * difference); startX = newX; startY = newY; for (int i = 0; i < enemyCount; ++i) { EnemyBase createdEnemy = createEnemy(newX, newY); //Check if the arrow has reached the middle part. if ( !reachedMiddle && i == floor((arrowCount - 1) / 2)) { reachedMiddle = true; } newX += difference; if (reachedMiddle) newY -= difference; else newY += difference; //Check if the remaining enemies can still make an arrow. if (i == arrowCount - 1 && (enemyCount - i -1) < maxArrowCount) break; //Check if the next enemy position is outside the game window. //If so start a new arrow. if (newX + createdEnemy.w > data.GAME_WIDTH) { newX = startX; startY -= spacingY; newY = startY; //Start a new arrow; reachedMiddle = false; arrowCount += (maxArrowCount * 2); } } break; } } /** Creates an enemy with a random element and returns it. */ public EnemyBase createEnemy(int enemyX, int enemyY) { for (int i = 0; i < enemyPool.length; ++i) { EnemyBase currEnemy = enemyPool[i]; if (!currEnemy.isActive) { int newType = (int)random(4) + 1; switch(newType) { case 1: currEnemy = new EnemyFire(); break; case 2: currEnemy = new EnemyWater(); break; case 3: currEnemy = new EnemyWind(); break; case 4: currEnemy = new EnemyEarth(); break; } enemyPool[i] = currEnemy; activeEnemies.add(currEnemy); currEnemy.init(); currEnemy.x = enemyX; currEnemy.y = enemyY; currentWaveEnemyCount++; return currEnemy; } } println("ERROR: Enemy Pool isn't big enough!"); return null; } //-------------------------------------------------- //------------------RETURN METHODS------------------ //-------------------------------------------------- public int checkPattern(int pattern) { switch (pattern) { case 1: pattern = enemiesLeft() > 4 * difficulty.patternSizeMultipler ? 1 : 0; break; case 2: pattern = enemiesLeft() > 4 * difficulty.patternSizeMultipler ? 2 : 0; break; case 3: pattern = enemiesLeft() > 5 * difficulty.patternSizeMultipler ? 3 : 0; break; case 4: pattern = enemiesLeft() > 5 * difficulty.patternSizeMultipler ? 4 : 0; break; case 5: pattern = enemiesLeft() > 5 * difficulty.patternSizeMultipler ? 5 : 0; if (enemiesLeft() % 2 != 1) pattern = 0; break; case 6: pattern = enemiesLeft() > 3 * difficulty.patternSizeMultipler ? 6 : 0; if (enemiesLeft() % 2 != 1) pattern = 0; break; case 7: pattern = enemiesLeft() > 5 * difficulty.patternSizeMultipler ? 7 : 0; break; case 8: pattern = enemiesLeft() > 4 * difficulty.patternSizeMultipler ? 8 : 0; break; } return pattern; } public boolean inBetween(int value, int min, int max) { return (value > min && value < max); } public boolean checkOutOfBounds(EnemyBase enemy) { return enemy.y > height + enemy.h; } public boolean spawnedAllEnemies() { return currentWaveEnemyCount >= waveEnemyCount; } public int enemiesLeft() { return waveEnemyCount - currentWaveEnemyCount; } }