tdgame test branch by gemini.

This commit is contained in:
Yggdrasil75
2026-02-18 12:28:18 -05:00
parent 14b4d06495
commit 2ad5f13596
14 changed files with 2038 additions and 0 deletions

240
util/tdgame/enemy.hpp Normal file
View File

@@ -0,0 +1,240 @@
#ifndef ENEMY_HPP
#define ENEMY_HPP
#include "../grid/mesh.hpp"
#include "customjson.hpp"
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <stdexcept>
// Forward declaration
class Enemy;
namespace fs = std::filesystem;
enum EnemyType { GROUND, AIR };
// Holds the prototype data for a type of enemy, loaded from JSON.
struct EnemyPrototype {
std::string typeId;
std::shared_ptr<Mesh> mesh;
float maxHp = 100.0f;
float speed = 1.0f;
int baseDamage = 1;
int reward = 5;
EnemyType type = EnemyType::GROUND;
std::vector<std::string> abilities;
};
// Represents a single active enemy instance in the game.
class Enemy {
private:
std::vector<Vector3f> _path;
size_t _pathIndex = 0;
bool _isAlive = true;
public:
int instanceId;
std::string typeId;
std::shared_ptr<Mesh> mesh;
Vector3f position;
float maxHp;
float hp;
float speed;
int baseDamage;
int reward;
EnemyType type;
Enemy(int instId, const EnemyPrototype& proto, const Vector3f& startPos)
: instanceId(instId),
typeId(proto.typeId),
mesh(std::make_shared<Mesh>(*proto.mesh)), // Deep copy mesh to allow individual modifications (e.g., color)
position(startPos),
maxHp(proto.maxHp),
hp(proto.maxHp),
speed(proto.speed),
baseDamage(proto.baseDamage),
reward(proto.reward),
type(proto.type)
{
if (mesh) {
mesh->setSubId(instId);
mesh->translate(position);
}
}
// A* pathfinding to get to the base
void setPath(const std::vector<Vector3f>& newPath) {
_path = newPath;
_pathIndex = 0;
}
const std::vector<Vector3f>& getPath() const {
return _path;
}
// Moves the enemy along its path. Returns true if it reached the end.
bool update(float deltaTime) {
if (!_isAlive || _path.empty() || _pathIndex >= _path.size()) {
return _pathIndex >= _path.size();
}
Vector3f target = _path[_pathIndex];
Vector3f direction = target - position;
float distanceToTarget = direction.norm();
float moveDist = speed * deltaTime;
if (moveDist >= distanceToTarget) {
position = target;
_pathIndex++;
if (_pathIndex >= _path.size()) {
// Reached the base
_isAlive = false;
return true;
}
} else {
position += direction.normalized() * moveDist;
}
if (mesh) {
// This is inefficient. A better approach would be to update a transform matrix.
// For now, we recreate the mesh at the new position.
auto original_verts = mesh->vertices(); // assuming this mesh is a prototype
for (auto& v : original_verts) {
v += position;
}
mesh->vertices(original_verts);
}
return false;
}
void takeDamage(float amount) {
if (!_isAlive) return;
hp -= amount;
if (hp <= 0) {
hp = 0;
_isAlive = false;
}
}
bool isAlive() const {
return _isAlive;
}
};
// Manages storage and retrieval of enemy types from JSON definitions.
class EnemyRegistry {
private:
std::map<std::string, EnemyPrototype> _prototypes;
int _nextInstanceId = 0;
EnemyRegistry() = default;
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 parseEnemyJson(const std::map<std::string, customJson::Node>& j) {
EnemyPrototype p;
p.typeId = get_value(j, "id", std::string("unknown"));
p.maxHp = get_value<float>(j, "maxHp", 100.0f);
p.speed = get_value<float>(j, "speed", 1.0f);
p.baseDamage = get_value<int>(j, "baseDamage", 1);
p.reward = get_value<int>(j, "reward", 5);
std::string typeStr = get_value(j, "type", std::string("GROUND"));
if (typeStr == "AIR") {
p.type = EnemyType::AIR;
} else {
p.type = EnemyType::GROUND;
}
if (j.count("abilities")) {
for (const auto& ability_node : j.at("abilities").as_array()) {
p.abilities.push_back(ability_node.as_string());
}
}
std::string mesh_path = get_value(j, "mesh_path", std::string(""));
if (!mesh_path.empty()) {
p.mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
if (!p.mesh->load(mesh_path)) {
std::cerr << "Warning: Failed to load mesh '" << mesh_path << "' for enemy '" << p.typeId << "'." << std::endl;
p.mesh = nullptr; // Invalidate if load fails
}
}
if (_prototypes.count(p.typeId)) {
std::cerr << "Warning: Duplicate enemy ID '" << p.typeId << "' found. Overwriting." << std::endl;
}
_prototypes[p.typeId] = p;
}
void loadEnemyFile(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<std::vector<customJson::Node>>(&root.value)) {
for (const auto& item : *arr) {
parseEnemyJson(item.as_object());
}
} else if (const auto* obj = std::get_if<std::map<std::string, customJson::Node>>(&root.value)) {
parseEnemyJson(*obj);
}
} catch (const std::exception& e) {
std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl;
}
}
public:
static EnemyRegistry& getInstance() {
static EnemyRegistry instance;
return instance;
}
EnemyRegistry(const EnemyRegistry&) = delete;
void operator=(const EnemyRegistry&) = delete;
void loadFromDirectory(const std::string& path) {
if (!fs::exists(path)) {
std::cerr << "EnemyRegistry: Directory " << path << " does not exist." << std::endl;
return;
}
for (const auto& entry : fs::directory_iterator(path)) {
if (entry.path().extension() == ".json") {
loadEnemyFile(entry.path().string());
}
}
std::cout << "EnemyRegistry: Loaded " << _prototypes.size() << " enemy definitions." << std::endl;
}
std::unique_ptr<Enemy> createEnemy(const std::string& typeId, const Vector3f& startPosition) {
if (_prototypes.count(typeId) == 0) {
std::cerr << "Error: Attempted to create unknown enemy type: " << typeId << std::endl;
return nullptr;
}
const auto& proto = _prototypes.at(typeId);
return std::make_unique<Enemy>(_nextInstanceId++, proto, startPosition);
}
};
#endif