diff --git a/source/core/audio/audio.hpp b/source/core/audio/audio.hpp index 957b383..e37e141 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -5,7 +5,6 @@ #include // Para std::function #include // Para std::unique_ptr #include // Para string -#include // Para move // Forward-declares per no incloure core/audio/jail_audio.hpp al header. Els // tres símbols (Music/Sound para el punter que exposa la API i Engine per al diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 31b2f07..1a2be14 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -2,7 +2,6 @@ #include #include -#include #include namespace Defaults { @@ -439,16 +438,16 @@ constexpr float CENTER_Y = Game::HEIGHT / 2.0F; // auto-derivado de Game::HEIGH // Posicions target (calculades dinàmicament des dels parámetros base) // Nota: std::cos/sin no són constexpr en C++20, pero funcionen en runtime // Les funciones inline són optimitzades por el compilador (zero overhead) -inline auto P1_TARGET_X() -> float { +inline auto p1TargetX() -> float { return CENTER_X + (CLOCK_RADIUS * std::cos(CLOCK_8_ANGLE)); } -inline auto P1_TARGET_Y() -> float { +inline auto p1TargetY() -> float { return CENTER_Y + ((Game::HEIGHT / 2.0F) * TARGET_Y_RATIO); } -inline auto P2_TARGET_X() -> float { +inline auto p2TargetX() -> float { return CENTER_X + (CLOCK_RADIUS * std::cos(CLOCK_4_ANGLE)); } -inline auto P2_TARGET_Y() -> float { +inline auto p2TargetY() -> float { return CENTER_Y + ((Game::HEIGHT / 2.0F) * TARGET_Y_RATIO); } diff --git a/source/core/graphics/shape.hpp b/source/core/graphics/shape.hpp index ba47e5b..e8fb2a6 100644 --- a/source/core/graphics/shape.hpp +++ b/source/core/graphics/shape.hpp @@ -37,11 +37,11 @@ class Shape { auto parseFile(const std::string& contingut) -> bool; // Getters - [[nodiscard]] auto get_primitives() const -> const std::vector& { + [[nodiscard]] auto getPrimitives() const -> const std::vector& { return primitives_; } [[nodiscard]] auto getCenter() const -> const Vec2& { return center_; } - [[nodiscard]] auto get_escala_defecte() const -> float { return escala_defecte_; } + [[nodiscard]] auto getDefaultScale() const -> float { return escala_defecte_; } [[nodiscard]] auto isValid() const -> bool { return !primitives_.empty(); } // Info de depuració diff --git a/source/core/graphics/shape_loader.cpp b/source/core/graphics/shape_loader.cpp index 198cf20..486ea99 100644 --- a/source/core/graphics/shape_loader.cpp +++ b/source/core/graphics/shape_loader.cpp @@ -10,13 +10,12 @@ namespace Graphics { // Inicialización de variables estàtiques -std::unordered_map> ShapeLoader::cache_; -std::string ShapeLoader::base_path_ = "data/shapes/"; +std::unordered_map> ShapeLoader::cache; auto ShapeLoader::load(const std::string& filename) -> std::shared_ptr { // Check cache first - auto it = cache_.find(filename); - if (it != cache_.end()) { + auto it = cache.find(filename); + if (it != cache.end()) { std::cout << "[ShapeLoader] Cache hit: " << filename << '\n'; return it->second; // Cache hit } @@ -56,17 +55,17 @@ auto ShapeLoader::load(const std::string& filename) -> std::shared_ptr { std::cout << "[ShapeLoader] Carregat: " << normalized << " (" << shape->getName() << ", " << shape->getNumPrimitives() << " primitives)" << '\n'; - cache_[filename] = shape; + cache[filename] = shape; return shape; } -void ShapeLoader::clear_cache() { - std::cout << "[ShapeLoader] Netejant caché (" << cache_.size() << " formes)" +void ShapeLoader::clearCache() { + std::cout << "[ShapeLoader] Netejant caché (" << cache.size() << " formes)" << '\n'; - cache_.clear(); + cache.clear(); } -auto ShapeLoader::get_cache_size() -> size_t { return cache_.size(); } +auto ShapeLoader::getCacheSize() -> size_t { return cache.size(); } auto ShapeLoader::resolvePath(const std::string& filename) -> std::string { // Si es un path absolut (comença con '/'), usar-lo directament @@ -75,12 +74,12 @@ auto ShapeLoader::resolvePath(const std::string& filename) -> std::string { } // Si ya conté el prefix base_path, usar-lo directament - if (filename.starts_with(base_path_)) { + if (filename.starts_with(BASE_PATH)) { return filename; } // Altrament, añadir base_path (ara suporta subdirectoris) - return base_path_ + filename; + return std::string(BASE_PATH) + filename; } } // namespace Graphics diff --git a/source/core/graphics/shape_loader.hpp b/source/core/graphics/shape_loader.hpp index 7db751a..330f09a 100644 --- a/source/core/graphics/shape_loader.hpp +++ b/source/core/graphics/shape_loader.hpp @@ -23,14 +23,14 @@ class ShapeLoader { static auto load(const std::string& filename) -> std::shared_ptr; // Netejar caché (útil per debug/recàrrega) - static void clear_cache(); + static void clearCache(); // Estadístiques (debug) - static auto get_cache_size() -> size_t; + static auto getCacheSize() -> size_t; private: - static std::unordered_map> cache_; - static std::string base_path_; // "data/shapes/" + static std::unordered_map> cache; + static constexpr const char* BASE_PATH = "data/shapes/"; // Helpers privats static auto resolvePath(const std::string& filename) -> std::string; diff --git a/source/core/graphics/starfield.cpp b/source/core/graphics/starfield.cpp index fde0c08..a587ec2 100644 --- a/source/core/graphics/starfield.cpp +++ b/source/core/graphics/starfield.cpp @@ -137,7 +137,7 @@ void Starfield::update(float delta_time) { } // Establir multiplicador de brightness -void Starfield::set_brightness(float multiplier) { +void Starfield::setBrightness(float multiplier) { multiplicador_brightness_ = std::max(0.0F, multiplier); // Evitar valors negatius } @@ -153,7 +153,7 @@ void Starfield::draw() { float brightness = computeBrightness(estrella); // Renderizar estrella sin rotación - Rendering::render_shape( + Rendering::renderShape( renderer_, shape_estrella_, estrella.position, diff --git a/source/core/graphics/starfield.hpp b/source/core/graphics/starfield.hpp index ae10392..de18ecf 100644 --- a/source/core/graphics/starfield.hpp +++ b/source/core/graphics/starfield.hpp @@ -43,8 +43,8 @@ class Starfield { void draw(); // Setters per ajustar parámetros en time real - void set_punt_fuga(const Vec2& point) { punt_fuga_ = point; } - void set_brightness(float multiplier); + void setVanishingPoint(const Vec2& point) { punt_fuga_ = point; } + void setBrightness(float multiplier); private: // Estructura interna per cada estrella diff --git a/source/core/graphics/vector_text.cpp b/source/core/graphics/vector_text.cpp index 6b3d708..9ac58c3 100644 --- a/source/core/graphics/vector_text.cpp +++ b/source/core/graphics/vector_text.cpp @@ -175,7 +175,7 @@ auto VectorText::getShapeFilename(char c) -> std::string { } } -auto VectorText::is_supported(char c) const -> bool { +auto VectorText::isSupported(char c) const -> bool { return chars_.contains(c); } @@ -221,7 +221,7 @@ void VectorText::render(const std::string& text, const Vec2& position, float sca // Ajustar X e Y para que position represente esquina superior izquierda // (render_shape espera el centro, así que sumamos la mitad de ancho y altura) Vec2 char_pos = {.x = current_x + (CHAR_WIDTH_SCALED / 2.0F), .y = position.y + (CHAR_HEIGHT_SCALED / 2.0F)}; - Rendering::render_shape(renderer_, it->second, char_pos, 0.0F, scale, 1.0F, brightness); + Rendering::renderShape(renderer_, it->second, char_pos, 0.0F, scale, 1.0F, brightness); // Avanzar posición current_x += CHAR_WIDTH_SCALED + SPACING_SCALED; @@ -236,8 +236,8 @@ void VectorText::render(const std::string& text, const Vec2& position, float sca void VectorText::renderCentered(const std::string& text, const Vec2& centre_punt, float scale, float spacing, float brightness) const { // Calcular dimensions del text - float text_width = get_text_width(text, scale, spacing); - float text_height = get_text_height(scale); + float text_width = getTextWidth(text, scale, spacing); + float text_height = getTextHeight(scale); // Calcular posición de l'esquina superior izquierda // restant la meitat de las dimensions del point central @@ -249,7 +249,7 @@ void VectorText::renderCentered(const std::string& text, const Vec2& centre_punt render(text, posicio_esquerra, scale, spacing, brightness); } -auto VectorText::get_text_width(const std::string& text, float scale, float spacing) -> float { +auto VectorText::getTextWidth(const std::string& text, float scale, float spacing) -> float { if (text.empty()) { return 0.0F; } @@ -276,7 +276,7 @@ auto VectorText::get_text_width(const std::string& text, float scale, float spac return (visual_chars * CHAR_WIDTH_SCALED) + ((visual_chars - 1) * SPACING_SCALED); } -auto VectorText::get_text_height(float scale) -> float { +auto VectorText::getTextHeight(float scale) -> float { return BASE_CHAR_HEIGHT * scale; } diff --git a/source/core/graphics/vector_text.hpp b/source/core/graphics/vector_text.hpp index 4ec88ea..07b02d4 100644 --- a/source/core/graphics/vector_text.hpp +++ b/source/core/graphics/vector_text.hpp @@ -40,13 +40,13 @@ class VectorText { // Calcular ancho total de un string (útil para centrado). // Es estático: no depende del estado del VectorText (el ancho viene de // las constantes BASE_CHAR_WIDTH/BASE_CHAR_HEIGHT del archivo .cpp). - [[nodiscard]] static auto get_text_width(const std::string& text, float scale = 1.0F, float spacing = 2.0F) -> float; + [[nodiscard]] static auto getTextWidth(const std::string& text, float scale = 1.0F, float spacing = 2.0F) -> float; // Calcular altura del texto (útil para centrado vertical). - [[nodiscard]] static auto get_text_height(float scale = 1.0F) -> float; + [[nodiscard]] static auto getTextHeight(float scale = 1.0F) -> float; // Verificar si un carácter está soportado - [[nodiscard]] auto is_supported(char c) const -> bool; + [[nodiscard]] auto isSupported(char c) const -> bool; private: Rendering::Renderer* renderer_; diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp index 886e3b3..5a718d3 100644 --- a/source/core/input/input.cpp +++ b/source/core/input/input.cpp @@ -2,11 +2,11 @@ #include // Para SDL_GetGamepadAxis, SDL_GamepadAxis, SDL_GamepadButton, SDL_GetError, SDL_JoystickID, SDL_AddGamepadMappingsFromFile, SDL_Event, SDL_EventType, SDL_GetGamepadButton, SDL_GetKeyboardState, SDL_INIT_GAMEPAD, SDL_InitSubSystem, SDL_LogError, SDL_OpenGamepad, SDL_PollEvent, SDL_WasInit, Sint16, SDL_Gamepad, SDL_LogCategory, SDL_Scancode +#include // Para std::ranges::any_of #include // Para basic_ostream, operator<<, cout, cerr #include // Para shared_ptr, __shared_ptr_access, allocator, operator==, make_shared -#include // Para __find_if_fn, find_if #include // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator -#include // Para pair, move +#include // Para move #include "game/options.hpp" // Para Options::controls @@ -190,12 +190,9 @@ auto Input::checkAnyButton(bool repeat) -> bool { // Comprueba si algún player (P1 o P2) presionó alguna acción de una lista auto Input::checkAnyPlayerAction(const std::span& actions, bool repeat) -> bool { - for (const auto& action : actions) { - if (checkActionPlayer1(action, repeat) || checkActionPlayer2(action, repeat)) { - return true; - } - } - return false; + return std::ranges::any_of(actions, [this, repeat](const InputAction& action) { + return checkActionPlayer1(action, repeat) || checkActionPlayer2(action, repeat); + }); } // Comprueba si hay algun mando conectado diff --git a/source/core/input/input.hpp b/source/core/input/input.hpp index d6b8384..30eff20 100644 --- a/source/core/input/input.hpp +++ b/source/core/input/input.hpp @@ -7,7 +7,6 @@ #include // Para span #include // Para string, basic_string #include // Para unordered_map -#include // Para pair #include // Para vector #include "core/input/input_types.hpp" // for InputAction diff --git a/source/core/input/input_types.cpp b/source/core/input/input_types.cpp index b6c4897..d4d4008 100644 --- a/source/core/input/input_types.cpp +++ b/source/core/input/input_types.cpp @@ -1,7 +1,5 @@ #include "input_types.hpp" -#include // Para pair - // Definición de los mapas const std::unordered_map ACTION_TO_STRING = { {InputAction::LEFT, "LEFT"}, diff --git a/source/core/math/easing.hpp b/source/core/math/easing.hpp index b9341fc..8abbcfd 100644 --- a/source/core/math/easing.hpp +++ b/source/core/math/easing.hpp @@ -8,21 +8,21 @@ namespace Easing { // Ease-out quadratic: empieza rápido, desacelera suavemente // t = progreso normalizado [0.0 - 1.0] // retorna value interpolado [0.0 - 1.0] -inline auto ease_out_quad(float t) -> float { +inline auto easeOutQuad(float t) -> float { return 1.0F - ((1.0F - t) * (1.0F - t)); } // Ease-in quadratic: empieza lento, acelera // t = progreso normalizado [0.0 - 1.0] // retorna value interpolado [0.0 - 1.0] -inline auto ease_in_quad(float t) -> float { +inline auto easeInQuad(float t) -> float { return t * t; } // Ease-in-out quadratic: acelera al inicio, desacelera al final // t = progreso normalizado [0.0 - 1.0] // retorna value interpolado [0.0 - 1.0] -inline auto ease_in_out_quad(float t) -> float { +inline auto easeInOutQuad(float t) -> float { return (t < 0.5F) ? 2.0F * t * t : 1.0F - ((-2.0F * t + 2.0F) * (-2.0F * t + 2.0F) / 2.0F); @@ -31,7 +31,7 @@ inline auto ease_in_out_quad(float t) -> float { // Ease-out cubic: desaceleración más suave que quadratic // t = progreso normalizado [0.0 - 1.0] // retorna value interpolado [0.0 - 1.0] -inline auto ease_out_cubic(float t) -> float { +inline auto easeOutCubic(float t) -> float { float t1 = 1.0F - t; return 1.0F - (t1 * t1 * t1); } diff --git a/source/core/physics/collision.hpp b/source/core/physics/collision.hpp index 4e5bf9c..9065dbe 100644 --- a/source/core/physics/collision.hpp +++ b/source/core/physics/collision.hpp @@ -9,7 +9,7 @@ namespace Physics { // Comprobación genèrica de colisión entre dues entidades -inline auto check_collision(const Entities::Entity& a, const Entities::Entity& b, float amplifier = 1.0F) -> bool { +inline auto checkCollision(const Entities::Entity& a, const Entities::Entity& b, float amplifier = 1.0F) -> bool { // Comprovar si ambdós són col·lisionables if (!a.isCollidable() || !b.isCollidable()) { return false; diff --git a/source/core/physics/physics_world.cpp b/source/core/physics/physics_world.cpp index fd7793c..4ccd551 100644 --- a/source/core/physics/physics_world.cpp +++ b/source/core/physics/physics_world.cpp @@ -121,58 +121,61 @@ void PhysicsWorld::resolveBodyCollisions() { for (std::size_t j = i + 1; j < COUNT; ++j) { auto* a = bodies_[i]; auto* b = bodies_[j]; - if (a == nullptr || b == nullptr) { - continue; - } - // Dos cuerpos estáticos no necesitan resolución - if (a->isStatic() && b->isStatic()) { - continue; - } - - const Vec2 DELTA = b->position - a->position; - const float DIST_SQ = DELTA.lengthSquared(); - const float SUM_R = a->radius + b->radius; - if (DIST_SQ > SUM_R * SUM_R || DIST_SQ <= 0.0F) { - continue; - } - - const float DIST = std::sqrt(DIST_SQ); - const Vec2 NORMAL = DELTA / DIST; // de A hacia B - - // Corrección posicional (resolver penetración) - const float PENETRATION = SUM_R - DIST; - const float TOTAL_INV_MASS = a->inverse_mass + b->inverse_mass; - if (TOTAL_INV_MASS > 0.0F) { - const Vec2 CORRECTION = NORMAL * (PENETRATION / TOTAL_INV_MASS); - if (!a->isStatic()) { - a->position -= CORRECTION * a->inverse_mass; - } - if (!b->isStatic()) { - b->position += CORRECTION * b->inverse_mass; - } - } - - // Velocidad relativa proyectada sobre la normal - const Vec2 V_REL = b->velocity - a->velocity; - const float VEL_ALONG_NORMAL = V_REL.dot(NORMAL); - // Si se están separando, no aplicar impulso - if (VEL_ALONG_NORMAL > 0.0F) { - continue; - } - - // Restitución promedio (Box2D usa max; promedio es más permisivo) - const float E = (a->restitution + b->restitution) * 0.5F; - const float J = -(1.0F + E) * VEL_ALONG_NORMAL / TOTAL_INV_MASS; - const Vec2 IMPULSE = NORMAL * J; - - if (!a->isStatic()) { - a->velocity -= IMPULSE * a->inverse_mass; - } - if (!b->isStatic()) { - b->velocity += IMPULSE * b->inverse_mass; + if (a != nullptr && b != nullptr) { + resolveBodyPair(*a, *b); } } } } +void PhysicsWorld::resolveBodyPair(RigidBody& a, RigidBody& b) { + // Dos cuerpos estáticos no necesitan resolución + if (a.isStatic() && b.isStatic()) { + return; + } + + const Vec2 DELTA = b.position - a.position; + const float DIST_SQ = DELTA.lengthSquared(); + const float SUM_R = a.radius + b.radius; + if (DIST_SQ > SUM_R * SUM_R || DIST_SQ <= 0.0F) { + return; + } + + const float DIST = std::sqrt(DIST_SQ); + const Vec2 NORMAL = DELTA / DIST; // de A hacia B + + // Corrección posicional (resolver penetración) + const float PENETRATION = SUM_R - DIST; + const float TOTAL_INV_MASS = a.inverse_mass + b.inverse_mass; + if (TOTAL_INV_MASS > 0.0F) { + const Vec2 CORRECTION = NORMAL * (PENETRATION / TOTAL_INV_MASS); + if (!a.isStatic()) { + a.position -= CORRECTION * a.inverse_mass; + } + if (!b.isStatic()) { + b.position += CORRECTION * b.inverse_mass; + } + } + + // Velocidad relativa proyectada sobre la normal + const Vec2 V_REL = b.velocity - a.velocity; + const float VEL_ALONG_NORMAL = V_REL.dot(NORMAL); + // Si se están separando, no aplicar impulso + if (VEL_ALONG_NORMAL > 0.0F) { + return; + } + + // Restitución promedio (Box2D usa max; promedio es más permisivo) + const float E = (a.restitution + b.restitution) * 0.5F; + const float J = -(1.0F + E) * VEL_ALONG_NORMAL / TOTAL_INV_MASS; + const Vec2 IMPULSE = NORMAL * J; + + if (!a.isStatic()) { + a.velocity -= IMPULSE * a.inverse_mass; + } + if (!b.isStatic()) { + b.velocity += IMPULSE * b.inverse_mass; + } +} + } // namespace Physics diff --git a/source/core/physics/physics_world.hpp b/source/core/physics/physics_world.hpp index f98bbe9..30ac588 100644 --- a/source/core/physics/physics_world.hpp +++ b/source/core/physics/physics_world.hpp @@ -59,6 +59,9 @@ class PhysicsWorld { void integrate(float dt); void resolveBoundsCollisions(); void resolveBodyCollisions(); + // Resol un únic parell (a, b): correcció posicional + impulso elàstic. + // Estàtic: només toca els dos cossos rebuts, no consulta el world. + static void resolveBodyPair(RigidBody& a, RigidBody& b); }; } // namespace Physics diff --git a/source/core/rendering/coordinate_transform.hpp b/source/core/rendering/coordinate_transform.hpp index c8a31ee..0da2c3b 100644 --- a/source/core/rendering/coordinate_transform.hpp +++ b/source/core/rendering/coordinate_transform.hpp @@ -11,21 +11,21 @@ namespace Rendering { extern float g_current_scale_factor; // Transforma coordenada lógica a física con arrodoniment -inline auto transform_x(int logical_x, float scale) -> int { +inline auto transformX(int logical_x, float scale) -> int { return static_cast(std::round(logical_x * scale)); } -inline auto transform_y(int logical_y, float scale) -> int { +inline auto transformY(int logical_y, float scale) -> int { return static_cast(std::round(logical_y * scale)); } // Variant que usa el factor de scale global -inline auto transform_x(int logical_x) -> int { - return transform_x(logical_x, g_current_scale_factor); +inline auto transformX(int logical_x) -> int { + return transformX(logical_x, g_current_scale_factor); } -inline auto transform_y(int logical_y) -> int { - return transform_y(logical_y, g_current_scale_factor); +inline auto transformY(int logical_y) -> int { + return transformY(logical_y, g_current_scale_factor); } } // namespace Rendering diff --git a/source/core/rendering/gpu/gpu_frame_renderer.cpp b/source/core/rendering/gpu/gpu_frame_renderer.cpp index 627ddc5..349ed86 100644 --- a/source/core/rendering/gpu/gpu_frame_renderer.cpp +++ b/source/core/rendering/gpu/gpu_frame_renderer.cpp @@ -362,7 +362,7 @@ void GpuFrameRenderer::compositePass() { ubo.flicker_amplitude = FLICKER_AMPLITUDE; ubo.flicker_frequency_hz = postfx_params_.flicker_frequency_hz; ubo.background_pulse_freq_hz = postfx_params_.background_pulse_freq_hz; - ubo.pad_a_ = 0.0F; + ubo.pad_a = 0.0F; ubo.background_min_r = BG_MIN_R; ubo.background_min_g = BG_MIN_G; ubo.background_min_b = BG_MIN_B; @@ -373,8 +373,8 @@ void GpuFrameRenderer::compositePass() { ubo.background_max_a = 1.0F; ubo.texel_size_x = 1.0F / logical_w_; ubo.texel_size_y = 1.0F / logical_h_; - ubo.pad_b_ = 0.0F; - ubo.pad_c_ = 0.0F; + ubo.pad_b = 0.0F; + ubo.pad_c = 0.0F; SDL_PushGPUFragmentUniformData(cmd_buffer_, 0, &ubo, sizeof(ubo)); diff --git a/source/core/rendering/gpu/gpu_line_pipeline.hpp b/source/core/rendering/gpu/gpu_line_pipeline.hpp index ae28f3b..da312e6 100644 --- a/source/core/rendering/gpu/gpu_line_pipeline.hpp +++ b/source/core/rendering/gpu/gpu_line_pipeline.hpp @@ -9,8 +9,6 @@ #include -#include - namespace Rendering::GPU { class GpuDevice; diff --git a/source/core/rendering/gpu/gpu_postfx_pipeline.hpp b/source/core/rendering/gpu/gpu_postfx_pipeline.hpp index 6dd5add..3b7ce07 100644 --- a/source/core/rendering/gpu/gpu_postfx_pipeline.hpp +++ b/source/core/rendering/gpu/gpu_postfx_pipeline.hpp @@ -29,7 +29,7 @@ struct PostFxUniforms { float flicker_amplitude; // Profundidad del flicker (0..1) float flicker_frequency_hz; // Hz float background_pulse_freq_hz; // Hz - float pad_a_; + float pad_a; float background_min_r; // Color min RGB en [0..1], A=1 float background_min_g; @@ -43,8 +43,8 @@ struct PostFxUniforms { float texel_size_x; // 1.0 / texture_width float texel_size_y; - float pad_b_; - float pad_c_; + float pad_b; + float pad_c; }; class GpuPostFxPipeline { diff --git a/source/core/rendering/sdl_manager.cpp b/source/core/rendering/sdl_manager.cpp index cf339ba..22d2b86 100644 --- a/source/core/rendering/sdl_manager.cpp +++ b/source/core/rendering/sdl_manager.cpp @@ -13,7 +13,6 @@ #include "core/defaults.hpp" #include "core/input/mouse.hpp" #include "core/rendering/coordinate_transform.hpp" -#include "core/rendering/line_renderer.hpp" #include "game/options.hpp" #include "project.h" diff --git a/source/core/rendering/shape_renderer.cpp b/source/core/rendering/shape_renderer.cpp index b0be7f2..7ec2d7e 100644 --- a/source/core/rendering/shape_renderer.cpp +++ b/source/core/rendering/shape_renderer.cpp @@ -5,7 +5,6 @@ #include -#include "core/defaults.hpp" #include "core/rendering/line_renderer.hpp" namespace Rendering { @@ -48,7 +47,7 @@ static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Ve float centered_y = point.y - shape_centre.y; // 2. Aplicar rotación 3D (si es proporciona) - if ((rotation_3d != nullptr) && rotation_3d->has_rotation()) { + if ((rotation_3d != nullptr) && rotation_3d->hasRotation()) { Vec2 rotated_3d = apply3dRotation(centered_x, centered_y, *rotation_3d); centered_x = rotated_3d.x; centered_y = rotated_3d.y; @@ -72,7 +71,7 @@ static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Ve return {.x = rotated_x + position.x, .y = rotated_y + position.y}; } -void render_shape(Rendering::Renderer* renderer, +void renderShape(Rendering::Renderer* renderer, const std::shared_ptr& shape, const Vec2& position, float angle, @@ -90,7 +89,7 @@ void render_shape(Rendering::Renderer* renderer, const Vec2& shape_centre = shape->getCenter(); - for (const auto& primitive : shape->get_primitives()) { + for (const auto& primitive : shape->getPrimitives()) { if (primitive.type == Graphics::PrimitiveType::POLYLINE) { // POLYLINE: conectar puntos consecutivos. for (size_t i = 0; i < primitive.points.size() - 1; i++) { diff --git a/source/core/rendering/shape_renderer.hpp b/source/core/rendering/shape_renderer.hpp index cdfde4f..88c156b 100644 --- a/source/core/rendering/shape_renderer.hpp +++ b/source/core/rendering/shape_renderer.hpp @@ -29,7 +29,7 @@ struct Rotation3D { yaw(y), roll(r) {} - [[nodiscard]] auto has_rotation() const -> bool { + [[nodiscard]] auto hasRotation() const -> bool { return pitch != 0.0F || yaw != 0.0F || roll != 0.0F; } }; @@ -42,7 +42,7 @@ struct Rotation3D { // - scale: factor de scale (1.0 = mida original) // - progress: progrés de l'animación (0.0-1.0, default 1.0 = tot visible) // - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness) -void render_shape(Rendering::Renderer* renderer, +void renderShape(Rendering::Renderer* renderer, const std::shared_ptr& shape, const Vec2& position, float angle, diff --git a/source/core/resources/resource_helper.cpp b/source/core/resources/resource_helper.cpp index f9b87cf..c30f63d 100644 --- a/source/core/resources/resource_helper.cpp +++ b/source/core/resources/resource_helper.cpp @@ -4,7 +4,6 @@ #include "resource_helper.hpp" #include -#include #include "resource_loader.hpp" diff --git a/source/core/resources/resource_loader.cpp b/source/core/resources/resource_loader.cpp index 13a22fd..cef931f 100644 --- a/source/core/resources/resource_loader.cpp +++ b/source/core/resources/resource_loader.cpp @@ -11,8 +11,8 @@ namespace Resource { // Singleton auto Loader::get() -> Loader& { - static Loader instance; - return instance; + static Loader instance_; + return instance_; } // Inicialitzar el sistema de recursos diff --git a/source/core/resources/resource_pack.hpp b/source/core/resources/resource_pack.hpp index a807ac2..a86a05e 100644 --- a/source/core/resources/resource_pack.hpp +++ b/source/core/resources/resource_pack.hpp @@ -62,7 +62,7 @@ class Pack { static auto readFile(const std::string& filepath) -> std::vector; [[nodiscard]] static auto calculateChecksum(const std::vector& data) -> uint32_t; static void encryptData(std::vector& data, const std::string& key); - void decryptData(std::vector& data, const std::string& key); + static void decryptData(std::vector& data, const std::string& key); }; } // namespace Resource diff --git a/source/core/system/game_config.hpp b/source/core/system/game_config.hpp index 9ee3562..5517b5c 100644 --- a/source/core/system/game_config.hpp +++ b/source/core/system/game_config.hpp @@ -19,29 +19,29 @@ struct MatchConfig { // Métodos auxiliars // Retorna true si solo hay un player active - [[nodiscard]] auto es_un_jugador() const -> bool { + [[nodiscard]] auto isSinglePlayer() const -> bool { return (jugador1_actiu && !jugador2_actiu) || (!jugador1_actiu && jugador2_actiu); } // Retorna true si hay dos jugadors active - [[nodiscard]] auto son_dos_jugadors() const -> bool { + [[nodiscard]] auto isCoop() const -> bool { return jugador1_actiu && jugador2_actiu; } // Retorna true si no hay sin player active - [[nodiscard]] auto cap_jugador() const -> bool { + [[nodiscard]] auto hasNoPlayers() const -> bool { return !jugador1_actiu && !jugador2_actiu; } // Compte de jugadors active (0, 1 o 2) - [[nodiscard]] auto compte_jugadors() const -> uint8_t { + [[nodiscard]] auto getPlayerCount() const -> uint8_t { return (jugador1_actiu ? 1 : 0) + (jugador2_actiu ? 1 : 0); } // Retorna l'ID de l'únic player active (0 o 1) // Solo vàlid si es_un_jugador() retorna true - [[nodiscard]] auto id_unic_jugador() const -> uint8_t { + [[nodiscard]] auto getSinglePlayerId() const -> uint8_t { if (jugador1_actiu && !jugador2_actiu) { return 0; } diff --git a/source/core/utils/path_utils.cpp b/source/core/utils/path_utils.cpp index f5ff676..924b83d 100644 --- a/source/core/utils/path_utils.cpp +++ b/source/core/utils/path_utils.cpp @@ -10,39 +10,39 @@ namespace Utils { // Variables globals per guardar argv[0] -static std::string executable_path_; -static std::string executable_directory_; +static std::string executable_path; +static std::string executable_directory; // Inicialitzar el sistema de rutes con argv[0] void initializePathSystem(const char* argv0) { if (argv0 == nullptr) { std::cerr << "[PathUtils] ADVERTÈNCIA: argv[0] es nullptr\n"; - executable_path_ = ""; - executable_directory_ = "."; + executable_path = ""; + executable_directory = "."; return; } - executable_path_ = argv0; + executable_path = argv0; // Extreure el directori std::filesystem::path path(argv0); - executable_directory_ = path.parent_path().string(); + executable_directory = path.parent_path().string(); - if (executable_directory_.empty()) { - executable_directory_ = "."; + if (executable_directory.empty()) { + executable_directory = "."; } - std::cout << "[PathUtils] Executable: " << executable_path_ << "\n"; - std::cout << "[PathUtils] Directori: " << executable_directory_ << "\n"; + std::cout << "[PathUtils] Executable: " << executable_path << "\n"; + std::cout << "[PathUtils] Directori: " << executable_directory << "\n"; } // Obtenir el directori de l'executable auto getExecutableDirectory() -> std::string { - if (executable_directory_.empty()) { + if (executable_directory.empty()) { std::cerr << "[PathUtils] ADVERTÈNCIA: Sistema de rutes no inicialitzat\n"; return "."; } - return executable_directory_; + return executable_directory; } // Detectar si estem dins un bundle de macOS diff --git a/source/game/constants.hpp b/source/game/constants.hpp index 8201d44..1a52e44 100644 --- a/source/game/constants.hpp +++ b/source/game/constants.hpp @@ -26,12 +26,12 @@ constexpr int VELOCITAT_MAX = static_cast(Defaults::Physics::BULLET_SPEED); constexpr float PI = Defaults::Math::PI; // Helpers per comprovar límits de zona -inline auto dins_zona_joc(float x, float y) -> bool { +inline auto isInPlayArea(float x, float y) -> bool { const SDL_FPoint POINT = {x, y}; return SDL_PointInRectFloat(&POINT, &Defaults::Zones::PLAYAREA); } -inline void obtenir_limits_zona(float& min_x, float& max_x, float& min_y, float& max_y) { +inline void getPlayAreaBounds(float& min_x, float& max_x, float& min_y, float& max_y) { const auto& zona = Defaults::Zones::PLAYAREA; min_x = zona.x; max_x = zona.x + zona.w; @@ -40,7 +40,7 @@ inline void obtenir_limits_zona(float& min_x, float& max_x, float& min_y, float& } // Obtenir límits segurs (compensant radi de l'entidad) -inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, float& min_y, float& max_y) { +inline void getSafePlayAreaBounds(float radi, float& min_x, float& max_x, float& min_y, float& max_y) { const auto& zona = Defaults::Zones::PLAYAREA; constexpr float MARGE_SEGURETAT = 10.0F; // Safety margin @@ -51,7 +51,7 @@ inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, f } // Obtenir centro de l'àrea de juego -inline void obtenir_centre_zona(float& centre_x, float& centre_y) { +inline void getPlayAreaCenter(float& centre_x, float& centre_y) { const auto& zona = Defaults::Zones::PLAYAREA; centre_x = zona.x + (zona.w / 2.0F); centre_y = zona.y + (zona.h / 2.0F); diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index e2db5b2..de47ae8 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -62,147 +62,150 @@ void DebrisManager::explode(const std::shared_ptr& shape, // Reproducir sonido de explosión Audio::get()->playSound(sound, Audio::Group::GAME); - // Obtenir centro de la shape para transformacions const Vec2& shape_centre = shape->getCenter(); - // Iterar sobre todas las primitives de la shape - for (const auto& primitive : shape->get_primitives()) { - // Processar cada segment de línia - std::vector> segments; + for (const auto& primitive : shape->getPrimitives()) { + for (const auto& [local_p1, local_p2] : extractSegments(primitive)) { + // Transformar points locals → coordenades mundials + Vec2 world_p1 = transformPoint(local_p1, shape_centre, centro, angle, scale); + Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); - if (primitive.type == Graphics::PrimitiveType::POLYLINE) { - // Polyline: extreure segments consecutius - for (size_t i = 0; i < primitive.points.size() - 1; i++) { - segments.emplace_back(primitive.points[i], primitive.points[i + 1]); - } - } else { // PrimitiveType::LINE - // Line: un únic segment - if (primitive.points.size() >= 2) { - segments.emplace_back(primitive.points[0], primitive.points[1]); + // Si el pool es ple, no té sentit continuar amb la resta de segments + if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, + velocitat_objecte, velocitat_angular, + factor_herencia_visual, color)) { + return; } } + } +} - // Crear debris para cada segment - for (const auto& [local_p1, local_p2] : segments) { - // 1. Transformar points locals → coordenades mundials - Vec2 world_p1 = - transformPoint(local_p1, shape_centre, centro, angle, scale); - Vec2 world_p2 = - transformPoint(local_p2, shape_centre, centro, angle, scale); +auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive) + -> std::vector> { + std::vector> segments; - // 2. Trobar slot lliure - Debris* debris = findFreeSlot(); - if (debris == nullptr) { - std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; - return; // Pool ple - } - - // 3. Inicialitzar geometria - debris->p1 = world_p1; - debris->p2 = world_p2; - - // 4. Calcular direcció de explosión (radial, des del centro hacia fuera) - Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro); - - // 5. Velocidad inicial (base ± variació aleatòria + velocity heretada) - float speed = - velocitat_base + - (((std::rand() / static_cast(RAND_MAX)) * 2.0F - 1.0F) * - Defaults::Physics::Debris::VARIACIO_VELOCITAT); - - // Heredar velocity de l'objecte original (suma vectorial) - debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x; - debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y; - debris->acceleration = Defaults::Physics::Debris::ACCELERACIO; - - // 6. Herència de velocity angular con sin + conversió de excés - - // 6a. Rotación de TRAYECTORIA con sin + conversió tangencial - if (std::abs(velocitat_angular) > 0.01F) { - // FASE 1: Aplicar herència i variació (igual que antes) - float factor_herencia = - Defaults::Physics::Debris::FACTOR_HERENCIA_MIN + - ((std::rand() / static_cast(RAND_MAX)) * - (Defaults::Physics::Debris::FACTOR_HERENCIA_MAX - - Defaults::Physics::Debris::FACTOR_HERENCIA_MIN)); - - float velocitat_ang_heretada = velocitat_angular * factor_herencia; - - float variacio = - ((std::rand() / static_cast(RAND_MAX)) * 0.2F) - 0.1F; - velocitat_ang_heretada *= (1.0F + variacio); - - // FASE 2: Aplicar sin i calcular excés - constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX; - float abs_ang = std::abs(velocitat_ang_heretada); - float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F; - - if (abs_ang > CAP) { - // Excés: convertir a velocity tangencial - float excess = abs_ang - CAP; - - // Radi de la shape (enemigos = 20 px) - float radius = 20.0F; - - // Velocidad tangencial = ω_excés × radi - float v_tangential = excess * radius; - - // Direcció tangencial: perpendicular a la radial (90° CCW) - // Si direccio = (dx, dy), tangent = (-dy, dx) - float tangent_x = -direccio.y; - float tangent_y = direccio.x; - - // Añadir velocity tangencial (suma vectorial) - debris->velocity.x += tangent_x * v_tangential; - debris->velocity.y += tangent_y * v_tangential; - - // Aplicar hacia velocity angular (preservar signe) - debris->velocitat_rot = sign_ang * CAP; - } else { - // Per sota del sin: comportament normal - debris->velocitat_rot = velocitat_ang_heretada; - } - } else { - debris->velocitat_rot = 0.0F; // Nave: sin curvas - } - - // 6b. Rotación VISUAL (proporcional según factor_herencia_visual) - if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) { - // Heredar rotación visual con factor proporcional - debris->velocitat_rot_visual = debris->velocitat_rot * factor_herencia_visual; - - // Variació aleatòria pequeña (±5%) per naturalitat - float variacio_visual = - ((std::rand() / static_cast(RAND_MAX)) * 0.1F) - 0.05F; - debris->velocitat_rot_visual *= (1.0F + variacio_visual); - } else { - // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular) - debris->velocitat_rot_visual = - Defaults::Physics::Debris::ROTACIO_MIN + - ((std::rand() / static_cast(RAND_MAX)) * - (Defaults::Physics::Debris::ROTACIO_MAX - - Defaults::Physics::Debris::ROTACIO_MIN)); - - // 50% probabilitat de rotación en sentit contrari - if (std::rand() % 2 == 0) { - debris->velocitat_rot_visual = -debris->velocitat_rot_visual; - } - } - - debris->angle_rotacio = 0.0F; - - // 7. Configurar vida i shrinking - debris->temps_vida = 0.0F; - debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA; - debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; - - // 8. Heredar brightness y color del padre - debris->brightness = brightness; - debris->color = color; - - // 9. Activar - debris->active = true; + if (primitive.type == Graphics::PrimitiveType::POLYLINE) { + // Polyline: extreure segments consecutius + for (size_t i = 0; i + 1 < primitive.points.size(); i++) { + segments.emplace_back(primitive.points[i], primitive.points[i + 1]); } + return segments; + } + // PrimitiveType::LINE: un únic segment (si té els 2 punts) + if (primitive.points.size() >= 2) { + segments.emplace_back(primitive.points[0], primitive.points[1]); + } + return segments; +} + +auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, + const Vec2& centro, float velocitat_base, float brightness, + const Vec2& velocitat_objecte, float velocitat_angular, + float factor_herencia_visual, SDL_Color color) -> bool { + Debris* debris = findFreeSlot(); + if (debris == nullptr) { + std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; + return false; + } + + // Geometria + debris->p1 = world_p1; + debris->p2 = world_p2; + + // Direcció radial (desde el centro hacia el segment) + Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro); + + // Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte) + float speed = + velocitat_base + + (((std::rand() / static_cast(RAND_MAX)) * 2.0F - 1.0F) * + Defaults::Physics::Debris::VARIACIO_VELOCITAT); + debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x; + debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y; + debris->acceleration = Defaults::Physics::Debris::ACCELERACIO; + + // Rotación de trayectoria (con conversió a tangencial si excedeix cap) + applyAngularVelocity(*debris, direccio, velocitat_angular); + + // Rotación visual (proporcional o aleatòria) + applyVisualRotation(*debris, velocitat_angular, factor_herencia_visual); + + debris->angle_rotacio = 0.0F; + + // Vida i shrinking + debris->temps_vida = 0.0F; + debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA; + debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; + + // Visuals heretades + debris->brightness = brightness; + debris->color = color; + + debris->active = true; + return true; +} + +void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, + float velocitat_angular) { + if (std::abs(velocitat_angular) <= 0.01F) { + debris.velocitat_rot = 0.0F; // Nave: sin curvas + return; + } + + // FASE 1: Aplicar herència i variació + float factor_herencia = + Defaults::Physics::Debris::FACTOR_HERENCIA_MIN + + ((std::rand() / static_cast(RAND_MAX)) * + (Defaults::Physics::Debris::FACTOR_HERENCIA_MAX - + Defaults::Physics::Debris::FACTOR_HERENCIA_MIN)); + float velocitat_ang_heretada = velocitat_angular * factor_herencia; + float variacio = ((std::rand() / static_cast(RAND_MAX)) * 0.2F) - 0.1F; + velocitat_ang_heretada *= (1.0F + variacio); + + // FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial + constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX; + float abs_ang = std::abs(velocitat_ang_heretada); + float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F; + + if (abs_ang <= CAP) { + debris.velocitat_rot = velocitat_ang_heretada; + return; + } + + // Excés: converteix l'excés de velocitat angular en velocitat tangencial lineal + float excess = abs_ang - CAP; + constexpr float RADIUS = 20.0F; // Radi típic de la shape (enemigos = 20 px) + float v_tangential = excess * RADIUS; + + // Direcció tangencial: perpendicular a la radial (90° CCW): tangent = (-dy, dx) + debris.velocity.x += -direccio.y * v_tangential; + debris.velocity.y += direccio.x * v_tangential; + + // Velocitat angular limitada al cap (preservant el signe) + debris.velocitat_rot = sign_ang * CAP; +} + +void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, + float factor_herencia_visual) { + if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) { + // Heredar rotación visual con factor proporcional + ±5% de variació + debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual; + float variacio_visual = + ((std::rand() / static_cast(RAND_MAX)) * 0.1F) - 0.05F; + debris.velocitat_rot_visual *= (1.0F + variacio_visual); + return; + } + + // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular) + debris.velocitat_rot_visual = + Defaults::Physics::Debris::ROTACIO_MIN + + ((std::rand() / static_cast(RAND_MAX)) * + (Defaults::Physics::Debris::ROTACIO_MAX - + Defaults::Physics::Debris::ROTACIO_MIN)); + + // 50% probabilitat de rotación en sentit contrari + if (std::rand() % 2 == 0) { + debris.velocitat_rot_visual = -debris.velocitat_rot_visual; } } diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 0652943..48c0a43 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include "core/defaults.hpp" #include "core/graphics/shape.hpp" @@ -72,6 +74,21 @@ class DebrisManager { // Calcular direcció de explosión (radial, des del centro hacia el segment). // Estático: solo opera sobre los puntos pasados, sin estado del manager. [[nodiscard]] static auto computeExplosionDirection(const Vec2& p1, const Vec2& p2, const Vec2& centre_objecte) -> Vec2; + + // Sub-pasos de explode() (descomposició per reduir complexitat cognitiva). + // extractSegments y los apply* son static (solo toquen el debris pasado). + [[nodiscard]] static auto extractSegments(const Graphics::ShapePrimitive& primitive) + -> std::vector>; + // Inicialitza un debris en un slot lliure i el deixa actiu. Retorna + // false si el pool está ple (la cridadora ha d'aturar el bucle). + auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, + const Vec2& centro, float velocitat_base, float brightness, + const Vec2& velocitat_objecte, float velocitat_angular, + float factor_herencia_visual, SDL_Color color) -> bool; + static void applyAngularVelocity(Debris& debris, const Vec2& direccio, + float velocitat_angular); + static void applyVisualRotation(Debris& debris, float velocitat_angular, + float factor_herencia_visual); }; } // namespace Effects diff --git a/source/game/entities/bullet.cpp b/source/game/entities/bullet.cpp index c5ab674..6a838cb 100644 --- a/source/game/entities/bullet.cpp +++ b/source/game/entities/bullet.cpp @@ -48,7 +48,7 @@ Bullet::Bullet(Rendering::Renderer* renderer) void Bullet::init() { // Inicialment inactiva - esta_ = false; + is_active_ = false; center_ = {.x = 0.0F, .y = 0.0F}; angle_ = 0.0F; grace_timer_ = 0.0F; @@ -63,7 +63,7 @@ void Bullet::init() { void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) { // Activar bullet - esta_ = true; + is_active_ = true; // Almacenar propietario (0=P1, 1=P2) owner_id_ = owner_id; @@ -90,7 +90,7 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) { } void Bullet::update(float delta_time) { - if (!esta_) { + if (!is_active_) { return; } @@ -106,7 +106,7 @@ void Bullet::update(float delta_time) { float max_x; float min_y; float max_y; - Constants::obtenir_limits_zona_segurs(Defaults::Entities::BULLET_RADIUS, + Constants::getSafePlayAreaBounds(Defaults::Entities::BULLET_RADIUS, min_x, max_x, min_y, @@ -125,16 +125,16 @@ void Bullet::postUpdate(float /*delta_time*/) { } void Bullet::desactivar() { - esta_ = false; + is_active_ = false; // Detener el cuerpo físico para que no acumule deriva mientras inactiva. body_.velocity = Vec2{}; body_.angular_velocity = 0.0F; } void Bullet::draw() const { - if (esta_ && shape_) { + if (is_active_ && shape_) { // Les bales roten segons l'angle de trayectòria (estático tras disparo) - Rendering::render_shape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, + Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, /*rotation_3d=*/nullptr, Defaults::Palette::BULLET); } } diff --git a/source/game/entities/bullet.hpp b/source/game/entities/bullet.hpp index 7e222c1..54bd204 100644 --- a/source/game/entities/bullet.hpp +++ b/source/game/entities/bullet.hpp @@ -23,18 +23,17 @@ class Bullet : public Entities::Entity { void draw() const override; // Override: Interfaz de Entity - [[nodiscard]] auto isActive() const -> bool override { return esta_; } + [[nodiscard]] auto isActive() const -> bool override { return is_active_; } // Override: Interfaz de colisión (gameplay-level: PLAYAREA bounds-check) [[nodiscard]] auto getCollisionRadius() const -> float override { return Defaults::Entities::BULLET_RADIUS; } [[nodiscard]] auto isCollidable() const -> bool override { - return esta_ && grace_timer_ <= 0.0F; + return is_active_ && grace_timer_ <= 0.0F; } // Getters (API pública sin cambios) - [[nodiscard]] auto esta_activa() const -> bool { return esta_; } [[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; } [[nodiscard]] auto getGraceTimer() const -> float { return grace_timer_; } void desactivar(); @@ -43,7 +42,7 @@ class Bullet : public Entities::Entity { // Miembros específicos de Bullet (heredados: renderer_, shape_, center_, angle_, brightness_, body_). // Inicializados en la declaración para que tanto el ctor por defecto como el que toma renderer // dejen el objeto en estado coherente (proyectil inactivo, sin owner, sin grace timer). - bool esta_{false}; + bool is_active_{false}; uint8_t owner_id_{0}; // 0=P1, 1=P2 float grace_timer_{0.0F}; // Grace period timer (0.0 = vulnerable) }; diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index cc6f707..388afe2 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -113,7 +113,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) { float max_x; float min_y; float max_y; - Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, min_x, max_x, min_y, max_y); + Constants::getSafePlayAreaBounds(Defaults::Entities::ENEMY_RADIUS, min_x, max_x, min_y, max_y); if (ship_pos != nullptr) { bool found_safe_position = false; @@ -229,7 +229,7 @@ void Enemy::draw() const { case EnemyType::QUADRAT: color = Defaults::Palette::QUADRAT; break; case EnemyType::MOLINILLO: color = Defaults::Palette::MOLINILLO; break; } - Rendering::render_shape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, + Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, /*rotation_3d=*/nullptr, color); } @@ -436,7 +436,7 @@ auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) - float max_x; float min_y; float max_y; - Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, min_x, max_x, min_y, max_y); + Constants::getSafePlayAreaBounds(Defaults::Entities::ENEMY_RADIUS, min_x, max_x, min_y, max_y); const int RANGE_X = static_cast(max_x - min_x); const int RANGE_Y = static_cast(max_y - min_y); diff --git a/source/game/entities/ship.cpp b/source/game/entities/ship.cpp index 3393517..e954759 100644 --- a/source/game/entities/ship.cpp +++ b/source/game/entities/ship.cpp @@ -46,7 +46,7 @@ void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) { } else { float centre_x; float centre_y; - Constants::obtenir_centre_zona(centre_x, centre_y); + Constants::getPlayAreaCenter(centre_x, centre_y); center_ = {.x = centre_x, .y = centre_y}; } @@ -158,6 +158,6 @@ void Ship::draw() const { const float VISUAL_PUSH = SPEED / 33.33F; const float SCALE = 1.0F + (VISUAL_PUSH / 12.0F); - Rendering::render_shape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, + Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, /*rotation_3d=*/nullptr, Defaults::Palette::SHIP); } diff --git a/source/game/options.cpp b/source/game/options.cpp index 4a23a62..9b4cf1b 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -224,119 +224,71 @@ void setConfigFile(const std::string& path) { config_file_path = path; } // Funciones auxiliars per load seccions del YAML +// Lee un campo escalar del YAML aplicando un validador; si la clau no +// existe, deja `dest` intacto; si la conversió o la validació fallen, +// asigna `fallback`. Estàtic per quedar dins de la unitat de traducció. +template +static void readField(const fkyaml::node& parent, const char* key, T& dest, + T fallback, Validator&& validate) { + if (!parent.contains(key)) { + return; + } + try { + auto val = parent[key].template get_value(); + dest = validate(val) ? val : fallback; + } catch (...) { + dest = fallback; + } +} + +// Variant sin validador: només lectura amb fallback en cas d'error. +template +static void readField(const fkyaml::node& parent, const char* key, T& dest, T fallback) { + if (!parent.contains(key)) { + return; + } + try { + dest = parent[key].template get_value(); + } catch (...) { + dest = fallback; + } +} + static void loadWindowConfigFromYaml(const fkyaml::node& yaml) { - if (yaml.contains("window")) { - const auto& win = yaml["window"]; + if (!yaml.contains("window")) { + return; + } + const auto& win = yaml["window"]; - if (win.contains("width")) { - try { - auto val = win["width"].get_value(); - window.width = (val >= Defaults::Window::MIN_WIDTH) - ? val - : Defaults::Window::WIDTH; - } catch (...) { - window.width = Defaults::Window::WIDTH; - } - } + readField(win, "width", window.width, Defaults::Window::WIDTH, + [](int v) { return v >= Defaults::Window::MIN_WIDTH; }); + readField(win, "height", window.height, Defaults::Window::HEIGHT, + [](int v) { return v >= Defaults::Window::MIN_HEIGHT; }); + readField(win, "fullscreen", window.fullscreen, false); - if (win.contains("height")) { - try { - auto val = win["height"].get_value(); - window.height = (val >= Defaults::Window::MIN_HEIGHT) - ? val - : Defaults::Window::HEIGHT; - } catch (...) { - window.height = Defaults::Window::HEIGHT; - } - } - - if (win.contains("fullscreen")) { - try { - window.fullscreen = win["fullscreen"].get_value(); - } catch (...) { - window.fullscreen = false; - } - } - - if (win.contains("zoom_factor")) { - try { - auto val = win["zoom_factor"].get_value(); - window.zoom_factor = (val >= Defaults::Window::MIN_ZOOM && val <= 10.0F) - ? val - : Defaults::Window::BASE_ZOOM; - } catch (...) { - window.zoom_factor = Defaults::Window::BASE_ZOOM; - } - } else { - // Legacy config: infer zoom from width - window.zoom_factor = static_cast(window.width) / Defaults::Window::WIDTH; - window.zoom_factor = std::max(Defaults::Window::MIN_ZOOM, window.zoom_factor); - } + if (win.contains("zoom_factor")) { + readField(win, "zoom_factor", window.zoom_factor, Defaults::Window::BASE_ZOOM, + [](float v) { return v >= Defaults::Window::MIN_ZOOM && v <= 10.0F; }); + } else { + // Legacy config: infer zoom from width + window.zoom_factor = static_cast(window.width) / Defaults::Window::WIDTH; + window.zoom_factor = std::max(Defaults::Window::MIN_ZOOM, window.zoom_factor); } } static void loadPhysicsConfigFromYaml(const fkyaml::node& yaml) { - if (yaml.contains("physics")) { - const auto& phys = yaml["physics"]; - - if (phys.contains("rotation_speed")) { - try { - auto val = phys["rotation_speed"].get_value(); - physics.rotation_speed = - (val > 0) ? val : Defaults::Physics::ROTATION_SPEED; - } catch (...) { - physics.rotation_speed = Defaults::Physics::ROTATION_SPEED; - } - } - - if (phys.contains("acceleration")) { - try { - auto val = phys["acceleration"].get_value(); - physics.acceleration = - (val > 0) ? val : Defaults::Physics::ACCELERATION; - } catch (...) { - physics.acceleration = Defaults::Physics::ACCELERATION; - } - } - - if (phys.contains("max_velocity")) { - try { - auto val = phys["max_velocity"].get_value(); - physics.max_velocity = - (val > 0) ? val : Defaults::Physics::MAX_VELOCITY; - } catch (...) { - physics.max_velocity = Defaults::Physics::MAX_VELOCITY; - } - } - - if (phys.contains("friction")) { - try { - auto val = phys["friction"].get_value(); - physics.friction = (val > 0) ? val : Defaults::Physics::FRICTION; - } catch (...) { - physics.friction = Defaults::Physics::FRICTION; - } - } - - if (phys.contains("enemy_speed")) { - try { - auto val = phys["enemy_speed"].get_value(); - physics.enemy_speed = (val > 0) ? val : Defaults::Physics::ENEMY_SPEED; - } catch (...) { - physics.enemy_speed = Defaults::Physics::ENEMY_SPEED; - } - } - - if (phys.contains("bullet_speed")) { - try { - auto val = phys["bullet_speed"].get_value(); - physics.bullet_speed = - (val > 0) ? val : Defaults::Physics::BULLET_SPEED; - } catch (...) { - physics.bullet_speed = Defaults::Physics::BULLET_SPEED; - } - } + if (!yaml.contains("physics")) { + return; } + const auto& phys = yaml["physics"]; + constexpr auto POSITIVE = [](float v) { return v > 0.0F; }; + + readField(phys, "rotation_speed", physics.rotation_speed, Defaults::Physics::ROTATION_SPEED, POSITIVE); + readField(phys, "acceleration", physics.acceleration, Defaults::Physics::ACCELERATION, POSITIVE); + readField(phys, "max_velocity", physics.max_velocity, Defaults::Physics::MAX_VELOCITY, POSITIVE); + readField(phys, "friction", physics.friction, Defaults::Physics::FRICTION, POSITIVE); + readField(phys, "enemy_speed", physics.enemy_speed, Defaults::Physics::ENEMY_SPEED, POSITIVE); + readField(phys, "bullet_speed", physics.bullet_speed, Defaults::Physics::BULLET_SPEED, POSITIVE); } static void loadGameplayConfigFromYaml(const fkyaml::node& yaml) { @@ -381,69 +333,39 @@ static void loadRenderingConfigFromYaml(const fkyaml::node& yaml) { } } -static void loadAudioConfigFromYaml(const fkyaml::node& yaml) { - if (yaml.contains("audio")) { - const auto& aud = yaml["audio"]; - - if (aud.contains("enabled")) { - try { - audio.enabled = aud["enabled"].get_value(); - } catch (...) { - audio.enabled = Defaults::Audio::ENABLED; - } - } - - if (aud.contains("volume")) { - try { - auto val = aud["volume"].get_value(); - audio.volume = (val >= 0.0F && val <= 1.0F) ? val : Defaults::Audio::VOLUME; - } catch (...) { - audio.volume = Defaults::Audio::VOLUME; - } - } - - if (aud.contains("music")) { - const auto& mus = aud["music"]; - - if (mus.contains("enabled")) { - try { - audio.music.enabled = mus["enabled"].get_value(); - } catch (...) { - audio.music.enabled = Defaults::Audio::MUSIC_ENABLED; - } - } - - if (mus.contains("volume")) { - try { - auto val = mus["volume"].get_value(); - audio.music.volume = (val >= 0.0F && val <= 1.0F) ? val : Defaults::Audio::MUSIC_VOLUME; - } catch (...) { - audio.music.volume = Defaults::Audio::MUSIC_VOLUME; - } - } - } - - if (aud.contains("sound")) { - const auto& snd = aud["sound"]; - - if (snd.contains("enabled")) { - try { - audio.sound.enabled = snd["enabled"].get_value(); - } catch (...) { - audio.sound.enabled = Defaults::Audio::SOUND_ENABLED; - } - } - - if (snd.contains("volume")) { - try { - auto val = snd["volume"].get_value(); - audio.sound.volume = (val >= 0.0F && val <= 1.0F) ? val : Defaults::Audio::SOUND_VOLUME; - } catch (...) { - audio.sound.volume = Defaults::Audio::SOUND_VOLUME; - } - } - } +static void loadAudioMusicSection(const fkyaml::node& aud) { + if (!aud.contains("music")) { + return; } + const auto& mus = aud["music"]; + constexpr auto UNIT_RANGE = [](float v) { return v >= 0.0F && v <= 1.0F; }; + + readField(mus, "enabled", audio.music.enabled, Defaults::Audio::MUSIC_ENABLED); + readField(mus, "volume", audio.music.volume, Defaults::Audio::MUSIC_VOLUME, UNIT_RANGE); +} + +static void loadAudioSoundSection(const fkyaml::node& aud) { + if (!aud.contains("sound")) { + return; + } + const auto& snd = aud["sound"]; + constexpr auto UNIT_RANGE = [](float v) { return v >= 0.0F && v <= 1.0F; }; + + readField(snd, "enabled", audio.sound.enabled, Defaults::Audio::SOUND_ENABLED); + readField(snd, "volume", audio.sound.volume, Defaults::Audio::SOUND_VOLUME, UNIT_RANGE); +} + +static void loadAudioConfigFromYaml(const fkyaml::node& yaml) { + if (!yaml.contains("audio")) { + return; + } + const auto& aud = yaml["audio"]; + constexpr auto UNIT_RANGE = [](float v) { return v >= 0.0F && v <= 1.0F; }; + + readField(aud, "enabled", audio.enabled, Defaults::Audio::ENABLED); + readField(aud, "volume", audio.volume, Defaults::Audio::VOLUME, UNIT_RANGE); + loadAudioMusicSection(aud); + loadAudioSoundSection(aud); } // Carregar controls del player 1 desde YAML diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index ca87f74..748f009 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -8,13 +8,9 @@ #include #include #include -#include #include "core/audio/audio.hpp" -#include "core/entities/entity.hpp" #include "core/input/input.hpp" -#include "core/math/easing.hpp" -#include "core/physics/collision.hpp" #include "core/rendering/line_renderer.hpp" #include "core/system/scene_context.hpp" #include "game/stage_system/stage_loader.hpp" @@ -223,7 +219,7 @@ void GameScene::stepShootingInput() { void GameScene::stepMidGameJoin() { // Permitir join solo durante PLAYING. - if (stage_manager_->get_estat() != StageSystem::EstatStage::PLAYING) { + if (stage_manager_->getState() != StageSystem::EstatStage::PLAYING) { return; } @@ -361,7 +357,7 @@ void GameScene::stepDeathSequence(float delta_time) { } void GameScene::stepStageStateMachine(float delta_time) { - const StageSystem::EstatStage STATE = stage_manager_->get_estat(); + const StageSystem::EstatStage STATE = stage_manager_->getState(); switch (STATE) { case StageSystem::EstatStage::INIT_HUD: runStageInitHud(delta_time); @@ -382,11 +378,11 @@ void GameScene::runStageInitHud(float delta_time) { // Update stage manager timer (puede cambiar el state). stage_manager_->update(delta_time); // Si el state cambió, salir para no usar el timer del nuevo state. - if (stage_manager_->get_estat() != StageSystem::EstatStage::INIT_HUD) { + if (stage_manager_->getState() != StageSystem::EstatStage::INIT_HUD) { return; } - float global_progress = 1.0F - (stage_manager_->get_timer_transicio() / Defaults::Game::INIT_HUD_DURATION); + float global_progress = 1.0F - (stage_manager_->getTransitionTimer() / Defaults::Game::INIT_HUD_DURATION); global_progress = std::min(1.0F, global_progress); const float SHIP1_P = Systems::InitHud::computeRangeProgress( @@ -429,8 +425,8 @@ void GameScene::runStagePlaying(float delta_time) { // Stage completado: cuando al menos un jugador está vivo y todos los enemies muertos. const bool ALGU_VIU = (hit_timer_per_player_[0] == 0.0F || hit_timer_per_player_[1] == 0.0F); - if (ALGU_VIU && stage_manager_->getSpawnController().tots_enemics_destruits(enemies_)) { - stage_manager_->stage_completat(); + if (ALGU_VIU && stage_manager_->getSpawnController().allEnemiesDestroyed(enemies_)) { + stage_manager_->markStageCompleted(); Audio::get()->playSound(Defaults::Sound::GOOD_JOB_COMMANDER, Audio::Group::GAME); return; } @@ -488,192 +484,159 @@ void GameScene::runCollisionDetections() { } void GameScene::draw() { - // Handle CONTINUE screen if (game_over_state_ == GameOverState::CONTINUE) { - // Draw game background elements first - drawMargins(); - - for (const auto& enemy : enemies_) { - enemy.draw(); - } - - for (const auto& bullet : bullets_) { - bullet.draw(); - } - - debris_manager_.draw(); - floating_score_manager_.draw(); - drawScoreboard(); - - // Draw CONTINUE screen overlay - drawContinue(); + drawContinueState(); return; } - // Handle final GAME OVER state if (game_over_state_ == GameOverState::GAME_OVER) { - // Game over: draw enemies, bullets, debris, and "GAME OVER" text - drawMargins(); - - for (const auto& enemy : enemies_) { - enemy.draw(); - } - - for (const auto& bullet : bullets_) { - bullet.draw(); - } - - debris_manager_.draw(); - floating_score_manager_.draw(); - - // Draw centered "GAME OVER" text - const std::string GAME_OVER_TEXT = "GAME OVER"; - constexpr float SCALE = Defaults::Game::GameOverScreen::TEXT_SCALE; - constexpr float SPACING = Defaults::Game::GameOverScreen::TEXT_SPACING; - - // Calcular centro de l'àrea de juego usant constants - const SDL_FRect& play_area = Defaults::Zones::PLAYAREA; - float centre_x = play_area.x + (play_area.w / 2.0F); - float centre_y = play_area.y + (play_area.h / 2.0F); - - text_.renderCentered(GAME_OVER_TEXT, {.x = centre_x, .y = centre_y}, SCALE, SPACING); - - drawScoreboard(); + drawGameOverState(); return; } - // [NEW] Stage state rendering - StageSystem::EstatStage state = stage_manager_->get_estat(); - - switch (state) { - case StageSystem::EstatStage::INIT_HUD: { - // Calcular progrés de cada animación independent - float timer = stage_manager_->get_timer_transicio(); - float total_time = Defaults::Game::INIT_HUD_DURATION; - float global_progress = 1.0F - (timer / total_time); - - // [NEW] Calcular progress independiente para cada elemento - float rect_progress = Systems::InitHud::computeRangeProgress( - global_progress, - Defaults::Game::INIT_HUD_RECT_RATIO_INIT, - Defaults::Game::INIT_HUD_RECT_RATIO_END); - - float score_progress = Systems::InitHud::computeRangeProgress( - global_progress, - Defaults::Game::INIT_HUD_SCORE_RATIO_INIT, - Defaults::Game::INIT_HUD_SCORE_RATIO_END); - - float ship1_progress = Systems::InitHud::computeRangeProgress( - global_progress, - Defaults::Game::INIT_HUD_SHIP1_RATIO_INIT, - Defaults::Game::INIT_HUD_SHIP1_RATIO_END); - - float ship2_progress = Systems::InitHud::computeRangeProgress( - global_progress, - Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT, - Defaults::Game::INIT_HUD_SHIP2_RATIO_END); - - // Dibuixar elements animats - if (rect_progress > 0.0F) { - // [NOU] Reproduir so cuando comença l'animación del rectangle - if (!init_hud_rect_sound_played_) { - Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME); - init_hud_rect_sound_played_ = true; - } - - Systems::InitHud::drawBordersAnimated(sdl_.getRenderer(), rect_progress); - } - - if (score_progress > 0.0F) { - Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress); - } - - // [MODIFICAT] Dibuixar naves con progress independent - if (ship1_progress > 0.0F && match_config_.jugador1_actiu && !ships_[0].isHit()) { - ships_[0].draw(); - } - - if (ship2_progress > 0.0F && match_config_.jugador2_actiu && !ships_[1].isHit()) { - ships_[1].draw(); - } - + switch (stage_manager_->getState()) { + case StageSystem::EstatStage::INIT_HUD: + drawInitHudState(); break; - } - case StageSystem::EstatStage::LEVEL_START: - drawMargins(); - // [NEW] Draw both ships if active and alive - for (uint8_t i = 0; i < 2; i++) { - bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu; - if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { - ships_[i].draw(); - } - } - - // [NEW] Draw bullets - for (const auto& bullet : bullets_) { - bullet.draw(); - } - - // [NEW] Draw debris - debris_manager_.draw(); - floating_score_manager_.draw(); - - // [EXISTING] Draw intro message and score - drawStageMessage(stage_manager_->get_missatge_level_start()); - drawScoreboard(); + drawLevelStartState(); break; - case StageSystem::EstatStage::PLAYING: - drawMargins(); - - // [EXISTING] Normal rendering - active ships - for (uint8_t i = 0; i < 2; i++) { - bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu; - if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { - ships_[i].draw(); - } - } - - for (const auto& enemy : enemies_) { - enemy.draw(); - } - - for (const auto& bullet : bullets_) { - bullet.draw(); - } - - debris_manager_.draw(); - floating_score_manager_.draw(); - drawScoreboard(); + drawPlayingState(); break; - case StageSystem::EstatStage::LEVEL_COMPLETED: - drawMargins(); - // [NEW] Draw both ships if active and alive - for (uint8_t i = 0; i < 2; i++) { - bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu; - if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { - ships_[i].draw(); - } - } - - // [NEW] Draw bullets (allow last shots to be visible) - for (const auto& bullet : bullets_) { - bullet.draw(); - } - - // [NEW] Draw debris (from last destroyed enemies) - debris_manager_.draw(); - floating_score_manager_.draw(); - - // [EXISTING] Draw completion message and score - drawStageMessage(StageSystem::Constants::MISSATGE_LEVEL_COMPLETED); - drawScoreboard(); + drawLevelCompletedState(); break; } } +void GameScene::drawEnemies() const { + for (const auto& enemy : enemies_) { + enemy.draw(); + } +} + +void GameScene::drawBullets() const { + for (const auto& bullet : bullets_) { + bullet.draw(); + } +} + +void GameScene::drawActiveShipsAlive() const { + for (uint8_t i = 0; i < 2; i++) { + bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu; + if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { + ships_[i].draw(); + } + } +} + +void GameScene::drawContinueState() { + drawMargins(); + drawEnemies(); + drawBullets(); + debris_manager_.draw(); + floating_score_manager_.draw(); + drawScoreboard(); + drawContinue(); +} + +void GameScene::drawGameOverState() { + drawMargins(); + drawEnemies(); + drawBullets(); + debris_manager_.draw(); + floating_score_manager_.draw(); + + const std::string GAME_OVER_TEXT = "GAME OVER"; + constexpr float SCALE = Defaults::Game::GameOverScreen::TEXT_SCALE; + constexpr float SPACING = Defaults::Game::GameOverScreen::TEXT_SPACING; + + const SDL_FRect& play_area = Defaults::Zones::PLAYAREA; + float centre_x = play_area.x + (play_area.w / 2.0F); + float centre_y = play_area.y + (play_area.h / 2.0F); + + text_.renderCentered(GAME_OVER_TEXT, {.x = centre_x, .y = centre_y}, SCALE, SPACING); + + drawScoreboard(); +} + +void GameScene::drawInitHudState() { + float timer = stage_manager_->getTransitionTimer(); + float total_time = Defaults::Game::INIT_HUD_DURATION; + float global_progress = 1.0F - (timer / total_time); + + float rect_progress = Systems::InitHud::computeRangeProgress( + global_progress, + Defaults::Game::INIT_HUD_RECT_RATIO_INIT, + Defaults::Game::INIT_HUD_RECT_RATIO_END); + + float score_progress = Systems::InitHud::computeRangeProgress( + global_progress, + Defaults::Game::INIT_HUD_SCORE_RATIO_INIT, + Defaults::Game::INIT_HUD_SCORE_RATIO_END); + + float ship1_progress = Systems::InitHud::computeRangeProgress( + global_progress, + Defaults::Game::INIT_HUD_SHIP1_RATIO_INIT, + Defaults::Game::INIT_HUD_SHIP1_RATIO_END); + + float ship2_progress = Systems::InitHud::computeRangeProgress( + global_progress, + Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT, + Defaults::Game::INIT_HUD_SHIP2_RATIO_END); + + if (rect_progress > 0.0F) { + if (!init_hud_rect_sound_played_) { + Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME); + init_hud_rect_sound_played_ = true; + } + Systems::InitHud::drawBordersAnimated(sdl_.getRenderer(), rect_progress); + } + + if (score_progress > 0.0F) { + Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress); + } + + if (ship1_progress > 0.0F && match_config_.jugador1_actiu && !ships_[0].isHit()) { + ships_[0].draw(); + } + + if (ship2_progress > 0.0F && match_config_.jugador2_actiu && !ships_[1].isHit()) { + ships_[1].draw(); + } +} + +void GameScene::drawLevelStartState() { + drawMargins(); + drawActiveShipsAlive(); + drawBullets(); + debris_manager_.draw(); + floating_score_manager_.draw(); + drawStageMessage(stage_manager_->getLevelStartMessage()); + drawScoreboard(); +} + +void GameScene::drawPlayingState() { + drawMargins(); + drawActiveShipsAlive(); + drawEnemies(); + drawBullets(); + debris_manager_.draw(); + floating_score_manager_.draw(); + drawScoreboard(); +} + +void GameScene::drawLevelCompletedState() { + drawMargins(); + drawActiveShipsAlive(); + drawBullets(); + debris_manager_.draw(); + floating_score_manager_.draw(); + drawStageMessage(StageSystem::Constants::MISSATGE_LEVEL_COMPLETED); + drawScoreboard(); +} + void GameScene::tocado(uint8_t player_id) { // Death sequence: 3 phases // Phase 1: First call (hit_timer_per_player_[player_id] == 0) - trigger explosion @@ -764,7 +727,7 @@ auto GameScene::buildScoreboard() const -> std::string { } // Nivel (2 dígits) - uint8_t stage_num = stage_manager_->get_stage_actual(); + uint8_t stage_num = stage_manager_->getCurrentStage(); std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num) : std::to_string(stage_num); @@ -801,7 +764,7 @@ void GameScene::drawStageMessage(const std::string& message) { float total_time; float typing_ratio; - if (stage_manager_->get_estat() == StageSystem::EstatStage::LEVEL_START) { + if (stage_manager_->getState() == StageSystem::EstatStage::LEVEL_START) { total_time = Defaults::Game::LEVEL_START_DURATION; typing_ratio = Defaults::Game::LEVEL_START_TYPING_RATIO; } else { // LEVEL_COMPLETED @@ -810,7 +773,7 @@ void GameScene::drawStageMessage(const std::string& message) { } // Calculate progress from timer (0.0 at start → 1.0 at end) - float remaining_time = stage_manager_->get_timer_transicio(); + float remaining_time = stage_manager_->getTransitionTimer(); float progress = 1.0F - (remaining_time / total_time); // Determine how many characters to show @@ -834,7 +797,7 @@ void GameScene::drawStageMessage(const std::string& message) { // =================================================== // Calculate text width at base scale (using FULL message for position calculation) - float text_width_at_base = text_.get_text_width(message, BASE_SCALE, SPACING); + float text_width_at_base = Graphics::VectorText::getTextWidth(message, BASE_SCALE, SPACING); // Auto-scale if text exceeds max width float scale = (text_width_at_base <= MAX_WIDTH) @@ -842,8 +805,8 @@ void GameScene::drawStageMessage(const std::string& message) { : MAX_WIDTH / text_width_at_base; // Recalculate dimensions with final scale (using FULL message for centering) - float full_text_width = text_.get_text_width(message, scale, SPACING); - float text_height = text_.get_text_height(scale); + float full_text_width = Graphics::VectorText::getTextWidth(message, scale, SPACING); + float text_height = Graphics::VectorText::getTextHeight(scale); // Calculate position as if FULL text was there (for fixed position typewriter) float x = play_area.x + ((play_area.w - full_text_width) / 2.0F); @@ -862,7 +825,7 @@ auto GameScene::getSpawnPoint(uint8_t player_id) const -> Vec2 { const SDL_FRect& zona = Defaults::Zones::PLAYAREA; float x_ratio; - if (match_config_.es_un_jugador()) { + if (match_config_.isSinglePlayer()) { // Un sol player: spawn al centro (50%) x_ratio = 0.5F; } else { @@ -901,7 +864,7 @@ void GameScene::fireBullet(uint8_t player_id) { // Buscar primera bullet inactiva en el pool del player int start_idx = player_id * 3; // P1=[0,1,2], P2=[3,4,5] for (int i = start_idx; i < start_idx + 3; i++) { - if (!bullets_[i].esta_activa()) { + if (!bullets_[i].isActive()) { bullets_[i].disparar(posicio_dispar, ship_angle, player_id); break; } diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 50a2af8..4223b1c 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -100,6 +100,18 @@ class GameScene final : public Scene { // [NEW] Stage system helpers void drawStageMessage(const std::string& message); + // Helpers de renderitzat (extracció de draw() per reduir complexitat). + // Cadascun gestiona un bloc concret; draw() només despatxa segons l'estat. + void drawEnemies() const; + void drawBullets() const; + void drawActiveShipsAlive() const; + void drawContinueState(); + void drawGameOverState(); + void drawInitHudState(); + void drawLevelStartState(); + void drawPlayingState(); + void drawLevelCompletedState(); + // [NEW] Función helper del marcador [[nodiscard]] auto buildScoreboard() const -> std::string; diff --git a/source/game/scenes/logo_scene.cpp b/source/game/scenes/logo_scene.cpp index 2a15db6..5166bc3 100644 --- a/source/game/scenes/logo_scene.cpp +++ b/source/game/scenes/logo_scene.cpp @@ -104,7 +104,7 @@ void LogoScene::initLetters() { float min_x = FLT_MAX; float max_x = -FLT_MAX; - for (const auto& prim : shape->get_primitives()) { + for (const auto& prim : shape->getPrimitives()) { for (const auto& point : prim.points) { min_x = std::min(min_x, point.x); max_x = std::max(max_x, point.x); @@ -321,7 +321,7 @@ void LogoScene::draw() { float current_scale = ESCALA_INICIAL + ((ESCALA_FINAL - ESCALA_INICIAL) * ease_factor); - Rendering::render_shape( + Rendering::renderShape( sdl_.getRenderer(), lletra.shape, pos_actual, @@ -344,7 +344,7 @@ void LogoScene::draw() { if (!explotades.contains(i)) { const auto& lletra = lletres_[i]; - Rendering::render_shape( + Rendering::renderShape( sdl_.getRenderer(), lletra.shape, lletra.position, diff --git a/source/game/scenes/logo_scene.hpp b/source/game/scenes/logo_scene.hpp index 2a43a82..418d1b2 100644 --- a/source/game/scenes/logo_scene.hpp +++ b/source/game/scenes/logo_scene.hpp @@ -13,7 +13,6 @@ #include "core/defaults.hpp" #include "core/graphics/shape.hpp" -#include "core/input/input_types.hpp" #include "core/rendering/sdl_manager.hpp" #include "core/system/scene.hpp" #include "core/system/scene_context.hpp" diff --git a/source/game/scenes/title_scene.cpp b/source/game/scenes/title_scene.cpp index c5d16f0..1c76878 100644 --- a/source/game/scenes/title_scene.cpp +++ b/source/game/scenes/title_scene.cpp @@ -11,6 +11,7 @@ #include #include "core/audio/audio.hpp" +#include "core/defaults.hpp" #include "core/graphics/shape_loader.hpp" #include "core/input/input.hpp" #include "core/rendering/shape_renderer.hpp" @@ -64,10 +65,10 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context) // Brightness depèn de l'opción if (estat_actual_ == TitleState::MAIN) { // Si saltem a MAIN, starfield instantàniament brillant - starfield_->set_brightness(BRIGHTNESS_STARFIELD); + starfield_->setBrightness(BRIGHTNESS_STARFIELD); } else { // Flux normal: comença con brightness 0.0 per fade-in - starfield_->set_brightness(0.0F); + starfield_->setBrightness(0.0F); } // Inicialitzar animador de naves 3D @@ -76,11 +77,11 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context) if (estat_actual_ == TitleState::MAIN) { // Jump to MAIN: empezar entrada inmediatamente - ship_animator_->set_visible(true); - ship_animator_->start_entry_animation(); + ship_animator_->setVisible(true); + ship_animator_->startEntryAnimation(); } else { // Flux normal: NO empezar entrada todavía (esperaran a MAIN) - ship_animator_->set_visible(false); + ship_animator_->setVisible(false); } // Inicialitzar lletres del título "ORNI ATTACK!" @@ -126,7 +127,7 @@ void TitleScene::initTitle() { float min_y = FLT_MAX; float max_y = -FLT_MAX; - for (const auto& prim : shape->get_primitives()) { + for (const auto& prim : shape->getPrimitives()) { for (const auto& point : prim.points) { min_x = std::min(min_x, point.x); max_x = std::max(max_x, point.x); @@ -200,7 +201,7 @@ void TitleScene::initTitle() { float min_y = FLT_MAX; float max_y = -FLT_MAX; - for (const auto& prim : shape->get_primitives()) { + for (const auto& prim : shape->getPrimitives()) { for (const auto& point : prim.points) { min_x = std::min(min_x, point.x); max_x = std::max(max_x, point.x); @@ -284,7 +285,7 @@ void TitleScene::inicialitzarJailgames() { float max_x = -FLT_MAX; float min_y = FLT_MAX; float max_y = -FLT_MAX; - for (const auto& prim : shape->get_primitives()) { + for (const auto& prim : shape->getPrimitives()) { for (const auto& point : prim.points) { min_x = std::min(min_x, point.x); max_x = std::max(max_x, point.x); @@ -326,7 +327,7 @@ void TitleScene::inicialitzarJailgames() { void TitleScene::dibuixarPeuTitol(float spacing) const { // Logo JAILGAMES pequeño sobre el copyright. for (const auto& lletra : lletres_jailgames_) { - Rendering::render_shape(sdl_.getRenderer(), lletra.shape, + Rendering::renderShape(sdl_.getRenderer(), lletra.shape, lletra.position, 0.0F, Defaults::Title::Layout::JAILGAMES_SCALE, 1.0F); @@ -365,180 +366,200 @@ void TitleScene::update(float delta_time) { } switch (estat_actual_) { - case TitleState::STARFIELD_FADE_IN: { - temps_acumulat_ += delta_time; - - // Calcular progrés del fade (0.0 → 1.0) - float progress = std::min(1.0F, temps_acumulat_ / DURACIO_FADE_IN); - - // Lerp brightness de 0.0 a BRIGHTNESS_STARFIELD - float brightness_actual = progress * BRIGHTNESS_STARFIELD; - starfield_->set_brightness(brightness_actual); - - // Transición a STARFIELD cuando el fade es completa - if (temps_acumulat_ >= DURACIO_FADE_IN) { - estat_actual_ = TitleState::STARFIELD; - temps_acumulat_ = 0.0F; // Reset timer per al següent state - starfield_->set_brightness(BRIGHTNESS_STARFIELD); // Assegurar value final - } + case TitleState::STARFIELD_FADE_IN: + updateStarfieldFadeInState(delta_time); break; - } - case TitleState::STARFIELD: - temps_acumulat_ += delta_time; - if (temps_acumulat_ >= DURACIO_INIT) { - estat_actual_ = TitleState::MAIN; - temps_estat_main_ = 0.0F; // Reset timer al entrar a MAIN - animacio_activa_ = false; // Comença estàtic - factor_lerp_ = 0.0F; // Sin animación aún - - // Naves esperaran ENTRANCE_DELAY antes de entrar (no start aquí) - } + updateStarfieldState(delta_time); break; - - case TitleState::MAIN: { - temps_estat_main_ += delta_time; - - // Iniciar animación de entrada de naves después del delay - if (temps_estat_main_ >= Defaults::Title::Ships::ENTRANCE_DELAY) { - if (ship_animator_ && !ship_animator_->is_visible()) { - ship_animator_->set_visible(true); - ship_animator_->start_entry_animation(); - } - } - - // Fase 1: Estàtic (0-10s) - if (temps_estat_main_ < DELAY_INICI_ANIMACIO) { - factor_lerp_ = 0.0F; - animacio_activa_ = false; - } - // Fase 2: Lerp (10-12s) - else if (temps_estat_main_ < DELAY_INICI_ANIMACIO + DURACIO_LERP) { - float temps_lerp = temps_estat_main_ - DELAY_INICI_ANIMACIO; - factor_lerp_ = temps_lerp / DURACIO_LERP; // 0.0 → 1.0 linealment - animacio_activa_ = true; - } - // Fase 3: Animación completa (12s+) - else { - factor_lerp_ = 1.0F; - animacio_activa_ = true; - } - - // Actualitzar animación del logo - updateLogoAnimation(delta_time); + case TitleState::MAIN: + updateMainState(delta_time); break; - } - case TitleState::PLAYER_JOIN_PHASE: - temps_acumulat_ += delta_time; - - // Continuar animación orbital durante la transición - updateLogoAnimation(delta_time); - - // [NOU] Continuar comprovant si l'altre player quiere unir-se durante la transición ("late join") - { - bool p1_actiu_abans = match_config_.jugador1_actiu; - bool p2_actiu_abans = match_config_.jugador2_actiu; - - if (checkStartGameButtonPressed()) { - // Updates match_config_ if pressed, logs are in the method - context_.setMatchConfig(match_config_); - - // Trigger animación de salida per la ship que acaba de unir-se - if (ship_animator_) { - if (match_config_.jugador1_actiu && !p1_actiu_abans) { - ship_animator_->trigger_exit_animation_for_player(1); - std::cout << "[TitleScene] P1 late join - ship exiting\n"; - } - if (match_config_.jugador2_actiu && !p2_actiu_abans) { - ship_animator_->trigger_exit_animation_for_player(2); - std::cout << "[TitleScene] P2 late join - ship exiting\n"; - } - } - - // Reproducir so de START cuando el segon player s'uneix - Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); - - // Reiniciar el timer per allargar el time de transición - temps_acumulat_ = 0.0F; - - std::cout << "[TitleScene] Segon player s'ha unit - so i timer reiniciats\n"; - } - } - - if (temps_acumulat_ >= DURACIO_TRANSITION) { - // Transición a pantalla negra - estat_actual_ = TitleState::BLACK_SCREEN; - temps_acumulat_ = 0.0F; - std::cout << "[TitleScene] Passant a BLACK_SCREEN\n"; - } + updatePlayerJoinPhaseState(delta_time); break; - case TitleState::BLACK_SCREEN: - temps_acumulat_ += delta_time; - - // No animation, no input checking - just wait - if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) { - // Transición a escena GAME (el Director detecta isFinished()). - context_.setNextScene(SceneType::GAME); - std::cout << "[TitleScene] Canviant a escena GAME\n"; - } + updateBlackScreenState(delta_time); break; } - // Verificar botones de skip (FIRE/THRUST/START) para saltar escenas ANTES de MAIN - if (estat_actual_ == TitleState::STARFIELD_FADE_IN || estat_actual_ == TitleState::STARFIELD) { - if (checkSkipButtonPressed()) { - // Saltar a MAIN - estat_actual_ = TitleState::MAIN; - starfield_->set_brightness(BRIGHTNESS_STARFIELD); - temps_estat_main_ = 0.0F; + handleSkipInput(); + handleStartInput(); +} - // Naves esperaran ENTRANCE_DELAY antes de entrar (no start aquí) - } +void TitleScene::updateStarfieldFadeInState(float delta_time) { + temps_acumulat_ += delta_time; + + // Calcular progrés del fade (0.0 → 1.0) + float progress = std::min(1.0F, temps_acumulat_ / DURACIO_FADE_IN); + + // Lerp brightness de 0.0 a BRIGHTNESS_STARFIELD + float brightness_actual = progress * BRIGHTNESS_STARFIELD; + starfield_->setBrightness(brightness_actual); + + // Transición a STARFIELD cuando el fade es completa + if (temps_acumulat_ >= DURACIO_FADE_IN) { + estat_actual_ = TitleState::STARFIELD; + temps_acumulat_ = 0.0F; // Reset timer per al següent state + starfield_->setBrightness(BRIGHTNESS_STARFIELD); // Assegurar value final + } +} + +void TitleScene::updateStarfieldState(float delta_time) { + temps_acumulat_ += delta_time; + if (temps_acumulat_ >= DURACIO_INIT) { + estat_actual_ = TitleState::MAIN; + temps_estat_main_ = 0.0F; // Reset timer al entrar a MAIN + animacio_activa_ = false; // Comença estàtic + factor_lerp_ = 0.0F; // Sin animación aún + + // Naves esperaran ENTRANCE_DELAY antes de entrar (no start aquí) + } +} + +void TitleScene::updateMainState(float delta_time) { + temps_estat_main_ += delta_time; + + // Iniciar animación de entrada de naves después del delay + if (temps_estat_main_ >= Defaults::Title::Ships::ENTRANCE_DELAY && + ship_animator_ && !ship_animator_->isVisible()) { + ship_animator_->setVisible(true); + ship_animator_->startEntryAnimation(); } + // Fase 1: Estàtic (0-10s) + if (temps_estat_main_ < DELAY_INICI_ANIMACIO) { + factor_lerp_ = 0.0F; + animacio_activa_ = false; + } + // Fase 2: Lerp (10-12s) + else if (temps_estat_main_ < DELAY_INICI_ANIMACIO + DURACIO_LERP) { + float temps_lerp = temps_estat_main_ - DELAY_INICI_ANIMACIO; + factor_lerp_ = temps_lerp / DURACIO_LERP; // 0.0 → 1.0 linealment + animacio_activa_ = true; + } + // Fase 3: Animación completa (12s+) + else { + factor_lerp_ = 1.0F; + animacio_activa_ = true; + } + + // Actualitzar animación del logo + updateLogoAnimation(delta_time); +} + +void TitleScene::updatePlayerJoinPhaseState(float delta_time) { + temps_acumulat_ += delta_time; + + // Continuar animación orbital durante la transición + updateLogoAnimation(delta_time); + + // [NOU] Continuar comprovant si l'altre player quiere unir-se durante la transición ("late join") + bool p1_actiu_abans = match_config_.jugador1_actiu; + bool p2_actiu_abans = match_config_.jugador2_actiu; + + if (checkStartGameButtonPressed()) { + // Updates match_config_ if pressed, logs are in the method + context_.setMatchConfig(match_config_); + + // Trigger animación de salida per la ship que acaba de unir-se + triggerExitForJoinedPlayers(p1_actiu_abans, p2_actiu_abans, "late join - "); + + // Reproducir so de START cuando el segon player s'uneix + Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); + + // Reiniciar el timer per allargar el time de transición + temps_acumulat_ = 0.0F; + + std::cout << "[TitleScene] Segon player s'ha unit - so i timer reiniciats\n"; + } + + if (temps_acumulat_ >= DURACIO_TRANSITION) { + // Transición a pantalla negra + estat_actual_ = TitleState::BLACK_SCREEN; + temps_acumulat_ = 0.0F; + std::cout << "[TitleScene] Passant a BLACK_SCREEN\n"; + } +} + +void TitleScene::updateBlackScreenState(float delta_time) { + temps_acumulat_ += delta_time; + + // No animation, no input checking - just wait + if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) { + // Transición a escena GAME (el Director detecta isFinished()). + context_.setNextScene(SceneType::GAME); + std::cout << "[TitleScene] Canviant a escena GAME\n"; + } +} + +void TitleScene::handleSkipInput() { + // Verificar botones de skip (FIRE/THRUST/START) para saltar escenas ANTES de MAIN + if (estat_actual_ != TitleState::STARFIELD_FADE_IN && estat_actual_ != TitleState::STARFIELD) { + return; + } + if (!checkSkipButtonPressed()) { + return; + } + // Saltar a MAIN + estat_actual_ = TitleState::MAIN; + starfield_->setBrightness(BRIGHTNESS_STARFIELD); + temps_estat_main_ = 0.0F; + // Naves esperaran ENTRANCE_DELAY antes de entrar (no start aquí) +} + +void TitleScene::handleStartInput() { // Verificar boton START para start match desde MAIN - if (estat_actual_ == TitleState::MAIN) { - // Guardar state anterior per detectar qui ha premut START AQUEST frame - bool p1_actiu_abans = match_config_.jugador1_actiu; - bool p2_actiu_abans = match_config_.jugador2_actiu; + if (estat_actual_ != TitleState::MAIN) { + return; + } - if (checkStartGameButtonPressed()) { - // Si START es prem durante el delay (naves aún invisibles), saltar-las a FLOATING - if (ship_animator_ && !ship_animator_->is_visible()) { - ship_animator_->set_visible(true); - ship_animator_->skip_to_floating_state(); - } + // Guardar state anterior per detectar qui ha premut START AQUEST frame + bool p1_actiu_abans = match_config_.jugador1_actiu; + bool p2_actiu_abans = match_config_.jugador2_actiu; - // Configurar match antes de canviar de escena - context_.setMatchConfig(match_config_); - std::cout << "[TitleScene] Configuración de match - P1: " - << (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU") - << ", P2: " - << (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU") - << '\n'; + if (!checkStartGameButtonPressed()) { + return; + } - // El setNextScene a GAME se hace al final de BLACK_SCREEN para no - // saltar la animación de salida (isFinished() lo recoge entonces). - estat_actual_ = TitleState::PLAYER_JOIN_PHASE; - temps_acumulat_ = 0.0F; + // Si START es prem durante el delay (naves aún invisibles), saltar-las a FLOATING + if (ship_animator_ && !ship_animator_->isVisible()) { + ship_animator_->setVisible(true); + ship_animator_->skipToFloatingState(); + } - // Trigger animación de salida NOMÉS per las naves que han premut START - if (ship_animator_) { - if (match_config_.jugador1_actiu && !p1_actiu_abans) { - ship_animator_->trigger_exit_animation_for_player(1); - std::cout << "[TitleScene] P1 ship exiting\n"; - } - if (match_config_.jugador2_actiu && !p2_actiu_abans) { - ship_animator_->trigger_exit_animation_for_player(2); - std::cout << "[TitleScene] P2 ship exiting\n"; - } - } + // Configurar match antes de canviar de escena + context_.setMatchConfig(match_config_); + std::cout << "[TitleScene] Configuración de match - P1: " + << (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU") + << ", P2: " + << (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU") + << '\n'; - Audio::get()->fadeOutMusic(MUSIC_FADE); - Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); - } + // El setNextScene a GAME se hace al final de BLACK_SCREEN para no + // saltar la animación de salida (isFinished() lo recoge entonces). + estat_actual_ = TitleState::PLAYER_JOIN_PHASE; + temps_acumulat_ = 0.0F; + + // Trigger animación de salida NOMÉS per las naves que han premut START + triggerExitForJoinedPlayers(p1_actiu_abans, p2_actiu_abans, ""); + + Audio::get()->fadeOutMusic(MUSIC_FADE); + Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); +} + +void TitleScene::triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, + const char* log_prefix) { + if (ship_animator_ == nullptr) { + return; + } + if (match_config_.jugador1_actiu && !p1_was_active) { + ship_animator_->triggerExitAnimationForPlayer(1); + std::cout << "[TitleScene] P1 " << log_prefix << "ship exiting\n"; + } + if (match_config_.jugador2_actiu && !p2_was_active) { + ship_animator_->triggerExitAnimationForPlayer(2); + std::cout << "[TitleScene] P2 " << log_prefix << "ship exiting\n"; } } @@ -618,7 +639,7 @@ void TitleScene::draw() { pos_shadow.x = posicions_originals_orni_[i].x + static_cast(std::round(shadow_offset_x)); pos_shadow.y = posicions_originals_orni_[i].y + static_cast(std::round(shadow_offset_y)); - Rendering::render_shape( + Rendering::renderShape( sdl_.getRenderer(), lletres_orni_[i].shape, pos_shadow, @@ -635,7 +656,7 @@ void TitleScene::draw() { pos_shadow.x = posicions_originals_attack_[i].x + static_cast(std::round(shadow_offset_x)); pos_shadow.y = posicions_originals_attack_[i].y + static_cast(std::round(shadow_offset_y)); - Rendering::render_shape( + Rendering::renderShape( sdl_.getRenderer(), lletres_attack_[i].shape, pos_shadow, @@ -650,7 +671,7 @@ void TitleScene::draw() { // Dibuixar "ORNI" (línia 1) for (const auto& lletra : lletres_orni_) { - Rendering::render_shape( + Rendering::renderShape( sdl_.getRenderer(), lletra.shape, lletra.position, @@ -662,7 +683,7 @@ void TitleScene::draw() { // Dibuixar "ATTACK!" (línia 2) for (const auto& lletra : lletres_attack_) { - Rendering::render_shape( + Rendering::renderShape( sdl_.getRenderer(), lletra.shape, lletra.position, diff --git a/source/game/scenes/title_scene.hpp b/source/game/scenes/title_scene.hpp index 99672b5..cc2efe7 100644 --- a/source/game/scenes/title_scene.hpp +++ b/source/game/scenes/title_scene.hpp @@ -11,7 +11,6 @@ #include #include -#include "core/defaults.hpp" #include "core/graphics/shape.hpp" #include "core/graphics/starfield.hpp" #include "core/graphics/vector_text.hpp" @@ -119,4 +118,18 @@ class TitleScene final : public Scene { void initTitle(); // Carrega i posiciona las lletres del título void inicialitzarJailgames(); // Carrega i posiciona el logo JAILGAMES pequeño void dibuixarPeuTitol(float spacing) const; // Logo JAILGAMES + línia de copyright + + // Sub-pasos de update() (extreure cada state per reduir complexitat). + void updateStarfieldFadeInState(float delta_time); + void updateStarfieldState(float delta_time); + void updateMainState(float delta_time); + void updatePlayerJoinPhaseState(float delta_time); + void updateBlackScreenState(float delta_time); + // Handlers de input globals (independents de l'state actual). + void handleSkipInput(); + void handleStartInput(); + // Helper compartit: dispara l'animación de salida per las naves del player que + // acaba de fer un join "en aquest frame" (jugadorX_actiu == true && !prev). + void triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, + const char* log_prefix); }; diff --git a/source/game/stage_system/spawn_controller.cpp b/source/game/stage_system/spawn_controller.cpp index 4198a43..1aa67b6 100644 --- a/source/game/stage_system/spawn_controller.cpp +++ b/source/game/stage_system/spawn_controller.cpp @@ -3,6 +3,7 @@ #include "spawn_controller.hpp" +#include #include #include #include @@ -81,25 +82,18 @@ void SpawnController::update(float delta_time, std::array& orni_array } } -auto SpawnController::tots_enemics_spawnejats() const -> bool { +auto SpawnController::allEnemiesSpawned() const -> bool { return index_spawn_actual_ >= spawn_queue_.size(); } -auto SpawnController::tots_enemics_destruits(const std::array& orni_array) const -> bool { - if (!tots_enemics_spawnejats()) { +auto SpawnController::allEnemiesDestroyed(const std::array& orni_array) const -> bool { + if (!allEnemiesSpawned()) { return false; } - - for (const auto& enemy : orni_array) { - if (enemy.isActive()) { - return false; - } - } - - return true; + return std::ranges::all_of(orni_array, [](const Enemy& enemy) { return !enemy.isActive(); }); } -auto SpawnController::get_enemics_vius(const std::array& orni_array) -> uint8_t { +auto SpawnController::getAliveEnemyCount(const std::array& orni_array) -> uint8_t { uint8_t count = 0; for (const auto& enemy : orni_array) { if (enemy.isActive()) { @@ -109,7 +103,7 @@ auto SpawnController::get_enemics_vius(const std::array& orni_array) return count; } -auto SpawnController::get_enemics_spawnejats() const -> uint8_t { +auto SpawnController::countSpawnedEnemies() const -> uint8_t { return static_cast(index_spawn_actual_); } diff --git a/source/game/stage_system/spawn_controller.hpp b/source/game/stage_system/spawn_controller.hpp index 50c06e8..a0657eb 100644 --- a/source/game/stage_system/spawn_controller.hpp +++ b/source/game/stage_system/spawn_controller.hpp @@ -33,11 +33,11 @@ class SpawnController { void update(float delta_time, std::array& orni_array, bool pausar = false); // Status queries - [[nodiscard]] auto tots_enemics_spawnejats() const -> bool; - [[nodiscard]] auto tots_enemics_destruits(const std::array& orni_array) const -> bool; + [[nodiscard]] auto allEnemiesSpawned() const -> bool; + [[nodiscard]] auto allEnemiesDestroyed(const std::array& orni_array) const -> bool; // Estático: solo recorre el array pasado; no consulta estado del controller. - [[nodiscard]] static auto get_enemics_vius(const std::array& orni_array) -> uint8_t; - [[nodiscard]] auto get_enemics_spawnejats() const -> uint8_t; + [[nodiscard]] static auto getAliveEnemyCount(const std::array& orni_array) -> uint8_t; + [[nodiscard]] auto countSpawnedEnemies() const -> uint8_t; // [NEW] Set ship position reference for safe spawn void setShipPosition(const Vec2* ship_pos) { ship_position_ = ship_pos; } diff --git a/source/game/stage_system/stage_config.hpp b/source/game/stage_system/stage_config.hpp index a170f61..2227bf9 100644 --- a/source/game/stage_system/stage_config.hpp +++ b/source/game/stage_system/stage_config.hpp @@ -55,7 +55,7 @@ struct StageConfig { MultiplicadorsDificultat multiplicadors; // Validació - [[nodiscard]] auto es_valid() const -> bool { + [[nodiscard]] auto isValid() const -> bool { // stage_id es uint8_t: el rango superior (<=255) está garantizado por // el tipo; basta con confirmar que no es 0 (sentinela "sin asignar"). return stage_id >= 1 && @@ -70,7 +70,7 @@ struct StageSystemConfig { std::vector stages; // Índex [0] = stage 1 // Obtenir configuración de un stage específic - [[nodiscard]] auto obte_stage(uint8_t stage_id) const -> const StageConfig* { + [[nodiscard]] auto findStage(uint8_t stage_id) const -> const StageConfig* { if (stage_id < 1 || stage_id > stages.size()) { return nullptr; } diff --git a/source/game/stage_system/stage_loader.cpp b/source/game/stage_system/stage_loader.cpp index 2817683..4bef59e 100644 --- a/source/game/stage_system/stage_loader.cpp +++ b/source/game/stage_system/stage_loader.cpp @@ -127,7 +127,7 @@ auto StageLoader::parseStage(const fkyaml::node& yaml, StageConfig& stage) -> bo return false; } - if (!stage.es_valid()) { + if (!stage.isValid()) { std::cerr << "[StageLoader] Error: stage " << static_cast(stage.stage_id) << " no es vàlid" << '\n'; return false; diff --git a/source/game/stage_system/stage_manager.cpp b/source/game/stage_system/stage_manager.cpp index fbd1774..aec2b51 100644 --- a/source/game/stage_system/stage_manager.cpp +++ b/source/game/stage_system/stage_manager.cpp @@ -51,20 +51,20 @@ void StageManager::update(float delta_time, bool pause_spawn) { } } -void StageManager::stage_completat() { +void StageManager::markStageCompleted() { std::cout << "[StageManager] Stage " << static_cast(stage_actual_) << " completat!" << '\n'; changeState(EstatStage::LEVEL_COMPLETED); } -auto StageManager::tot_completat() const -> bool { +auto StageManager::isGameComplete() const -> bool { return stage_actual_ >= config_->metadata.total_stages && estat_ == EstatStage::LEVEL_COMPLETED && timer_transicio_ <= 0.0F; } -auto StageManager::get_config_actual() const -> const StageConfig* { - return config_->obte_stage(stage_actual_); +auto StageManager::getCurrentConfig() const -> const StageConfig* { + return config_->findStage(stage_actual_); } void StageManager::changeState(EstatStage nou_estat) { @@ -155,7 +155,7 @@ void StageManager::processLevelCompleted(float delta_time) { } void StageManager::loadStage(uint8_t stage_id) { - const StageConfig* stage_config = config_->obte_stage(stage_id); + const StageConfig* stage_config = config_->findStage(stage_id); if (stage_config == nullptr) { std::cerr << "[StageManager] Error: no es pot trobar stage " << static_cast(stage_id) << '\n'; diff --git a/source/game/stage_system/stage_manager.hpp b/source/game/stage_system/stage_manager.hpp index 6bb4d05..dd19d94 100644 --- a/source/game/stage_system/stage_manager.hpp +++ b/source/game/stage_system/stage_manager.hpp @@ -28,15 +28,15 @@ class StageManager { void update(float delta_time, bool pause_spawn = false); // Stage progression - void stage_completat(); // Call when all enemies destroyed - [[nodiscard]] auto tot_completat() const -> bool; // All 10 stages done? + void markStageCompleted(); // Call when all enemies destroyed + [[nodiscard]] auto isGameComplete() const -> bool; // All 10 stages done? // Current state queries - [[nodiscard]] auto get_estat() const -> EstatStage { return estat_; } - [[nodiscard]] auto get_stage_actual() const -> uint8_t { return stage_actual_; } - [[nodiscard]] auto get_config_actual() const -> const StageConfig*; - [[nodiscard]] auto get_timer_transicio() const -> float { return timer_transicio_; } - [[nodiscard]] auto get_missatge_level_start() const -> const std::string& { return missatge_level_start_actual_; } + [[nodiscard]] auto getState() const -> EstatStage { return estat_; } + [[nodiscard]] auto getCurrentStage() const -> uint8_t { return stage_actual_; } + [[nodiscard]] auto getCurrentConfig() const -> const StageConfig*; + [[nodiscard]] auto getTransitionTimer() const -> float { return timer_transicio_; } + [[nodiscard]] auto getLevelStartMessage() const -> const std::string& { return missatge_level_start_actual_; } // Spawn control (delegate to SpawnController) auto getSpawnController() -> SpawnController& { return spawn_controller_; } diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index 97ea628..4ee9587 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -16,7 +16,7 @@ void detectBulletEnemy(Context& ctx) { for (auto& bullet : ctx.bullets) { for (auto& enemy : ctx.enemies) { - if (!Physics::check_collision(bullet, enemy, AMPLIFIER)) { + if (!Physics::checkCollision(bullet, enemy, AMPLIFIER)) { continue; } @@ -86,7 +86,7 @@ void detectShipEnemy(Context& ctx) { if (enemy.isInvulnerable()) { continue; } - if (Physics::check_collision(ctx.ships[i], enemy, AMPLIFIER)) { + if (Physics::checkCollision(ctx.ships[i], enemy, AMPLIFIER)) { ctx.on_player_hit(i); break; // Solo una colisión por player por frame } @@ -102,7 +102,7 @@ void detectBulletPlayer(Context& ctx) { constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER; for (auto& bullet : ctx.bullets) { - if (!bullet.esta_activa() || bullet.getGraceTimer() > 0.0F) { + if (!bullet.isActive() || bullet.getGraceTimer() > 0.0F) { continue; } const uint8_t BULLET_OWNER = bullet.getOwnerId(); @@ -120,7 +120,7 @@ void detectBulletPlayer(Context& ctx) { continue; } - if (!Physics::check_collision(bullet, ctx.ships[player_id], AMPLIFIER)) { + if (!Physics::checkCollision(bullet, ctx.ships[player_id], AMPLIFIER)) { continue; } diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 9943d44..a5e9c04 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include "core/defaults.hpp" @@ -30,7 +29,7 @@ auto computeRangeProgress(float global_progress, } auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 { - const float EASED = Easing::ease_out_quad(progress); + const float EASED = Easing::easeOutQuad(progress); const SDL_FRect& zone = Defaults::Zones::PLAYAREA; // Y inicial: 50 px bajo la zona de juego. const float Y_INI = zone.y + zone.h + 50.0F; @@ -40,7 +39,7 @@ auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 { void drawBordersAnimated(Rendering::Renderer* renderer, float progress) { const SDL_FRect& zone = Defaults::Zones::PLAYAREA; - const float EASED = Easing::ease_out_quad(progress); + const float EASED = Easing::easeOutQuad(progress); const int X1 = static_cast(zone.x); const int Y1 = static_cast(zone.y); @@ -81,7 +80,7 @@ void drawBordersAnimated(Rendering::Renderer* renderer, float progress) { void drawScoreboardAnimated(const Graphics::VectorText& text, const std::string& scoreboard_text, float progress) { - const float EASED = Easing::ease_out_quad(progress); + const float EASED = Easing::easeOutQuad(progress); constexpr float SCALE = 0.85F; constexpr float SPACING = 0.0F; diff --git a/source/game/title/ship_animator.cpp b/source/game/title/ship_animator.cpp index 3a99c33..7e26f70 100644 --- a/source/game/title/ship_animator.cpp +++ b/source/game/title/ship_animator.cpp @@ -61,7 +61,7 @@ void ShipAnimator::draw() const { } // Renderizar ship (perspectiva ya incorporada a la shape) - Rendering::render_shape( + Rendering::renderShape( renderer_, ship.shape, ship.current_position, @@ -73,7 +73,7 @@ void ShipAnimator::draw() const { } } -void ShipAnimator::start_entry_animation() { +void ShipAnimator::startEntryAnimation() { using namespace Defaults::Title::Ships; // Configurar ship P1 para l'animación de entrada @@ -91,7 +91,7 @@ void ShipAnimator::start_entry_animation() { ships_[1].current_scale = ships_[1].initial_scale; } -void ShipAnimator::trigger_exit_animation() { +void ShipAnimator::triggerExitAnimation() { // Configurar ambdues naves para l'animación de salida for (auto& ship : ships_) { // Canviar state a EXITING @@ -106,7 +106,7 @@ void ShipAnimator::trigger_exit_animation() { } } -void ShipAnimator::skip_to_floating_state() { +void ShipAnimator::skipToFloatingState() { // Posar ambdues naves directament en state FLOATING for (auto& ship : ships_) { ship.state = ShipState::FLOATING; @@ -122,17 +122,12 @@ void ShipAnimator::skip_to_floating_state() { } } -auto ShipAnimator::is_visible() const -> bool { +auto ShipAnimator::isVisible() const -> bool { // Retorna true si almenys una ship es visible - for (const auto& ship : ships_) { - if (ship.visible) { - return true; - } - } - return false; + return std::ranges::any_of(ships_, [](const TitleShip& ship) { return ship.visible; }); } -void ShipAnimator::trigger_exit_animation_for_player(int player_id) { +void ShipAnimator::triggerExitAnimationForPlayer(int player_id) { // Trobar la ship del player especificat for (auto& ship : ships_) { if (ship.player_id == player_id) { @@ -150,20 +145,15 @@ void ShipAnimator::trigger_exit_animation_for_player(int player_id) { } } -void ShipAnimator::set_visible(bool visible) { +void ShipAnimator::setVisible(bool visible) { for (auto& ship : ships_) { ship.visible = visible; } } -auto ShipAnimator::is_animation_complete() const -> bool { +auto ShipAnimator::isAnimationComplete() const -> bool { // Comprovar si todas las naves són invisibles (han completat l'animación de salida) - for (const auto& ship : ships_) { - if (ship.visible) { - return false; // Aún hay alguna ship visible - } - } - return true; // Todas las naves són invisibles + return std::ranges::all_of(ships_, [](const TitleShip& ship) { return !ship.visible; }); } // Métodos de animación (stubs) @@ -185,7 +175,7 @@ void ShipAnimator::updateEntering(TitleShip& ship, float delta_time) { float progress = std::min(1.0F, elapsed / ENTRY_DURATION); // Aplicar easing (ease_out_quad per arribada suau) - float eased_progress = Easing::ease_out_quad(progress); + float eased_progress = Easing::easeOutQuad(progress); // Lerp posición (inicial → objetivo) ship.current_position.x = Easing::lerp(ship.initial_position.x, ship.target_position.x, eased_progress); @@ -230,7 +220,7 @@ void ShipAnimator::updateExiting(TitleShip& ship, float delta_time) { float progress = std::min(1.0F, ship.state_time / EXIT_DURATION); // Aplicar easing (ease_in_quad per aceleración hacia el point de fuga) - float eased_progress = Easing::ease_in_quad(progress); + float eased_progress = Easing::easeInQuad(progress); // Vec2 de fuga (centro del starfield) constexpr Vec2 VANISHING_POINT{.x = VANISHING_POINT_X, .y = VANISHING_POINT_Y}; @@ -258,7 +248,7 @@ void ShipAnimator::configureShipP1(TitleShip& ship) { ship.state_time = 0.0F; // Posicions (clock 8, bottom-left) - ship.target_position = {.x = P1_TARGET_X(), .y = P1_TARGET_Y()}; + ship.target_position = {.x = p1TargetX(), .y = p1TargetY()}; // Calcular posición inicial (fuera de pantalla) ship.initial_position = computeOffscreenPosition(CLOCK_8_ANGLE); @@ -293,7 +283,7 @@ void ShipAnimator::configureShipP2(TitleShip& ship) { ship.state_time = 0.0F; // Posicions (clock 4, bottom-right) - ship.target_position = {.x = P2_TARGET_X(), .y = P2_TARGET_Y()}; + ship.target_position = {.x = p2TargetX(), .y = p2TargetY()}; // Calcular posición inicial (fuera de pantalla) ship.initial_position = computeOffscreenPosition(CLOCK_4_ANGLE); diff --git a/source/game/title/ship_animator.hpp b/source/game/title/ship_animator.hpp index 7c6e684..fa1af4f 100644 --- a/source/game/title/ship_animator.hpp +++ b/source/game/title/ship_animator.hpp @@ -75,15 +75,15 @@ class ShipAnimator { void draw() const; // Control de state (cridat per TitleScene) - void start_entry_animation(); - void trigger_exit_animation(); // Anima todas las naves - void trigger_exit_animation_for_player(int player_id); // Anima solo una ship (P1=1, P2=2) - void skip_to_floating_state(); // Salta directament a FLOATING sin animación + void startEntryAnimation(); + void triggerExitAnimation(); // Anima todas las naves + void triggerExitAnimationForPlayer(int player_id); // Anima solo una ship (P1=1, P2=2) + void skipToFloatingState(); // Salta directament a FLOATING sin animación // Control de visibilitat - void set_visible(bool visible); - [[nodiscard]] auto is_animation_complete() const -> bool; - [[nodiscard]] auto is_visible() const -> bool; // Comprova si alguna ship es visible + void setVisible(bool visible); + [[nodiscard]] auto isAnimationComplete() const -> bool; + [[nodiscard]] auto isVisible() const -> bool; // Comprova si alguna ship es visible private: Rendering::Renderer* renderer_; diff --git a/source/main.cpp b/source/main.cpp index 5837ec7..831bc43 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -14,5 +14,5 @@ auto main(int argc, char* argv[]) -> int { Director director(args); // Executar bucle principal del juego - return director.run(); + return Director::run(); }