Files
orni-attack/source/game/entities/ship.cpp
T
JailDesigner bbbb8d47ae Lint: rename públicos al inglés + refactor cognitive-complexity + unused-includes
Identifier-naming: rename de métodos públicos y cross-file al inglés
(camelBack), traducción de campos y locales en el proceso (TitleShip,
StageManager, SpawnController, ShipAnimator, helpers de PlayArea, etc.).

Refactor por cognitive-complexity (>25): GameScene::draw (59→3) con 9
helpers de estado, PhysicsWorld::resolveBodyCollisions (35→5) extrayendo
resolveBodyPair, Options::load{Window,Physics,Audio}ConfigFromYaml
(32/49/57→5/2/3) con templates readField, TitleScene::update (68→4) con
5 sub-pasos por estado + handleSkipInput/handleStartInput +
triggerExitForJoinedPlayers, DebrisManager::explode (39→3) con
extractSegments/spawnDebris/applyAngularVelocity/applyVisualRotation.

use-anyofallof: bucles → std::ranges::any_of/all_of en Input,
ShipAnimator y SpawnController.

readability-static-accessed-through-instance: Director::run y
VectorText::getTextWidth/Height invocados por clase.

readability-convert-member-functions-to-static: ResourcePack::decryptData.

unused-includes: eliminación de <utility>, <cstdint>, <vector>,
<iostream>, defaults.hpp y otros no usados directamente en headers y
unidades de traducción. Restablecido core/defaults.hpp en title_scene.cpp
(falsa "unused" del header).

Bug fix: eliminado isActive() duplicado en Bullet (redeclaración tras
rename de esta_activa→isActive que chocaba con el override de Entity).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:41:33 +02:00

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::getPlayAreaCenter(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::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_,
/*rotation_3d=*/nullptr, Defaults::Palette::SHIP);
}