Files
stupidsimcpp/util/tdgame/tile.hpp
2026-02-18 12:28:18 -05:00

222 lines
7.3 KiB
C++

#ifndef TDGAME_TILE_HPP
#define TDGAME_TILE_HPP
#include "../grid/mesh.hpp"
#include "enemy.hpp"
#include "customjson.hpp"
#include <vector>
#include <string>
#include <map>
#include <optional>
#include <memory>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <variant>
namespace fs = std::filesystem;
struct PathProperties {
float speedMultiplier = 1.0f;
std::vector<std::string> effects;
bool isFlyingPath = true;
bool isGroundPath = true;
};
struct WallProperties {
bool blocksGround = true;
bool blocksAir = false;
bool blocksProjectiles = true;
std::vector<std::string> 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<WaveDefinition> 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<std::string> 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> mesh;
std::optional<PathProperties> path;
std::optional<WallProperties> wall;
std::optional<BaseProperties> base;
std::optional<SpawnProperties> spawn;
std::optional<TowerBaseProperties> towerBase;
std::map<std::string, float> 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<std::string, Tile> _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<std::vector<customJson::Node>>(&root.value)) {
for (const auto& item : *arr) {
parseTileJson(item.as_object());
}
} else if (const auto* obj = std::get_if<std::map<std::string, customJson::Node>>(&root.value)) {
parseTileJson(*obj);
}
} catch (const std::exception& e) {
std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl;
}
}
template<typename T>
T get_value(const std::map<std::string, customJson::Node>& 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<T, bool>) return node.as_bool();
if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, int>) return static_cast<T>(node.as_double());
if constexpr (std::is_same_v<T, std::string>) return node.as_string();
return default_val;
};
void parseTileJson(const std::map<std::string, customJson::Node>& 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<float>(p_obj, "speed_mult", 1.0f);
p.isGroundPath = get_value<bool>(p_obj, "ground", true);
p.isFlyingPath = get_value<bool>(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<bool>(w_obj, "block_ground", true);
w.blocksAir = get_value<bool>(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<bool>(s_obj, "loop", true);
sp.loopHealthScaler = get_value<float>(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<std::string>(wj, "enemy_id", "grunt");
wd.count = get_value<int>(wj, "count", 5);
wd.interval = get_value<float>(wj, "interval", 1.0f);
wd.healthMult = get_value<float>(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