From bc41169176e2afa767086896aa56430d46d852ed Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 25 May 2026 12:36:26 +0200 Subject: [PATCH] feat(enemy): afegir tipus STAR (estrella de 5 puntes) i 3 nous shapes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nou enemic STAR amb shape star_5.shp, escala 0.7 i color groc pur. Reusa el comportament zigzag del Pentagon i carrega via EnemyRegistry. - DistribucioEnemics estesa amb camp 'star' opcional (default 0) per mantenir compat amb stages antics. - Stage 1 reconfigurat a 25/25/25/25 per mostrar els 4 tipus. - Afegits també shapes bullet_long.shp i bullet_double.shp (encara no utilitzats; preparats per futures variants de bala). --- data/entities/star/star.yaml | 55 +++ data/shapes/bullet_double.shp | 17 + data/shapes/bullet_long.shp | 28 ++ data/shapes/star_5.shp | 15 + data/stages/stages.yaml | 9 +- source/game/entities/enemy.cpp | 3 + source/game/entities/enemy.hpp | 3 +- source/game/entities/enemy_config.cpp | 1 + source/game/entities/enemy_registry.cpp | 8 +- source/game/entities/enemy_registry.hpp | 1 + source/game/stage_system/spawn_controller.cpp | 5 +- source/game/stage_system/stage_config.hpp | 3 +- source/game/stage_system/stage_loader.cpp | 406 +++++++++--------- 13 files changed, 343 insertions(+), 211 deletions(-) create mode 100644 data/entities/star/star.yaml create mode 100644 data/shapes/bullet_double.shp create mode 100644 data/shapes/bullet_long.shp create mode 100644 data/shapes/star_5.shp diff --git a/data/entities/star/star.yaml b/data/entities/star/star.yaml new file mode 100644 index 0000000..c870e69 --- /dev/null +++ b/data/entities/star/star.yaml @@ -0,0 +1,55 @@ +name: star +ai_type: star # Validat contra el directori; mapeja a EnemyType::STAR. + +shape: + path: star_5.shp + scale: 0.7 # Lleugerament més petit que els altres enemics per diferenciar visualment. + collision_factor: 1.0 + +physics: + mass: 5.0 + speed: 35.0 # Mateixos paràmetres que pentagon (esquivador lent). + rotation_delta_min: 0.75 + rotation_delta_max: 3.75 + restitution: 1.0 + linear_damping: 0.0 + angular_damping: 0.0 + +behavior: + # Hereta el comportament de Pentagon (zigzag esquivador). + angle_change_max: 1.0 + zigzag_prob_per_second: 0.8 + +animation: + pulse: + trigger_prob_per_second: 0.01 + duration_min: 1.0 + duration_max: 3.0 + amplitude_min: 0.08 + amplitude_max: 0.20 + frequency_min: 1.5 + frequency_max: 3.0 + rotation_accel: + trigger_prob_per_second: 0.02 + duration_min: 3.0 + duration_max: 8.0 + multiplier_min: 0.3 + multiplier_max: 4.0 + +wounded: + duration: 1.0 + blink_hz: 10.0 + +spawn: + invulnerability_duration: 3.0 + invulnerability_brightness_start: 0.3 + invulnerability_brightness_end: 0.7 + invulnerability_scale_start: 0.0 + invulnerability_scale_end: 1.0 + safety_distance: 36.0 + +colors: + normal: [255, 255, 0] # Groc estrella + wounded: [255, 220, 60] + +score: 100 diff --git a/data/shapes/bullet_double.shp b/data/shapes/bullet_double.shp new file mode 100644 index 0000000..eeaa14e --- /dev/null +++ b/data/shapes/bullet_double.shp @@ -0,0 +1,17 @@ +# bullet_double.shp - Bala anular (dos cercles concèntrics) +# © 2026 JailDesigner +# +# Dos octàgons concèntrics al centre (0,0): +# - Exterior: radi 4 (lleugerament més gran que la bala estàndard, radi 3) +# - Interior: radi 2 (lleugerament més petit que la bala estàndard) +# Aspecte d'anell / aura de plasma. Bounding radius natiu = 4. + +name: bullet_double +scale: 1.0 +center: 0, 0 + +# Cercle exterior (octàgon, radi 4) +polyline: 0,-4 2.83,-2.83 4,0 2.83,2.83 0,4 -2.83,2.83 -4,0 -2.83,-2.83 0,-4 + +# Cercle interior (octàgon, radi 2) +polyline: 0,-2 1.41,-1.41 2,0 1.41,1.41 0,2 -1.41,1.41 -2,0 -1.41,-1.41 0,-2 diff --git a/data/shapes/bullet_long.shp b/data/shapes/bullet_long.shp new file mode 100644 index 0000000..03581c8 --- /dev/null +++ b/data/shapes/bullet_long.shp @@ -0,0 +1,28 @@ +# bullet_long.shp - Bala allargada (dos octàgons tangents + tapes superior i inferior) +# © 2026 JailDesigner +# +# Dos cercles (octàgons radi 3) tangents externament al punt (0,0), units +# per una línia horitzontal superior i una d'inferior. La silueta resultant +# és una càpsula amb la separació visible dels dos cercles al centre. +# +# Geometria: +# Centre octàgon esquerre: (-3, 0) +# Centre octàgon dret: ( 3, 0) +# Punt de tangència: ( 0, 0) +# Bounding radius natiu ≈ 6 (extrem horitzontal a x=±6). + +name: bullet_long +scale: 1.0 +center: 0, 0 + +# Octàgon esquerre (centre x=-3, radi 3) +polyline: -3,-3 -0.88,-2.12 0,0 -0.88,2.12 -3,3 -5.12,2.12 -6,0 -5.12,-2.12 -3,-3 + +# Octàgon dret (centre x=3, radi 3) +polyline: 3,-3 5.12,-2.12 6,0 5.12,2.12 3,3 0.88,2.12 0,0 0.88,-2.12 3,-3 + +# Tapa superior: uneix el cim de l'octàgon esquerre amb el del dret +polyline: -3,-3 3,-3 + +# Tapa inferior: uneix la base de l'octàgon esquerre amb la del dret +polyline: -3,3 3,3 diff --git a/data/shapes/star_5.shp b/data/shapes/star_5.shp new file mode 100644 index 0000000..62b5a57 --- /dev/null +++ b/data/shapes/star_5.shp @@ -0,0 +1,15 @@ +# star_5.shp - ORNI enemic (estrella de 5 puntes, només perímetre) +# © 2026 JailDesigner +# +# Pentagrama clàssic: 5 vèrtexs exteriors (radi 20) alternant amb 5 vèrtexs +# interiors (radi 7.64 = 20/φ² ≈ proporció àuria) per donar puntes esveltes. +# Vèrtex apuntant amunt (igual que enemy_pentagon). +# +# Sense línies interiors: una única polyline que recorre el perímetre. +# Bounding radius natiu ≈ 20 (alineat amb pentagon/square/pinwheel). + +name: star_5 +scale: 1.0 +center: 0, 0 + +polyline: 0,-20 4.49,-6.18 19.02,-6.18 7.27,2.36 11.76,16.18 0,7.64 -11.76,16.18 -7.27,2.36 -19.02,-6.18 -4.49,-6.18 0,-20 diff --git a/data/stages/stages.yaml b/data/stages/stages.yaml index 7c3acfb..03824a5 100644 --- a/data/stages/stages.yaml +++ b/data/stages/stages.yaml @@ -7,7 +7,7 @@ metadata: description: "Progressive difficulty curve from novice to expert" stages: - # STAGE 1: Tutorial - Mix de tots els tipus, velocitat lenta + # STAGE 1: Tutorial - Mix de tots 4 tipus al 25% per mostrar-los junts - stage_id: 1 total_enemies: 50 spawn_config: @@ -15,9 +15,10 @@ stages: initial_delay: 0.3 spawn_interval: 0.4 enemy_distribution: - pentagon: 34 - cuadrado: 33 - molinillo: 33 + pentagon: 25 + cuadrado: 25 + molinillo: 25 + star: 25 difficulty_multipliers: speed_multiplier: 0.7 rotation_multiplier: 0.8 diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index af40730..b1b206d 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -170,6 +170,9 @@ void Enemy::update(float delta_time) { if (!isWounded()) { switch (type_) { case EnemyType::PENTAGON: + case EnemyType::STAR: + // STAR reusa el zigzag esquivador de Pentagon. Si en el futur + // vol comportament propi, separa-li el cas. behaviorPentagon(delta_time); break; case EnemyType::SQUARE: diff --git a/source/game/entities/enemy.hpp b/source/game/entities/enemy.hpp index a803911..041c68b 100644 --- a/source/game/entities/enemy.hpp +++ b/source/game/entities/enemy.hpp @@ -13,7 +13,8 @@ enum class EnemyType : uint8_t { PENTAGON = 0, // Pentágono esquivador (zigzag) SQUARE = 1, // Square perseguidor (tracks ship) - PINWHEEL = 2 // Molinillo agresivo (rápido, girando) + PINWHEEL = 2, // Molinillo agresivo (rápido, girando) + STAR = 3 // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag) }; // Forward declaration — EnemyConfig viu a enemy_config.hpp i s'inclou només a enemy.cpp. diff --git a/source/game/entities/enemy_config.cpp b/source/game/entities/enemy_config.cpp index c0b28b7..95d323e 100644 --- a/source/game/entities/enemy_config.cpp +++ b/source/game/entities/enemy_config.cpp @@ -29,6 +29,7 @@ namespace { if (s == "pentagon") { return EnemyType::PENTAGON; } if (s == "square") { return EnemyType::SQUARE; } if (s == "pinwheel") { return EnemyType::PINWHEEL; } + if (s == "star") { return EnemyType::STAR; } return std::nullopt; } diff --git a/source/game/entities/enemy_registry.cpp b/source/game/entities/enemy_registry.cpp index ceee87b..6aa8087 100644 --- a/source/game/entities/enemy_registry.cpp +++ b/source/game/entities/enemy_registry.cpp @@ -12,6 +12,7 @@ EnemyConfig EnemyRegistry::pentagon_config; EnemyConfig EnemyRegistry::square_config; EnemyConfig EnemyRegistry::pinwheel_config; +EnemyConfig EnemyRegistry::star_config; bool EnemyRegistry::loaded = false; namespace { @@ -36,10 +37,11 @@ namespace { auto EnemyRegistry::loadAll() -> bool { const bool OK = loadOne("pentagon", EnemyType::PENTAGON, pentagon_config) && loadOne("square", EnemyType::SQUARE, square_config) && - loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_config); + loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_config) && + loadOne("star", EnemyType::STAR, star_config); loaded = OK; if (OK) { - std::cout << "[EnemyRegistry] 3 configuracions d'enemic carregades.\n"; + std::cout << "[EnemyRegistry] 4 configuracions d'enemic carregades.\n"; } return OK; } @@ -56,6 +58,8 @@ auto EnemyRegistry::get(EnemyType type) -> const EnemyConfig& { return square_config; case EnemyType::PINWHEEL: return pinwheel_config; + case EnemyType::STAR: + return star_config; } std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n"; std::exit(EXIT_FAILURE); diff --git a/source/game/entities/enemy_registry.hpp b/source/game/entities/enemy_registry.hpp index 3f4e8a4..49e4d3c 100644 --- a/source/game/entities/enemy_registry.hpp +++ b/source/game/entities/enemy_registry.hpp @@ -26,5 +26,6 @@ class EnemyRegistry { static EnemyConfig pentagon_config; static EnemyConfig square_config; static EnemyConfig pinwheel_config; + static EnemyConfig star_config; static bool loaded; }; diff --git a/source/game/stage_system/spawn_controller.cpp b/source/game/stage_system/spawn_controller.cpp index 82a769a..2ec6314 100644 --- a/source/game/stage_system/spawn_controller.cpp +++ b/source/game/stage_system/spawn_controller.cpp @@ -136,7 +136,10 @@ namespace StageSystem { if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) { return EnemyType::SQUARE; } - return EnemyType::PINWHEEL; + if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado + config_->distribucio.molinillo) { + return EnemyType::PINWHEEL; + } + return EnemyType::STAR; } void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) { diff --git a/source/game/stage_system/stage_config.hpp b/source/game/stage_system/stage_config.hpp index 1ed4ce8..f990a98 100644 --- a/source/game/stage_system/stage_config.hpp +++ b/source/game/stage_system/stage_config.hpp @@ -28,6 +28,7 @@ namespace StageSystem { uint8_t pentagon; // 0-100 uint8_t cuadrado; // 0-100 uint8_t molinillo; // 0-100 + uint8_t star{0}; // 0-100 (opcional al YAML; default 0 per compat amb stages antics) // Suma ha de ser 100, validat en StageLoader }; @@ -59,7 +60,7 @@ namespace StageSystem { // el tipo; basta con confirmar que no es 0 (sentinela "sin asignar"). return stage_id >= 1 && total_enemies > 0 && total_enemies <= 200 && - distribucio.pentagon + distribucio.cuadrado + distribucio.molinillo == 100; + distribucio.pentagon + distribucio.cuadrado + distribucio.molinillo + distribucio.star == 100; } }; diff --git a/source/game/stage_system/stage_loader.cpp b/source/game/stage_system/stage_loader.cpp index 4bef59e..ae3ba5a 100644 --- a/source/game/stage_system/stage_loader.cpp +++ b/source/game/stage_system/stage_loader.cpp @@ -19,241 +19,243 @@ namespace StageSystem { -auto StageLoader::load(const std::string& path) -> std::unique_ptr { - try { - // Normalize path: "data/stages/stages.yaml" → "stages/stages.yaml" - std::string normalized = path; - if (normalized.starts_with("data/")) { - normalized = normalized.substr(5); - } + auto StageLoader::load(const std::string& path) -> std::unique_ptr { + try { + // Normalize path: "data/stages/stages.yaml" → "stages/stages.yaml" + std::string normalized = path; + if (normalized.starts_with("data/")) { + normalized = normalized.substr(5); + } - // Load from resource system - std::vector data = Resource::Helper::loadFile(normalized); - if (data.empty()) { - std::cerr << "[StageLoader] Error: no es pot load " << normalized << '\n'; - return nullptr; - } - - // Convert to string - std::string yaml_content(data.begin(), data.end()); - std::stringstream stream(yaml_content); - - // Parse YAML - fkyaml::node yaml = fkyaml::node::deserialize(stream); - auto config = std::make_unique(); - - // Parse metadata - if (!yaml.contains("metadata")) { - std::cerr << "[StageLoader] Error: falta camp 'metadata'" << '\n'; - return nullptr; - } - if (!parseMetadata(yaml["metadata"], config->metadata)) { - return nullptr; - } - - // Parse stages - if (!yaml.contains("stages")) { - std::cerr << "[StageLoader] Error: falta camp 'stages'" << '\n'; - return nullptr; - } - - if (!yaml["stages"].is_sequence()) { - std::cerr << "[StageLoader] Error: 'stages' ha de ser una list" << '\n'; - return nullptr; - } - - for (const auto& stage_yaml : yaml["stages"]) { - StageConfig stage; - if (!parseStage(stage_yaml, stage)) { + // Load from resource system + std::vector data = Resource::Helper::loadFile(normalized); + if (data.empty()) { + std::cerr << "[StageLoader] Error: no es pot load " << normalized << '\n'; return nullptr; } - config->stages.push_back(stage); - } - // Validar configuración - if (!validateConfig(*config)) { + // Convert to string + std::string yaml_content(data.begin(), data.end()); + std::stringstream stream(yaml_content); + + // Parse YAML + fkyaml::node yaml = fkyaml::node::deserialize(stream); + auto config = std::make_unique(); + + // Parse metadata + if (!yaml.contains("metadata")) { + std::cerr << "[StageLoader] Error: falta camp 'metadata'" << '\n'; + return nullptr; + } + if (!parseMetadata(yaml["metadata"], config->metadata)) { + return nullptr; + } + + // Parse stages + if (!yaml.contains("stages")) { + std::cerr << "[StageLoader] Error: falta camp 'stages'" << '\n'; + return nullptr; + } + + if (!yaml["stages"].is_sequence()) { + std::cerr << "[StageLoader] Error: 'stages' ha de ser una list" << '\n'; + return nullptr; + } + + for (const auto& stage_yaml : yaml["stages"]) { + StageConfig stage; + if (!parseStage(stage_yaml, stage)) { + return nullptr; + } + config->stages.push_back(stage); + } + + // Validar configuración + if (!validateConfig(*config)) { + return nullptr; + } + + std::cout << "[StageLoader] Carregats " << config->stages.size() + << " stages correctament" << '\n'; + return config; + + } catch (const std::exception& e) { + std::cerr << "[StageLoader] Excepció: " << e.what() << '\n'; return nullptr; } - - std::cout << "[StageLoader] Carregats " << config->stages.size() - << " stages correctament" << '\n'; - return config; - - } catch (const std::exception& e) { - std::cerr << "[StageLoader] Excepció: " << e.what() << '\n'; - return nullptr; } -} -auto StageLoader::parseMetadata(const fkyaml::node& yaml, MetadataStages& meta) -> bool { - try { - if (!yaml.contains("version") || !yaml.contains("total_stages")) { - std::cerr << "[StageLoader] Error: metadata incompleta" << '\n'; + auto StageLoader::parseMetadata(const fkyaml::node& yaml, MetadataStages& meta) -> bool { + try { + if (!yaml.contains("version") || !yaml.contains("total_stages")) { + std::cerr << "[StageLoader] Error: metadata incompleta" << '\n'; + return false; + } + + meta.version = yaml["version"].get_value(); + meta.total_stages = yaml["total_stages"].get_value(); + meta.descripcio = yaml.contains("description") + ? yaml["description"].get_value() + : ""; + + return true; + } catch (const std::exception& e) { + std::cerr << "[StageLoader] Error parsing metadata: " << e.what() << '\n'; return false; } - - meta.version = yaml["version"].get_value(); - meta.total_stages = yaml["total_stages"].get_value(); - meta.descripcio = yaml.contains("description") - ? yaml["description"].get_value() - : ""; - - return true; - } catch (const std::exception& e) { - std::cerr << "[StageLoader] Error parsing metadata: " << e.what() << '\n'; - return false; } -} -auto StageLoader::parseStage(const fkyaml::node& yaml, StageConfig& stage) -> bool { - try { - if (!yaml.contains("stage_id") || !yaml.contains("total_enemies") || - !yaml.contains("spawn_config") || !yaml.contains("enemy_distribution") || - !yaml.contains("difficulty_multipliers")) { - std::cerr << "[StageLoader] Error: stage incompleta" << '\n'; + auto StageLoader::parseStage(const fkyaml::node& yaml, StageConfig& stage) -> bool { + try { + if (!yaml.contains("stage_id") || !yaml.contains("total_enemies") || + !yaml.contains("spawn_config") || !yaml.contains("enemy_distribution") || + !yaml.contains("difficulty_multipliers")) { + std::cerr << "[StageLoader] Error: stage incompleta" << '\n'; + return false; + } + + stage.stage_id = yaml["stage_id"].get_value(); + stage.total_enemies = yaml["total_enemies"].get_value(); + + if (!parseSpawnConfig(yaml["spawn_config"], stage.config_spawn)) { + return false; + } + if (!parseDistribution(yaml["enemy_distribution"], stage.distribucio)) { + return false; + } + if (!parseMultipliers(yaml["difficulty_multipliers"], stage.multiplicadors)) { + return false; + } + + if (!stage.isValid()) { + std::cerr << "[StageLoader] Error: stage " << static_cast(stage.stage_id) + << " no es vàlid" << '\n'; + return false; + } + + return true; + } catch (const std::exception& e) { + std::cerr << "[StageLoader] Error parsing stage: " << e.what() << '\n'; return false; } - - stage.stage_id = yaml["stage_id"].get_value(); - stage.total_enemies = yaml["total_enemies"].get_value(); - - if (!parseSpawnConfig(yaml["spawn_config"], stage.config_spawn)) { - return false; - } - if (!parseDistribution(yaml["enemy_distribution"], stage.distribucio)) { - return false; - } - if (!parseMultipliers(yaml["difficulty_multipliers"], stage.multiplicadors)) { - return false; - } - - if (!stage.isValid()) { - std::cerr << "[StageLoader] Error: stage " << static_cast(stage.stage_id) - << " no es vàlid" << '\n'; - return false; - } - - return true; - } catch (const std::exception& e) { - std::cerr << "[StageLoader] Error parsing stage: " << e.what() << '\n'; - return false; } -} -auto StageLoader::parseSpawnConfig(const fkyaml::node& yaml, ConfigSpawn& config) -> bool { - try { - if (!yaml.contains("mode") || !yaml.contains("initial_delay") || - !yaml.contains("spawn_interval")) { - std::cerr << "[StageLoader] Error: spawn_config incompleta" << '\n'; + auto StageLoader::parseSpawnConfig(const fkyaml::node& yaml, ConfigSpawn& config) -> bool { + try { + if (!yaml.contains("mode") || !yaml.contains("initial_delay") || + !yaml.contains("spawn_interval")) { + std::cerr << "[StageLoader] Error: spawn_config incompleta" << '\n'; + return false; + } + + auto mode_str = yaml["mode"].get_value(); + config.mode = parseSpawnMode(mode_str); + config.delay_inicial = yaml["initial_delay"].get_value(); + config.interval_spawn = yaml["spawn_interval"].get_value(); + + return true; + } catch (const std::exception& e) { + std::cerr << "[StageLoader] Error parsing spawn_config: " << e.what() << '\n'; return false; } - - auto mode_str = yaml["mode"].get_value(); - config.mode = parseSpawnMode(mode_str); - config.delay_inicial = yaml["initial_delay"].get_value(); - config.interval_spawn = yaml["spawn_interval"].get_value(); - - return true; - } catch (const std::exception& e) { - std::cerr << "[StageLoader] Error parsing spawn_config: " << e.what() << '\n'; - return false; } -} -auto StageLoader::parseDistribution(const fkyaml::node& yaml, DistribucioEnemics& dist) -> bool { - try { - if (!yaml.contains("pentagon") || !yaml.contains("cuadrado") || - !yaml.contains("molinillo")) { - std::cerr << "[StageLoader] Error: enemy_distribution incompleta" << '\n'; + auto StageLoader::parseDistribution(const fkyaml::node& yaml, DistribucioEnemics& dist) -> bool { + try { + if (!yaml.contains("pentagon") || !yaml.contains("cuadrado") || + !yaml.contains("molinillo")) { + std::cerr << "[StageLoader] Error: enemy_distribution incompleta" << '\n'; + return false; + } + + dist.pentagon = yaml["pentagon"].get_value(); + dist.cuadrado = yaml["cuadrado"].get_value(); + dist.molinillo = yaml["molinillo"].get_value(); + // 'star' és opcional per compatibilitat amb stages antics (default 0). + dist.star = yaml.contains("star") ? yaml["star"].get_value() : 0; + + // Validar que suma 100 + int sum = dist.pentagon + dist.cuadrado + dist.molinillo + dist.star; + if (sum != 100) { + std::cerr << "[StageLoader] Error: distribució no suma 100 (suma=" << sum << ")" << '\n'; + return false; + } + + return true; + } catch (const std::exception& e) { + std::cerr << "[StageLoader] Error parsing distribution: " << e.what() << '\n'; return false; } - - dist.pentagon = yaml["pentagon"].get_value(); - dist.cuadrado = yaml["cuadrado"].get_value(); - dist.molinillo = yaml["molinillo"].get_value(); - - // Validar que suma 100 - int sum = dist.pentagon + dist.cuadrado + dist.molinillo; - if (sum != 100) { - std::cerr << "[StageLoader] Error: distribució no suma 100 (suma=" << sum << ")" << '\n'; - return false; - } - - return true; - } catch (const std::exception& e) { - std::cerr << "[StageLoader] Error parsing distribution: " << e.what() << '\n'; - return false; } -} -auto StageLoader::parseMultipliers(const fkyaml::node& yaml, MultiplicadorsDificultat& mult) -> bool { - try { - if (!yaml.contains("speed_multiplier") || !yaml.contains("rotation_multiplier") || - !yaml.contains("tracking_strength")) { - std::cerr << "[StageLoader] Error: difficulty_multipliers incompleta" << '\n'; + auto StageLoader::parseMultipliers(const fkyaml::node& yaml, MultiplicadorsDificultat& mult) -> bool { + try { + if (!yaml.contains("speed_multiplier") || !yaml.contains("rotation_multiplier") || + !yaml.contains("tracking_strength")) { + std::cerr << "[StageLoader] Error: difficulty_multipliers incompleta" << '\n'; + return false; + } + + mult.velocity = yaml["speed_multiplier"].get_value(); + mult.rotation = yaml["rotation_multiplier"].get_value(); + mult.tracking_strength = yaml["tracking_strength"].get_value(); + + // Validar rangs raonables + if (mult.velocity < 0.1F || mult.velocity > 5.0F) { + std::cerr << "[StageLoader] Warning: speed_multiplier fuera de rang (0.1-5.0)" << '\n'; + } + if (mult.rotation < 0.1F || mult.rotation > 5.0F) { + std::cerr << "[StageLoader] Warning: rotation_multiplier fuera de rang (0.1-5.0)" << '\n'; + } + if (mult.tracking_strength < 0.0F || mult.tracking_strength > 2.0F) { + std::cerr << "[StageLoader] Warning: tracking_strength fuera de rang (0.0-2.0)" << '\n'; + } + + return true; + } catch (const std::exception& e) { + std::cerr << "[StageLoader] Error parsing multipliers: " << e.what() << '\n'; return false; } - - mult.velocity = yaml["speed_multiplier"].get_value(); - mult.rotation = yaml["rotation_multiplier"].get_value(); - mult.tracking_strength = yaml["tracking_strength"].get_value(); - - // Validar rangs raonables - if (mult.velocity < 0.1F || mult.velocity > 5.0F) { - std::cerr << "[StageLoader] Warning: speed_multiplier fuera de rang (0.1-5.0)" << '\n'; - } - if (mult.rotation < 0.1F || mult.rotation > 5.0F) { - std::cerr << "[StageLoader] Warning: rotation_multiplier fuera de rang (0.1-5.0)" << '\n'; - } - if (mult.tracking_strength < 0.0F || mult.tracking_strength > 2.0F) { - std::cerr << "[StageLoader] Warning: tracking_strength fuera de rang (0.0-2.0)" << '\n'; - } - - return true; - } catch (const std::exception& e) { - std::cerr << "[StageLoader] Error parsing multipliers: " << e.what() << '\n'; - return false; } -} -auto StageLoader::parseSpawnMode(const std::string& mode_str) -> ModeSpawn { - if (mode_str == "progressive") { + auto StageLoader::parseSpawnMode(const std::string& mode_str) -> ModeSpawn { + if (mode_str == "progressive") { + return ModeSpawn::PROGRESSIVE; + } + if (mode_str == "immediate") { + return ModeSpawn::IMMEDIATE; + } + if (mode_str == "wave") { + return ModeSpawn::WAVE; + } + std::cerr << "[StageLoader] Warning: mode de spawn desconegut '" << mode_str + << "', usant PROGRESSIVE" << '\n'; return ModeSpawn::PROGRESSIVE; } - if (mode_str == "immediate") { - return ModeSpawn::IMMEDIATE; - } - if (mode_str == "wave") { - return ModeSpawn::WAVE; - } - std::cerr << "[StageLoader] Warning: mode de spawn desconegut '" << mode_str - << "', usant PROGRESSIVE" << '\n'; - return ModeSpawn::PROGRESSIVE; -} -auto StageLoader::validateConfig(const StageSystemConfig& config) -> bool { - if (config.stages.empty()) { - std::cerr << "[StageLoader] Error: sin stage carregat" << '\n'; - return false; - } - - if (config.stages.size() != config.metadata.total_stages) { - std::cerr << "[StageLoader] Warning: nombre de stages (" << config.stages.size() - << ") no coincideix con metadata.total_stages (" - << static_cast(config.metadata.total_stages) << ")" << '\n'; - } - - // Validar stage_id consecutius - for (size_t i = 0; i < config.stages.size(); i++) { - if (config.stages[i].stage_id != i + 1) { - std::cerr << "[StageLoader] Error: stage_id no consecutius (esperat " - << i + 1 << ", trobat " << static_cast(config.stages[i].stage_id) - << ")" << '\n'; + auto StageLoader::validateConfig(const StageSystemConfig& config) -> bool { + if (config.stages.empty()) { + std::cerr << "[StageLoader] Error: sin stage carregat" << '\n'; return false; } + + if (config.stages.size() != config.metadata.total_stages) { + std::cerr << "[StageLoader] Warning: nombre de stages (" << config.stages.size() + << ") no coincideix con metadata.total_stages (" + << static_cast(config.metadata.total_stages) << ")" << '\n'; + } + + // Validar stage_id consecutius + for (size_t i = 0; i < config.stages.size(); i++) { + if (config.stages[i].stage_id != i + 1) { + std::cerr << "[StageLoader] Error: stage_id no consecutius (esperat " + << i + 1 << ", trobat " << static_cast(config.stages[i].stage_id) + << ")" << '\n'; + return false; + } + } + + return true; } - return true; -} - } // namespace StageSystem