feat(enemy): migrar el moviment dels enemics a un sistema d'IA declaratiu
This commit is contained in:
@@ -222,6 +222,138 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
auto movementTypeFromString(const std::string& s) -> std::optional<MovementType> {
|
||||
if (s == "zigzag") { return MovementType::ZIGZAG; }
|
||||
if (s == "tracking") { return MovementType::TRACKING; }
|
||||
if (s == "rectilinear_proximity") { return MovementType::RECTILINEAR_PROXIMITY; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto aiActionTypeFromString(const std::string& s) -> std::optional<AiActionType> {
|
||||
if (s == "shoot") { return AiActionType::SHOOT; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto aimModeFromString(const std::string& s) -> std::optional<AimMode> {
|
||||
if (s == "random") { return AimMode::RANDOM; }
|
||||
if (s == "aimed") { return AimMode::AIMED; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto parseMovement(const fkyaml::node& mv_node, const std::string& enemy_name, MovementConfig& out) -> bool {
|
||||
if (!mv_node.contains("type")) {
|
||||
std::cerr << "[EnemyConfig] Error: falta 'ai.movement.type' a " << enemy_name << '\n';
|
||||
return false;
|
||||
}
|
||||
const auto TYPE_STR = mv_node["type"].get_value<std::string>();
|
||||
const auto PARSED = movementTypeFromString(TYPE_STR);
|
||||
if (!PARSED) {
|
||||
std::cerr << "[EnemyConfig] Error: movement type desconegut '" << TYPE_STR
|
||||
<< "' a " << enemy_name << '\n';
|
||||
return false;
|
||||
}
|
||||
out.type = *PARSED;
|
||||
const auto READ_OPT = [&mv_node](const char* key, float& dst) {
|
||||
if (mv_node.contains(key)) {
|
||||
dst = mv_node[key].get_value<float>();
|
||||
}
|
||||
};
|
||||
READ_OPT("angle_change_max", out.angle_change_max);
|
||||
READ_OPT("zigzag_prob_per_second", out.zigzag_prob_per_second);
|
||||
READ_OPT("tracking_strength", out.tracking_strength);
|
||||
READ_OPT("tracking_interval", out.tracking_interval);
|
||||
READ_OPT("rotation_proximity_multiplier", out.rotation_proximity_multiplier);
|
||||
READ_OPT("proximity_distance", out.proximity_distance);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto parseTickList(const fkyaml::node& list_node, const std::string& enemy_name, std::vector<AiTickAction>& out) -> bool {
|
||||
if (!list_node.is_sequence()) {
|
||||
std::cerr << "[EnemyConfig] Error: 'ai.tick' 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 ai.tick ("
|
||||
<< enemy_name << ")\n";
|
||||
return false;
|
||||
}
|
||||
const auto STR = item["action"].get_value<std::string>();
|
||||
const auto PARSED = aiActionTypeFromString(STR);
|
||||
if (!PARSED) {
|
||||
std::cerr << "[EnemyConfig] Error: acció d'ai desconeguda '" << STR
|
||||
<< "' a " << enemy_name << '\n';
|
||||
return false;
|
||||
}
|
||||
AiTickAction action;
|
||||
action.type = *PARSED;
|
||||
if (item.contains("interval")) {
|
||||
action.interval = item["interval"].get_value<float>();
|
||||
}
|
||||
if (item.contains("aim_mode")) {
|
||||
const auto AIM_STR = item["aim_mode"].get_value<std::string>();
|
||||
const auto AIM = aimModeFromString(AIM_STR);
|
||||
if (!AIM) {
|
||||
std::cerr << "[EnemyConfig] Error: aim_mode desconegut '" << AIM_STR
|
||||
<< "' a ai.tick (" << enemy_name << ")\n";
|
||||
return false;
|
||||
}
|
||||
action.aim_mode = *AIM;
|
||||
}
|
||||
if (item.contains("jitter_rad")) {
|
||||
action.jitter_rad = item["jitter_rad"].get_value<float>();
|
||||
}
|
||||
if (item.contains("bullet")) {
|
||||
action.bullet_config_name = item["bullet"].get_value<std::string>();
|
||||
}
|
||||
out.push_back(action);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Migració progressiva: si el YAML no porta secció `ai:`, derivem el
|
||||
// movement a partir de l'ai_type i copiem els paràmetres de la BehaviorCfg
|
||||
// ja parsejada. Comportament idèntic al hardcoded actual.
|
||||
void fillLegacyAiDefaults(EnemyType ai_type, const EnemyConfig::BehaviorCfg& legacy, EnemyAiConfig& out) {
|
||||
switch (ai_type) {
|
||||
case EnemyType::PENTAGON:
|
||||
case EnemyType::STAR:
|
||||
out.movement.type = MovementType::ZIGZAG;
|
||||
out.movement.angle_change_max = legacy.angle_change_max;
|
||||
out.movement.zigzag_prob_per_second = legacy.zigzag_prob_per_second;
|
||||
break;
|
||||
case EnemyType::SQUARE:
|
||||
out.movement.type = MovementType::TRACKING;
|
||||
out.movement.tracking_strength = legacy.tracking_strength;
|
||||
out.movement.tracking_interval = legacy.tracking_interval;
|
||||
break;
|
||||
case EnemyType::PINWHEEL:
|
||||
out.movement.type = MovementType::RECTILINEAR_PROXIMITY;
|
||||
out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
|
||||
out.movement.proximity_distance = legacy.proximity_distance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto parseAi(const fkyaml::node& node, const std::string& name, EnemyType ai_type, const EnemyConfig::BehaviorCfg& legacy, EnemyAiConfig& out) -> bool {
|
||||
if (!node.contains("ai")) {
|
||||
fillLegacyAiDefaults(ai_type, legacy, out);
|
||||
return true;
|
||||
}
|
||||
const auto& ai = node["ai"];
|
||||
if (!ai.contains("movement")) {
|
||||
std::cerr << "[EnemyConfig] Error: falta 'ai.movement' a " << name << '\n';
|
||||
return false;
|
||||
}
|
||||
if (!parseMovement(ai["movement"], name, out.movement)) {
|
||||
return false;
|
||||
}
|
||||
if (ai.contains("tick") && !parseTickList(ai["tick"], name, out.tick)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto parseEvents(const fkyaml::node& node, const std::string& name, EnemyEventConfig& out) -> bool {
|
||||
if (!node.contains("events")) {
|
||||
fillLegacyDefaults(out);
|
||||
@@ -268,6 +400,7 @@ auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||
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; }
|
||||
if (!parseAi(node, cfg.name, cfg.ai_type, cfg.behavior, cfg.ai)) { return std::nullopt; }
|
||||
|
||||
return cfg;
|
||||
} catch (const std::exception& e) {
|
||||
|
||||
Reference in New Issue
Block a user