diff --git a/assets/waves/waves.json b/assets/waves/waves.json new file mode 100644 index 0000000..b392d17 --- /dev/null +++ b/assets/waves/waves.json @@ -0,0 +1,35 @@ +{ + "levels": [ + { + "level": 1, + "waves": [ + { + "waveNumber": 1, + "enemyGroups": [ + { "type": "WastelandMarauder", "count": 4, "interval": 1.5, "path": 0 }, + { "type": "EmpireScout", "count": 2, "interval": 2.0, "path": 1 } + ] + }, + { + "waveNumber": 2, + "enemyGroups": [ + { "type": "RadiationBerserker", "count": 3, "interval": 1.2, "path": 0 }, + { "type": "EmpireScout", "count": 10, "interval": 1.8, "path": 1 } + ] + } + ] + }, + { + "level": 2, + "waves": [ + { + "waveNumber": 1, + "enemyGroups": [ + { "type": "EmpireScout", "count": 8, "interval": 1.2, "path": 0 } + ] + } + ] + } + ] +} + diff --git a/src/GameManager.cpp b/src/GameManager.cpp index aef8cad..d4a58ab 100644 --- a/src/GameManager.cpp +++ b/src/GameManager.cpp @@ -77,10 +77,6 @@ void GameManager::switchToMainMenu() { mCurrentState = GameState::MainMenu; } -bool GameManager::isLevelCompleted(int level) const { - return mTowerDefenseEngine.isLevelCompleted(level); -} - GameEngine& GameManager::getGame(){ return mTowerDefenseEngine; } diff --git a/src/GameManager.h b/src/GameManager.h index a35bb78..dcc5edb 100644 --- a/src/GameManager.h +++ b/src/GameManager.h @@ -20,7 +20,6 @@ class GameManager { void switchToRPG(int crystals); void enterRPG(int saveNumber); void switchToMainMenu(); - bool isLevelCompleted(int level) const; GameEngine& getGame(); RPGEngine& getGameEngine(); diff --git a/src/TowerDefense/gamengine/GameEngine.cpp b/src/TowerDefense/gamengine/GameEngine.cpp index e967af5..a9926dd 100644 --- a/src/TowerDefense/gamengine/GameEngine.cpp +++ b/src/TowerDefense/gamengine/GameEngine.cpp @@ -14,8 +14,6 @@ GameEngine::GameEngine(sf::RenderWindow& window, GameManager* gameManager) mWindow(window), mSpawnTimer(0.0f), mTowerHealth(100), - mCurrentWave(1, 1, { 0 }), - mCurrentWaveNumber(1), mCurrentLevel(1), mMap(mCurrentLevel), mSelectedTower(nullptr), @@ -216,6 +214,7 @@ void GameEngine::processEvents() { if (!gameStarted) { if (startGameButton.getGlobalBounds().contains(mousePos)) { gameStarted = true; + mWaveManager->startNextWave(); continue; } } @@ -398,27 +397,17 @@ void GameEngine::update() { if (gameStarted) { // Update current wave - mCurrentWave.update(dt, mEnemies); + mWaveManager->update(dt, mEnemies); bool enemiesLeft = std::any_of(mEnemies.begin(), mEnemies.end(), [](const Enemy& enemy) { return !enemy.isDead(); // Check if there are any living enemies }); - if (mCurrentWave.isComplete() && !enemiesLeft) { - // 3 is the max number of waves for now - if (mCurrentWaveNumber < 3) { - mCurrentWaveNumber++; - switch (mCurrentWaveNumber) { - case 2: - mCurrentWave = Wave(mCurrentLevel, mCurrentWaveNumber, { 1 }); - break; - case 3: - mCurrentWave = Wave(mCurrentLevel, mCurrentWaveNumber, { 0, 1 }); - break; - } - } else { + if (mWaveManager->isWaveComplete() && !enemiesLeft) { + if (!mWaveManager->isLevelComplete()) + mWaveManager->startNextWave(); + else mLevelCompleted = true; - } } // Update towers @@ -607,8 +596,6 @@ void GameEngine::init(int level, int crystals, const std::vector& available mTowers.clear(); mEnemies.clear(); mProjectiles.clear(); - mCurrentWaveNumber = 1; - mCurrentWave = Wave(level, mCurrentWaveNumber, { 0 }); mTowerHealth = 100; gameStarted = false; mGameOver = false; @@ -625,16 +612,8 @@ void GameEngine::init(int level, int crystals, const std::vector& available mPlayer.setPosition(sf::Vector2f(100, 100)); mPlayer.setAnimation(4); -} - -bool GameEngine::isLevelCompleted(int level) const { - // Check if all waves are complete and there are no remaining enemies - bool wavesComplete = !gameStarted || (mCurrentWaveNumber >= 3); - bool noEnemiesLeft = std::none_of(mEnemies.begin(), mEnemies.end(), [](const Enemy& enemy) { - return !enemy.isDead(); // There are no living enemies - }); - - return wavesComplete && noEnemiesLeft; + mWaveDatabase.loadFromFile("assets/waves/waves.json"); + mWaveManager = std::make_unique(mWaveDatabase, level); } int GameEngine::getCrystals() const { @@ -644,3 +623,4 @@ int GameEngine::getCrystals() const { int GameEngine::getInitialCrystals() { return mInitialCrystals; } + diff --git a/src/TowerDefense/gamengine/GameEngine.h b/src/TowerDefense/gamengine/GameEngine.h index b2d6cd8..2fff265 100644 --- a/src/TowerDefense/gamengine/GameEngine.h +++ b/src/TowerDefense/gamengine/GameEngine.h @@ -4,6 +4,8 @@ #include "../enemy/Enemy.h" #include "../tower/Tower.h" #include "../waves/Wave.h" +#include "../waves/WaveDatabase.h" +#include "../waves/WaveManager.h" #include "../projectiles/Projectile.h" #include "../tower/TowerSelectionMenu.h" #include "../tower/TowerMenu.h" @@ -30,7 +32,6 @@ class GameEngine { bool isGameOver() const; void init(int level, int crytals, const std::vector& availableTowers); - bool isLevelCompleted(int level) const; int getCrystals() const; int getInitialCrystals(); @@ -55,9 +56,9 @@ class GameEngine { sf::RectangleShape mHealthBar; sf::RectangleShape mHealthBarBackground; - Wave mCurrentWave; - unsigned int mCurrentWaveNumber; unsigned int mCurrentLevel; + WaveDatabase mWaveDatabase; + std::unique_ptr mWaveManager; sf::Text mCrystalText; sf::Font mFont; diff --git a/src/TowerDefense/waves/Wave.cpp b/src/TowerDefense/waves/Wave.cpp index c91d480..3f296d8 100644 --- a/src/TowerDefense/waves/Wave.cpp +++ b/src/TowerDefense/waves/Wave.cpp @@ -4,50 +4,29 @@ #include #include "PathsConfig.h" - -Wave::Wave(int level, int waveNumber, const std::vector& pathIndices) - : mTotalEnemies(0), mEnemiesSpawned(0), mSpawnTimer(0.0f), mCurrentEnemyType(0) { - - if (level > 0 && level <= getPaths().size()) { - const auto& levelPaths = getPaths()[level - 1]; - for (int index : pathIndices) { - if (index >= 0 && index < levelPaths.size()) - mPaths.push_back(levelPaths[index]); - } - } - - setupWave(level, waveNumber); - - for (const auto& enemyInfo : mEnemyTypes) - mTotalEnemies += enemyInfo.count; -} - -void Wave::setupWave(int level, int waveNumber) { - // TODO : setup waves for new levels - if (level == 1) { - if (waveNumber == 1) - mEnemyTypes.push_back({ 15, [](const Path& path) { return WastelandMarauder(path); } }); - if (waveNumber == 2) - mEnemyTypes.push_back({ 10, [](const Path& path) { return RadiationBerserker(path); } }); - if (waveNumber == 3) - mEnemyTypes.push_back({ 40, [](const Path& path) { return EmpireScout(path); } }); +Wave::Wave(int level, int waveNumber, const std::vector& paths, + const std::vector& enemyTypes) + : mTotalEnemies(0), mEnemiesSpawned(0), mPaths(paths), mGlobalSpawnTimer(0.0f) { + mEnemyTypes = enemyTypes; + for (const auto& info : mEnemyTypes) { + mEnemiesSpawnedPerType.push_back(0); + mTotalEnemies += info.count; } } void Wave::update(float dt, std::vector& enemies) { - mSpawnTimer += dt; + mGlobalSpawnTimer += dt; + for (size_t i = 0; i < mEnemyTypes.size(); ++i) { + const auto& info = mEnemyTypes[i]; + + if (mEnemiesSpawnedPerType[i] < info.count && + mGlobalSpawnTimer >= info.spawnInterval * (mEnemiesSpawnedPerType[i] + 1)) { - while (mCurrentEnemyType < mEnemyTypes.size() && - mSpawnTimer >= mEnemyTypes[mCurrentEnemyType].createEnemy(mPaths[0]).getSpawnTime() && - mEnemiesSpawned < mTotalEnemies) { + const Path& path = mPaths[info.pathNumber % mPaths.size()]; + enemies.push_back(createEnemy(info.type, path)); - mSpawnTimer = 0.0f; - if (mEnemiesSpawned < mEnemyTypes[mCurrentEnemyType].count) { - const Path& path = mPaths[rand() % mPaths.size()]; - enemies.push_back(mEnemyTypes[mCurrentEnemyType].createEnemy(path)); + mEnemiesSpawnedPerType[i]++; mEnemiesSpawned++; - } else { - mCurrentEnemyType++; } } } @@ -60,3 +39,15 @@ int Wave::getEnemyCount() const { return mTotalEnemies; } +Enemy Wave::createEnemy(const std::string& type, const Path& path) const { + if (type == "WastelandMarauder") + return WastelandMarauder(path); + else if (type == "RadiationBerserker") + return RadiationBerserker(path); + else if (type == "EmpireScout") + return EmpireScout(path); + + // Default fallback + return WastelandMarauder(path); +} + diff --git a/src/TowerDefense/waves/Wave.h b/src/TowerDefense/waves/Wave.h index da02277..a29d297 100644 --- a/src/TowerDefense/waves/Wave.h +++ b/src/TowerDefense/waves/Wave.h @@ -12,26 +12,28 @@ using Path = std::vector; class Wave { public: - Wave(int level, int waveNumber, const std::vector& pathIndices); + struct EnemyTypeInfo { + std::string type; + int count; + float spawnInterval; + int pathNumber; + }; + + Wave(int level, int waveNumber, const std::vector& paths, + const std::vector& enemyTypes); void update(float dt, std::vector& enemies); bool isComplete() const; - int getEnemyCount() const; private: - struct EnemyInfo { - int count; - std::function createEnemy; - }; - int mTotalEnemies; int mEnemiesSpawned; - float mSpawnTimer; + float mGlobalSpawnTimer; std::vector mPaths; - std::vector mEnemyTypes; - int mCurrentEnemyType; + std::vector mEnemyTypes; + std::vector mEnemiesSpawnedPerType; - void setupWave(int level, int waveNumber); + Enemy createEnemy(const std::string& type, const Path& path) const; }; diff --git a/src/TowerDefense/waves/WaveDatabase.cpp b/src/TowerDefense/waves/WaveDatabase.cpp new file mode 100644 index 0000000..311ca64 --- /dev/null +++ b/src/TowerDefense/waves/WaveDatabase.cpp @@ -0,0 +1,52 @@ +#include "WaveDatabase.h" +#include +#include + +bool WaveDatabase::loadFromFile(const std::string& filepath) { + std::ifstream file(filepath); + if (!file.is_open()) { + std::cerr << "Failed to open waves file: " << filepath << "\n"; + return false; + } + + json j; + file >> j; + + if (!j.contains("levels") || !j["levels"].is_array()) { + std::cerr << "Invalid waves.json format\n"; + return false; + } + + for (const auto& levelJson : j["levels"]) { + LevelConfig level; + level.level = levelJson.value("level", 0); + + for (const auto& waveJson : levelJson["waves"]) { + WaveConfig wave; + wave.waveNumber = waveJson.value("waveNumber", 0); + + for (const auto& enemyJson : waveJson["enemyGroups"]) { + EnemyTypeInfo info; + info.type = enemyJson.value("type", ""); + info.count = enemyJson.value("count", 0); + info.interval = enemyJson.value("interval", 1.0f); + info.pathNumber = enemyJson.value("path", 0); + wave.enemyGroups.push_back(info); + } + + level.waves.push_back(wave); + } + + mLevels.push_back(level); + } + + return true; +} + +const LevelConfig* WaveDatabase::getLevelConfig(int level) const { + for (const auto& lvl : mLevels) + if (lvl.level == level) + return &lvl; + return nullptr; +} + diff --git a/src/TowerDefense/waves/WaveDatabase.h b/src/TowerDefense/waves/WaveDatabase.h new file mode 100644 index 0000000..da120d6 --- /dev/null +++ b/src/TowerDefense/waves/WaveDatabase.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include +#include "../enemy/Enemy.h" + +using json = nlohmann::json; + +struct EnemyTypeInfo { + std::string type; + int count; + float interval; + int pathNumber; +}; + +struct WaveConfig { + int waveNumber; + std::vector enemyGroups; +}; + +struct LevelConfig { + int level; + std::vector waves; +}; + +class WaveDatabase { +public: + bool loadFromFile(const std::string& filepath); + const LevelConfig* getLevelConfig(int level) const; + +private: + std::vector mLevels; +}; + diff --git a/src/TowerDefense/waves/WaveManager.cpp b/src/TowerDefense/waves/WaveManager.cpp new file mode 100644 index 0000000..505fd22 --- /dev/null +++ b/src/TowerDefense/waves/WaveManager.cpp @@ -0,0 +1,59 @@ +#include "WaveManager.h" +#include +#include + +WaveManager::WaveManager(const WaveDatabase& db, int level) + : mDatabase(db), mCurrentWaveIndex(-1) +{ + mLevelConfig = mDatabase.getLevelConfig(level); + if (!mLevelConfig) { + std::cout << "No config found for level " << level << "\n"; + return; + } + + const auto& allPaths = getPaths(); + if (level - 1 < (int)allPaths.size()) + mPaths = allPaths[level - 1]; + else + std::cout << "No paths defined for level " << level << "\n"; +} + +void WaveManager::startNextWave() { + if (!mLevelConfig) return; + + mCurrentWaveIndex++; + if (mCurrentWaveIndex >= (int)mLevelConfig->waves.size()) return; + + const auto& config = mLevelConfig->waves[mCurrentWaveIndex]; + + std::vector waveEnemyTypes; + for (const auto& dbEnemy : config.enemyGroups) { + Wave::EnemyTypeInfo e; + e.type = dbEnemy.type; + e.count = dbEnemy.count; + e.spawnInterval = dbEnemy.interval; + e.pathNumber = dbEnemy.pathNumber; + waveEnemyTypes.push_back(e); + } + + mCurrentWave = std::make_unique( + mLevelConfig->level, + config.waveNumber, + mPaths, + waveEnemyTypes + ); +} + +void WaveManager::update(float dt, std::vector& enemies) { + if (mCurrentWave) + mCurrentWave->update(dt, enemies); +} + +bool WaveManager::isWaveComplete() const { + return mCurrentWave && mCurrentWave->isComplete(); +} + +bool WaveManager::isLevelComplete() const { + return mLevelConfig && mCurrentWaveIndex >= (int)mLevelConfig->waves.size() - 1 && isWaveComplete(); +} + diff --git a/src/TowerDefense/waves/WaveManager.h b/src/TowerDefense/waves/WaveManager.h new file mode 100644 index 0000000..eb8c961 --- /dev/null +++ b/src/TowerDefense/waves/WaveManager.h @@ -0,0 +1,24 @@ +#pragma once +#include "WaveDatabase.h" +#include "Wave.h" +#include "PathsConfig.h" +#include + +class WaveManager { +public: + WaveManager(const WaveDatabase& db, int level); + + void startNextWave(); + void update(float dt, std::vector& enemies); + + bool isWaveComplete() const; + bool isLevelComplete() const; + +private: + const WaveDatabase& mDatabase; + const LevelConfig* mLevelConfig; + int mCurrentWaveIndex; + std::unique_ptr mCurrentWave; + std::vector mPaths; +}; +