Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions assets/waves/waves.json
Original file line number Diff line number Diff line change
@@ -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 }
]
}
]
}
]
}

4 changes: 0 additions & 4 deletions src/GameManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 0 additions & 1 deletion src/GameManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
38 changes: 9 additions & 29 deletions src/TowerDefense/gamengine/GameEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -216,6 +214,7 @@ void GameEngine::processEvents() {
if (!gameStarted) {
if (startGameButton.getGlobalBounds().contains(mousePos)) {
gameStarted = true;
mWaveManager->startNextWave();
continue;
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -607,8 +596,6 @@ void GameEngine::init(int level, int crystals, const std::vector<int>& available
mTowers.clear();
mEnemies.clear();
mProjectiles.clear();
mCurrentWaveNumber = 1;
mCurrentWave = Wave(level, mCurrentWaveNumber, { 0 });
mTowerHealth = 100;
gameStarted = false;
mGameOver = false;
Expand All @@ -625,16 +612,8 @@ void GameEngine::init(int level, int crystals, const std::vector<int>& 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<WaveManager>(mWaveDatabase, level);
}

int GameEngine::getCrystals() const {
Expand All @@ -644,3 +623,4 @@ int GameEngine::getCrystals() const {
int GameEngine::getInitialCrystals() {
return mInitialCrystals;
}

7 changes: 4 additions & 3 deletions src/TowerDefense/gamengine/GameEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -30,7 +32,6 @@ class GameEngine {

bool isGameOver() const;
void init(int level, int crytals, const std::vector<int>& availableTowers);
bool isLevelCompleted(int level) const;
int getCrystals() const;
int getInitialCrystals();

Expand All @@ -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<WaveManager> mWaveManager;

sf::Text mCrystalText;
sf::Font mFont;
Expand Down
65 changes: 28 additions & 37 deletions src/TowerDefense/waves/Wave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,29 @@
#include <iostream>
#include "PathsConfig.h"


Wave::Wave(int level, int waveNumber, const std::vector<int>& 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<Path>& paths,
const std::vector<EnemyTypeInfo>& 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<Enemy>& 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++;
}
}
}
Expand All @@ -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);
}

24 changes: 13 additions & 11 deletions src/TowerDefense/waves/Wave.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,28 @@ using Path = std::vector<sf::Vector2f>;

class Wave {
public:
Wave(int level, int waveNumber, const std::vector<int>& pathIndices);
struct EnemyTypeInfo {
std::string type;
int count;
float spawnInterval;
int pathNumber;
};

Wave(int level, int waveNumber, const std::vector<Path>& paths,
const std::vector<EnemyTypeInfo>& enemyTypes);

void update(float dt, std::vector<Enemy>& enemies);
bool isComplete() const;

int getEnemyCount() const;

private:
struct EnemyInfo {
int count;
std::function<Enemy(const Path&)> createEnemy;
};

int mTotalEnemies;
int mEnemiesSpawned;
float mSpawnTimer;
float mGlobalSpawnTimer;
std::vector<Path> mPaths;
std::vector<EnemyInfo> mEnemyTypes;
int mCurrentEnemyType;
std::vector<EnemyTypeInfo> mEnemyTypes;
std::vector<int> mEnemiesSpawnedPerType;

void setupWave(int level, int waveNumber);
Enemy createEnemy(const std::string& type, const Path& path) const;
};

52 changes: 52 additions & 0 deletions src/TowerDefense/waves/WaveDatabase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include "WaveDatabase.h"
#include <fstream>
#include <iostream>

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;
}

35 changes: 35 additions & 0 deletions src/TowerDefense/waves/WaveDatabase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <nlohmann/json.hpp>
#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<EnemyTypeInfo> enemyGroups;
};

struct LevelConfig {
int level;
std::vector<WaveConfig> waves;
};

class WaveDatabase {
public:
bool loadFromFile(const std::string& filepath);
const LevelConfig* getLevelConfig(int level) const;

private:
std::vector<LevelConfig> mLevels;
};

Loading
Loading