240 lines
7.5 KiB
C++
240 lines
7.5 KiB
C++
#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 |