refactor(enemies): renombra QUADRAT/MOLINILLO a SQUARE/PINWHEEL
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
// enemies.hpp - Configuració per tipus d'enemic (Pentagon/Cuadrado/Molinillo), spawn i scoring
|
// enemies.hpp - Configuració per tipus d'enemic (Pentagon/Square/Molinillo), spawn i scoring
|
||||||
// © 2026 JailDesigner
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -27,8 +27,8 @@ namespace Defaults::Enemies {
|
|||||||
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
||||||
} // namespace Pentagon
|
} // namespace Pentagon
|
||||||
|
|
||||||
// Cuadrado (perseguidor - tracks player)
|
// Square (perseguidor - tracks player)
|
||||||
namespace Cuadrado {
|
namespace Square {
|
||||||
constexpr float VELOCITAT = 40.0F; // px/s (medium speed)
|
constexpr float VELOCITAT = 40.0F; // px/s (medium speed)
|
||||||
constexpr float MASS = 8.0F; // Más pesado, "tanque"
|
constexpr float MASS = 8.0F; // Más pesado, "tanque"
|
||||||
constexpr float TRACKING_STRENGTH = 0.5F; // Interpolation toward player (0.0-1.0)
|
constexpr float TRACKING_STRENGTH = 0.5F; // Interpolation toward player (0.0-1.0)
|
||||||
@@ -36,10 +36,10 @@ namespace Defaults::Enemies {
|
|||||||
constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%]
|
constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%]
|
||||||
constexpr float DROTACIO_MAX = 1.5F; // [+50%]
|
constexpr float DROTACIO_MAX = 1.5F; // [+50%]
|
||||||
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
||||||
} // namespace Cuadrado
|
} // namespace Square
|
||||||
|
|
||||||
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
||||||
namespace Molinillo {
|
namespace Pinwheel {
|
||||||
constexpr float VELOCITAT = 50.0F; // px/s (fastest)
|
constexpr float VELOCITAT = 50.0F; // px/s (fastest)
|
||||||
constexpr float MASS = 4.0F; // Más liviano, ágil
|
constexpr float MASS = 4.0F; // Más liviano, ágil
|
||||||
constexpr float CANVI_ANGLE_PROB = 0.05F; // 5% per wall hit (rare direction change)
|
constexpr float CANVI_ANGLE_PROB = 0.05F; // 5% per wall hit (rare direction change)
|
||||||
@@ -49,7 +49,7 @@ namespace Defaults::Enemies {
|
|||||||
constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0F; // Spin-up multiplier when near ship
|
constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0F; // Spin-up multiplier when near ship
|
||||||
constexpr float PROXIMITY_DISTANCE = 100.0F; // Distance threshold (px)
|
constexpr float PROXIMITY_DISTANCE = 100.0F; // Distance threshold (px)
|
||||||
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
|
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
|
||||||
} // namespace Molinillo
|
} // namespace Pinwheel
|
||||||
|
|
||||||
// Animation parameters (shared)
|
// Animation parameters (shared)
|
||||||
namespace Animation {
|
namespace Animation {
|
||||||
@@ -93,9 +93,9 @@ namespace Defaults::Enemies {
|
|||||||
|
|
||||||
// Scoring system (puntuación per type de enemy)
|
// Scoring system (puntuación per type de enemy)
|
||||||
namespace Scoring {
|
namespace Scoring {
|
||||||
constexpr int PENTAGON_SCORE = 100; // Pentágono (esquivador, 35 px/s)
|
constexpr int PENTAGON_SCORE = 100; // Pentágono (esquivador, 35 px/s)
|
||||||
constexpr int QUADRAT_SCORE = 150; // Cuadrado (perseguidor, 40 px/s)
|
constexpr int SQUARE_SCORE = 150; // Square (perseguidor, 40 px/s)
|
||||||
constexpr int MOLINILLO_SCORE = 200; // Molinillo (agressiu, 50 px/s)
|
constexpr int PINWHEEL_SCORE = 200; // Molinillo (agressiu, 50 px/s)
|
||||||
} // namespace Scoring
|
} // namespace Scoring
|
||||||
|
|
||||||
} // namespace Defaults::Enemies
|
} // namespace Defaults::Enemies
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ namespace Defaults::Palette {
|
|||||||
// brillantor perceptual sota el bloom (sense alterar la identitat de color).
|
// brillantor perceptual sota el bloom (sense alterar la identitat de color).
|
||||||
// El canal dominant es manté a 255 a cada color per maximitzar la saturació
|
// El canal dominant es manté a 255 a cada color per maximitzar la saturació
|
||||||
// visible quan el halo s'expandeix.
|
// visible quan el halo s'expandeix.
|
||||||
constexpr SDL_Color SHIP = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanco neutro
|
constexpr SDL_Color SHIP = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanco neutro
|
||||||
constexpr SDL_Color BULLET = {.r = 155, .g = 255, .b = 175, .a = 255}; // Verde laser
|
constexpr SDL_Color BULLET = {.r = 155, .g = 255, .b = 175, .a = 255}; // Verde laser
|
||||||
constexpr SDL_Color PENTAGON = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cyan pur "esquivador"
|
constexpr SDL_Color PENTAGON = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cyan pur "esquivador"
|
||||||
constexpr SDL_Color QUADRAT = {.r = 255, .g = 0, .b = 0, .a = 255}; // Roig pur "tank"
|
constexpr SDL_Color SQUARE = {.r = 255, .g = 0, .b = 0, .a = 255}; // Roig pur "tank"
|
||||||
constexpr SDL_Color MOLINILLO = {.r = 255, .g = 0, .b = 255, .a = 255}; // Magenta pur "agressiu"
|
constexpr SDL_Color PINWHEEL = {.r = 255, .g = 0, .b = 255, .a = 255}; // Magenta pur "agressiu"
|
||||||
constexpr SDL_Color WOUNDED = {.r = 255, .g = 220, .b = 60, .a = 255}; // Dorado: enemigo herido
|
constexpr SDL_Color WOUNDED = {.r = 255, .g = 220, .b = 60, .a = 255}; // Dorado: enemigo herido
|
||||||
|
|
||||||
} // namespace Defaults::Palette
|
} // namespace Defaults::Palette
|
||||||
|
|||||||
@@ -242,12 +242,12 @@ namespace Graphics {
|
|||||||
|
|
||||||
// Calcular posición de l'esquina superior izquierda
|
// Calcular posición de l'esquina superior izquierda
|
||||||
// restant la meitat de las dimensions del point central
|
// restant la meitat de las dimensions del point central
|
||||||
Vec2 posicio_esquerra = {
|
Vec2 top_left_position = {
|
||||||
.x = centre_punt.x - (text_width / 2.0F),
|
.x = centre_punt.x - (text_width / 2.0F),
|
||||||
.y = centre_punt.y - (text_height / 2.0F)};
|
.y = centre_punt.y - (text_height / 2.0F)};
|
||||||
|
|
||||||
// Delegar al método render() existent
|
// Delegar al método render() existent
|
||||||
render(text, posicio_esquerra, scale, spacing, brightness, color);
|
render(text, top_left_position, scale, spacing, brightness, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VectorText::getTextWidth(const std::string& text, float scale, float spacing) -> float {
|
auto VectorText::getTextWidth(const std::string& text, float scale, float spacing) -> float {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void Bullet::init() {
|
|||||||
body_.clearAccumulators();
|
body_.clearAccumulators();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
|
void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id) {
|
||||||
// Activar bullet
|
// Activar bullet
|
||||||
is_active_ = true;
|
is_active_ = true;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Bullet : public Entities::Entity {
|
|||||||
explicit Bullet(Rendering::Renderer* renderer);
|
explicit Bullet(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
void init() override;
|
void init() override;
|
||||||
void disparar(const Vec2& position, float angle, uint8_t owner_id);
|
void fire(const Vec2& position, float angle, uint8_t owner_id);
|
||||||
void update(float delta_time) override;
|
void update(float delta_time) override;
|
||||||
void postUpdate(float delta_time) override;
|
void postUpdate(float delta_time) override;
|
||||||
void draw() const override;
|
void draw() const override;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace {
|
|||||||
Enemy::Enemy(Rendering::Renderer* renderer)
|
Enemy::Enemy(Rendering::Renderer* renderer)
|
||||||
: Entity(renderer),
|
: Entity(renderer),
|
||||||
|
|
||||||
tracking_strength_(Defaults::Enemies::Cuadrado::TRACKING_STRENGTH) {
|
tracking_strength_(Defaults::Enemies::Square::TRACKING_STRENGTH) {
|
||||||
brightness_ = Defaults::Brightness::ENEMIC;
|
brightness_ = Defaults::Brightness::ENEMIC;
|
||||||
|
|
||||||
// Configuración del cuerpo físico — defaults para enemy genérico.
|
// Configuración del cuerpo físico — defaults para enemy genérico.
|
||||||
@@ -71,21 +71,21 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|||||||
type_mass = Defaults::Enemies::Pentagon::MASS;
|
type_mass = Defaults::Enemies::Pentagon::MASS;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
shape_file = Defaults::Enemies::Cuadrado::SHAPE_FILE;
|
shape_file = Defaults::Enemies::Square::SHAPE_FILE;
|
||||||
base_speed = Defaults::Enemies::Cuadrado::VELOCITAT;
|
base_speed = Defaults::Enemies::Square::VELOCITAT;
|
||||||
drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN;
|
drotacio_min = Defaults::Enemies::Square::DROTACIO_MIN;
|
||||||
drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX;
|
drotacio_max = Defaults::Enemies::Square::DROTACIO_MAX;
|
||||||
type_mass = Defaults::Enemies::Cuadrado::MASS;
|
type_mass = Defaults::Enemies::Square::MASS;
|
||||||
tracking_timer_ = 0.0F;
|
tracking_timer_ = 0.0F;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE;
|
shape_file = Defaults::Enemies::Pinwheel::SHAPE_FILE;
|
||||||
base_speed = Defaults::Enemies::Molinillo::VELOCITAT;
|
base_speed = Defaults::Enemies::Pinwheel::VELOCITAT;
|
||||||
drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN;
|
drotacio_min = Defaults::Enemies::Pinwheel::DROTACIO_MIN;
|
||||||
drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX;
|
drotacio_max = Defaults::Enemies::Pinwheel::DROTACIO_MAX;
|
||||||
type_mass = Defaults::Enemies::Molinillo::MASS;
|
type_mass = Defaults::Enemies::Pinwheel::MASS;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -209,11 +209,11 @@ void Enemy::update(float delta_time) {
|
|||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
behaviorPentagon(delta_time);
|
behaviorPentagon(delta_time);
|
||||||
break;
|
break;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
behaviorQuadrat(delta_time);
|
behaviorSquare(delta_time);
|
||||||
break;
|
break;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
behaviorMolinillo(delta_time);
|
behaviorPinwheel(delta_time);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,11 +242,11 @@ void Enemy::draw() const {
|
|||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
color = Defaults::Palette::PENTAGON;
|
color = Defaults::Palette::PENTAGON;
|
||||||
break;
|
break;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
color = Defaults::Palette::QUADRAT;
|
color = Defaults::Palette::SQUARE;
|
||||||
break;
|
break;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
color = Defaults::Palette::MOLINILLO;
|
color = Defaults::Palette::PINWHEEL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ void Enemy::draw() const {
|
|||||||
Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, color);
|
Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::destruir() {
|
void Enemy::destroy() {
|
||||||
esta_ = false;
|
esta_ = false;
|
||||||
body_.velocity = Vec2{};
|
body_.velocity = Vec2{};
|
||||||
body_.angular_velocity = 0.0F;
|
body_.angular_velocity = 0.0F;
|
||||||
@@ -273,7 +273,7 @@ void Enemy::destruir() {
|
|||||||
last_hit_by_ = 0xFF;
|
last_hit_by_ = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::herir(uint8_t shooter_id) {
|
void Enemy::hurt(uint8_t shooter_id) {
|
||||||
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
||||||
last_hit_by_ = shooter_id;
|
last_hit_by_ = shooter_id;
|
||||||
// El so HIT ara el reprodueix la bala quan es trenca en debris
|
// El so HIT ara el reprodueix la bala quan es trenca en debris
|
||||||
@@ -320,12 +320,12 @@ void Enemy::behaviorPentagon(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// QUADRAT: tracking discreto cada TRACKING_INTERVAL. Ajusta dirección
|
// SQUARE: tracking discreto cada TRACKING_INTERVAL. Ajusta dirección
|
||||||
// hacia el ship mezclando con tracking_strength_.
|
// hacia el ship mezclando con tracking_strength_.
|
||||||
void Enemy::behaviorQuadrat(float delta_time) {
|
void Enemy::behaviorSquare(float delta_time) {
|
||||||
tracking_timer_ += delta_time;
|
tracking_timer_ += delta_time;
|
||||||
|
|
||||||
if (tracking_timer_ >= Defaults::Enemies::Cuadrado::TRACKING_INTERVAL && ship_position_ != nullptr) {
|
if (tracking_timer_ >= Defaults::Enemies::Square::TRACKING_INTERVAL && ship_position_ != nullptr) {
|
||||||
tracking_timer_ = 0.0F;
|
tracking_timer_ = 0.0F;
|
||||||
|
|
||||||
const Vec2 TO_SHIP = *ship_position_ - center_;
|
const Vec2 TO_SHIP = *ship_position_ - center_;
|
||||||
@@ -348,16 +348,16 @@ void Enemy::behaviorQuadrat(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MOLINILLO: movimiento recto + boost de rotación visual cerca del ship.
|
// PINWHEEL: movimiento recto + boost de rotación visual cerca del ship.
|
||||||
// Sin tracking — solo cambios de dirección raros (igual que Pentagon pero
|
// Sin tracking — solo cambios de dirección raros (igual que Pentagon pero
|
||||||
// con probabilidad mucho menor).
|
// con probabilidad mucho menor).
|
||||||
void Enemy::behaviorMolinillo(float /*delta_time*/) {
|
void Enemy::behaviorPinwheel(float /*delta_time*/) {
|
||||||
// Boost de rotación visual por proximidad al ship
|
// Boost de rotación visual por proximidad al ship
|
||||||
if (ship_position_ != nullptr) {
|
if (ship_position_ != nullptr) {
|
||||||
const Vec2 TO_SHIP = *ship_position_ - center_;
|
const Vec2 TO_SHIP = *ship_position_ - center_;
|
||||||
const float DIST = TO_SHIP.length();
|
const float DIST = TO_SHIP.length();
|
||||||
if (DIST < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) {
|
if (DIST < Defaults::Enemies::Pinwheel::PROXIMITY_DISTANCE) {
|
||||||
drotacio_ = animacio_.drotacio_base * Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER;
|
drotacio_ = animacio_.drotacio_base * Defaults::Enemies::Pinwheel::DROTACIO_PROXIMITY_MULTIPLIER;
|
||||||
} else {
|
} else {
|
||||||
drotacio_ = animacio_.drotacio_base;
|
drotacio_ = animacio_.drotacio_base;
|
||||||
}
|
}
|
||||||
@@ -455,10 +455,10 @@ auto Enemy::getBaseVelocity() const -> float {
|
|||||||
switch (type_) {
|
switch (type_) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
return Defaults::Enemies::Pentagon::VELOCITAT;
|
return Defaults::Enemies::Pentagon::VELOCITAT;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
return Defaults::Enemies::Cuadrado::VELOCITAT;
|
return Defaults::Enemies::Square::VELOCITAT;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
return Defaults::Enemies::Molinillo::VELOCITAT;
|
return Defaults::Enemies::Pinwheel::VELOCITAT;
|
||||||
default:
|
default:
|
||||||
return Defaults::Enemies::Pentagon::VELOCITAT;
|
return Defaults::Enemies::Pentagon::VELOCITAT;
|
||||||
}
|
}
|
||||||
@@ -469,7 +469,7 @@ auto Enemy::getBaseRotation() const -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::setTrackingStrength(float strength) {
|
void Enemy::setTrackingStrength(float strength) {
|
||||||
if (type_ == EnemyType::QUADRAT) {
|
if (type_ == EnemyType::SQUARE) {
|
||||||
tracking_strength_ = strength;
|
tracking_strength_ = strength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
// Tipo de enemy
|
// Tipo de enemy
|
||||||
enum class EnemyType : uint8_t {
|
enum class EnemyType : uint8_t {
|
||||||
PENTAGON = 0, // Pentágono esquivador (zigzag)
|
PENTAGON = 0, // Pentágono esquivador (zigzag)
|
||||||
QUADRAT = 1, // Cuadrado perseguidor (tracks ship)
|
SQUARE = 1, // Square perseguidor (tracks ship)
|
||||||
MOLINILLO = 2 // Molinillo agresivo (rápido, girando)
|
PINWHEEL = 2 // Molinillo agresivo (rápido, girando)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Estado de animación (palpitación + rotación acelerada)
|
// Estado de animación (palpitación + rotación acelerada)
|
||||||
@@ -60,7 +60,7 @@ class Enemy : public Entities::Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marcar destruido (desactiva el cuerpo físicamente: radius=0)
|
// Marcar destruido (desactiva el cuerpo físicamente: radius=0)
|
||||||
void destruir();
|
void destroy();
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
[[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; }
|
[[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; }
|
||||||
@@ -90,7 +90,7 @@ class Enemy : public Entities::Entity {
|
|||||||
|
|
||||||
// Estado "herido": entre primer impacto de bala y explosión diferida.
|
// Estado "herido": entre primer impacto de bala y explosión diferida.
|
||||||
// shooter_id: id del jugador que herí; 0xFF = sin atribución (cadena, etc.).
|
// shooter_id: id del jugador que herí; 0xFF = sin atribución (cadena, etc.).
|
||||||
void herir(uint8_t shooter_id = 0xFF);
|
void hurt(uint8_t shooter_id = 0xFF);
|
||||||
[[nodiscard]] auto isWounded() const -> bool { return wounded_timer_ > 0.0F; }
|
[[nodiscard]] auto isWounded() const -> bool { return wounded_timer_ > 0.0F; }
|
||||||
[[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; }
|
[[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; }
|
||||||
[[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; }
|
[[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; }
|
||||||
@@ -130,8 +130,8 @@ class Enemy : public Entities::Entity {
|
|||||||
void updatePalpitation(float delta_time);
|
void updatePalpitation(float delta_time);
|
||||||
void updateRotationAcceleration(float delta_time);
|
void updateRotationAcceleration(float delta_time);
|
||||||
void behaviorPentagon(float delta_time);
|
void behaviorPentagon(float delta_time);
|
||||||
void behaviorQuadrat(float delta_time);
|
void behaviorSquare(float delta_time);
|
||||||
void behaviorMolinillo(float delta_time);
|
void behaviorPinwheel(float delta_time);
|
||||||
[[nodiscard]] auto computeCurrentScale() const -> float;
|
[[nodiscard]] auto computeCurrentScale() const -> float;
|
||||||
// Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy.
|
// Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy.
|
||||||
static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool;
|
static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool;
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ void Ship::draw() const {
|
|||||||
Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
|
Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ship::herir() {
|
void Ship::hurt() {
|
||||||
hurt_timer_ = Defaults::Ship::Hurt::DURATION;
|
hurt_timer_ = Defaults::Ship::Hurt::DURATION;
|
||||||
Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Ship : public Entities::Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Estat "ferit": primera col·lisió amb enemic dispara HURT; segona durant HURT mata.
|
// Estat "ferit": primera col·lisió amb enemic dispara HURT; segona durant HURT mata.
|
||||||
void herir();
|
void hurt();
|
||||||
[[nodiscard]] auto isHurt() const -> bool { return hurt_timer_ > 0.0F; }
|
[[nodiscard]] auto isHurt() const -> bool { return hurt_timer_ > 0.0F; }
|
||||||
[[nodiscard]] auto getHurtTimer() const -> float { return hurt_timer_; }
|
[[nodiscard]] auto getHurtTimer() const -> float { return hurt_timer_; }
|
||||||
|
|
||||||
|
|||||||
@@ -927,7 +927,7 @@ void GameScene::fireBullet(uint8_t player_id) {
|
|||||||
float sin_a = std::sin(ship_angle);
|
float sin_a = std::sin(ship_angle);
|
||||||
float tip_x = (LOCAL_TIP_X * cos_a) - (LOCAL_TIP_Y * sin_a) + ship_centre.x;
|
float tip_x = (LOCAL_TIP_X * cos_a) - (LOCAL_TIP_Y * sin_a) + ship_centre.x;
|
||||||
float tip_y = (LOCAL_TIP_X * sin_a) + (LOCAL_TIP_Y * cos_a) + ship_centre.y;
|
float tip_y = (LOCAL_TIP_X * sin_a) + (LOCAL_TIP_Y * cos_a) + ship_centre.y;
|
||||||
Vec2 posicio_dispar = {.x = tip_x, .y = tip_y};
|
Vec2 fire_position = {.x = tip_x, .y = tip_y};
|
||||||
|
|
||||||
// Buscar primera bullet inactiva en el pool del player.
|
// Buscar primera bullet inactiva en el pool del player.
|
||||||
// El pool global té MAX_BALES slots per jugador (P1=[0..MAX-1], P2=[MAX..2*MAX-1]).
|
// El pool global té MAX_BALES slots per jugador (P1=[0..MAX-1], P2=[MAX..2*MAX-1]).
|
||||||
@@ -935,7 +935,7 @@ void GameScene::fireBullet(uint8_t player_id) {
|
|||||||
const int START_IDX = player_id * SLOTS_PER_PLAYER;
|
const int START_IDX = player_id * SLOTS_PER_PLAYER;
|
||||||
for (int i = START_IDX; i < START_IDX + SLOTS_PER_PLAYER; i++) {
|
for (int i = START_IDX; i < START_IDX + SLOTS_PER_PLAYER; i++) {
|
||||||
if (!bullets_[i].isActive()) {
|
if (!bullets_[i].isActive()) {
|
||||||
bullets_[i].disparar(posicio_dispar, ship_angle, player_id);
|
bullets_[i].fire(fire_position, ship_angle, player_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,152 +16,152 @@
|
|||||||
|
|
||||||
namespace StageSystem {
|
namespace StageSystem {
|
||||||
|
|
||||||
SpawnController::SpawnController() = default;
|
SpawnController::SpawnController() = default;
|
||||||
|
|
||||||
void SpawnController::configure(const StageConfig* config) {
|
void SpawnController::configure(const StageConfig* config) {
|
||||||
config_ = config;
|
config_ = config;
|
||||||
}
|
|
||||||
|
|
||||||
void SpawnController::start() {
|
|
||||||
if (config_ == nullptr) {
|
|
||||||
std::cerr << "[SpawnController] Error: config_ es null" << '\n';
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
void SpawnController::start() {
|
||||||
generateSpawnEvents();
|
if (config_ == nullptr) {
|
||||||
|
std::cerr << "[SpawnController] Error: config_ es null" << '\n';
|
||||||
std::cout << "[SpawnController] Stage " << static_cast<int>(config_->stage_id)
|
return;
|
||||||
<< ": generats " << spawn_queue_.size() << " spawn events" << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpawnController::reset() {
|
|
||||||
spawn_queue_.clear();
|
|
||||||
temps_transcorregut_ = 0.0F;
|
|
||||||
index_spawn_actual_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpawnController::update(float delta_time, std::array<Enemy, 15>& orni_array, bool pausar) {
|
|
||||||
if ((config_ == nullptr) || spawn_queue_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment timer only when not paused
|
|
||||||
if (!pausar) {
|
|
||||||
temps_transcorregut_ += delta_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process spawn events
|
|
||||||
while (index_spawn_actual_ < spawn_queue_.size()) {
|
|
||||||
SpawnEvent& event = spawn_queue_[index_spawn_actual_];
|
|
||||||
|
|
||||||
if (event.spawnejat) {
|
|
||||||
index_spawn_actual_++;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (temps_transcorregut_ >= event.temps_spawn) {
|
reset();
|
||||||
// Find first inactive enemy
|
generateSpawnEvents();
|
||||||
for (auto& enemy : orni_array) {
|
|
||||||
if (!enemy.isActive()) {
|
std::cout << "[SpawnController] Stage " << static_cast<int>(config_->stage_id)
|
||||||
spawnEnemy(enemy, event.type, ship_position_);
|
<< ": generats " << spawn_queue_.size() << " spawn events" << '\n';
|
||||||
event.spawnejat = true;
|
}
|
||||||
index_spawn_actual_++;
|
|
||||||
|
void SpawnController::reset() {
|
||||||
|
spawn_queue_.clear();
|
||||||
|
temps_transcorregut_ = 0.0F;
|
||||||
|
index_spawn_actual_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::update(float delta_time, std::array<Enemy, 15>& orni_array, bool pausar) {
|
||||||
|
if ((config_ == nullptr) || spawn_queue_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment timer only when not paused
|
||||||
|
if (!pausar) {
|
||||||
|
temps_transcorregut_ += delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process spawn events
|
||||||
|
while (index_spawn_actual_ < spawn_queue_.size()) {
|
||||||
|
SpawnEvent& event = spawn_queue_[index_spawn_actual_];
|
||||||
|
|
||||||
|
if (event.spawnejat) {
|
||||||
|
index_spawn_actual_++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temps_transcorregut_ >= event.temps_spawn) {
|
||||||
|
// Find first inactive enemy
|
||||||
|
for (auto& enemy : orni_array) {
|
||||||
|
if (!enemy.isActive()) {
|
||||||
|
spawnEnemy(enemy, event.type, ship_position_);
|
||||||
|
event.spawnejat = true;
|
||||||
|
index_spawn_actual_++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no slot available, try next frame
|
||||||
|
if (!event.spawnejat) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Not yet time for this spawn
|
||||||
// If no slot available, try next frame
|
|
||||||
if (!event.spawnejat) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Not yet time for this spawn
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::allEnemiesSpawned() const -> bool {
|
auto SpawnController::allEnemiesSpawned() const -> bool {
|
||||||
return index_spawn_actual_ >= spawn_queue_.size();
|
return index_spawn_actual_ >= spawn_queue_.size();
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::allEnemiesDestroyed(const std::array<Enemy, 15>& orni_array) const -> bool {
|
|
||||||
if (!allEnemiesSpawned()) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return std::ranges::all_of(orni_array, [](const Enemy& enemy) { return !enemy.isActive(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::getAliveEnemyCount(const std::array<Enemy, 15>& orni_array) -> uint8_t {
|
auto SpawnController::allEnemiesDestroyed(const std::array<Enemy, 15>& orni_array) const -> bool {
|
||||||
uint8_t count = 0;
|
if (!allEnemiesSpawned()) {
|
||||||
for (const auto& enemy : orni_array) {
|
return false;
|
||||||
if (enemy.isActive()) {
|
}
|
||||||
count++;
|
return std::ranges::all_of(orni_array, [](const Enemy& enemy) { return !enemy.isActive(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SpawnController::getAliveEnemyCount(const std::array<Enemy, 15>& orni_array) -> uint8_t {
|
||||||
|
uint8_t count = 0;
|
||||||
|
for (const auto& enemy : orni_array) {
|
||||||
|
if (enemy.isActive()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SpawnController::countSpawnedEnemies() const -> uint8_t {
|
||||||
|
return static_cast<uint8_t>(index_spawn_actual_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::generateSpawnEvents() {
|
||||||
|
if (config_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < config_->total_enemies; i++) {
|
||||||
|
float spawn_time = config_->config_spawn.delay_inicial +
|
||||||
|
(i * config_->config_spawn.interval_spawn);
|
||||||
|
|
||||||
|
EnemyType type = selectRandomType();
|
||||||
|
|
||||||
|
spawn_queue_.push_back({spawn_time, type, false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::countSpawnedEnemies() const -> uint8_t {
|
auto SpawnController::selectRandomType() const -> EnemyType {
|
||||||
return static_cast<uint8_t>(index_spawn_actual_);
|
if (config_ == nullptr) {
|
||||||
}
|
return EnemyType::PENTAGON;
|
||||||
|
}
|
||||||
|
|
||||||
void SpawnController::generateSpawnEvents() {
|
// Weighted random selection based on distribution
|
||||||
if (config_ == nullptr) {
|
int rand_val = std::rand() % 100;
|
||||||
return;
|
|
||||||
|
if (std::cmp_less(rand_val, config_->distribucio.pentagon)) {
|
||||||
|
return EnemyType::PENTAGON;
|
||||||
|
}
|
||||||
|
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
|
||||||
|
return EnemyType::SQUARE;
|
||||||
|
}
|
||||||
|
return EnemyType::PINWHEEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint8_t i = 0; i < config_->total_enemies; i++) {
|
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
|
||||||
float spawn_time = config_->config_spawn.delay_inicial +
|
// Initialize enemy (with safe spawn if ship_pos provided)
|
||||||
(i * config_->config_spawn.interval_spawn);
|
enemy.init(type, ship_pos);
|
||||||
|
|
||||||
EnemyType type = selectRandomType();
|
// Apply difficulty multipliers
|
||||||
|
applyMultipliers(enemy);
|
||||||
spawn_queue_.push_back({spawn_time, type, false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::selectRandomType() const -> EnemyType {
|
|
||||||
if (config_ == nullptr) {
|
|
||||||
return EnemyType::PENTAGON;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weighted random selection based on distribution
|
void SpawnController::applyMultipliers(Enemy& enemy) const {
|
||||||
int rand_val = std::rand() % 100;
|
if (config_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (std::cmp_less(rand_val, config_->distribucio.pentagon)) {
|
// Apply velocity multiplier
|
||||||
return EnemyType::PENTAGON;
|
float base_vel = enemy.getBaseVelocity();
|
||||||
|
enemy.setVelocity(base_vel * config_->multiplicadors.velocity);
|
||||||
|
|
||||||
|
// Apply rotation multiplier
|
||||||
|
float base_rot = enemy.getBaseRotation();
|
||||||
|
enemy.setRotation(base_rot * config_->multiplicadors.rotation);
|
||||||
|
|
||||||
|
// Apply tracking strength (only affects SQUARE)
|
||||||
|
enemy.setTrackingStrength(config_->multiplicadors.tracking_strength);
|
||||||
}
|
}
|
||||||
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
|
|
||||||
return EnemyType::QUADRAT;
|
|
||||||
}
|
|
||||||
return EnemyType::MOLINILLO;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
|
|
||||||
// Initialize enemy (with safe spawn if ship_pos provided)
|
|
||||||
enemy.init(type, ship_pos);
|
|
||||||
|
|
||||||
// Apply difficulty multipliers
|
|
||||||
applyMultipliers(enemy);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpawnController::applyMultipliers(Enemy& enemy) const {
|
|
||||||
if (config_ == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply velocity multiplier
|
|
||||||
float base_vel = enemy.getBaseVelocity();
|
|
||||||
enemy.setVelocity(base_vel * config_->multiplicadors.velocity);
|
|
||||||
|
|
||||||
// Apply rotation multiplier
|
|
||||||
float base_rot = enemy.getBaseRotation();
|
|
||||||
enemy.setRotation(base_rot * config_->multiplicadors.rotation);
|
|
||||||
|
|
||||||
// Apply tracking strength (only affects QUADRAT)
|
|
||||||
enemy.setTrackingStrength(config_->multiplicadors.tracking_strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace StageSystem
|
} // namespace StageSystem
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace StageSystem {
|
|||||||
struct MultiplicadorsDificultat {
|
struct MultiplicadorsDificultat {
|
||||||
float velocity; // 0.5-2.0 típic
|
float velocity; // 0.5-2.0 típic
|
||||||
float rotation; // 0.5-2.0 típic
|
float rotation; // 0.5-2.0 típic
|
||||||
float tracking_strength; // 0.0-1.5 (aplicat a Cuadrado)
|
float tracking_strength; // 0.0-1.5 (aplicat a Square)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metadades del file YAML
|
// Metadades del file YAML
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ namespace Systems::Collision {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
return Defaults::Enemies::Scoring::PENTAGON_SCORE;
|
return Defaults::Enemies::Scoring::PENTAGON_SCORE;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
return Defaults::Enemies::Scoring::QUADRAT_SCORE;
|
return Defaults::Enemies::Scoring::SQUARE_SCORE;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
return Defaults::Enemies::Scoring::MOLINILLO_SCORE;
|
return Defaults::Enemies::Scoring::PINWHEEL_SCORE;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,10 @@ namespace Systems::Collision {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
return Defaults::Palette::PENTAGON;
|
return Defaults::Palette::PENTAGON;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
return Defaults::Palette::QUADRAT;
|
return Defaults::Palette::SQUARE;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
return Defaults::Palette::MOLINILLO;
|
return Defaults::Palette::PINWHEEL;
|
||||||
}
|
}
|
||||||
return SDL_Color{};
|
return SDL_Color{};
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
ctx.floating_score_manager.crear(POINTS, ENEMY_POS);
|
ctx.floating_score_manager.crear(POINTS, ENEMY_POS);
|
||||||
|
|
||||||
enemy.destruir();
|
enemy.destroy();
|
||||||
|
|
||||||
constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suave)
|
constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suave)
|
||||||
const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
||||||
@@ -141,7 +141,7 @@ namespace Systems::Collision {
|
|||||||
explodeNow(ctx, enemy, SHOOTER);
|
explodeNow(ctx, enemy, SHOOTER);
|
||||||
} else {
|
} else {
|
||||||
// Primer impacto → entra en estado herido (explosión diferida).
|
// Primer impacto → entra en estado herido (explosión diferida).
|
||||||
enemy.herir(SHOOTER);
|
enemy.hurt(SHOOTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
breakBullet(ctx.debris_manager, bullet);
|
breakBullet(ctx.debris_manager, bullet);
|
||||||
@@ -182,9 +182,9 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
// El sano queda herido, propagando el shooter original.
|
// El sano queda herido, propagando el shooter original.
|
||||||
if (A_WOUNDED) {
|
if (A_WOUNDED) {
|
||||||
b.herir(a.getLastHitBy());
|
b.hurt(a.getLastHitBy());
|
||||||
} else {
|
} else {
|
||||||
a.herir(b.getLastHitBy());
|
a.hurt(b.getLastHitBy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ namespace Systems::Collision {
|
|||||||
} else {
|
} else {
|
||||||
// Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
|
// Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
|
||||||
// l'enemic no rep dany per decisió de disseny).
|
// l'enemic no rep dany per decisió de disseny).
|
||||||
ctx.ships[i].herir();
|
ctx.ships[i].hurt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.ships[i].setTouchingEnemyPrevFrame(TOUCHING_NOW);
|
ctx.ships[i].setTouchingEnemyPrevFrame(TOUCHING_NOW);
|
||||||
|
|||||||
Reference in New Issue
Block a user