afegida progresió
This commit is contained in:
@@ -24,7 +24,8 @@ Enemic::Enemic(SDL_Renderer* renderer)
|
||||
brightness_(Defaults::Brightness::ENEMIC),
|
||||
tipus_(TipusEnemic::PENTAGON),
|
||||
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
|
||||
// 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;
|
||||
|
||||
// Apply tracking strength
|
||||
angle_ += angle_diff * Defaults::Enemies::Quadrat::TRACKING_STRENGTH;
|
||||
// Apply tracking strength (uses member variable, defaults to 0.5)
|
||||
angle_ += angle_diff * tracking_strength_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,3 +395,29 @@ float Enemic::calcular_escala_actual() const {
|
||||
|
||||
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
|
||||
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:
|
||||
SDL_Renderer* renderer_;
|
||||
|
||||
@@ -76,6 +86,7 @@ class Enemic {
|
||||
// [NEW] Behavior state (type-specific)
|
||||
float tracking_timer_; // For Quadrat: time since last angle update
|
||||
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
|
||||
void mou(float delta_time);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "core/rendering/line_renderer.hpp"
|
||||
#include "core/system/gestor_escenes.hpp"
|
||||
#include "core/system/global_events.hpp"
|
||||
#include "game/stage_system/stage_loader.hpp"
|
||||
|
||||
EscenaJoc::EscenaJoc(SDLManager& sdl)
|
||||
: sdl_(sdl),
|
||||
@@ -105,6 +106,19 @@ void EscenaJoc::inicialitzar() {
|
||||
// Basat en el codi Pascal original: line 376
|
||||
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ó
|
||||
itocado_ = 0;
|
||||
|
||||
@@ -119,22 +133,11 @@ void EscenaJoc::inicialitzar() {
|
||||
// Inicialitzar nau
|
||||
nau_.inicialitzar();
|
||||
|
||||
// Inicialitzar enemics (ORNIs) amb tipus aleatoris
|
||||
// [MODIFIED] Initialize enemies as inactive (stage system will spawn them)
|
||||
for (auto& enemy : orni_) {
|
||||
// Random type distribution: ~40% Pentagon, ~30% Quadrat, ~30% Molinillo
|
||||
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 = Enemic(sdl_.obte_renderer());
|
||||
enemy.set_ship_position(&nau_.get_centre()); // Set ship reference for tracking
|
||||
// DON'T call enemy.inicialitzar() here - stage system handles spawning
|
||||
}
|
||||
|
||||
// Inicialitzar bales
|
||||
@@ -210,28 +213,53 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
// *** NORMAL GAMEPLAY ***
|
||||
// *** STAGE SYSTEM STATE MACHINE ***
|
||||
|
||||
// Update ship (input + physics)
|
||||
nau_.processar_input(delta_time);
|
||||
nau_.actualitzar(delta_time);
|
||||
StageSystem::EstatStage estat = stage_manager_->get_estat();
|
||||
|
||||
// Update enemy movement and rotation
|
||||
for (auto& enemy : orni_) {
|
||||
enemy.actualitzar(delta_time);
|
||||
switch (estat) {
|
||||
case StageSystem::EstatStage::LEVEL_START:
|
||||
// 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() {
|
||||
@@ -270,26 +298,38 @@ void EscenaJoc::dibuixar() {
|
||||
return;
|
||||
}
|
||||
|
||||
// During death sequence, don't draw ship (debris draws automatically)
|
||||
if (itocado_ == 0.0f) {
|
||||
nau_.dibuixar();
|
||||
// [NEW] Stage state rendering
|
||||
StageSystem::EstatStage estat = stage_manager_->get_estat();
|
||||
|
||||
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) {
|
||||
@@ -399,8 +439,13 @@ void EscenaJoc::dibuixar_marges() const {
|
||||
}
|
||||
|
||||
void EscenaJoc::dibuixar_marcador() {
|
||||
// Display actual lives count (user requested "LIFES" plural English)
|
||||
std::string text = "SCORE: 01000 LIFES: " + std::to_string(num_vides_) + " LEVEL: 01";
|
||||
// [MODIFIED] Display current stage number from stage manager
|
||||
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ó
|
||||
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/enemic.hpp"
|
||||
#include "../entities/nau.hpp"
|
||||
#include "../stage_system/stage_manager.hpp"
|
||||
#include "core/graphics/vector_text.hpp"
|
||||
#include "core/rendering/sdl_manager.hpp"
|
||||
#include "core/types.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
// Classe principal del joc (escena)
|
||||
class EscenaJoc {
|
||||
public:
|
||||
@@ -53,12 +56,19 @@ class EscenaJoc {
|
||||
// Text vectorial
|
||||
Graphics::VectorText text_;
|
||||
|
||||
// [NEW] Stage system
|
||||
std::unique_ptr<StageSystem::ConfigSistemaStages> stage_config_;
|
||||
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||
|
||||
// Funcions privades
|
||||
void tocado();
|
||||
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
||||
void detectar_col·lisio_nau_enemics(); // Ship-enemy collision detection
|
||||
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
||||
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
||||
|
||||
// [NEW] Stage system helpers
|
||||
void dibuixar_missatge_stage(const std::string& missatge);
|
||||
};
|
||||
|
||||
#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