feat(enemy): sistema d'events declaratius via YAML
This commit is contained in:
@@ -177,6 +177,79 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> {
|
||||
if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
|
||||
if (s == "destroy") { return EnemyActionType::DESTROY; }
|
||||
if (s == "add_score") { return EnemyActionType::ADD_SCORE; }
|
||||
if (s == "create_debris") { return EnemyActionType::CREATE_DEBRIS; }
|
||||
if (s == "create_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
|
||||
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto parseActionList(const fkyaml::node& list_node, const std::string& enemy_name, const char* event_name, std::vector<EnemyAction>& out) -> bool {
|
||||
if (!list_node.is_sequence()) {
|
||||
std::cerr << "[EnemyConfig] Error: '" << event_name << "' ha de ser una llista a "
|
||||
<< enemy_name << '\n';
|
||||
return false;
|
||||
}
|
||||
for (const auto& item : list_node) {
|
||||
if (!item.contains("action")) {
|
||||
std::cerr << "[EnemyConfig] Error: entrada sense 'action' a " << event_name
|
||||
<< " (" << enemy_name << ")\n";
|
||||
return false;
|
||||
}
|
||||
const auto STR = item["action"].get_value<std::string>();
|
||||
const auto PARSED = actionTypeFromString(STR);
|
||||
if (!PARSED) {
|
||||
std::cerr << "[EnemyConfig] Error: acció desconeguda '" << STR << "' a "
|
||||
<< event_name << " (" << enemy_name << ")\n";
|
||||
return false;
|
||||
}
|
||||
out.push_back({*PARSED});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Defaults: replica el flux hardcoded actual (set_hurt → destroy → score+debris+fireworks).
|
||||
void fillLegacyDefaults(EnemyEventConfig& events) {
|
||||
events.on_hit = {{EnemyActionType::SET_HURT}};
|
||||
events.on_hurt_end = {{EnemyActionType::DESTROY}};
|
||||
events.on_destroy = {
|
||||
{EnemyActionType::ADD_SCORE},
|
||||
{EnemyActionType::CREATE_DEBRIS},
|
||||
{EnemyActionType::CREATE_FIREWORKS},
|
||||
};
|
||||
}
|
||||
|
||||
auto parseEvents(const fkyaml::node& node, const std::string& name, EnemyEventConfig& out) -> bool {
|
||||
if (!node.contains("events")) {
|
||||
fillLegacyDefaults(out);
|
||||
return true;
|
||||
}
|
||||
const auto& e = node["events"];
|
||||
if (e.contains("on_hit") && !parseActionList(e["on_hit"], name, "on_hit", out.on_hit)) {
|
||||
return false;
|
||||
}
|
||||
if (e.contains("on_hurt_end") &&
|
||||
!parseActionList(e["on_hurt_end"], name, "on_hurt_end", out.on_hurt_end)) {
|
||||
return false;
|
||||
}
|
||||
if (e.contains("on_destroy") &&
|
||||
!parseActionList(e["on_destroy"], name, "on_destroy", out.on_destroy)) {
|
||||
return false;
|
||||
}
|
||||
// Validació: destroy no pot aparèixer dins on_destroy (recursió infinita).
|
||||
for (const auto& a : out.on_destroy) {
|
||||
if (a.type == EnemyActionType::DESTROY) {
|
||||
std::cerr << "[EnemyConfig] Error: 'destroy' no pot aparèixer dins 'on_destroy' a "
|
||||
<< name << " (recursió infinita)\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||
@@ -194,6 +267,7 @@ auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||
if (!parseSpawn(node, cfg.name, cfg.spawn)) { return std::nullopt; }
|
||||
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
||||
if (!parseScore(node, cfg.name, cfg.score)) { return std::nullopt; }
|
||||
if (!parseEvents(node, cfg.name, cfg.events)) { return std::nullopt; }
|
||||
|
||||
return cfg;
|
||||
} catch (const std::exception& e) {
|
||||
|
||||
Reference in New Issue
Block a user