feat(entities): migrar la configuració dels 3 enemics a data/entities/<type>/*.yaml
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// enemy_config.cpp - Implementació del parser de EnemyConfig
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "game/entities/enemy_config.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
auto parseColor(const fkyaml::node& node, SDL_Color& out) -> bool {
|
||||
if (!node.is_sequence() || node.size() != 3) {
|
||||
return false;
|
||||
}
|
||||
const auto R = node[0].get_value<uint32_t>();
|
||||
const auto G = node[1].get_value<uint32_t>();
|
||||
const auto B = node[2].get_value<uint32_t>();
|
||||
out = SDL_Color{
|
||||
.r = static_cast<uint8_t>(R),
|
||||
.g = static_cast<uint8_t>(G),
|
||||
.b = static_cast<uint8_t>(B),
|
||||
.a = 255};
|
||||
return true;
|
||||
}
|
||||
|
||||
auto aiTypeFromString(const std::string& s) -> std::optional<EnemyType> {
|
||||
if (s == "pentagon") { return EnemyType::PENTAGON; }
|
||||
if (s == "square") { return EnemyType::SQUARE; }
|
||||
if (s == "pinwheel") { return EnemyType::PINWHEEL; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||
-> std::optional<EnemyConfig> {
|
||||
try {
|
||||
EnemyConfig cfg;
|
||||
|
||||
cfg.name = node.contains("name") ? node["name"].get_value<std::string>() : "enemy";
|
||||
|
||||
// ai_type — validació estricta contra el tipus esperat
|
||||
if (!node.contains("ai_type")) {
|
||||
std::cerr << "[EnemyConfig] Error: falta 'ai_type' a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto AI_STR = node["ai_type"].get_value<std::string>();
|
||||
const auto PARSED = aiTypeFromString(AI_STR);
|
||||
if (!PARSED) {
|
||||
std::cerr << "[EnemyConfig] Error: ai_type desconegut '" << AI_STR << "' a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
if (*PARSED != expected_ai_type) {
|
||||
std::cerr << "[EnemyConfig] Error: ai_type '" << AI_STR
|
||||
<< "' no coincideix amb el tipus esperat (per directori) a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
cfg.ai_type = *PARSED;
|
||||
|
||||
// shape
|
||||
if (!node.contains("shape") || !node["shape"].contains("path")) {
|
||||
std::cerr << "[EnemyConfig] Error: falta 'shape.path' a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
cfg.shape.path = node["shape"]["path"].get_value<std::string>();
|
||||
|
||||
// physics
|
||||
if (!node.contains("physics")) {
|
||||
std::cerr << "[EnemyConfig] Error: falta 'physics' a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto& physics = node["physics"];
|
||||
cfg.physics.mass = physics["mass"].get_value<float>();
|
||||
cfg.physics.speed = physics["speed"].get_value<float>();
|
||||
cfg.physics.rotation_delta_min = physics["rotation_delta_min"].get_value<float>();
|
||||
cfg.physics.rotation_delta_max = physics["rotation_delta_max"].get_value<float>();
|
||||
cfg.physics.collision_radius = physics["collision_radius"].get_value<float>();
|
||||
|
||||
// behavior — tots els camps són opcionals; només l'AI corresponent els consumeix.
|
||||
if (node.contains("behavior")) {
|
||||
const auto& b = node["behavior"];
|
||||
if (b.contains("zigzag_prob_per_second")) {
|
||||
cfg.behavior.zigzag_prob_per_second = b["zigzag_prob_per_second"].get_value<float>();
|
||||
}
|
||||
if (b.contains("angle_change_max")) {
|
||||
cfg.behavior.angle_change_max = b["angle_change_max"].get_value<float>();
|
||||
}
|
||||
if (b.contains("tracking_strength")) {
|
||||
cfg.behavior.tracking_strength = b["tracking_strength"].get_value<float>();
|
||||
}
|
||||
if (b.contains("tracking_interval")) {
|
||||
cfg.behavior.tracking_interval = b["tracking_interval"].get_value<float>();
|
||||
}
|
||||
if (b.contains("rotation_proximity_multiplier")) {
|
||||
cfg.behavior.rotation_proximity_multiplier = b["rotation_proximity_multiplier"].get_value<float>();
|
||||
}
|
||||
if (b.contains("proximity_distance")) {
|
||||
cfg.behavior.proximity_distance = b["proximity_distance"].get_value<float>();
|
||||
}
|
||||
}
|
||||
|
||||
// colors
|
||||
if (!node.contains("colors") ||
|
||||
!parseColor(node["colors"]["normal"], cfg.colors.normal) ||
|
||||
!parseColor(node["colors"]["wounded"], cfg.colors.wounded)) {
|
||||
std::cerr << "[EnemyConfig] Error: 'colors.normal' / 'colors.wounded' no són [r,g,b] a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// score
|
||||
if (!node.contains("score")) {
|
||||
std::cerr << "[EnemyConfig] Error: falta 'score' a " << cfg.name << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
cfg.score = node["score"].get_value<int>();
|
||||
|
||||
return cfg;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[EnemyConfig] Excepció parsejant: " << e.what() << '\n';
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user