c45e524109
Pase automático de clang-tidy --fix sobre el conjunto de checks que son puro transform de sintaxis y no rompen API. Invocado con --format-style=none para que clang-tidy NO arrastre clang-format sobre las líneas tocadas (evita la regla NamespaceIndentation: All del .clang-format reformateando solo trozos del archivo). Checks aplicados: - modernize-use-trailing-return-type (193 hits): 'int foo()' → 'auto foo() -> int'. Estilo coherente con la convención del proyecto. - modernize-use-default-member-init (36 hits): inicialización de miembros pasa de la lista del constructor a la declaración. Reduce duplicación cuando hay varios constructores con los mismos defaults. - modernize-use-auto (6 hits): tipos largos sustituidos por auto donde el tipo es evidente del contexto (new T, dynamic_cast, etc). - modernize-use-starts-ends-with (2 hits): s.rfind(x) == 0 → s.starts_with(x), aprovechando C++20. - performance-enum-size (10 hits): enums pequeños declaran tipo subyacente (uint8_t / similar) para reducir tamaño y precisar layout. NO aplicado en este pase (riesgo de cambios semánticos o de API): - readability-identifier-naming (renames pueden romper callsites parciales) - readability-convert-member-functions-to-static (cambia firma) - readability-use-anyofallof (reescribe loops, side effects) - readability-function-cognitive-complexity (requiere refactor manual) - bugs reales (bugprone-*, clang-diagnostic-*) → uno a uno Cambios manuales asociados: - SDLManager::clear() ahora devuelve bool: propaga el resultado de beginFrame al caller para que Director::runFrameLoop salte draw+present cuando la swapchain no esté disponible (ventana minimizada). Antes la función ignoraba el [[nodiscard]] del beginFrame y los vértices se acumulaban en el batch sin nadie que los consumiera. - vector_text.cpp: borrada la línea suelta "// Test pre-commit hook" que quedó como cruft. clang-tidy crashea en LLVM 19.1 con performance-noexcept-move-constructor (recursión infinita en ExceptionSpecAnalyzer al procesar std::set); check deshabilitado en .clang-tidy con comentario explicativo. Build limpio, smoke test OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
164 lines
5.8 KiB
C++
164 lines
5.8 KiB
C++
// ship.cpp - Implementación de la nave del player
|
|
// © 2026 JailDesigner
|
|
|
|
#include "game/entities/ship.hpp"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
|
|
#include "core/defaults.hpp"
|
|
#include "core/entities/entity.hpp"
|
|
#include "core/graphics/shape_loader.hpp"
|
|
#include "core/input/input.hpp"
|
|
#include "core/input/input_types.hpp"
|
|
#include "core/rendering/shape_renderer.hpp"
|
|
#include "core/types.hpp"
|
|
#include "game/constants.hpp"
|
|
|
|
Ship::Ship(Rendering::Renderer* renderer, const char* shape_file)
|
|
: Entity(renderer)
|
|
{
|
|
// Brightness específico para naves
|
|
brightness_ = Defaults::Brightness::NAU;
|
|
|
|
// Configuración del cuerpo físico
|
|
body_.setMass(10.0F); // Masa de referencia para choques
|
|
body_.radius = Defaults::Entities::SHIP_RADIUS; // Radio de colisión
|
|
body_.restitution = 0.6F; // Rebote moderado contra paredes
|
|
body_.linear_damping = 1.5F; // Fricción exponencial (s⁻¹)
|
|
body_.angular_damping = 0.0F; // La rotación es 100% por input, no inercial
|
|
|
|
// Cargar shape compartida desde archivo
|
|
shape_ = Graphics::ShapeLoader::load(shape_file);
|
|
if (!shape_ || !shape_->isValid()) {
|
|
std::cerr << "[Ship] Error: no se ha podido cargar " << shape_file << '\n';
|
|
}
|
|
}
|
|
|
|
void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
|
|
// Posición inicial
|
|
if (spawn_point != nullptr) {
|
|
center_ = *spawn_point;
|
|
} else {
|
|
float centre_x;
|
|
float centre_y;
|
|
Constants::obtenir_centre_zona(centre_x, centre_y);
|
|
center_ = {.x = centre_x, .y = centre_y};
|
|
}
|
|
|
|
// Reset orientación
|
|
angle_ = 0.0F;
|
|
|
|
// Sincronizar cuerpo físico con la posición/orientación inicial
|
|
body_.position = center_;
|
|
body_.angle = angle_;
|
|
body_.velocity = Vec2{};
|
|
body_.angular_velocity = 0.0F;
|
|
body_.clearAccumulators();
|
|
|
|
// Activar invulnerabilidad solo si es respawn
|
|
invulnerable_timer_ = activar_invulnerabilitat ? Defaults::Ship::INVULNERABILITY_DURATION : 0.0F;
|
|
is_hit_ = false;
|
|
}
|
|
|
|
void Ship::processInput(float delta_time, uint8_t player_id) {
|
|
// Solo procesa input si la nave está viva
|
|
if (is_hit_) {
|
|
return;
|
|
}
|
|
|
|
auto* input = Input::get();
|
|
|
|
// Rotación: control directo del ángulo (no física, no inercial).
|
|
// Se actualiza también body_.angle para que el dibujado tras
|
|
// postUpdate refleje el cambio inmediatamente.
|
|
const bool ROTATE_RIGHT = (player_id == 0)
|
|
? input->checkActionPlayer1(InputAction::RIGHT, Input::ALLOW_REPEAT)
|
|
: input->checkActionPlayer2(InputAction::RIGHT, Input::ALLOW_REPEAT);
|
|
const bool ROTATE_LEFT = (player_id == 0)
|
|
? input->checkActionPlayer1(InputAction::LEFT, Input::ALLOW_REPEAT)
|
|
: input->checkActionPlayer2(InputAction::LEFT, Input::ALLOW_REPEAT);
|
|
const bool THRUST = (player_id == 0)
|
|
? input->checkActionPlayer1(InputAction::THRUST, Input::ALLOW_REPEAT)
|
|
: input->checkActionPlayer2(InputAction::THRUST, Input::ALLOW_REPEAT);
|
|
|
|
if (ROTATE_RIGHT) {
|
|
body_.angle += Defaults::Physics::ROTATION_SPEED * delta_time;
|
|
}
|
|
if (ROTATE_LEFT) {
|
|
body_.angle -= Defaults::Physics::ROTATION_SPEED * delta_time;
|
|
}
|
|
|
|
// Thrust: fuerza vectorial en la dirección de la nariz.
|
|
// angle - PI/2 porque angle=0 apunta hacia arriba (eje Y negativo SDL).
|
|
if (THRUST) {
|
|
const float DIR_X = std::cos(body_.angle - (Constants::PI / 2.0F));
|
|
const float DIR_Y = std::sin(body_.angle - (Constants::PI / 2.0F));
|
|
// Fuerza = masa * aceleración: 10 kg * 400 px/s² = 4000 (unidades arcade)
|
|
const float MAGNITUDE = body_.mass * Defaults::Physics::ACCELERATION;
|
|
body_.applyForce(Vec2{.x = DIR_X * MAGNITUDE, .y = DIR_Y * MAGNITUDE});
|
|
}
|
|
}
|
|
|
|
void Ship::update(float delta_time) {
|
|
// Solo update si la nave está viva
|
|
if (is_hit_) {
|
|
return;
|
|
}
|
|
|
|
// Decrementar timer de invulnerabilidad
|
|
if (invulnerable_timer_ > 0.0F) {
|
|
invulnerable_timer_ -= delta_time;
|
|
invulnerable_timer_ = std::max(invulnerable_timer_, 0.0F);
|
|
}
|
|
|
|
// El movimiento real lo hace PhysicsWorld::update().
|
|
// Aquí solo lógica de estado.
|
|
|
|
// Cap de velocidad: el thrust acumula fuerza sin límite; limitamos
|
|
// la magnitud de body_.velocity tras aplicar fuerzas para preservar
|
|
// el feel arcade del MAX_VELOCITY original.
|
|
const float CURRENT_SPEED = body_.velocity.length();
|
|
if (CURRENT_SPEED > Defaults::Physics::MAX_VELOCITY) {
|
|
body_.velocity = body_.velocity * (Defaults::Physics::MAX_VELOCITY / CURRENT_SPEED);
|
|
}
|
|
}
|
|
|
|
void Ship::postUpdate(float /*delta_time*/) {
|
|
// Sincronizar mirror desde body_ tras la integración del world.
|
|
center_ = body_.position;
|
|
angle_ = body_.angle;
|
|
}
|
|
|
|
void Ship::draw() const {
|
|
if (is_hit_) {
|
|
return;
|
|
}
|
|
|
|
// Parpadeo si invulnerable
|
|
if (isInvulnerable()) {
|
|
const float BLINK_CYCLE = Defaults::Ship::BLINK_VISIBLE_TIME + Defaults::Ship::BLINK_INVISIBLE_TIME;
|
|
const float TIME_IN_CYCLE = std::fmod(invulnerable_timer_, BLINK_CYCLE);
|
|
if (TIME_IN_CYCLE < Defaults::Ship::BLINK_INVISIBLE_TIME) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!shape_) {
|
|
return;
|
|
}
|
|
|
|
// Efecto visual de empuje: escala proporcional a la velocidad.
|
|
// 0..200 px/s → escala 1.0..1.5 (manteniendo la sensación del Pascal original).
|
|
const float SPEED = getSpeed();
|
|
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_,
|
|
/*rotation_3d=*/nullptr, Defaults::Palette::SHIP);
|
|
}
|