#ifndef TDGAME_TILE_HPP #define TDGAME_TILE_HPP #include "../grid/mesh.hpp" #include "enemy.hpp" #include "customjson.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; struct PathProperties { float speedMultiplier = 1.0f; std::vector effects; bool isFlyingPath = true; bool isGroundPath = true; }; struct WallProperties { bool blocksGround = true; bool blocksAir = false; bool blocksProjectiles = true; std::vector whitelist; }; struct BaseProperties { float healthBonus = 0.0f; float defenseBonus = 0.0f; int levelRequired = 0; }; struct WaveDefinition { std::string enemyId; int count; float interval; float healthMult = 1.0f; float speedMult = 1.0f; float rewardMult = 1.0f; }; struct SpawnProperties { std::vector waves; bool loopWaves = true; float loopHealthScaler = 0.1f; float loopSpeedScaler = 0.05f; int currentWaveIndex = 0; }; struct TowerBaseProperties { float rangeMultiplier = 1.0f; float damageMultiplier = 1.0f; float fireRateMultiplier = 1.0f; std::vector allowedTowerTypes; }; enum TileType { EMPTY, PATH, WALL, BASE, SPAWN, TOWER_BASE, MULTI, SPECIAL }; struct Tile { int x = 0; int z = 0; std::string id = "void"; TileType type = TileType::EMPTY; std::shared_ptr mesh; std::optional path; std::optional wall; std::optional base; std::optional spawn; std::optional towerBase; std::map specialParams; Tile() = default; bool isWalkable() const { return path.has_value() && path->isGroundPath; } bool isBuildable() const { return towerBase.has_value() || type == TileType::EMPTY; } void setMeshColor(Color c) { if(mesh) mesh->colors({c}); } }; class TileRegistry { private: std::map _prototypes; public: static TileRegistry& getInstance() { static TileRegistry instance; return instance; } void loadFromDirectory(const std::string& path) { if (!fs::exists(path)) { std::cerr << "TileRegistry: Directory " << path << " does not exist." << std::endl; return; } for (const auto& entry : fs::directory_iterator(path)) { if (entry.path().extension() == ".json") { loadTileFile(entry.path().string()); } } std::cout << "TileRegistry: Loaded " << _prototypes.size() << " tile definitions." << std::endl; } Tile createTile(const std::string& id, int x, int z) { if (_prototypes.count(id)) { Tile t = _prototypes.at(id); t.x = x; t.z = z; return t; } Tile t; t.x = x; t.z = z; t.id = "error"; std::cerr << "TileRegistry: Warning, requested unknown tile ID: " << id << std::endl; return t; } private: void loadTileFile(const std::string& filepath) { std::ifstream f(filepath); if (!f.is_open()) return; std::stringstream buffer; buffer << f.rdbuf(); std::string content = buffer.str(); try { customJson::Node root = customJson::parse(content); if (const auto* arr = std::get_if>(&root.value)) { for (const auto& item : *arr) { parseTileJson(item.as_object()); } } else if (const auto* obj = std::get_if>(&root.value)) { parseTileJson(*obj); } } catch (const std::exception& e) { std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl; } } 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 parseTileJson(const std::map& j) { Tile t; t.id = get_value(j, "id", std::string("unknown")); std::string typeStr = get_value(j, "type", std::string("empty")); if (typeStr == "path") t.type = TileType::PATH; else if (typeStr == "wall") t.type = TileType::WALL; else if (typeStr == "base") t.type = TileType::BASE; else if (typeStr == "spawn") t.type = TileType::SPAWN; else if (typeStr == "tower_base") t.type = TileType::TOWER_BASE; else if (typeStr == "multi") t.type = TileType::MULTI; else if (typeStr == "special") t.type = TileType::SPECIAL; else t.type = TileType::EMPTY; if (j.count("path")) { const auto& p_obj = j.at("path").as_object(); PathProperties p; p.speedMultiplier = get_value(p_obj, "speed_mult", 1.0f); p.isGroundPath = get_value(p_obj, "ground", true); p.isFlyingPath = get_value(p_obj, "air", true); if(p_obj.count("effects")) { for(const auto& effect_node : p_obj.at("effects").as_array()) { p.effects.push_back(effect_node.as_string()); } } t.path = p; } if (j.count("wall")) { const auto& w_obj = j.at("wall").as_object(); WallProperties w; w.blocksGround = get_value(w_obj, "block_ground", true); w.blocksAir = get_value(w_obj, "block_air", false); t.wall = w; } if (j.count("spawn")) { const auto& s_obj = j.at("spawn").as_object(); SpawnProperties sp; sp.loopWaves = get_value(s_obj, "loop", true); sp.loopHealthScaler = get_value(s_obj, "loop_hp_scale", 0.1f); if (s_obj.count("waves")) { for (const auto& w_node : s_obj.at("waves").as_array()) { const auto& wj = w_node.as_object(); WaveDefinition wd; wd.enemyId = get_value(wj, "enemy_id", "grunt"); wd.count = get_value(wj, "count", 5); wd.interval = get_value(wj, "interval", 1.0f); wd.healthMult = get_value(wj, "hp_mult", 1.0f); sp.waves.push_back(wd); } } t.spawn = sp; } if (_prototypes.count(t.id)) { std::cerr << "Warning: Duplicate tile ID '" << t.id << "' found. Overwriting." << std::endl; } _prototypes[t.id] = t; } }; #endif