27242f54fe
Segunda entidad migrada. Los enemigos (Pentagon, Quadrat, Molinillo) ahora viven en el PhysicsWorld con velocidad vectorial. Las colisiones entre enemigos quedan habilitadas automaticamente (novedad: antes no se chocaban). Cambios en enemy.hpp: - Eliminado: float velocity_ (escalar) - Eliminado: void mou() (lo hace el world) - Anadido: override postUpdate() - Anadido: helper privado setVelocityFromAngle(angle, speed) - Anadido: direction_change_timer_ para zigzag periodico del Pentagon Cambios en enemy.cpp: - Constructor configura body_ (mass=5 default, radius=0 inactivo, restitution=1.0 elastico, sin damping) - init() ajusta masa por tipo: * Pentagon: 5.0 (esquivador ligero) * Quadrat: 8.0 (tanque pesado) * Molinillo: 4.0 (agil rapido) - init() setea body_.radius = ENEMY_RADIUS al spawn - behaviorPentagon: zigzag por probabilidad temporal (0.8/s) en lugar de detectar paredes; el rebote contra muros lo hace PhysicsWorld - behaviorQuadrat: tracking discreto cada TRACKING_INTERVAL — mezcla velocity actual con direccion al ship (LERP por tracking_strength) - behaviorMolinillo: solo boost de rotacion visual cerca del ship; movimiento puramente lineal integrado por el world - destruir() pone velocity=0, angular=0, radius=0 - postUpdate() sincroniza center_ desde body_.position - setVelocity(speed) mantiene la direccion, cambia solo la magnitud Renames a camelBack (.clang-tidy del proyecto): - get_drotacio -> getRotationDelta - get_base_velocity -> getBaseVelocity, get_base_rotation -> getBaseRotation - set_ship_position -> setShipPosition - set_velocity -> setVelocity, set_rotation -> setRotation - set_tracking_strength -> setTrackingStrength - get_temps_invulnerabilitat -> getInvulnerabilityTime - actualitzar_animacio -> updateAnimation - actualitzar_palpitacio -> updatePalpitation - actualitzar_rotacio_accelerada -> updateRotationAcceleration - comportament_pentagon/quadrat/molinillo -> behaviorPentagon/Quadrat/Molinillo - calcular_escala_actual -> computeCurrentScale - intent_spawn_safe -> attemptSafeSpawn (callsites actualizados en spawn_controller y game_scene) Cambios en GameScene: - En init(): physics_world_.addBody(&enemy.getBody()) por cada slot (los inactivos tienen radius=0, no estorban) - En update(): postUpdate() de cada enemy tras physics_world_.update Cambios de comportamiento visibles esperados: - Enemigos rebotan elasticamente contra paredes (restitution=1.0) - Enemigos se chocan entre si (impulsos elasticos con masas distintas por tipo: Quadrat empuja mas, Molinillo rebota mas) - Pentagon zigzag periodico en lugar de solo al chocar pared - Molinillo: comportamiento mas predecible (linea recta) Aviso: Bullet sigue con su movimiento ad-hoc (Fase 6e pendiente). Smoke test xvfb OK. Validacion gameplay del usuario pendiente. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
451 lines
18 KiB
C++
451 lines
18 KiB
C++
// enemy.cpp - Implementación de enemigos (ORNIs)
|
|
// © 1999 Visente i Sergi (versión Pascal)
|
|
// © 2025 Port a C++20 con SDL3
|
|
|
|
#include "game/entities/enemy.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
|
|
#include "core/defaults.hpp"
|
|
#include "core/entities/entity.hpp"
|
|
#include "core/graphics/shape_loader.hpp"
|
|
#include "core/rendering/shape_renderer.hpp"
|
|
#include "core/types.hpp"
|
|
#include "game/constants.hpp"
|
|
|
|
namespace {
|
|
|
|
// Velocidad inicial vectorial a partir de un ángulo (rad).
|
|
// angle=0 apunta hacia arriba (eje Y negativo SDL), como el resto del juego.
|
|
auto angleToDirection(float angle) -> Vec2 {
|
|
return Vec2{
|
|
.x = std::cos(angle - (Constants::PI / 2.0F)),
|
|
.y = std::sin(angle - (Constants::PI / 2.0F)),
|
|
};
|
|
}
|
|
|
|
// Recupera el "ángulo equivalente" de un body en movimiento (para zigzag).
|
|
// Si está parado, devuelve 0.
|
|
auto velocityToAngle(const Vec2& velocity) -> float {
|
|
if (velocity.lengthSquared() < 0.0001F) {
|
|
return 0.0F;
|
|
}
|
|
// El movimiento (vx, vy) corresponde a angle - PI/2; invertimos.
|
|
return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Enemy::Enemy(SDL_Renderer* renderer)
|
|
: Entity(renderer),
|
|
drotacio_(0.0F),
|
|
rotacio_(0.0F),
|
|
esta_(false),
|
|
type_(EnemyType::PENTAGON),
|
|
tracking_timer_(0.0F),
|
|
ship_position_(nullptr),
|
|
tracking_strength_(0.5F),
|
|
direction_change_timer_(0.0F),
|
|
timer_invulnerabilitat_(0.0F) {
|
|
brightness_ = Defaults::Brightness::ENEMIC;
|
|
|
|
// Configuración del cuerpo físico — defaults para enemy genérico.
|
|
// init() ajusta velocidad y masa según el tipo (Pentagon/Quadrat/Molinillo).
|
|
body_.setMass(5.0F); // Más liviano que la nave (10.0)
|
|
body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo)
|
|
body_.restitution = 1.0F; // Rebote elástico perfecto contra paredes
|
|
body_.linear_damping = 0.0F; // Sin fricción: mantienen velocidad
|
|
body_.angular_damping = 0.0F; // Idem
|
|
}
|
|
|
|
void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|
type_ = type;
|
|
|
|
const char* shape_file = nullptr;
|
|
float base_speed = 0.0F;
|
|
float drotacio_min = 0.0F;
|
|
float drotacio_max = 0.0F;
|
|
float type_mass = 5.0F;
|
|
|
|
switch (type_) {
|
|
case EnemyType::PENTAGON:
|
|
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
|
base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
|
|
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
|
|
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
|
|
type_mass = 5.0F;
|
|
break;
|
|
|
|
case EnemyType::QUADRAT:
|
|
shape_file = Defaults::Enemies::Cuadrado::SHAPE_FILE;
|
|
base_speed = Defaults::Enemies::Cuadrado::VELOCITAT;
|
|
drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN;
|
|
drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX;
|
|
type_mass = 8.0F; // Más pesado, "tanque"
|
|
tracking_timer_ = 0.0F;
|
|
break;
|
|
|
|
case EnemyType::MOLINILLO:
|
|
shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE;
|
|
base_speed = Defaults::Enemies::Molinillo::VELOCITAT;
|
|
drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN;
|
|
drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX;
|
|
type_mass = 4.0F; // Más liviano, ágil
|
|
break;
|
|
|
|
default:
|
|
std::cerr << "[Enemy] Error: tipo desconocido ("
|
|
<< static_cast<int>(type_) << "), usando PENTAGON\n";
|
|
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
|
base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
|
|
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
|
|
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
|
|
break;
|
|
}
|
|
|
|
body_.setMass(type_mass);
|
|
body_.radius = Defaults::Entities::ENEMY_RADIUS;
|
|
|
|
// Cargar shape
|
|
shape_ = Graphics::ShapeLoader::load(shape_file);
|
|
if (!shape_ || !shape_->isValid()) {
|
|
std::cerr << "[Enemy] Error: no se ha podido cargar " << shape_file << '\n';
|
|
}
|
|
|
|
// Posición aleatoria con comprobación de seguridad
|
|
float min_x;
|
|
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);
|
|
|
|
if (ship_pos != nullptr) {
|
|
bool found_safe_position = false;
|
|
for (int attempt = 0; attempt < Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS; attempt++) {
|
|
float candidate_x;
|
|
float candidate_y;
|
|
if (attemptSafeSpawn(*ship_pos, candidate_x, candidate_y)) {
|
|
center_.x = candidate_x;
|
|
center_.y = candidate_y;
|
|
found_safe_position = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found_safe_position) {
|
|
const int RANGE_X = static_cast<int>(max_x - min_x);
|
|
const int RANGE_Y = static_cast<int>(max_y - min_y);
|
|
center_.x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
|
|
center_.y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
|
|
std::cout << "[Enemy] Advertencia: spawn sin zona segura tras "
|
|
<< Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS << " intentos\n";
|
|
}
|
|
} else {
|
|
const int RANGE_X = static_cast<int>(max_x - min_x);
|
|
const int RANGE_Y = static_cast<int>(max_y - min_y);
|
|
center_.x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
|
|
center_.y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
|
|
}
|
|
|
|
// Dirección inicial aleatoria, velocidad escalar según tipo
|
|
const float ANGLE_INICIAL = (std::rand() % 360) * Constants::PI / 180.0F;
|
|
setVelocityFromAngle(ANGLE_INICIAL, base_speed);
|
|
|
|
// Sincronizar body_ con posición inicial
|
|
body_.position = center_;
|
|
body_.angle = 0.0F;
|
|
body_.angular_velocity = 0.0F;
|
|
body_.clearAccumulators();
|
|
|
|
// Rotación visual aleatoria (independiente del body)
|
|
const float DROTACIO_RANGE = drotacio_max - drotacio_min;
|
|
drotacio_ = drotacio_min + ((static_cast<float>(std::rand()) / RAND_MAX) * DROTACIO_RANGE);
|
|
rotacio_ = 0.0F;
|
|
|
|
// Estado de animación
|
|
animacio_ = EnemyAnimation();
|
|
animacio_.drotacio_base = drotacio_;
|
|
animacio_.drotacio_objetivo = drotacio_;
|
|
animacio_.drotacio_t = 1.0F;
|
|
|
|
// Invulnerabilidad post-spawn
|
|
timer_invulnerabilitat_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
|
|
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
|
|
|
// Timer para próximo cambio de dirección (Pentagon)
|
|
direction_change_timer_ = 0.0F;
|
|
|
|
esta_ = true;
|
|
}
|
|
|
|
void Enemy::update(float delta_time) {
|
|
if (!esta_) {
|
|
return;
|
|
}
|
|
|
|
// Decremento de invulnerabilidad + LERP de brightness
|
|
if (timer_invulnerabilitat_ > 0.0F) {
|
|
timer_invulnerabilitat_ -= delta_time;
|
|
timer_invulnerabilitat_ = std::max(timer_invulnerabilitat_, 0.0F);
|
|
|
|
const float T_INV = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
|
|
const float T = 1.0F - T_INV;
|
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
|
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
|
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_END;
|
|
brightness_ = START + ((END - START) * SMOOTH_T);
|
|
}
|
|
|
|
// Comportamiento por tipo (ajusta body_.velocity, NO mueve posición)
|
|
switch (type_) {
|
|
case EnemyType::PENTAGON:
|
|
behaviorPentagon(delta_time);
|
|
break;
|
|
case EnemyType::QUADRAT:
|
|
behaviorQuadrat(delta_time);
|
|
break;
|
|
case EnemyType::MOLINILLO:
|
|
behaviorMolinillo(delta_time);
|
|
break;
|
|
}
|
|
|
|
// Animaciones (palpitación + rotación acelerada)
|
|
updateAnimation(delta_time);
|
|
|
|
// Rotación visual (decoración, no afecta movimiento)
|
|
rotacio_ += drotacio_ * delta_time;
|
|
}
|
|
|
|
void Enemy::postUpdate(float /*delta_time*/) {
|
|
// Sincronizar mirror tras la integración del world.
|
|
if (esta_) {
|
|
center_ = body_.position;
|
|
}
|
|
}
|
|
|
|
void Enemy::draw() const {
|
|
if (!esta_ || !shape_) {
|
|
return;
|
|
}
|
|
const float SCALE = computeCurrentScale();
|
|
Rendering::render_shape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_);
|
|
}
|
|
|
|
void Enemy::destruir() {
|
|
esta_ = false;
|
|
body_.velocity = Vec2{};
|
|
body_.angular_velocity = 0.0F;
|
|
body_.radius = 0.0F; // No colisiona mientras está inactivo
|
|
}
|
|
|
|
void Enemy::setVelocity(float speed) {
|
|
// Mantener la dirección actual del body, cambiar solo la magnitud.
|
|
const float CURRENT_SPEED = body_.velocity.length();
|
|
if (CURRENT_SPEED > 0.0F) {
|
|
body_.velocity = body_.velocity * (speed / CURRENT_SPEED);
|
|
} else {
|
|
// Sin dirección actual: usar ángulo aleatorio
|
|
const float A = (std::rand() % 360) * Constants::PI / 180.0F;
|
|
setVelocityFromAngle(A, speed);
|
|
}
|
|
}
|
|
|
|
void Enemy::setVelocityFromAngle(float angle_movement, float speed) {
|
|
body_.velocity = angleToDirection(angle_movement) * speed;
|
|
}
|
|
|
|
// PENTAGON: zigzag esquivador. Cambios de dirección periódicos (probabilísticos)
|
|
// en lugar de detectar paredes; el rebote contra muros lo hace PhysicsWorld
|
|
// con restitution=1.0.
|
|
void Enemy::behaviorPentagon(float delta_time) {
|
|
direction_change_timer_ += delta_time;
|
|
|
|
// Probabilidad de zigzag por segundo (calibrada para sensación equivalente
|
|
// a la versión vieja que disparaba en cada toque de pared).
|
|
constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F;
|
|
const float RAND_VAL = static_cast<float>(std::rand()) / RAND_MAX;
|
|
if (RAND_VAL < ZIGZAG_PROB_PER_SECOND * delta_time) {
|
|
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
|
|
const float DELTA = (static_cast<float>(std::rand()) / RAND_MAX) *
|
|
Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
|
|
const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
|
|
const float SPEED = body_.velocity.length();
|
|
setVelocityFromAngle(NEW_ANGLE, SPEED);
|
|
direction_change_timer_ = 0.0F;
|
|
}
|
|
}
|
|
|
|
// QUADRAT: tracking discreto cada TRACKING_INTERVAL. Ajusta dirección
|
|
// hacia el ship mezclando con tracking_strength_.
|
|
void Enemy::behaviorQuadrat(float delta_time) {
|
|
tracking_timer_ += delta_time;
|
|
|
|
if (tracking_timer_ >= Defaults::Enemies::Cuadrado::TRACKING_INTERVAL && ship_position_ != nullptr) {
|
|
tracking_timer_ = 0.0F;
|
|
|
|
const Vec2 TO_SHIP = *ship_position_ - center_;
|
|
const float DIST = TO_SHIP.length();
|
|
if (DIST > 0.0F) {
|
|
const Vec2 DESIRED_DIR = TO_SHIP / DIST;
|
|
const float SPEED = body_.velocity.length();
|
|
const Vec2 DESIRED_VEL = DESIRED_DIR * SPEED;
|
|
|
|
// Mezcla LERP: velocidad actual con la deseada según tracking_strength_.
|
|
body_.velocity = (body_.velocity * (1.0F - tracking_strength_)) +
|
|
(DESIRED_VEL * tracking_strength_);
|
|
|
|
// Renormalizar a la velocidad escalar original
|
|
const float NEW_SPEED = body_.velocity.length();
|
|
if (NEW_SPEED > 0.0F) {
|
|
body_.velocity = body_.velocity * (SPEED / NEW_SPEED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOLINILLO: movimiento recto + boost de rotación visual cerca del ship.
|
|
// Sin tracking — solo cambios de dirección raros (igual que Pentagon pero
|
|
// con probabilidad mucho menor).
|
|
void Enemy::behaviorMolinillo(float /*delta_time*/) {
|
|
// Boost de rotación visual por proximidad al ship
|
|
if (ship_position_ != nullptr) {
|
|
const Vec2 TO_SHIP = *ship_position_ - center_;
|
|
const float DIST = TO_SHIP.length();
|
|
if (DIST < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) {
|
|
drotacio_ = animacio_.drotacio_base * Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER;
|
|
} else {
|
|
drotacio_ = animacio_.drotacio_base;
|
|
}
|
|
}
|
|
// Movimiento lineal puro: el world se encarga de integrar y rebotar.
|
|
}
|
|
|
|
void Enemy::updateAnimation(float delta_time) {
|
|
updatePalpitation(delta_time);
|
|
updateRotationAcceleration(delta_time);
|
|
}
|
|
|
|
void Enemy::updatePalpitation(float delta_time) {
|
|
if (animacio_.palpitacio_activa) {
|
|
animacio_.palpitacio_fase += 2.0F * Constants::PI * animacio_.palpitacio_frequencia * delta_time;
|
|
animacio_.palpitacio_temps_restant -= delta_time;
|
|
if (animacio_.palpitacio_temps_restant <= 0.0F) {
|
|
animacio_.palpitacio_activa = false;
|
|
}
|
|
} else {
|
|
const float RAND_VAL = static_cast<float>(std::rand()) / RAND_MAX;
|
|
const float TRIGGER_PROB = Defaults::Enemies::Animation::PALPITACIO_TRIGGER_PROB * delta_time;
|
|
if (RAND_VAL < TRIGGER_PROB) {
|
|
animacio_.palpitacio_activa = true;
|
|
animacio_.palpitacio_fase = 0.0F;
|
|
|
|
const float FREQ_RANGE = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX -
|
|
Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN;
|
|
animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN +
|
|
((static_cast<float>(std::rand()) / RAND_MAX) * FREQ_RANGE);
|
|
|
|
const float AMP_RANGE = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX -
|
|
Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN;
|
|
animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN +
|
|
((static_cast<float>(std::rand()) / RAND_MAX) * AMP_RANGE);
|
|
|
|
const float DUR_RANGE = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX -
|
|
Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN;
|
|
animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN +
|
|
((static_cast<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Enemy::updateRotationAcceleration(float delta_time) {
|
|
if (animacio_.drotacio_t < 1.0F) {
|
|
animacio_.drotacio_t += delta_time / animacio_.drotacio_duracio;
|
|
if (animacio_.drotacio_t >= 1.0F) {
|
|
animacio_.drotacio_t = 1.0F;
|
|
animacio_.drotacio_base = animacio_.drotacio_objetivo;
|
|
drotacio_ = animacio_.drotacio_base;
|
|
} else {
|
|
const float T = animacio_.drotacio_t;
|
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
|
const float INITIAL = animacio_.drotacio_base;
|
|
const float TARGET = animacio_.drotacio_objetivo;
|
|
drotacio_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
|
|
}
|
|
} else {
|
|
const float RAND_VAL = static_cast<float>(std::rand()) / RAND_MAX;
|
|
const float TRIGGER_PROB = Defaults::Enemies::Animation::ROTACIO_ACCEL_TRIGGER_PROB * delta_time;
|
|
if (RAND_VAL < TRIGGER_PROB) {
|
|
animacio_.drotacio_t = 0.0F;
|
|
|
|
const float MULT_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX -
|
|
Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
|
|
const float MULTIPLIER = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN +
|
|
((static_cast<float>(std::rand()) / RAND_MAX) * MULT_RANGE);
|
|
animacio_.drotacio_objetivo = animacio_.drotacio_base * MULTIPLIER;
|
|
|
|
const float DUR_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX -
|
|
Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN;
|
|
animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN +
|
|
((static_cast<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto Enemy::computeCurrentScale() const -> float {
|
|
float scale = 1.0F;
|
|
if (timer_invulnerabilitat_ > 0.0F) {
|
|
const float T_INV = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
|
|
const float T = 1.0F - T_INV;
|
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
|
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
|
|
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
|
|
scale = START + ((END - START) * SMOOTH_T);
|
|
} else if (animacio_.palpitacio_activa) {
|
|
scale += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
|
|
}
|
|
return scale;
|
|
}
|
|
|
|
auto Enemy::getBaseVelocity() const -> float {
|
|
switch (type_) {
|
|
case EnemyType::PENTAGON:
|
|
return Defaults::Enemies::Pentagon::VELOCITAT;
|
|
case EnemyType::QUADRAT:
|
|
return Defaults::Enemies::Cuadrado::VELOCITAT;
|
|
case EnemyType::MOLINILLO:
|
|
return Defaults::Enemies::Molinillo::VELOCITAT;
|
|
default:
|
|
return Defaults::Enemies::Pentagon::VELOCITAT;
|
|
}
|
|
}
|
|
|
|
auto Enemy::getBaseRotation() const -> float {
|
|
return animacio_.drotacio_base != 0.0F ? animacio_.drotacio_base : drotacio_;
|
|
}
|
|
|
|
void Enemy::setTrackingStrength(float strength) {
|
|
if (type_ == EnemyType::QUADRAT) {
|
|
tracking_strength_ = strength;
|
|
}
|
|
}
|
|
|
|
auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool {
|
|
float min_x;
|
|
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);
|
|
|
|
const int RANGE_X = static_cast<int>(max_x - min_x);
|
|
const int RANGE_Y = static_cast<int>(max_y - min_y);
|
|
out_x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
|
|
out_y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
|
|
|
|
const float DX = out_x - ship_pos.x;
|
|
const float DY = out_y - ship_pos.y;
|
|
const float DISTANCE = std::sqrt((DX * DX) + (DY * DY));
|
|
return DISTANCE >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
|
|
}
|