diff --git a/source/core/system/context_escenes.hpp b/source/core/system/context_escenes.hpp index ddece8b..8a6db74 100644 --- a/source/core/system/context_escenes.hpp +++ b/source/core/system/context_escenes.hpp @@ -3,6 +3,8 @@ #pragma once +#include "core/system/game_config.hpp" + namespace GestorEscenes { // Context de transició entre escenes @@ -58,9 +60,20 @@ public: opcio_ = Opcio::NONE; } + // Configurar partida abans de transicionar a JOC + void set_config_partida(const GameConfig::ConfigPartida& config) { + config_partida_ = config; + } + + // Obtenir configuració de partida (consumit per EscenaJoc) + [[nodiscard]] const GameConfig::ConfigPartida& get_config_partida() const { + return config_partida_; + } + private: - Escena escena_desti_; // Escena a la qual transicionar - Opcio opcio_; // Opció específica per l'escena + Escena escena_desti_; // Escena a la qual transicionar + Opcio opcio_; // Opció específica per l'escena + GameConfig::ConfigPartida config_partida_; // Configuració de partida (jugadors actius, mode) }; // Variable global inline per gestionar l'escena actual (backward compatibility) diff --git a/source/core/system/game_config.hpp b/source/core/system/game_config.hpp new file mode 100644 index 0000000..8326584 --- /dev/null +++ b/source/core/system/game_config.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +namespace GameConfig { + +// Mode de joc +enum class Mode { + NORMAL, // Partida normal + DEMO // Mode demostració (futur) +}; + +// Configuració d'una partida +struct ConfigPartida { + bool jugador1_actiu{false}; // És actiu el jugador 1? + bool jugador2_actiu{false}; // És actiu el jugador 2? + Mode mode{Mode::NORMAL}; // Mode de joc + + // Mètodes auxiliars + + // Retorna true si només hi ha un jugador actiu + [[nodiscard]] bool es_un_jugador() const { + return (jugador1_actiu && !jugador2_actiu) || + (!jugador1_actiu && jugador2_actiu); + } + + // Retorna true si hi ha dos jugadors actius + [[nodiscard]] bool son_dos_jugadors() const { + return jugador1_actiu && jugador2_actiu; + } + + // Retorna true si no hi ha cap jugador actiu + [[nodiscard]] bool cap_jugador() const { + return !jugador1_actiu && !jugador2_actiu; + } + + // Compte de jugadors actius (0, 1 o 2) + [[nodiscard]] uint8_t compte_jugadors() const { + return (jugador1_actiu ? 1 : 0) + (jugador2_actiu ? 1 : 0); + } + + // Retorna l'ID de l'únic jugador actiu (0 o 1) + // Només vàlid si es_un_jugador() retorna true + [[nodiscard]] uint8_t id_unic_jugador() const { + if (jugador1_actiu && !jugador2_actiu) return 0; + if (!jugador1_actiu && jugador2_actiu) return 1; + return 0; // Fallback (cal comprovar es_un_jugador() primer) + } +}; + +} // namespace GameConfig diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 5179d20..58c0822 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -30,6 +30,16 @@ EscenaJoc::EscenaJoc(SDLManager& sdl, ContextEscenes& context) debris_manager_(sdl.obte_renderer()), gestor_puntuacio_(sdl.obte_renderer()), text_(sdl.obte_renderer()) { + // Recuperar configuració de partida des del context + config_partida_ = context_.get_config_partida(); + + // Debug output de la configuració + std::cout << "[EscenaJoc] Configuració de partida - P1: " + << (config_partida_.jugador1_actiu ? "ACTIU" : "INACTIU") + << ", P2: " + << (config_partida_.jugador2_actiu ? "ACTIU" : "INACTIU") + << std::endl; + // Consumir opcions (preparació per MODE_DEMO futur) auto opcio = context_.consumir_opcio(); (void)opcio; // Suprimir warning de variable no usada @@ -155,10 +165,22 @@ void EscenaJoc::inicialitzar() { punt_spawn_.x = zona.x + zona.w * 0.5f; punt_spawn_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO; - // Inicialitzar AMBAS naus amb posicions específiques + // Inicialitzar naus segons configuració (només jugadors actius) for (uint8_t i = 0; i < 2; i++) { - Punt spawn_pos = obtenir_punt_spawn(i); - naus_[i].inicialitzar(&spawn_pos, false); // No invulnerability at start + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + + if (jugador_actiu) { + // Jugador actiu: inicialitzar normalment + Punt spawn_pos = obtenir_punt_spawn(i); + naus_[i].inicialitzar(&spawn_pos, false); // No invulnerability at start + std::cout << "[EscenaJoc] Jugador " << (i + 1) << " inicialitzat\n"; + } else { + // Jugador inactiu: marcar com a mort permanent + naus_[i].marcar_tocada(); + itocado_per_jugador_[i] = 999.0f; // Valor sentinella (permanent inactiu) + vides_per_jugador_[i] = 0; // Sense vides + std::cout << "[EscenaJoc] Jugador " << (i + 1) << " inactiu\n"; + } } // [MODIFIED] Initialize enemies as inactive (stage system will spawn them) @@ -183,14 +205,18 @@ void EscenaJoc::actualitzar(float delta_time) { if (!game_over_) { auto* input = Input::get(); - // Jugador 1 dispara - if (input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) { - disparar_bala(0); + // Jugador 1 dispara (només si està actiu) + if (config_partida_.jugador1_actiu) { + if (input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) { + disparar_bala(0); + } } - // Jugador 2 dispara - if (input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) { - disparar_bala(1); + // Jugador 2 dispara (només si està actiu) + if (config_partida_.jugador2_actiu) { + if (input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) { + disparar_bala(1); + } } } @@ -247,8 +273,11 @@ void EscenaJoc::actualitzar(float delta_time) { // Set sentinel value to prevent re-entering this block itocado_per_jugador_[i] = 999.0f; - // Check if BOTH players are dead (game over) - if (vides_per_jugador_[0] <= 0 && vides_per_jugador_[1] <= 0) { + // Check if ALL ACTIVE players are dead (game over) + bool p1_dead = !config_partida_.jugador1_actiu || vides_per_jugador_[0] <= 0; + bool p2_dead = !config_partida_.jugador2_actiu || vides_per_jugador_[1] <= 0; + + if (p1_dead && p2_dead) { game_over_ = true; game_over_timer_ = Defaults::Game::GAME_OVER_DURATION; } @@ -298,10 +327,13 @@ void EscenaJoc::actualitzar(float delta_time) { float ship_anim_progress = ship_progress / Defaults::Game::INIT_HUD_SHIP_RATIO; ship_anim_progress = std::min(1.0f, ship_anim_progress); - // Actualitzar posició de la nau P1 segons animació (P2 apareix directamente) + // [NOU] Determinar quina nau ha d'animar-se (jugador actiu) + uint8_t ship_to_animate = config_partida_.jugador1_actiu ? 0 : 1; + + // Actualitzar posició de la nau activa segons animació if (ship_anim_progress < 1.0f) { Punt pos_animada = calcular_posicio_nau_init_hud(ship_anim_progress); - naus_[0].set_centre(pos_animada); // Solo P1 animación + naus_[ship_to_animate].set_centre(pos_animada); // Nau del jugador actiu } // Una vegada l'animació acaba, permetre control normal @@ -323,7 +355,8 @@ void EscenaJoc::actualitzar(float delta_time) { // [NEW] Allow both ships movement and shooting during intro for (uint8_t i = 0; i < 2; i++) { - if (itocado_per_jugador_[i] == 0.0f) { // Only alive players + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + if (jugador_actiu && itocado_per_jugador_[i] == 0.0f) { // Only active, alive players naus_[i].processar_input(delta_time, i); naus_[i].actualitzar(delta_time); } @@ -355,9 +388,10 @@ void EscenaJoc::actualitzar(float delta_time) { } } - // [EXISTING] Normal gameplay - update BOTH players + // [EXISTING] Normal gameplay - update active players for (uint8_t i = 0; i < 2; i++) { - if (itocado_per_jugador_[i] == 0.0f) { // Only alive players + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + if (jugador_actiu && itocado_per_jugador_[i] == 0.0f) { // Only active, alive players naus_[i].processar_input(delta_time, i); naus_[i].actualitzar(delta_time); } @@ -384,7 +418,8 @@ void EscenaJoc::actualitzar(float delta_time) { // [NEW] Allow both ships movement and shooting during outro for (uint8_t i = 0; i < 2; i++) { - if (itocado_per_jugador_[i] == 0.0f) { // Only alive players + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + if (jugador_actiu && itocado_per_jugador_[i] == 0.0f) { // Only active, alive players naus_[i].processar_input(delta_time, i); naus_[i].actualitzar(delta_time); } @@ -468,10 +503,10 @@ void EscenaJoc::dibuixar() { dibuixar_marcador_animat(score_progress); } - // Dibuixar nau P1 (usant el sistema normal, la posició ja està actualitzada) - // Durante INIT_HUD solo se anima P1 - if (ship_progress > 0.0f && !naus_[0].esta_tocada()) { - naus_[0].dibuixar(); + // Dibuixar nau del jugador actiu (posició ja actualitzada en actualitzar()) + uint8_t ship_to_draw = config_partida_.jugador1_actiu ? 0 : 1; + if (ship_progress > 0.0f && !naus_[ship_to_draw].esta_tocada()) { + naus_[ship_to_draw].dibuixar(); } break; @@ -479,9 +514,10 @@ void EscenaJoc::dibuixar() { case StageSystem::EstatStage::LEVEL_START: dibuixar_marges(); - // [NEW] Draw both ships if alive + // [NEW] Draw both ships if active and alive for (uint8_t i = 0; i < 2; i++) { - if (itocado_per_jugador_[i] == 0.0f) { + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + if (jugador_actiu && itocado_per_jugador_[i] == 0.0f) { naus_[i].dibuixar(); } } @@ -503,9 +539,10 @@ void EscenaJoc::dibuixar() { case StageSystem::EstatStage::PLAYING: dibuixar_marges(); - // [EXISTING] Normal rendering - both ships + // [EXISTING] Normal rendering - active ships for (uint8_t i = 0; i < 2; i++) { - if (itocado_per_jugador_[i] == 0.0f) { + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + if (jugador_actiu && itocado_per_jugador_[i] == 0.0f) { naus_[i].dibuixar(); } } @@ -525,9 +562,10 @@ void EscenaJoc::dibuixar() { case StageSystem::EstatStage::LEVEL_COMPLETED: dibuixar_marges(); - // [NEW] Draw both ships if alive + // [NEW] Draw both ships if active and alive for (uint8_t i = 0; i < 2; i++) { - if (itocado_per_jugador_[i] == 0.0f) { + bool jugador_actiu = (i == 0) ? config_partida_.jugador1_actiu : config_partida_.jugador2_actiu; + if (jugador_actiu && itocado_per_jugador_[i] == 0.0f) { naus_[i].dibuixar(); } } @@ -746,31 +784,41 @@ Punt EscenaJoc::calcular_posicio_nau_init_hud(float progress) const { } std::string EscenaJoc::construir_marcador() const { - // Puntuació P1 (6 dígits) - std::string score_p1 = std::to_string(puntuacio_per_jugador_[0]); - score_p1 = std::string(6 - std::min(6, static_cast(score_p1.length())), '0') + score_p1; - - // Vides P1 (2 dígits) - std::string vides_p1 = (vides_per_jugador_[0] < 10) - ? "0" + std::to_string(vides_per_jugador_[0]) - : std::to_string(vides_per_jugador_[0]); + // Puntuació P1 (6 dígits) - mostrar zeros si inactiu + std::string score_p1; + std::string vides_p1; + if (config_partida_.jugador1_actiu) { + score_p1 = std::to_string(puntuacio_per_jugador_[0]); + score_p1 = std::string(6 - std::min(6, static_cast(score_p1.length())), '0') + score_p1; + vides_p1 = (vides_per_jugador_[0] < 10) + ? "0" + std::to_string(vides_per_jugador_[0]) + : std::to_string(vides_per_jugador_[0]); + } else { + score_p1 = "000000"; + vides_p1 = "00"; + } // Nivell (2 dígits) 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); - // Puntuació P2 (6 dígits) - std::string score_p2 = std::to_string(puntuacio_per_jugador_[1]); - score_p2 = std::string(6 - std::min(6, static_cast(score_p2.length())), '0') + score_p2; - - // Vides P2 (2 dígits) - std::string vides_p2 = (vides_per_jugador_[1] < 10) - ? "0" + std::to_string(vides_per_jugador_[1]) - : std::to_string(vides_per_jugador_[1]); + // Puntuació P2 (6 dígits) - mostrar zeros si inactiu + std::string score_p2; + std::string vides_p2; + if (config_partida_.jugador2_actiu) { + score_p2 = std::to_string(puntuacio_per_jugador_[1]); + score_p2 = std::string(6 - std::min(6, static_cast(score_p2.length())), '0') + score_p2; + vides_p2 = (vides_per_jugador_[1] < 10) + ? "0" + std::to_string(vides_per_jugador_[1]) + : std::to_string(vides_per_jugador_[1]); + } else { + score_p2 = "000000"; + vides_p2 = "00"; + } // Format: "123456 03 LEVEL 01 654321 02" - // Nota: dos espais entre seccions + // Nota: dos espais entre seccions, mantenir ambdós slots sempre visibles return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2; } @@ -975,9 +1023,17 @@ void EscenaJoc::dibuixar_missatge_stage(const std::string& missatge) { Punt EscenaJoc::obtenir_punt_spawn(uint8_t player_id) const { const SDL_FRect& zona = Defaults::Zones::PLAYAREA; - float x_ratio = (player_id == 0) - ? Defaults::Game::P1_SPAWN_X_RATIO - : Defaults::Game::P2_SPAWN_X_RATIO; + + float x_ratio; + if (config_partida_.es_un_jugador()) { + // Un sol jugador: spawn al centre (50%) + x_ratio = 0.5f; + } else { + // Dos jugadors: spawn a posicions separades + x_ratio = (player_id == 0) + ? Defaults::Game::P1_SPAWN_X_RATIO // 33% + : Defaults::Game::P2_SPAWN_X_RATIO; // 67% + } return { zona.x + zona.w * x_ratio, diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp index bb8f59d..a265aa9 100644 --- a/source/game/escenes/escena_joc.hpp +++ b/source/game/escenes/escena_joc.hpp @@ -20,6 +20,7 @@ #include "core/graphics/vector_text.hpp" #include "core/rendering/sdl_manager.hpp" #include "core/system/context_escenes.hpp" +#include "core/system/game_config.hpp" #include "core/types.hpp" #include @@ -38,6 +39,7 @@ class EscenaJoc { private: SDLManager& sdl_; GestorEscenes::ContextEscenes& context_; + GameConfig::ConfigPartida config_partida_; // Configuració de jugadors actius // Efectes visuals Effects::DebrisManager debris_manager_; diff --git a/source/game/escenes/escena_titol.cpp b/source/game/escenes/escena_titol.cpp index 60023a1..84c7025 100644 --- a/source/game/escenes/escena_titol.cpp +++ b/source/game/escenes/escena_titol.cpp @@ -34,6 +34,11 @@ EscenaTitol::EscenaTitol(SDLManager& sdl, ContextEscenes& context) factor_lerp_(0.0f) { std::cout << "Escena Titol: Inicialitzant...\n"; + // Inicialitzar configuració de partida (cap jugador actiu per defecte) + config_partida_.jugador1_actiu = false; + config_partida_.jugador2_actiu = false; + config_partida_.mode = GameConfig::Mode::NORMAL; + // Processar opció del context auto opcio = context_.consumir_opcio(); @@ -366,6 +371,25 @@ void EscenaTitol::actualitzar(float delta_time) { // Continuar animació orbital durant la transició actualitzar_animacio_logo(delta_time); + // [NOU] Continuar comprovant si l'altre jugador vol unir-se durant la transició + auto* input = Input::get(); + for (auto action : SKIP_BUTTONS_TITOL) { + if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT)) { + if (!config_partida_.jugador1_actiu) { + config_partida_.jugador1_actiu = true; + context_.set_config_partida(config_partida_); + std::cout << "[EscenaTitol] P1 s'ha unit durant la transició!\n"; + } + } + if (input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) { + if (!config_partida_.jugador2_actiu) { + config_partida_.jugador2_actiu = true; + context_.set_config_partida(config_partida_); + std::cout << "[EscenaTitol] P2 s'ha unit durant la transició!\n"; + } + } + } + if (temps_acumulat_ >= DURACIO_TRANSITION) { // Transició a JOC (la música ja s'ha parat en el fade) GestorEscenes::actual = Escena::JOC; @@ -391,6 +415,14 @@ void EscenaTitol::actualitzar(float delta_time) { case EstatTitol::MAIN: // Iniciar partida (transición a JOC) + // Configurar partida abans de canviar d'escena + context_.set_config_partida(config_partida_); + std::cout << "[EscenaTitol] Configuració de partida - P1: " + << (config_partida_.jugador1_actiu ? "ACTIU" : "INACTIU") + << ", P2: " + << (config_partida_.jugador2_actiu ? "ACTIU" : "INACTIU") + << std::endl; + context_.canviar_escena(Escena::JOC); estat_actual_ = EstatTitol::TRANSITION_TO_GAME; temps_acumulat_ = 0.0f; @@ -577,13 +609,21 @@ void EscenaTitol::dibuixar() { auto EscenaTitol::checkSkipButtonPressed() -> bool { auto* input = Input::get(); + bool p1_pressed = false; + bool p2_pressed = false; + for (auto action : SKIP_BUTTONS_TITOL) { - if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT) || - input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) { - return true; + if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT)) { + p1_pressed = true; + config_partida_.jugador1_actiu = true; // Marcar P1 com a actiu + } + if (input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) { + p2_pressed = true; + config_partida_.jugador2_actiu = true; // Marcar P2 com a actiu } } - return false; + + return p1_pressed || p2_pressed; } void EscenaTitol::processar_events(const SDL_Event& event) { diff --git a/source/game/escenes/escena_titol.hpp b/source/game/escenes/escena_titol.hpp index 19717fe..a2ec97d 100644 --- a/source/game/escenes/escena_titol.hpp +++ b/source/game/escenes/escena_titol.hpp @@ -17,6 +17,7 @@ #include "core/input/input_types.hpp" #include "core/rendering/sdl_manager.hpp" #include "core/system/context_escenes.hpp" +#include "core/system/game_config.hpp" #include "core/types.hpp" // Botones que permiten saltar/avanzar la escena (extensible) @@ -50,6 +51,7 @@ class EscenaTitol { SDLManager& sdl_; GestorEscenes::ContextEscenes& context_; + GameConfig::ConfigPartida config_partida_; // Configuració de jugadors actius Graphics::VectorText text_; // Sistema de text vectorial std::unique_ptr starfield_; // Camp d'estrelles de fons EstatTitol estat_actual_; // Estat actual de la màquina