Files
orni-attack/source/game/entities/ship.cpp
T

169 lines
5.5 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 <utility>
#include "core/audio/audio.hpp"
#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, PlayerConfig config, const char* shape_override)
: Entity(renderer),
config_(std::move(config)) {
brightness_ = Defaults::Brightness::NAU;
body_.setMass(config_.physics.mass);
body_.radius = config_.physics.collision_radius;
body_.restitution = config_.physics.restitution;
body_.linear_damping = config_.physics.linear_damping;
body_.angular_damping = config_.physics.angular_damping;
// El shape pot venir del YAML o ser overridden (ex: P2 amb "ship2.shp").
const std::string SHAPE_PATH = (shape_override != nullptr) ? shape_override : config_.shape.path;
shape_ = Graphics::ShapeLoader::load(SHAPE_PATH);
if (!shape_ || !shape_->isValid()) {
std::cerr << "[Ship] Error: no se ha podido cargar " << SHAPE_PATH << '\n';
}
}
void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
if (spawn_point != nullptr) {
center_ = *spawn_point;
} else {
float center_x;
float center_y;
Constants::getPlayAreaCenter(center_x, center_y);
center_ = {.x = center_x, .y = center_y};
}
angle_ = 0.0F;
body_.position = center_;
body_.angle = angle_;
body_.velocity = Vec2{};
body_.angular_velocity = 0.0F;
body_.clearAccumulators();
invulnerable_timer_ = activar_invulnerabilitat ? config_.invulnerability.duration : 0.0F;
is_hit_ = false;
hurt_timer_ = 0.0F;
touching_enemy_prev_frame_ = false;
}
void Ship::processInput(float delta_time, uint8_t player_id) {
if (is_hit_) {
return;
}
auto* input = Input::get();
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 += config_.physics.rotation_speed * delta_time;
}
if (ROTATE_LEFT) {
body_.angle -= config_.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));
const float MAGNITUDE = body_.mass * config_.physics.acceleration;
body_.applyForce(Vec2{.x = DIR_X * MAGNITUDE, .y = DIR_Y * MAGNITUDE});
}
}
void Ship::update(float delta_time) {
if (is_hit_) {
return;
}
if (invulnerable_timer_ > 0.0F) {
invulnerable_timer_ -= delta_time;
invulnerable_timer_ = std::max(invulnerable_timer_, 0.0F);
}
if (hurt_timer_ > 0.0F) {
hurt_timer_ -= delta_time;
hurt_timer_ = std::max(hurt_timer_, 0.0F);
}
// 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 > config_.physics.max_velocity) {
body_.velocity = body_.velocity * (config_.physics.max_velocity / CURRENT_SPEED);
}
}
void Ship::postUpdate(float /*delta_time*/) {
center_ = body_.position;
angle_ = body_.angle;
}
void Ship::draw() const {
if (is_hit_) {
return;
}
if (isInvulnerable()) {
const float BLINK_CYCLE = config_.invulnerability.blink_visible + config_.invulnerability.blink_invisible;
const float TIME_IN_CYCLE = std::fmod(invulnerable_timer_, BLINK_CYCLE);
if (TIME_IN_CYCLE < config_.invulnerability.blink_invisible) {
return;
}
}
if (!shape_) {
return;
}
// Efecto visual de empuje: escala proporcional a la velocidad.
const float SPEED = getSpeed();
const float VISUAL_PUSH = SPEED / config_.visual_thrust.push_divisor;
const float SCALE = 1.0F + (VISUAL_PUSH / config_.visual_thrust.scale_divisor);
// Parpelleig daurat mentre està ferida: alterna color normal ↔ color hurt.
SDL_Color color = config_.colors.normal;
if (hurt_timer_ > 0.0F) {
const float CYCLE = 1.0F / config_.hurt.blink_hz;
const float T = std::fmod(hurt_timer_, CYCLE);
if (T < (CYCLE / 2.0F)) {
color = config_.colors.hurt;
}
}
Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
}
void Ship::hurt() {
hurt_timer_ = config_.hurt.duration;
Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
}