afegida progresió
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# CMakeLists.txt
|
# CMakeLists.txt
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(orni VERSION 0.4.0)
|
project(orni VERSION 0.5.0)
|
||||||
|
|
||||||
# Info del proyecto
|
# Info del proyecto
|
||||||
set(PROJECT_LONG_NAME "Orni Attack")
|
set(PROJECT_LONG_NAME "Orni Attack")
|
||||||
|
|||||||
168
data/stages/stages.yaml
Normal file
168
data/stages/stages.yaml
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# stages.yaml - Configuració de les 10 etapes d'Orni Attack
|
||||||
|
# © 2025 Orni Attack
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
version: "1.0"
|
||||||
|
total_stages: 10
|
||||||
|
description: "Progressive difficulty curve from novice to expert"
|
||||||
|
|
||||||
|
stages:
|
||||||
|
# STAGE 1: Tutorial - Only pentagons, slow speed
|
||||||
|
- stage_id: 1
|
||||||
|
total_enemies: 5
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 2.0
|
||||||
|
spawn_interval: 3.0
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 100
|
||||||
|
quadrat: 0
|
||||||
|
molinillo: 0
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 0.7
|
||||||
|
rotation_multiplier: 0.8
|
||||||
|
tracking_strength: 0.0
|
||||||
|
|
||||||
|
# STAGE 2: Introduction to tracking enemies
|
||||||
|
- stage_id: 2
|
||||||
|
total_enemies: 7
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 1.5
|
||||||
|
spawn_interval: 2.5
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 70
|
||||||
|
quadrat: 30
|
||||||
|
molinillo: 0
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 0.85
|
||||||
|
rotation_multiplier: 0.9
|
||||||
|
tracking_strength: 0.3
|
||||||
|
|
||||||
|
# STAGE 3: All enemy types, normal speed
|
||||||
|
- stage_id: 3
|
||||||
|
total_enemies: 10
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 1.0
|
||||||
|
spawn_interval: 2.0
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 50
|
||||||
|
quadrat: 30
|
||||||
|
molinillo: 20
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.0
|
||||||
|
rotation_multiplier: 1.0
|
||||||
|
tracking_strength: 0.5
|
||||||
|
|
||||||
|
# STAGE 4: Increased count, faster enemies
|
||||||
|
- stage_id: 4
|
||||||
|
total_enemies: 12
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.8
|
||||||
|
spawn_interval: 1.8
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 40
|
||||||
|
quadrat: 35
|
||||||
|
molinillo: 25
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.1
|
||||||
|
rotation_multiplier: 1.15
|
||||||
|
tracking_strength: 0.6
|
||||||
|
|
||||||
|
# STAGE 5: Maximum count reached
|
||||||
|
- stage_id: 5
|
||||||
|
total_enemies: 15
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.5
|
||||||
|
spawn_interval: 1.5
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 35
|
||||||
|
quadrat: 35
|
||||||
|
molinillo: 30
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.2
|
||||||
|
rotation_multiplier: 1.25
|
||||||
|
tracking_strength: 0.7
|
||||||
|
|
||||||
|
# STAGE 6: Molinillo becomes dominant
|
||||||
|
- stage_id: 6
|
||||||
|
total_enemies: 15
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.3
|
||||||
|
spawn_interval: 1.3
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 30
|
||||||
|
quadrat: 30
|
||||||
|
molinillo: 40
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.3
|
||||||
|
rotation_multiplier: 1.4
|
||||||
|
tracking_strength: 0.8
|
||||||
|
|
||||||
|
# STAGE 7: High intensity, fast spawns
|
||||||
|
- stage_id: 7
|
||||||
|
total_enemies: 15
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.2
|
||||||
|
spawn_interval: 1.0
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 25
|
||||||
|
quadrat: 30
|
||||||
|
molinillo: 45
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.4
|
||||||
|
rotation_multiplier: 1.5
|
||||||
|
tracking_strength: 0.9
|
||||||
|
|
||||||
|
# STAGE 8: Expert level, 50% molinillos
|
||||||
|
- stage_id: 8
|
||||||
|
total_enemies: 15
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.1
|
||||||
|
spawn_interval: 0.8
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 20
|
||||||
|
quadrat: 30
|
||||||
|
molinillo: 50
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.5
|
||||||
|
rotation_multiplier: 1.6
|
||||||
|
tracking_strength: 1.0
|
||||||
|
|
||||||
|
# STAGE 9: Near-maximum difficulty
|
||||||
|
- stage_id: 9
|
||||||
|
total_enemies: 15
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.0
|
||||||
|
spawn_interval: 0.6
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 15
|
||||||
|
quadrat: 25
|
||||||
|
molinillo: 60
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.6
|
||||||
|
rotation_multiplier: 1.7
|
||||||
|
tracking_strength: 1.1
|
||||||
|
|
||||||
|
# STAGE 10: Final challenge, 70% molinillos
|
||||||
|
- stage_id: 10
|
||||||
|
total_enemies: 15
|
||||||
|
spawn_config:
|
||||||
|
mode: "progressive"
|
||||||
|
initial_delay: 0.0
|
||||||
|
spawn_interval: 0.5
|
||||||
|
enemy_distribution:
|
||||||
|
pentagon: 10
|
||||||
|
quadrat: 20
|
||||||
|
molinillo: 70
|
||||||
|
difficulty_multipliers:
|
||||||
|
speed_multiplier: 1.8
|
||||||
|
rotation_multiplier: 2.0
|
||||||
|
tracking_strength: 1.2
|
||||||
@@ -79,6 +79,7 @@ constexpr int STARTING_LIVES = 3; // Initial lives
|
|||||||
constexpr float DEATH_DURATION = 3.0f; // Seconds of death animation
|
constexpr float DEATH_DURATION = 3.0f; // Seconds of death animation
|
||||||
constexpr float GAME_OVER_DURATION = 5.0f; // Seconds to display game over
|
constexpr float GAME_OVER_DURATION = 5.0f; // Seconds to display game over
|
||||||
constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80f; // 80% hitbox (generous)
|
constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80f; // 80% hitbox (generous)
|
||||||
|
constexpr float STAGE_TRANSITION_DURATION = 3.0f; // Seconds for LEVEL_START/COMPLETED transitions
|
||||||
} // namespace Game
|
} // namespace Game
|
||||||
|
|
||||||
// Física (valores actuales del juego, sincronizados con joc_asteroides.cpp)
|
// Física (valores actuales del juego, sincronizados con joc_asteroides.cpp)
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ Enemic::Enemic(SDL_Renderer* renderer)
|
|||||||
brightness_(Defaults::Brightness::ENEMIC),
|
brightness_(Defaults::Brightness::ENEMIC),
|
||||||
tipus_(TipusEnemic::PENTAGON),
|
tipus_(TipusEnemic::PENTAGON),
|
||||||
tracking_timer_(0.0f),
|
tracking_timer_(0.0f),
|
||||||
ship_position_(nullptr) {
|
ship_position_(nullptr),
|
||||||
|
tracking_strength_(0.5f) { // Default tracking strength
|
||||||
// [NUEVO] Forma es carrega a inicialitzar() segons el tipus
|
// [NUEVO] Forma es carrega a inicialitzar() segons el tipus
|
||||||
// Constructor no carrega forma per permetre tipus diferents
|
// Constructor no carrega forma per permetre tipus diferents
|
||||||
}
|
}
|
||||||
@@ -201,8 +202,8 @@ void Enemic::comportament_quadrat(float delta_time) {
|
|||||||
while (angle_diff > Constants::PI) angle_diff -= 2.0f * Constants::PI;
|
while (angle_diff > Constants::PI) angle_diff -= 2.0f * Constants::PI;
|
||||||
while (angle_diff < -Constants::PI) angle_diff += 2.0f * Constants::PI;
|
while (angle_diff < -Constants::PI) angle_diff += 2.0f * Constants::PI;
|
||||||
|
|
||||||
// Apply tracking strength
|
// Apply tracking strength (uses member variable, defaults to 0.5)
|
||||||
angle_ += angle_diff * Defaults::Enemies::Quadrat::TRACKING_STRENGTH;
|
angle_ += angle_diff * tracking_strength_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,3 +395,29 @@ float Enemic::calcular_escala_actual() const {
|
|||||||
|
|
||||||
return escala;
|
return escala;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [NEW] Stage system API implementations
|
||||||
|
|
||||||
|
float Enemic::get_base_velocity() const {
|
||||||
|
switch (tipus_) {
|
||||||
|
case TipusEnemic::PENTAGON:
|
||||||
|
return Defaults::Enemies::Pentagon::VELOCITAT;
|
||||||
|
case TipusEnemic::QUADRAT:
|
||||||
|
return Defaults::Enemies::Quadrat::VELOCITAT;
|
||||||
|
case TipusEnemic::MOLINILLO:
|
||||||
|
return Defaults::Enemies::Molinillo::VELOCITAT;
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Enemic::get_base_rotation() const {
|
||||||
|
// Return the base rotation speed (drotacio_base if available, otherwise current drotacio_)
|
||||||
|
return animacio_.drotacio_base != 0.0f ? animacio_.drotacio_base : drotacio_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::set_tracking_strength(float strength) {
|
||||||
|
// Only applies to QUADRAT type
|
||||||
|
if (tipus_ == TipusEnemic::QUADRAT) {
|
||||||
|
tracking_strength_ = strength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,16 @@ class Enemic {
|
|||||||
// Set ship position reference for tracking behavior
|
// Set ship position reference for tracking behavior
|
||||||
void set_ship_position(const Punt* ship_pos) { ship_position_ = ship_pos; }
|
void set_ship_position(const Punt* ship_pos) { ship_position_ = ship_pos; }
|
||||||
|
|
||||||
|
// [NEW] Getters for stage system (base stats)
|
||||||
|
float get_base_velocity() const;
|
||||||
|
float get_base_rotation() const;
|
||||||
|
TipusEnemic get_tipus() const { return tipus_; }
|
||||||
|
|
||||||
|
// [NEW] Setters for difficulty multipliers (stage system)
|
||||||
|
void set_velocity(float vel) { velocitat_ = vel; }
|
||||||
|
void set_rotation(float rot) { drotacio_ = rot; animacio_.drotacio_base = rot; }
|
||||||
|
void set_tracking_strength(float strength);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Renderer* renderer_;
|
SDL_Renderer* renderer_;
|
||||||
|
|
||||||
@@ -76,6 +86,7 @@ class Enemic {
|
|||||||
// [NEW] Behavior state (type-specific)
|
// [NEW] Behavior state (type-specific)
|
||||||
float tracking_timer_; // For Quadrat: time since last angle update
|
float tracking_timer_; // For Quadrat: time since last angle update
|
||||||
const Punt* ship_position_; // Pointer to ship position (for tracking)
|
const Punt* ship_position_; // Pointer to ship position (for tracking)
|
||||||
|
float tracking_strength_; // For Quadrat: tracking intensity (0.0-1.5), default 0.5
|
||||||
|
|
||||||
// [EXISTING] Private methods
|
// [EXISTING] Private methods
|
||||||
void mou(float delta_time);
|
void mou(float delta_time);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "core/rendering/line_renderer.hpp"
|
#include "core/rendering/line_renderer.hpp"
|
||||||
#include "core/system/gestor_escenes.hpp"
|
#include "core/system/gestor_escenes.hpp"
|
||||||
#include "core/system/global_events.hpp"
|
#include "core/system/global_events.hpp"
|
||||||
|
#include "game/stage_system/stage_loader.hpp"
|
||||||
|
|
||||||
EscenaJoc::EscenaJoc(SDLManager& sdl)
|
EscenaJoc::EscenaJoc(SDLManager& sdl)
|
||||||
: sdl_(sdl),
|
: sdl_(sdl),
|
||||||
@@ -105,6 +106,19 @@ void EscenaJoc::inicialitzar() {
|
|||||||
// Basat en el codi Pascal original: line 376
|
// Basat en el codi Pascal original: line 376
|
||||||
std::srand(static_cast<unsigned>(std::time(nullptr)));
|
std::srand(static_cast<unsigned>(std::time(nullptr)));
|
||||||
|
|
||||||
|
// [NEW] Load stage configuration (only once)
|
||||||
|
if (!stage_config_) {
|
||||||
|
stage_config_ = StageSystem::StageLoader::carregar("data/stages/stages.yaml");
|
||||||
|
if (!stage_config_) {
|
||||||
|
std::cerr << "[EscenaJoc] Error: no s'ha pogut carregar stages.yaml" << std::endl;
|
||||||
|
// Continue without stage system (will crash, but helps debugging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [NEW] Initialize stage manager
|
||||||
|
stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get());
|
||||||
|
stage_manager_->inicialitzar();
|
||||||
|
|
||||||
// Inicialitzar estat de col·lisió
|
// Inicialitzar estat de col·lisió
|
||||||
itocado_ = 0;
|
itocado_ = 0;
|
||||||
|
|
||||||
@@ -119,22 +133,11 @@ void EscenaJoc::inicialitzar() {
|
|||||||
// Inicialitzar nau
|
// Inicialitzar nau
|
||||||
nau_.inicialitzar();
|
nau_.inicialitzar();
|
||||||
|
|
||||||
// Inicialitzar enemics (ORNIs) amb tipus aleatoris
|
// [MODIFIED] Initialize enemies as inactive (stage system will spawn them)
|
||||||
for (auto& enemy : orni_) {
|
for (auto& enemy : orni_) {
|
||||||
// Random type distribution: ~40% Pentagon, ~30% Quadrat, ~30% Molinillo
|
enemy = Enemic(sdl_.obte_renderer());
|
||||||
int rand_val = std::rand() % 10;
|
|
||||||
TipusEnemic tipus;
|
|
||||||
|
|
||||||
if (rand_val < 4) {
|
|
||||||
tipus = TipusEnemic::PENTAGON;
|
|
||||||
} else if (rand_val < 7) {
|
|
||||||
tipus = TipusEnemic::QUADRAT;
|
|
||||||
} else {
|
|
||||||
tipus = TipusEnemic::MOLINILLO;
|
|
||||||
}
|
|
||||||
|
|
||||||
enemy.inicialitzar(tipus);
|
|
||||||
enemy.set_ship_position(&nau_.get_centre()); // Set ship reference for tracking
|
enemy.set_ship_position(&nau_.get_centre()); // Set ship reference for tracking
|
||||||
|
// DON'T call enemy.inicialitzar() here - stage system handles spawning
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicialitzar bales
|
// Inicialitzar bales
|
||||||
@@ -210,28 +213,53 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// *** NORMAL GAMEPLAY ***
|
// *** STAGE SYSTEM STATE MACHINE ***
|
||||||
|
|
||||||
// Update ship (input + physics)
|
StageSystem::EstatStage estat = stage_manager_->get_estat();
|
||||||
nau_.processar_input(delta_time);
|
|
||||||
nau_.actualitzar(delta_time);
|
|
||||||
|
|
||||||
// Update enemy movement and rotation
|
switch (estat) {
|
||||||
for (auto& enemy : orni_) {
|
case StageSystem::EstatStage::LEVEL_START:
|
||||||
enemy.actualitzar(delta_time);
|
// Frozen gameplay, countdown timer only
|
||||||
|
stage_manager_->actualitzar(delta_time);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StageSystem::EstatStage::PLAYING: {
|
||||||
|
// [NEW] Update stage manager (spawns enemies, pass pause flag)
|
||||||
|
bool pausar_spawn = (itocado_ > 0.0f); // Pause during death animation
|
||||||
|
stage_manager_->get_spawn_controller().actualitzar(delta_time, orni_, pausar_spawn);
|
||||||
|
|
||||||
|
// [NEW] Check stage completion (only when not in death sequence)
|
||||||
|
if (itocado_ == 0.0f) {
|
||||||
|
auto& spawn_ctrl = stage_manager_->get_spawn_controller();
|
||||||
|
if (spawn_ctrl.tots_enemics_destruits(orni_)) {
|
||||||
|
stage_manager_->stage_completat();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [EXISTING] Normal gameplay
|
||||||
|
nau_.processar_input(delta_time);
|
||||||
|
nau_.actualitzar(delta_time);
|
||||||
|
|
||||||
|
for (auto& enemy : orni_) {
|
||||||
|
enemy.actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& bala : bales_) {
|
||||||
|
bala.actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
detectar_col·lisions_bales_enemics();
|
||||||
|
detectar_col·lisio_nau_enemics();
|
||||||
|
debris_manager_.actualitzar(delta_time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case StageSystem::EstatStage::LEVEL_COMPLETED:
|
||||||
|
// Frozen gameplay, countdown timer only
|
||||||
|
stage_manager_->actualitzar(delta_time);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bullet movement
|
|
||||||
for (auto& bala : bales_) {
|
|
||||||
bala.actualitzar(delta_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect collisions
|
|
||||||
detectar_col·lisions_bales_enemics();
|
|
||||||
detectar_col·lisio_nau_enemics(); // New collision check
|
|
||||||
|
|
||||||
// Update debris
|
|
||||||
debris_manager_.actualitzar(delta_time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::dibuixar() {
|
void EscenaJoc::dibuixar() {
|
||||||
@@ -270,26 +298,38 @@ void EscenaJoc::dibuixar() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// During death sequence, don't draw ship (debris draws automatically)
|
// [NEW] Stage state rendering
|
||||||
if (itocado_ == 0.0f) {
|
StageSystem::EstatStage estat = stage_manager_->get_estat();
|
||||||
nau_.dibuixar();
|
|
||||||
|
switch (estat) {
|
||||||
|
case StageSystem::EstatStage::LEVEL_START:
|
||||||
|
dibuixar_missatge_stage(StageSystem::Constants::MISSATGE_LEVEL_START);
|
||||||
|
dibuixar_marcador();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StageSystem::EstatStage::PLAYING:
|
||||||
|
// [EXISTING] Normal rendering
|
||||||
|
if (itocado_ == 0.0f) {
|
||||||
|
nau_.dibuixar();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& enemy : orni_) {
|
||||||
|
enemy.dibuixar();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& bala : bales_) {
|
||||||
|
bala.dibuixar();
|
||||||
|
}
|
||||||
|
|
||||||
|
debris_manager_.dibuixar();
|
||||||
|
dibuixar_marcador();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StageSystem::EstatStage::LEVEL_COMPLETED:
|
||||||
|
dibuixar_missatge_stage(StageSystem::Constants::MISSATGE_LEVEL_COMPLETED);
|
||||||
|
dibuixar_marcador();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw enemies (always)
|
|
||||||
for (const auto& enemy : orni_) {
|
|
||||||
enemy.dibuixar();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw bullets (always)
|
|
||||||
for (const auto& bala : bales_) {
|
|
||||||
bala.dibuixar();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw debris
|
|
||||||
debris_manager_.dibuixar();
|
|
||||||
|
|
||||||
// Draw scoreboard
|
|
||||||
dibuixar_marcador();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::processar_input(const SDL_Event& event) {
|
void EscenaJoc::processar_input(const SDL_Event& event) {
|
||||||
@@ -399,8 +439,13 @@ void EscenaJoc::dibuixar_marges() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::dibuixar_marcador() {
|
void EscenaJoc::dibuixar_marcador() {
|
||||||
// Display actual lives count (user requested "LIFES" plural English)
|
// [MODIFIED] Display current stage number from stage manager
|
||||||
std::string text = "SCORE: 01000 LIFES: " + std::to_string(num_vides_) + " LEVEL: 01";
|
uint8_t stage_num = stage_manager_->get_stage_actual();
|
||||||
|
std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num)
|
||||||
|
: std::to_string(stage_num);
|
||||||
|
|
||||||
|
std::string text = "SCORE: 01000 LIFES: " + std::to_string(num_vides_) +
|
||||||
|
" LEVEL: " + stage_str;
|
||||||
|
|
||||||
// Paràmetres de renderització
|
// Paràmetres de renderització
|
||||||
const float escala = 0.85f;
|
const float escala = 0.85f;
|
||||||
@@ -513,3 +558,20 @@ void EscenaJoc::detectar_col·lisio_nau_enemics() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [NEW] Stage system helper methods
|
||||||
|
|
||||||
|
void EscenaJoc::dibuixar_missatge_stage(const std::string& missatge) {
|
||||||
|
constexpr float escala = 1.5f;
|
||||||
|
constexpr float spacing = 3.0f;
|
||||||
|
|
||||||
|
float text_width = text_.get_text_width(missatge, escala, spacing);
|
||||||
|
float text_height = text_.get_text_height(escala);
|
||||||
|
|
||||||
|
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
|
||||||
|
float x = play_area.x + (play_area.w - text_width) / 2.0f;
|
||||||
|
float y = play_area.y + (play_area.h - text_height) / 2.0f;
|
||||||
|
|
||||||
|
Punt pos = {static_cast<float>(x), static_cast<float>(y)};
|
||||||
|
text_.render(missatge, pos, escala, spacing);
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,10 +15,13 @@
|
|||||||
#include "../entities/bala.hpp"
|
#include "../entities/bala.hpp"
|
||||||
#include "../entities/enemic.hpp"
|
#include "../entities/enemic.hpp"
|
||||||
#include "../entities/nau.hpp"
|
#include "../entities/nau.hpp"
|
||||||
|
#include "../stage_system/stage_manager.hpp"
|
||||||
#include "core/graphics/vector_text.hpp"
|
#include "core/graphics/vector_text.hpp"
|
||||||
#include "core/rendering/sdl_manager.hpp"
|
#include "core/rendering/sdl_manager.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// Classe principal del joc (escena)
|
// Classe principal del joc (escena)
|
||||||
class EscenaJoc {
|
class EscenaJoc {
|
||||||
public:
|
public:
|
||||||
@@ -53,12 +56,19 @@ class EscenaJoc {
|
|||||||
// Text vectorial
|
// Text vectorial
|
||||||
Graphics::VectorText text_;
|
Graphics::VectorText text_;
|
||||||
|
|
||||||
|
// [NEW] Stage system
|
||||||
|
std::unique_ptr<StageSystem::ConfigSistemaStages> stage_config_;
|
||||||
|
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||||
|
|
||||||
// Funcions privades
|
// Funcions privades
|
||||||
void tocado();
|
void tocado();
|
||||||
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
||||||
void detectar_col·lisio_nau_enemics(); // Ship-enemy collision detection
|
void detectar_col·lisio_nau_enemics(); // Ship-enemy collision detection
|
||||||
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
||||||
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
||||||
|
|
||||||
|
// [NEW] Stage system helpers
|
||||||
|
void dibuixar_missatge_stage(const std::string& missatge);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ESCENA_JOC_HPP
|
#endif // ESCENA_JOC_HPP
|
||||||
|
|||||||
167
source/game/stage_system/spawn_controller.cpp
Normal file
167
source/game/stage_system/spawn_controller.cpp
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// spawn_controller.cpp - Implementació del controlador de spawn
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#include "spawn_controller.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
SpawnController::SpawnController()
|
||||||
|
: config_(nullptr), temps_transcorregut_(0.0f), index_spawn_actual_(0) {}
|
||||||
|
|
||||||
|
void SpawnController::configurar(const ConfigStage* config) {
|
||||||
|
config_ = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::iniciar() {
|
||||||
|
if (!config_) {
|
||||||
|
std::cerr << "[SpawnController] Error: config_ és null" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
generar_spawn_events();
|
||||||
|
|
||||||
|
std::cout << "[SpawnController] Stage " << static_cast<int>(config_->stage_id)
|
||||||
|
<< ": generats " << spawn_queue_.size() << " spawn events" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::reset() {
|
||||||
|
spawn_queue_.clear();
|
||||||
|
temps_transcorregut_ = 0.0f;
|
||||||
|
index_spawn_actual_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::actualitzar(float delta_time, std::array<Enemic, 15>& orni_array, bool pausar) {
|
||||||
|
if (!config_ || spawn_queue_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment timer only when not paused
|
||||||
|
if (!pausar) {
|
||||||
|
temps_transcorregut_ += delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process spawn events
|
||||||
|
while (index_spawn_actual_ < spawn_queue_.size()) {
|
||||||
|
SpawnEvent& event = spawn_queue_[index_spawn_actual_];
|
||||||
|
|
||||||
|
if (event.spawnejat) {
|
||||||
|
index_spawn_actual_++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temps_transcorregut_ >= event.temps_spawn) {
|
||||||
|
// Find first inactive enemy
|
||||||
|
for (auto& enemic : orni_array) {
|
||||||
|
if (!enemic.esta_actiu()) {
|
||||||
|
spawn_enemic(enemic, event.tipus);
|
||||||
|
event.spawnejat = true;
|
||||||
|
index_spawn_actual_++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no slot available, try next frame
|
||||||
|
if (!event.spawnejat) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not yet time for this spawn
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpawnController::tots_enemics_spawnejats() const {
|
||||||
|
return index_spawn_actual_ >= spawn_queue_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpawnController::tots_enemics_destruits(const std::array<Enemic, 15>& orni_array) const {
|
||||||
|
if (!tots_enemics_spawnejats()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& enemic : orni_array) {
|
||||||
|
if (enemic.esta_actiu()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SpawnController::get_enemics_vius(const std::array<Enemic, 15>& orni_array) const {
|
||||||
|
uint8_t count = 0;
|
||||||
|
for (const auto& enemic : orni_array) {
|
||||||
|
if (enemic.esta_actiu()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SpawnController::get_enemics_spawnejats() const {
|
||||||
|
return static_cast<uint8_t>(index_spawn_actual_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::generar_spawn_events() {
|
||||||
|
if (!config_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < config_->total_enemics; i++) {
|
||||||
|
float spawn_time = config_->config_spawn.delay_inicial +
|
||||||
|
(i * config_->config_spawn.interval_spawn);
|
||||||
|
|
||||||
|
TipusEnemic tipus = seleccionar_tipus_aleatori();
|
||||||
|
|
||||||
|
spawn_queue_.push_back({spawn_time, tipus, false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TipusEnemic SpawnController::seleccionar_tipus_aleatori() const {
|
||||||
|
if (!config_) {
|
||||||
|
return TipusEnemic::PENTAGON;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weighted random selection based on distribution
|
||||||
|
int rand_val = std::rand() % 100;
|
||||||
|
|
||||||
|
if (rand_val < config_->distribucio.pentagon) {
|
||||||
|
return TipusEnemic::PENTAGON;
|
||||||
|
} else if (rand_val < config_->distribucio.pentagon + config_->distribucio.quadrat) {
|
||||||
|
return TipusEnemic::QUADRAT;
|
||||||
|
} else {
|
||||||
|
return TipusEnemic::MOLINILLO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::spawn_enemic(Enemic& enemic, TipusEnemic tipus) {
|
||||||
|
// Initialize enemy
|
||||||
|
enemic.inicialitzar(tipus);
|
||||||
|
|
||||||
|
// Apply difficulty multipliers
|
||||||
|
aplicar_multiplicadors(enemic);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::aplicar_multiplicadors(Enemic& enemic) const {
|
||||||
|
if (!config_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply velocity multiplier
|
||||||
|
float base_vel = enemic.get_base_velocity();
|
||||||
|
enemic.set_velocity(base_vel * config_->multiplicadors.velocitat);
|
||||||
|
|
||||||
|
// Apply rotation multiplier
|
||||||
|
float base_rot = enemic.get_base_rotation();
|
||||||
|
enemic.set_rotation(base_rot * config_->multiplicadors.rotacio);
|
||||||
|
|
||||||
|
// Apply tracking strength (only affects QUADRAT)
|
||||||
|
enemic.set_tracking_strength(config_->multiplicadors.tracking_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
54
source/game/stage_system/spawn_controller.hpp
Normal file
54
source/game/stage_system/spawn_controller.hpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// spawn_controller.hpp - Controlador de spawn d'enemics
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/types.hpp"
|
||||||
|
#include "game/entities/enemic.hpp"
|
||||||
|
#include "stage_config.hpp"
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
// Informació de spawn planificat
|
||||||
|
struct SpawnEvent {
|
||||||
|
float temps_spawn; // Temps absolut (segons) per spawnejar
|
||||||
|
TipusEnemic tipus; // Tipus d'enemic
|
||||||
|
bool spawnejat; // Ja s'ha processat?
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpawnController {
|
||||||
|
public:
|
||||||
|
SpawnController();
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
void configurar(const ConfigStage* config); // Set stage config
|
||||||
|
void iniciar(); // Generate spawn schedule
|
||||||
|
void reset(); // Clear all pending spawns
|
||||||
|
|
||||||
|
// Update
|
||||||
|
void actualitzar(float delta_time, std::array<Enemic, 15>& orni_array, bool pausar = false);
|
||||||
|
|
||||||
|
// Status queries
|
||||||
|
bool tots_enemics_spawnejats() const;
|
||||||
|
bool tots_enemics_destruits(const std::array<Enemic, 15>& orni_array) const;
|
||||||
|
uint8_t get_enemics_vius(const std::array<Enemic, 15>& orni_array) const;
|
||||||
|
uint8_t get_enemics_spawnejats() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const ConfigStage* config_; // Non-owning pointer to current stage config
|
||||||
|
std::vector<SpawnEvent> spawn_queue_;
|
||||||
|
float temps_transcorregut_; // Elapsed time since stage start
|
||||||
|
uint8_t index_spawn_actual_; // Next spawn to process
|
||||||
|
|
||||||
|
// Spawn generation
|
||||||
|
void generar_spawn_events();
|
||||||
|
TipusEnemic seleccionar_tipus_aleatori() const;
|
||||||
|
void spawn_enemic(Enemic& enemic, TipusEnemic tipus);
|
||||||
|
void aplicar_multiplicadors(Enemic& enemic) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
84
source/game/stage_system/stage_config.hpp
Normal file
84
source/game/stage_system/stage_config.hpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// stage_config.hpp - Estructures de dades per configuració d'stages
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
// Tipus de mode de spawn
|
||||||
|
enum class ModeSpawn {
|
||||||
|
PROGRESSIVE, // Spawn progressiu amb intervals
|
||||||
|
IMMEDIATE, // Tots els enemics de cop
|
||||||
|
WAVE // Onades de 3-5 enemics (futura extensió)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configuració de spawn
|
||||||
|
struct ConfigSpawn {
|
||||||
|
ModeSpawn mode;
|
||||||
|
float delay_inicial; // Segons abans del primer spawn
|
||||||
|
float interval_spawn; // Segons entre spawns consecutius
|
||||||
|
};
|
||||||
|
|
||||||
|
// Distribució de tipus d'enemics (percentatges)
|
||||||
|
struct DistribucioEnemics {
|
||||||
|
uint8_t pentagon; // 0-100
|
||||||
|
uint8_t quadrat; // 0-100
|
||||||
|
uint8_t molinillo; // 0-100
|
||||||
|
// Suma ha de ser 100, validat en StageLoader
|
||||||
|
};
|
||||||
|
|
||||||
|
// Multiplicadors de dificultat
|
||||||
|
struct MultiplicadorsDificultat {
|
||||||
|
float velocitat; // 0.5-2.0 típic
|
||||||
|
float rotacio; // 0.5-2.0 típic
|
||||||
|
float tracking_strength; // 0.0-1.5 (aplicat a Quadrat)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Metadades del fitxer YAML
|
||||||
|
struct MetadataStages {
|
||||||
|
std::string version;
|
||||||
|
uint8_t total_stages;
|
||||||
|
std::string descripcio;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configuració completa d'un stage
|
||||||
|
struct ConfigStage {
|
||||||
|
uint8_t stage_id; // 1-10
|
||||||
|
uint8_t total_enemics; // 5-15
|
||||||
|
ConfigSpawn config_spawn;
|
||||||
|
DistribucioEnemics distribucio;
|
||||||
|
MultiplicadorsDificultat multiplicadors;
|
||||||
|
|
||||||
|
// Validació
|
||||||
|
bool es_valid() const {
|
||||||
|
return stage_id >= 1 && stage_id <= 255 &&
|
||||||
|
total_enemics > 0 && total_enemics <= 15 &&
|
||||||
|
distribucio.pentagon + distribucio.quadrat + distribucio.molinillo == 100;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configuració completa del sistema (carregada des de YAML)
|
||||||
|
struct ConfigSistemaStages {
|
||||||
|
MetadataStages metadata;
|
||||||
|
std::vector<ConfigStage> stages; // Índex [0] = stage 1
|
||||||
|
|
||||||
|
// Obtenir configuració d'un stage específic
|
||||||
|
const ConfigStage* obte_stage(uint8_t stage_id) const {
|
||||||
|
if (stage_id < 1 || stage_id > stages.size()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &stages[stage_id - 1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constants per missatges de transició
|
||||||
|
namespace Constants {
|
||||||
|
constexpr const char* MISSATGE_LEVEL_START = "ENEMY INCOMING";
|
||||||
|
constexpr const char* MISSATGE_LEVEL_COMPLETED = "GOOD JOB COMMANDER!";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
238
source/game/stage_system/stage_loader.cpp
Normal file
238
source/game/stage_system/stage_loader.cpp
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
// stage_loader.cpp - Implementació del carregador de configuració YAML
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#include "stage_loader.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
std::unique_ptr<ConfigSistemaStages> StageLoader::carregar(const std::string& path) {
|
||||||
|
try {
|
||||||
|
// Llegir fitxer YAML
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "[StageLoader] Error: no es pot obrir el fitxer " << path << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse YAML
|
||||||
|
fkyaml::node yaml = fkyaml::node::deserialize(file);
|
||||||
|
auto config = std::make_unique<ConfigSistemaStages>();
|
||||||
|
|
||||||
|
// Parse metadata
|
||||||
|
if (!yaml.contains("metadata")) {
|
||||||
|
std::cerr << "[StageLoader] Error: falta camp 'metadata'" << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!parse_metadata(yaml["metadata"], config->metadata)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse stages
|
||||||
|
if (!yaml.contains("stages")) {
|
||||||
|
std::cerr << "[StageLoader] Error: falta camp 'stages'" << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!yaml["stages"].is_sequence()) {
|
||||||
|
std::cerr << "[StageLoader] Error: 'stages' ha de ser una llista" << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& stage_yaml : yaml["stages"]) {
|
||||||
|
ConfigStage stage;
|
||||||
|
if (!parse_stage(stage_yaml, stage)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
config->stages.push_back(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar configuració
|
||||||
|
if (!validar_config(*config)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[StageLoader] Carregats " << config->stages.size()
|
||||||
|
<< " stages correctament" << std::endl;
|
||||||
|
return config;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[StageLoader] Excepció: " << e.what() << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLoader::parse_metadata(const fkyaml::node& yaml, MetadataStages& meta) {
|
||||||
|
try {
|
||||||
|
if (!yaml.contains("version") || !yaml.contains("total_stages")) {
|
||||||
|
std::cerr << "[StageLoader] Error: metadata incompleta" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.version = yaml["version"].get_value<std::string>();
|
||||||
|
meta.total_stages = yaml["total_stages"].get_value<uint8_t>();
|
||||||
|
meta.descripcio = yaml.contains("description")
|
||||||
|
? yaml["description"].get_value<std::string>()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[StageLoader] Error parsing metadata: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLoader::parse_stage(const fkyaml::node& yaml, ConfigStage& stage) {
|
||||||
|
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" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.stage_id = yaml["stage_id"].get_value<uint8_t>();
|
||||||
|
stage.total_enemics = yaml["total_enemies"].get_value<uint8_t>();
|
||||||
|
|
||||||
|
if (!parse_spawn_config(yaml["spawn_config"], stage.config_spawn)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!parse_distribution(yaml["enemy_distribution"], stage.distribucio)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!parse_multipliers(yaml["difficulty_multipliers"], stage.multiplicadors)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stage.es_valid()) {
|
||||||
|
std::cerr << "[StageLoader] Error: stage " << static_cast<int>(stage.stage_id)
|
||||||
|
<< " no és vàlid" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[StageLoader] Error parsing stage: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLoader::parse_spawn_config(const fkyaml::node& yaml, ConfigSpawn& config) {
|
||||||
|
try {
|
||||||
|
if (!yaml.contains("mode") || !yaml.contains("initial_delay") ||
|
||||||
|
!yaml.contains("spawn_interval")) {
|
||||||
|
std::cerr << "[StageLoader] Error: spawn_config incompleta" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mode_str = yaml["mode"].get_value<std::string>();
|
||||||
|
config.mode = parse_spawn_mode(mode_str);
|
||||||
|
config.delay_inicial = yaml["initial_delay"].get_value<float>();
|
||||||
|
config.interval_spawn = yaml["spawn_interval"].get_value<float>();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[StageLoader] Error parsing spawn_config: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLoader::parse_distribution(const fkyaml::node& yaml, DistribucioEnemics& dist) {
|
||||||
|
try {
|
||||||
|
if (!yaml.contains("pentagon") || !yaml.contains("quadrat") ||
|
||||||
|
!yaml.contains("molinillo")) {
|
||||||
|
std::cerr << "[StageLoader] Error: enemy_distribution incompleta" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dist.pentagon = yaml["pentagon"].get_value<uint8_t>();
|
||||||
|
dist.quadrat = yaml["quadrat"].get_value<uint8_t>();
|
||||||
|
dist.molinillo = yaml["molinillo"].get_value<uint8_t>();
|
||||||
|
|
||||||
|
// Validar que suma 100
|
||||||
|
int sum = dist.pentagon + dist.quadrat + dist.molinillo;
|
||||||
|
if (sum != 100) {
|
||||||
|
std::cerr << "[StageLoader] Error: distribució no suma 100 (suma=" << sum << ")" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[StageLoader] Error parsing distribution: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLoader::parse_multipliers(const fkyaml::node& yaml, MultiplicadorsDificultat& mult) {
|
||||||
|
try {
|
||||||
|
if (!yaml.contains("speed_multiplier") || !yaml.contains("rotation_multiplier") ||
|
||||||
|
!yaml.contains("tracking_strength")) {
|
||||||
|
std::cerr << "[StageLoader] Error: difficulty_multipliers incompleta" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mult.velocitat = yaml["speed_multiplier"].get_value<float>();
|
||||||
|
mult.rotacio = yaml["rotation_multiplier"].get_value<float>();
|
||||||
|
mult.tracking_strength = yaml["tracking_strength"].get_value<float>();
|
||||||
|
|
||||||
|
// Validar rangs raonables
|
||||||
|
if (mult.velocitat < 0.1f || mult.velocitat > 5.0f) {
|
||||||
|
std::cerr << "[StageLoader] Warning: speed_multiplier fora de rang (0.1-5.0)" << std::endl;
|
||||||
|
}
|
||||||
|
if (mult.rotacio < 0.1f || mult.rotacio > 5.0f) {
|
||||||
|
std::cerr << "[StageLoader] Warning: rotation_multiplier fora de rang (0.1-5.0)" << std::endl;
|
||||||
|
}
|
||||||
|
if (mult.tracking_strength < 0.0f || mult.tracking_strength > 2.0f) {
|
||||||
|
std::cerr << "[StageLoader] Warning: tracking_strength fora de rang (0.0-2.0)" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[StageLoader] Error parsing multipliers: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModeSpawn StageLoader::parse_spawn_mode(const std::string& mode_str) {
|
||||||
|
if (mode_str == "progressive") {
|
||||||
|
return ModeSpawn::PROGRESSIVE;
|
||||||
|
} else if (mode_str == "immediate") {
|
||||||
|
return ModeSpawn::IMMEDIATE;
|
||||||
|
} else if (mode_str == "wave") {
|
||||||
|
return ModeSpawn::WAVE;
|
||||||
|
} else {
|
||||||
|
std::cerr << "[StageLoader] Warning: mode de spawn desconegut '" << mode_str
|
||||||
|
<< "', usant PROGRESSIVE" << std::endl;
|
||||||
|
return ModeSpawn::PROGRESSIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLoader::validar_config(const ConfigSistemaStages& config) {
|
||||||
|
if (config.stages.empty()) {
|
||||||
|
std::cerr << "[StageLoader] Error: cap stage carregat" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.stages.size() != config.metadata.total_stages) {
|
||||||
|
std::cerr << "[StageLoader] Warning: nombre de stages (" << config.stages.size()
|
||||||
|
<< ") no coincideix amb metadata.total_stages ("
|
||||||
|
<< static_cast<int>(config.metadata.total_stages) << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<int>(config.stages[i].stage_id)
|
||||||
|
<< ")" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
32
source/game/stage_system/stage_loader.hpp
Normal file
32
source/game/stage_system/stage_loader.hpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// stage_loader.hpp - Carregador de configuració YAML
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "stage_config.hpp"
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
class StageLoader {
|
||||||
|
public:
|
||||||
|
// Carregar configuració des de fitxer YAML
|
||||||
|
// Retorna nullptr si hi ha errors
|
||||||
|
static std::unique_ptr<ConfigSistemaStages> carregar(const std::string& path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Parsing helpers (implementats en .cpp)
|
||||||
|
static bool parse_metadata(const fkyaml::node& yaml, MetadataStages& meta);
|
||||||
|
static bool parse_stage(const fkyaml::node& yaml, ConfigStage& stage);
|
||||||
|
static bool parse_spawn_config(const fkyaml::node& yaml, ConfigSpawn& config);
|
||||||
|
static bool parse_distribution(const fkyaml::node& yaml, DistribucioEnemics& dist);
|
||||||
|
static bool parse_multipliers(const fkyaml::node& yaml, MultiplicadorsDificultat& mult);
|
||||||
|
static ModeSpawn parse_spawn_mode(const std::string& mode_str);
|
||||||
|
|
||||||
|
// Validació
|
||||||
|
static bool validar_config(const ConfigSistemaStages& config);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
138
source/game/stage_system/stage_manager.cpp
Normal file
138
source/game/stage_system/stage_manager.cpp
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
// stage_manager.cpp - Implementació del gestor d'stages
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#include "stage_manager.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/defaults.hpp"
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
StageManager::StageManager(const ConfigSistemaStages* config)
|
||||||
|
: config_(config),
|
||||||
|
estat_(EstatStage::LEVEL_START),
|
||||||
|
stage_actual_(1),
|
||||||
|
timer_transicio_(0.0f) {
|
||||||
|
if (!config_) {
|
||||||
|
std::cerr << "[StageManager] Error: config és null" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::inicialitzar() {
|
||||||
|
stage_actual_ = 1;
|
||||||
|
carregar_stage(stage_actual_);
|
||||||
|
canviar_estat(EstatStage::LEVEL_START);
|
||||||
|
|
||||||
|
std::cout << "[StageManager] Inicialitzat a stage " << static_cast<int>(stage_actual_)
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::actualitzar(float delta_time, bool pausar_spawn) {
|
||||||
|
switch (estat_) {
|
||||||
|
case EstatStage::LEVEL_START:
|
||||||
|
processar_level_start(delta_time);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EstatStage::PLAYING:
|
||||||
|
processar_playing(delta_time, pausar_spawn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EstatStage::LEVEL_COMPLETED:
|
||||||
|
processar_level_completed(delta_time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::stage_completat() {
|
||||||
|
std::cout << "[StageManager] Stage " << static_cast<int>(stage_actual_) << " completat!"
|
||||||
|
<< std::endl;
|
||||||
|
canviar_estat(EstatStage::LEVEL_COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageManager::tot_completat() const {
|
||||||
|
return stage_actual_ >= config_->metadata.total_stages &&
|
||||||
|
estat_ == EstatStage::LEVEL_COMPLETED &&
|
||||||
|
timer_transicio_ <= 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigStage* StageManager::get_config_actual() const {
|
||||||
|
return config_->obte_stage(stage_actual_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::canviar_estat(EstatStage nou_estat) {
|
||||||
|
estat_ = nou_estat;
|
||||||
|
|
||||||
|
// Reset transition timer for LEVEL_START and LEVEL_COMPLETED
|
||||||
|
if (nou_estat == EstatStage::LEVEL_START || nou_estat == EstatStage::LEVEL_COMPLETED) {
|
||||||
|
timer_transicio_ = Defaults::Game::STAGE_TRANSITION_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[StageManager] Canvi d'estat: ";
|
||||||
|
switch (nou_estat) {
|
||||||
|
case EstatStage::LEVEL_START:
|
||||||
|
std::cout << "LEVEL_START";
|
||||||
|
break;
|
||||||
|
case EstatStage::PLAYING:
|
||||||
|
std::cout << "PLAYING";
|
||||||
|
break;
|
||||||
|
case EstatStage::LEVEL_COMPLETED:
|
||||||
|
std::cout << "LEVEL_COMPLETED";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::processar_level_start(float delta_time) {
|
||||||
|
timer_transicio_ -= delta_time;
|
||||||
|
|
||||||
|
if (timer_transicio_ <= 0.0f) {
|
||||||
|
canviar_estat(EstatStage::PLAYING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::processar_playing(float delta_time, bool pausar_spawn) {
|
||||||
|
// Update spawn controller (pauses when pausar_spawn = true)
|
||||||
|
// Note: The actual enemy array update happens in EscenaJoc::actualitzar()
|
||||||
|
// This is just for internal timekeeping
|
||||||
|
(void)delta_time; // Spawn controller is updated externally
|
||||||
|
(void)pausar_spawn; // Passed to spawn_controller_.actualitzar() by EscenaJoc
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::processar_level_completed(float delta_time) {
|
||||||
|
timer_transicio_ -= delta_time;
|
||||||
|
|
||||||
|
if (timer_transicio_ <= 0.0f) {
|
||||||
|
// Advance to next stage
|
||||||
|
stage_actual_++;
|
||||||
|
|
||||||
|
// Loop back to stage 1 after final stage
|
||||||
|
if (stage_actual_ > config_->metadata.total_stages) {
|
||||||
|
stage_actual_ = 1;
|
||||||
|
std::cout << "[StageManager] Totes les stages completades! Tornant a stage 1"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load next stage
|
||||||
|
carregar_stage(stage_actual_);
|
||||||
|
canviar_estat(EstatStage::LEVEL_START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StageManager::carregar_stage(uint8_t stage_id) {
|
||||||
|
const ConfigStage* stage_config = config_->obte_stage(stage_id);
|
||||||
|
if (!stage_config) {
|
||||||
|
std::cerr << "[StageManager] Error: no es pot trobar stage " << static_cast<int>(stage_id)
|
||||||
|
<< std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure spawn controller
|
||||||
|
spawn_controller_.configurar(stage_config);
|
||||||
|
spawn_controller_.iniciar();
|
||||||
|
|
||||||
|
std::cout << "[StageManager] Carregat stage " << static_cast<int>(stage_id) << ": "
|
||||||
|
<< static_cast<int>(stage_config->total_enemics) << " enemics" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
59
source/game/stage_system/stage_manager.hpp
Normal file
59
source/game/stage_system/stage_manager.hpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// stage_manager.hpp - Gestor d'estat i progressió d'stages
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "spawn_controller.hpp"
|
||||||
|
#include "stage_config.hpp"
|
||||||
|
|
||||||
|
namespace StageSystem {
|
||||||
|
|
||||||
|
// Estats del stage system
|
||||||
|
enum class EstatStage {
|
||||||
|
LEVEL_START, // Pantalla "ENEMY INCOMING" (3s)
|
||||||
|
PLAYING, // Gameplay normal
|
||||||
|
LEVEL_COMPLETED // Pantalla "GOOD JOB COMMANDER!" (3s)
|
||||||
|
};
|
||||||
|
|
||||||
|
class StageManager {
|
||||||
|
public:
|
||||||
|
explicit StageManager(const ConfigSistemaStages* config);
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
void inicialitzar(); // Reset to stage 1
|
||||||
|
void actualitzar(float delta_time, bool pausar_spawn = false);
|
||||||
|
|
||||||
|
// Stage progression
|
||||||
|
void stage_completat(); // Call when all enemies destroyed
|
||||||
|
bool tot_completat() const; // All 10 stages done?
|
||||||
|
|
||||||
|
// Current state queries
|
||||||
|
EstatStage get_estat() const { return estat_; }
|
||||||
|
uint8_t get_stage_actual() const { return stage_actual_; }
|
||||||
|
const ConfigStage* get_config_actual() const;
|
||||||
|
float get_timer_transicio() const { return timer_transicio_; }
|
||||||
|
|
||||||
|
// Spawn control (delegate to SpawnController)
|
||||||
|
SpawnController& get_spawn_controller() { return spawn_controller_; }
|
||||||
|
const SpawnController& get_spawn_controller() const { return spawn_controller_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const ConfigSistemaStages* config_; // Non-owning pointer
|
||||||
|
SpawnController spawn_controller_;
|
||||||
|
|
||||||
|
EstatStage estat_;
|
||||||
|
uint8_t stage_actual_; // 1-10
|
||||||
|
float timer_transicio_; // Timer for LEVEL_START/LEVEL_COMPLETED (3.0s → 0.0s)
|
||||||
|
|
||||||
|
// State transitions
|
||||||
|
void canviar_estat(EstatStage nou_estat);
|
||||||
|
void processar_level_start(float delta_time);
|
||||||
|
void processar_playing(float delta_time, bool pausar_spawn);
|
||||||
|
void processar_level_completed(float delta_time);
|
||||||
|
void carregar_stage(uint8_t stage_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace StageSystem
|
||||||
Reference in New Issue
Block a user