#ifndef TDGAME_TOWER_HPP #define TDGAME_TOWER_HPP #include "../grid/mesh.hpp" #include "enemy.hpp" #include "customjson.hpp" #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; // Forward declaration class Tower; enum class TargetingPriority { FIRST, // Enemy furthest along the path LAST, // Enemy least far along the path CLOSEST, // Enemy closest to the tower STRONGEST, // Enemy with the highest max HP WEAKEST // Enemy with the lowest current HP }; // Represents a single upgrade level for a tower struct TowerUpgrade { int cost = 50; float rangeBonus = 1.0f; float damageBonus = 5.0f; float fireRateBonus = 0.2f; }; // Holds the prototype data for a type of tower, loaded from JSON. struct TowerPrototype { std::string typeId; std::string name; std::shared_ptr mesh; int baseCost = 100; float baseRange = 10.0f; float baseDamage = 10.0f; float baseFireRate = 1.0f; // shots per second TargetingPriority targetingPriority = TargetingPriority::FIRST; std::string projectileTypeId; // For a future projectile system std::vector upgrades; }; // Represents a single active tower instance in the game. class Tower { private: int _level = 1; float _fireCooldown = 0.0f; Enemy* _target = nullptr; // Raw pointer to the target, validated each frame public: int instanceId; std::string typeId; std::shared_ptr mesh; Vector3f position; // Current stats (including upgrades) float range; float damage; float fireRate; int cost; TargetingPriority targetingPriority; Tower(int instId, const TowerPrototype& proto, const Vector3f& pos) : instanceId(instId), typeId(proto.typeId), position(pos), range(proto.baseRange), damage(proto.baseDamage), fireRate(proto.baseFireRate), cost(proto.baseCost), targetingPriority(proto.targetingPriority) { if (proto.mesh) { mesh = std::make_shared(*proto.mesh); // Deep copy for individual manipulation mesh->setSubId(instId); mesh->translate(position); } } // Main logic loop for targeting and firing void update(float deltaTime, const std::vector>& enemies) { _fireCooldown -= deltaTime; bool targetIsValid = false; if (_target != nullptr) { if (_target->isAlive() && (_target->position - position).norm() <= range) { targetIsValid = true; } } if (!targetIsValid) { _target = nullptr; findTarget(enemies); } if (_target != nullptr && _fireCooldown <= 0.0f) { // "Fire" at the target. A real implementation would create a projectile. _target->takeDamage(damage); // Reset cooldown _fireCooldown = 1.0f / fireRate; } } // Finds a new target based on the tower's targeting priority void findTarget(const std::vector>& enemies) { _target = nullptr; Enemy* bestTarget = nullptr; float bestMetric = -1.0f; // For FIRST, STRONGEST (higher is better) if (targetingPriority == TargetingPriority::CLOSEST || targetingPriority == TargetingPriority::WEAKEST || targetingPriority == TargetingPriority::LAST) { bestMetric = std::numeric_limits::max(); // For priorities where lower is better } for (const auto& enemyPtr : enemies) { if (!enemyPtr->isAlive()) continue; float distance = (enemyPtr->position - this->position).norm(); if (distance > this->range) continue; switch (targetingPriority) { case TargetingPriority::FIRST: if (distance < (bestMetric == -1.0f ? std::numeric_limits::max() : bestMetric)) { bestMetric = distance; bestTarget = enemyPtr.get(); } break; case TargetingPriority::LAST: if (distance < bestMetric) { bestMetric = distance; bestTarget = enemyPtr.get(); } break; case TargetingPriority::CLOSEST: if (distance < bestMetric) { bestMetric = distance; bestTarget = enemyPtr.get(); } break; case TargetingPriority::STRONGEST: if (enemyPtr->maxHp > bestMetric) { bestMetric = enemyPtr->maxHp; bestTarget = enemyPtr.get(); } break; case TargetingPriority::WEAKEST: if (enemyPtr->hp < bestMetric) { bestMetric = enemyPtr->hp; bestTarget = enemyPtr.get(); } break; } } _target = bestTarget; } // Applies the next available upgrade from the prototype bool upgrade(const TowerPrototype& proto) { // Upgrades are 0-indexed for level 2, 3, etc. // For level 'L', we need upgrade at index 'L-1'. if (_level - 1 < proto.upgrades.size()) { const auto& up = proto.upgrades[_level - 1]; this->cost += up.cost; this->range += up.rangeBonus; this->damage += up.damageBonus; this->fireRate += up.fireRateBonus; _level++; return true; } return false; // Max level reached } int getLevel() const { return _level; } int getSellPrice() const { return static_cast(cost * 0.75f); } const Enemy* getTarget() const { return _target; } }; // Manages storage and retrieval of tower types from JSON definitions. class TowerRegistry { private: std::map _prototypes; int _nextInstanceId = 0; TowerRegistry() = default; template T get_value(const std::map& obj, const std::string& key, T default_val) { if (!obj.count(key) || obj.at(key).is_null()) return default_val; const auto& node = obj.at(key); if constexpr (std::is_same_v) return node.as_bool(); if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) return static_cast(node.as_double()); if constexpr (std::is_same_v) return node.as_string(); return default_val; }; void parseTowerJson(const std::map& j) { TowerPrototype p; p.typeId = get_value(j, "id", std::string("unknown")); p.name = get_value(j, "name", std::string("Unnamed Tower")); p.baseCost = get_value(j, "cost", 100); p.baseRange = get_value(j, "range", 10.0f); p.baseDamage = get_value(j, "damage", 10.0f); p.baseFireRate = get_value(j, "fire_rate", 1.0f); p.projectileTypeId = get_value(j, "projectile_id", std::string("")); std::string priorityStr = get_value(j, "targeting", std::string("first")); if (priorityStr == "last") p.targetingPriority = TargetingPriority::LAST; else if (priorityStr == "closest") p.targetingPriority = TargetingPriority::CLOSEST; else if (priorityStr == "strongest") p.targetingPriority = TargetingPriority::STRONGEST; else if (priorityStr == "weakest") p.targetingPriority = TargetingPriority::WEAKEST; else p.targetingPriority = TargetingPriority::FIRST; if (j.count("upgrades") && !j.at("upgrades").is_null()) { for (const auto& up_node : j.at("upgrades").as_array()) { const auto& up_obj = up_node.as_object(); TowerUpgrade up; up.cost = get_value(up_obj, "cost", 50); up.rangeBonus = get_value(up_obj, "range_bonus", 1.0f); up.damageBonus = get_value(up_obj, "damage_bonus", 5.0f); up.fireRateBonus = get_value(up_obj, "fire_rate_bonus", 0.2f); p.upgrades.push_back(up); } } std::string mesh_path = get_value(j, "mesh_path", std::string("")); if (!mesh_path.empty()) { p.mesh = std::make_shared(0, std::vector{}, std::vector>{}, std::vector{}); if (!p.mesh->load(mesh_path)) { std::cerr << "Warning: Failed to load mesh '" << mesh_path << "' for tower '" << p.typeId << "'." << std::endl; p.mesh = nullptr; } } if (_prototypes.count(p.typeId)) { std::cerr << "Warning: Duplicate tower ID '" << p.typeId << "' found. Overwriting." << std::endl; } _prototypes[p.typeId] = p; } void loadTowerFile(const std::string& filepath) { std::ifstream f(filepath); if (!f.is_open()) return; std::stringstream buffer; buffer << f.rdbuf(); try { customJson::Node root = customJson::parse(buffer.str()); if (const auto* arr = std::get_if>(&root.value)) { for (const auto& item : *arr) { parseTowerJson(item.as_object()); } } else if (const auto* obj = std::get_if>(&root.value)) { parseTowerJson(*obj); } } catch (const std::exception& e) { std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl; } } public: static TowerRegistry& getInstance() { static TowerRegistry instance; return instance; } TowerRegistry(const TowerRegistry&) = delete; void operator=(const TowerRegistry&) = delete; void loadFromDirectory(const std::string& path) { if (!fs::exists(path)) { std::cerr << "TowerRegistry: Directory " << path << " does not exist." << std::endl; return; } for (const auto& entry : fs::directory_iterator(path)) { if (entry.path().extension() == ".json") { loadTowerFile(entry.path().string()); } } std::cout << "TowerRegistry: Loaded " << _prototypes.size() << " tower definitions." << std::endl; } std::unique_ptr createTower(const std::string& typeId, const Vector3f& position) { if (_prototypes.count(typeId) == 0) { std::cerr << "Error: Attempted to create unknown tower type: " << typeId << std::endl; return nullptr; } const auto& proto = _prototypes.at(typeId); return std::make_unique(_nextInstanceId++, proto, position); } const TowerPrototype* getPrototype(const std::string& typeId) const { auto it = _prototypes.find(typeId); if (it != _prototypes.end()) { return &it->second; } return nullptr; } }; #endif