feat(enemy): sistema d'HP declaratiu i nou enemic big_pentagon
This commit is contained in:
@@ -133,6 +133,9 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
||||
invulnerability_timer_ = cfg.spawn.invulnerability_duration;
|
||||
brightness_ = cfg.spawn.invulnerability_brightness_start;
|
||||
|
||||
health_ = cfg.health;
|
||||
flash_timer_ = 0.0F;
|
||||
|
||||
is_active_ = true;
|
||||
}
|
||||
|
||||
@@ -150,6 +153,11 @@ void Enemy::update(float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
if (flash_timer_ > 0.0F) {
|
||||
flash_timer_ -= delta_time;
|
||||
flash_timer_ = std::max(flash_timer_, 0.0F);
|
||||
}
|
||||
|
||||
if (invulnerability_timer_ > 0.0F) {
|
||||
invulnerability_timer_ -= delta_time;
|
||||
invulnerability_timer_ = std::max(invulnerability_timer_, 0.0F);
|
||||
@@ -192,7 +200,15 @@ void Enemy::draw() const {
|
||||
}
|
||||
}
|
||||
|
||||
Rendering::renderShape(renderer_, shape_, center_, rotation_, SCALE, 1.0F, brightness_, color);
|
||||
// Flash d'impacte parcial (HP>1): força el color a blanc i el brillo a
|
||||
// 1.0 durant la finestra de flash. Té prioritat sobre el blink wounded.
|
||||
float effective_brightness = brightness_;
|
||||
if (flash_timer_ > 0.0F) {
|
||||
color = SDL_Color{.r = 255, .g = 255, .b = 255, .a = 255};
|
||||
effective_brightness = 1.0F;
|
||||
}
|
||||
|
||||
Rendering::renderShape(renderer_, shape_, center_, rotation_, SCALE, 1.0F, effective_brightness, color);
|
||||
}
|
||||
|
||||
void Enemy::destroy() {
|
||||
@@ -203,6 +219,7 @@ void Enemy::destroy() {
|
||||
wounded_timer_ = 0.0F;
|
||||
wound_expired_this_frame_ = false;
|
||||
last_hit_by_ = 0xFF;
|
||||
flash_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
void Enemy::hurt(uint8_t shooter_id) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "core/defaults/enemies.hpp"
|
||||
#include "core/entities/entity.hpp"
|
||||
#include "core/types.hpp"
|
||||
#include "game/entities/enemy_ai.hpp"
|
||||
@@ -16,10 +17,11 @@ class Ship;
|
||||
|
||||
// Tipo de enemy
|
||||
enum class EnemyType : uint8_t {
|
||||
PENTAGON = 0, // Pentágono esquivador (zigzag)
|
||||
SQUARE = 1, // Square perseguidor (tracks ship)
|
||||
PINWHEEL = 2, // Molinillo agresivo (rápido, girando)
|
||||
STAR = 3 // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag)
|
||||
PENTAGON = 0, // Pentágono esquivador (zigzag)
|
||||
SQUARE = 1, // Square perseguidor (tracks ship)
|
||||
PINWHEEL = 2, // Molinillo agresivo (rápido, girando)
|
||||
STAR = 3, // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag)
|
||||
BIG_PENTAGON = 4, // Pentàgon gegant tough (HP=10, chase lent — primer enemic HP>1)
|
||||
};
|
||||
|
||||
// Forward declaration — EnemyConfig viu a enemy_config.hpp i s'inclou només a enemy.cpp.
|
||||
@@ -116,6 +118,20 @@ class Enemy : public Entities::Entity {
|
||||
void consumeWoundExpired() { wound_expired_this_frame_ = false; }
|
||||
[[nodiscard]] auto getLastHitBy() const -> uint8_t { return last_hit_by_; }
|
||||
|
||||
// Salut: decrementada per l'acció DECREASE_HEALTH al dispatcher d'events.
|
||||
// Quan arriba a 0 o menys, el dispatcher dispara ON_NO_HEALTH (que
|
||||
// típicament encadena SET_HURT o DESTROY al YAML). last_hit_by s'actualitza
|
||||
// al decrement perquè la mort posterior atribueixi correctament el kill.
|
||||
[[nodiscard]] auto getHealth() const -> int { return health_; }
|
||||
void decrementHealth(uint8_t shooter_id = 0xFF) {
|
||||
--health_;
|
||||
last_hit_by_ = shooter_id;
|
||||
}
|
||||
|
||||
// Flash visual: brief impacto-feedback quan rep un hit no letal (HP>1).
|
||||
// Disparat per l'acció FLASH; el render alça la lluminositat mentre dura.
|
||||
void triggerFlash() { flash_timer_ = Defaults::Enemies::Visual::FLASH_DURATION; }
|
||||
|
||||
// Aplica un impulso (cambio inmediato de velocidad mass-aware) al cuerpo físico.
|
||||
void applyImpulse(const Vec2& impulse);
|
||||
|
||||
@@ -154,6 +170,16 @@ class Enemy : public Entities::Entity {
|
||||
bool wound_expired_this_frame_{false};
|
||||
uint8_t last_hit_by_{0xFF};
|
||||
|
||||
// Salut per-instància. Reseteja a config_->health a init(); el dispatcher
|
||||
// d'events la decrementa via DECREASE_HEALTH i dispara ON_NO_HEALTH quan
|
||||
// creua zero. Permet enemics tough (HP>1) sense canvis al motor.
|
||||
int health_{1};
|
||||
|
||||
// Flash visual temporitzat per a feedback d'impacte parcial (HP>1).
|
||||
// L'acció FLASH el reseteja a FLASH_DURATION; draw() alça la lluminositat
|
||||
// mentre dura, i update() el decrementa.
|
||||
float flash_timer_{0.0F};
|
||||
|
||||
// Métodos privados
|
||||
void updateAnimation(float delta_time);
|
||||
void updatePulse(float delta_time);
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace {
|
||||
if (s == "square") { return EnemyType::SQUARE; }
|
||||
if (s == "pinwheel") { return EnemyType::PINWHEEL; }
|
||||
if (s == "star") { return EnemyType::STAR; }
|
||||
if (s == "big_pentagon") { return EnemyType::BIG_PENTAGON; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -177,13 +178,24 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
// health és opcional: si el YAML no l'inclou, el default {1} de l'struct
|
||||
// ja cobreix el comportament de tots els enemics actuals (1 hit → mort).
|
||||
void parseHealth(const fkyaml::node& node, int& out) {
|
||||
if (node.contains("health")) {
|
||||
out = node["health"].get_value<int>();
|
||||
}
|
||||
}
|
||||
|
||||
auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> {
|
||||
if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
|
||||
if (s == "destroy") { return EnemyActionType::DESTROY; }
|
||||
if (s == "add_score") { return EnemyActionType::ADD_SCORE; }
|
||||
if (s == "create_debris") { return EnemyActionType::CREATE_DEBRIS; }
|
||||
if (s == "create_debris_partial") { return EnemyActionType::CREATE_DEBRIS_PARTIAL; }
|
||||
if (s == "create_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
|
||||
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
||||
if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; }
|
||||
if (s == "flash") { return EnemyActionType::FLASH; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -340,6 +352,13 @@ namespace {
|
||||
out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
|
||||
out.movement.proximity_distance = legacy.proximity_distance;
|
||||
break;
|
||||
case EnemyType::BIG_PENTAGON:
|
||||
// Sense legacy fallback: el YAML del big_pentagon ha de definir
|
||||
// ai.movement explícitament. Default chase lent perquè el switch
|
||||
// siga exhaustiu i no falli si algú omet el bloc ai.
|
||||
out.movement.type = MovementType::CHASE;
|
||||
out.movement.chase_strength = 0.3F;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,6 +390,10 @@ namespace {
|
||||
if (e.contains("on_hit") && !parseActionList(e["on_hit"], name, "on_hit", out.on_hit)) {
|
||||
return false;
|
||||
}
|
||||
if (e.contains("on_no_health") &&
|
||||
!parseActionList(e["on_no_health"], name, "on_no_health", out.on_no_health)) {
|
||||
return false;
|
||||
}
|
||||
if (e.contains("on_hurt_end") &&
|
||||
!parseActionList(e["on_hurt_end"], name, "on_hurt_end", out.on_hurt_end)) {
|
||||
return false;
|
||||
@@ -407,6 +430,7 @@ auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||
if (!parseSpawn(node, cfg.name, cfg.spawn)) { return std::nullopt; }
|
||||
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
||||
if (!parseScore(node, cfg.name, cfg.score)) { return std::nullopt; }
|
||||
parseHealth(node, cfg.health);
|
||||
if (!parseEvents(node, cfg.name, cfg.events)) { return std::nullopt; }
|
||||
if (!parseAi(node, cfg.name, cfg.ai_type, cfg.behavior, cfg.ai)) { return std::nullopt; }
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ struct EnemyConfig {
|
||||
SpawnCfg spawn;
|
||||
ColorsCfg colors;
|
||||
int score;
|
||||
// Salut inicial: per defecte 1 (un balazo → on_no_health). Els YAMLs poden
|
||||
// pujar-lo (p.ex. 10 per a un enemic tough). El sistema d'events és qui
|
||||
// decideix què passa quan la salut arriba a 0 via on_no_health.
|
||||
int health{1};
|
||||
EnemyEventConfig events;
|
||||
EnemyAiConfig ai;
|
||||
|
||||
|
||||
@@ -11,18 +11,22 @@
|
||||
#include <vector>
|
||||
|
||||
enum class EnemyEventType : uint8_t {
|
||||
ON_HIT, // Impactat per una bala
|
||||
ON_HURT_END, // Timer wounded ha expirat aquest frame
|
||||
ON_DESTROY, // L'acció destroy s'està executant (efectes col·laterals)
|
||||
ON_HIT, // Impactat per una bala
|
||||
ON_NO_HEALTH, // health ha arribat a 0 o menys aquest frame (via DECREASE_HEALTH)
|
||||
ON_HURT_END, // Timer wounded ha expirat aquest frame
|
||||
ON_DESTROY, // L'acció destroy s'està executant (efectes col·laterals)
|
||||
};
|
||||
|
||||
enum class EnemyActionType : uint8_t {
|
||||
SET_HURT, // Entra estat wounded (o destrueix si ja era wounded)
|
||||
DESTROY, // Dispara on_destroy + desactiva físicament
|
||||
ADD_SCORE, // Suma config.score al shooter + floating score
|
||||
CREATE_DEBRIS, // Explosió de debris amb herència de velocitat
|
||||
CREATE_FIREWORKS, // Burst radial de firework
|
||||
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
|
||||
SET_HURT, // Entra estat wounded (o destrueix si ja era wounded)
|
||||
DESTROY, // Dispara on_destroy + desactiva físicament
|
||||
ADD_SCORE, // Suma config.score al shooter + floating score
|
||||
CREATE_DEBRIS, // Explosió de debris amb herència de velocitat
|
||||
CREATE_DEBRIS_PARTIAL, // Debris de xip parcial (trossos a escala 0.3, per hits HP>1)
|
||||
CREATE_FIREWORKS, // Burst radial de firework
|
||||
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
|
||||
DECREASE_HEALTH, // Decrementa health_; si <=0, dispatcha ON_NO_HEALTH
|
||||
FLASH, // Flash visual breu (feedback per impacte parcial)
|
||||
};
|
||||
|
||||
struct EnemyAction {
|
||||
@@ -31,6 +35,7 @@ struct EnemyAction {
|
||||
|
||||
struct EnemyEventConfig {
|
||||
std::vector<EnemyAction> on_hit;
|
||||
std::vector<EnemyAction> on_no_health;
|
||||
std::vector<EnemyAction> on_hurt_end;
|
||||
std::vector<EnemyAction> on_destroy;
|
||||
|
||||
@@ -38,6 +43,8 @@ struct EnemyEventConfig {
|
||||
switch (event) {
|
||||
case EnemyEventType::ON_HIT:
|
||||
return on_hit;
|
||||
case EnemyEventType::ON_NO_HEALTH:
|
||||
return on_no_health;
|
||||
case EnemyEventType::ON_HURT_END:
|
||||
return on_hurt_end;
|
||||
case EnemyEventType::ON_DESTROY:
|
||||
|
||||
@@ -13,6 +13,7 @@ EnemyConfig EnemyRegistry::pentagon_config;
|
||||
EnemyConfig EnemyRegistry::square_config;
|
||||
EnemyConfig EnemyRegistry::pinwheel_config;
|
||||
EnemyConfig EnemyRegistry::star_config;
|
||||
EnemyConfig EnemyRegistry::big_pentagon_config;
|
||||
bool EnemyRegistry::loaded = false;
|
||||
|
||||
namespace {
|
||||
@@ -38,10 +39,11 @@ auto EnemyRegistry::loadAll() -> bool {
|
||||
const bool OK = loadOne("pentagon", EnemyType::PENTAGON, pentagon_config) &&
|
||||
loadOne("square", EnemyType::SQUARE, square_config) &&
|
||||
loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_config) &&
|
||||
loadOne("star", EnemyType::STAR, star_config);
|
||||
loadOne("star", EnemyType::STAR, star_config) &&
|
||||
loadOne("big_pentagon", EnemyType::BIG_PENTAGON, big_pentagon_config);
|
||||
loaded = OK;
|
||||
if (OK) {
|
||||
std::cout << "[EnemyRegistry] 4 configuracions d'enemic carregades.\n";
|
||||
std::cout << "[EnemyRegistry] 5 configuracions d'enemic carregades.\n";
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
@@ -60,6 +62,8 @@ auto EnemyRegistry::get(EnemyType type) -> const EnemyConfig& {
|
||||
return pinwheel_config;
|
||||
case EnemyType::STAR:
|
||||
return star_config;
|
||||
case EnemyType::BIG_PENTAGON:
|
||||
return big_pentagon_config;
|
||||
}
|
||||
std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n";
|
||||
std::exit(EXIT_FAILURE);
|
||||
|
||||
@@ -27,5 +27,6 @@ class EnemyRegistry {
|
||||
static EnemyConfig square_config;
|
||||
static EnemyConfig pinwheel_config;
|
||||
static EnemyConfig star_config;
|
||||
static EnemyConfig big_pentagon_config;
|
||||
static bool loaded;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user