feat(enemy): sistema d'HP declaratiu i nou enemic big_pentagon
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
name: big_pentagon
|
||||||
|
ai_type: big_pentagon # Validat contra el directori; mapeja a EnemyType::BIG_PENTAGON.
|
||||||
|
|
||||||
|
# Reusa la shape de pentagon però a escala 2.0 — primer enemic gegant del joc.
|
||||||
|
shape:
|
||||||
|
path: enemy_pentagon.shp
|
||||||
|
scale: 2.0
|
||||||
|
collision_factor: 1.0
|
||||||
|
|
||||||
|
physics:
|
||||||
|
mass: 20.0 # Massa gran: la bala el frena poc, els trossos volen amb la força de la bala.
|
||||||
|
speed: 30.0 # Avanç lent, com a "tanc carregant".
|
||||||
|
rotation_delta_min: 0.3
|
||||||
|
rotation_delta_max: 1.5
|
||||||
|
restitution: 1.0
|
||||||
|
linear_damping: 0.0
|
||||||
|
angular_damping: 0.0
|
||||||
|
|
||||||
|
ai:
|
||||||
|
# Persecució lenta del ship més proper: amb chase_strength baix la inèrcia
|
||||||
|
# és molt notable, fa el comportament del "boss lent" característic.
|
||||||
|
movement:
|
||||||
|
type: chase
|
||||||
|
chase_strength: 0.3
|
||||||
|
|
||||||
|
animation:
|
||||||
|
pulse:
|
||||||
|
trigger_prob_per_second: 0.01
|
||||||
|
duration_min: 1.0
|
||||||
|
duration_max: 3.0
|
||||||
|
amplitude_min: 0.08
|
||||||
|
amplitude_max: 0.20
|
||||||
|
frequency_min: 1.5
|
||||||
|
frequency_max: 3.0
|
||||||
|
rotation_accel:
|
||||||
|
trigger_prob_per_second: 0.02
|
||||||
|
duration_min: 3.0
|
||||||
|
duration_max: 8.0
|
||||||
|
multiplier_min: 0.3
|
||||||
|
multiplier_max: 4.0
|
||||||
|
|
||||||
|
wounded:
|
||||||
|
duration: 1.5 # Una mica més llarg que els altres (és un boss).
|
||||||
|
blink_hz: 10.0
|
||||||
|
|
||||||
|
spawn:
|
||||||
|
invulnerability_duration: 3.0
|
||||||
|
invulnerability_brightness_start: 0.3
|
||||||
|
invulnerability_brightness_end: 0.7
|
||||||
|
invulnerability_scale_start: 0.0
|
||||||
|
invulnerability_scale_end: 1.0
|
||||||
|
safety_distance: 72.0 # Doble del normal — és el doble de gran.
|
||||||
|
|
||||||
|
colors:
|
||||||
|
normal: [0, 180, 255] # Blau elèctric per distingir-lo del pentagon cyan.
|
||||||
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
|
score: 500 # 5x un enemic normal: aguanta 10x més.
|
||||||
|
|
||||||
|
# Estrenant el sistema HP: 10 unitats. Cada bala fa decrease_health + flash
|
||||||
|
# + create_debris_partial (xip a 0.3x). En el 10è hit, on_no_health
|
||||||
|
# encadena set_hurt (entra wounded). Wound expira → destroy → on_destroy
|
||||||
|
# fa l'explosió completa.
|
||||||
|
health: 10
|
||||||
|
|
||||||
|
events:
|
||||||
|
on_hit:
|
||||||
|
- action: decrease_health # primer: si arriba a 0 dispara on_no_health
|
||||||
|
- action: flash # feedback visual de damage parcial
|
||||||
|
- action: create_debris_partial # xip a 0.3x mida (sense ser letal)
|
||||||
|
- action: apply_impulse # empenta el cos (sense will_die és segur en hit no-letal)
|
||||||
|
on_no_health:
|
||||||
|
- action: set_hurt # entra wounded; segon hit durant wounded matarà
|
||||||
|
on_hurt_end:
|
||||||
|
- action: destroy
|
||||||
|
on_destroy:
|
||||||
|
- action: add_score
|
||||||
|
- action: create_debris # explosió completa
|
||||||
|
- action: create_fireworks
|
||||||
@@ -55,10 +55,14 @@ colors:
|
|||||||
score: 100
|
score: 100
|
||||||
|
|
||||||
events:
|
events:
|
||||||
# Comportament clàssic: dos impactes per matar (set_hurt entra wounded;
|
# HP=1 (default): decrement → on_no_health → set_hurt → wounded → mort.
|
||||||
# el segon hit detecta wounded i destrueix automàticament).
|
# decrease_health primer perquè si la mort cau aquí (segon hit durant wounded),
|
||||||
|
# el dispatcher salta la resta del chain (incloent apply_impulse) sobre el
|
||||||
|
# cos ja destruït.
|
||||||
on_hit:
|
on_hit:
|
||||||
|
- action: decrease_health
|
||||||
- action: apply_impulse
|
- action: apply_impulse
|
||||||
|
on_no_health:
|
||||||
- action: set_hurt
|
- action: set_hurt
|
||||||
on_hurt_end:
|
on_hurt_end:
|
||||||
- action: destroy
|
- action: destroy
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ colors:
|
|||||||
score: 200
|
score: 200
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
# HP=1 (default): decrement → on_no_health → set_hurt → wounded → mort.
|
||||||
on_hit:
|
on_hit:
|
||||||
|
- action: decrease_health
|
||||||
- action: apply_impulse
|
- action: apply_impulse
|
||||||
|
on_no_health:
|
||||||
- action: set_hurt
|
- action: set_hurt
|
||||||
on_hurt_end:
|
on_hurt_end:
|
||||||
- action: destroy
|
- action: destroy
|
||||||
|
|||||||
@@ -56,8 +56,11 @@ colors:
|
|||||||
score: 150
|
score: 150
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
# HP=1 (default): decrement → on_no_health → set_hurt → wounded → mort.
|
||||||
on_hit:
|
on_hit:
|
||||||
|
- action: decrease_health
|
||||||
- action: apply_impulse
|
- action: apply_impulse
|
||||||
|
on_no_health:
|
||||||
- action: set_hurt
|
- action: set_hurt
|
||||||
on_hurt_end:
|
on_hurt_end:
|
||||||
- action: destroy
|
- action: destroy
|
||||||
|
|||||||
@@ -66,8 +66,10 @@ score: 100
|
|||||||
|
|
||||||
events:
|
events:
|
||||||
# STAR: mor al primer impacte, sense passar per wounded.
|
# STAR: mor al primer impacte, sense passar per wounded.
|
||||||
|
# HP=1 (default): decrement → on_no_health → destroy directe (sense wounded).
|
||||||
on_hit:
|
on_hit:
|
||||||
- action: apply_impulse
|
- action: decrease_health
|
||||||
|
on_no_health:
|
||||||
- action: destroy
|
- action: destroy
|
||||||
on_destroy:
|
on_destroy:
|
||||||
- action: add_score
|
- action: add_score
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ metadata:
|
|||||||
description: "Progressive difficulty curve from novice to expert"
|
description: "Progressive difficulty curve from novice to expert"
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
# STAGE 1: Tutorial - Mix de tots 4 tipus al 25% per mostrar-los junts
|
# STAGE 1: Tutorial - Mix de tots 5 tipus per mostrar-los junts (inclou big_pentagon)
|
||||||
- stage_id: 1
|
- stage_id: 1
|
||||||
total_enemies: 50
|
total_enemies: 50
|
||||||
spawn_config:
|
spawn_config:
|
||||||
@@ -15,10 +15,11 @@ stages:
|
|||||||
initial_delay: 0.3
|
initial_delay: 0.3
|
||||||
spawn_interval: 0.4
|
spawn_interval: 0.4
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 25
|
pentagon: 20
|
||||||
cuadrado: 25
|
cuadrado: 20
|
||||||
molinillo: 25
|
molinillo: 20
|
||||||
star: 25
|
star: 20
|
||||||
|
big_pentagon: 20
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 0.7
|
speed_multiplier: 0.7
|
||||||
rotation_multiplier: 0.8
|
rotation_multiplier: 0.8
|
||||||
|
|||||||
@@ -16,3 +16,20 @@ namespace Defaults::Enemies::Spawn {
|
|||||||
constexpr int MAX_SPAWN_ATTEMPTS = 50;
|
constexpr int MAX_SPAWN_ATTEMPTS = 50;
|
||||||
|
|
||||||
} // namespace Defaults::Enemies::Spawn
|
} // namespace Defaults::Enemies::Spawn
|
||||||
|
|
||||||
|
namespace Defaults::Enemies::Visual {
|
||||||
|
|
||||||
|
// Duració del "flash" que dispara l'acció FLASH (feedback per impacte
|
||||||
|
// parcial en enemics HP>1). Curt: l'efecte ha de llegir-se com un cop,
|
||||||
|
// no com una transició.
|
||||||
|
constexpr float FLASH_DURATION = 0.08F;
|
||||||
|
|
||||||
|
} // namespace Defaults::Enemies::Visual
|
||||||
|
|
||||||
|
namespace Defaults::Enemies::Debris {
|
||||||
|
|
||||||
|
// Escala dels fragments per a l'acció CREATE_DEBRIS_PARTIAL (xip d'impacte
|
||||||
|
// en enemics HP>1). 0.3 = trossos petits, com de "casc esquerdat".
|
||||||
|
constexpr float PARTIAL_PIECE_SCALE = 0.3F;
|
||||||
|
|
||||||
|
} // namespace Defaults::Enemies::Debris
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ namespace Effects {
|
|||||||
float lifetime,
|
float lifetime,
|
||||||
float friction,
|
float friction,
|
||||||
int segment_multiplier,
|
int segment_multiplier,
|
||||||
const Vec2& bullet_impulse_velocity) {
|
const Vec2& bullet_impulse_velocity,
|
||||||
|
float piece_scale) {
|
||||||
if (!shape || !shape->isValid()) {
|
if (!shape || !shape->isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -85,7 +86,7 @@ namespace Effects {
|
|||||||
Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale);
|
Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale);
|
||||||
|
|
||||||
// Si el pool es ple, no té sentit continuar amb la resta de segments
|
// Si el pool es ple, no té sentit continuar amb la resta de segments
|
||||||
if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction, bullet_impulse_velocity)) {
|
if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction, bullet_impulse_velocity, piece_scale)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,26 +112,36 @@ namespace Effects {
|
|||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity) -> bool {
|
auto DebrisManager::spawnDebris(const Vec2& world_p1_in, const Vec2& world_p2_in, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity, float piece_scale) -> bool {
|
||||||
Debris* debris = findFreeSlot();
|
Debris* debris = findFreeSlot();
|
||||||
if (debris == nullptr) {
|
if (debris == nullptr) {
|
||||||
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
|
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Escala el segment al voltant del seu punt mitjà segons piece_scale
|
||||||
|
// (1.0 = original; 0.3 = "esquerda petita"). La resta del càlcul (angle,
|
||||||
|
// half_length, p1/p2) en deriva naturalment.
|
||||||
|
const Vec2 MID = {.x = (world_p1_in.x + world_p2_in.x) / 2.0F,
|
||||||
|
.y = (world_p1_in.y + world_p2_in.y) / 2.0F};
|
||||||
|
const Vec2 WORLD_P1 = {.x = MID.x + ((world_p1_in.x - MID.x) * piece_scale),
|
||||||
|
.y = MID.y + ((world_p1_in.y - MID.y) * piece_scale)};
|
||||||
|
const Vec2 WORLD_P2 = {.x = MID.x + ((world_p2_in.x - MID.x) * piece_scale),
|
||||||
|
.y = MID.y + ((world_p2_in.y - MID.y) * piece_scale)};
|
||||||
|
|
||||||
// Geometria autoritaritzada: centro + original_angle + original_half_length.
|
// Geometria autoritaritzada: centro + original_angle + original_half_length.
|
||||||
// p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades.
|
// p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades.
|
||||||
const float DX = world_p2.x - world_p1.x;
|
const float DX = WORLD_P2.x - WORLD_P1.x;
|
||||||
const float DY = world_p2.y - world_p1.y;
|
const float DY = WORLD_P2.y - WORLD_P1.y;
|
||||||
debris->centro = {.x = (world_p1.x + world_p2.x) / 2.0F,
|
debris->centro = {.x = (WORLD_P1.x + WORLD_P2.x) / 2.0F,
|
||||||
.y = (world_p1.y + world_p2.y) / 2.0F};
|
.y = (WORLD_P1.y + WORLD_P2.y) / 2.0F};
|
||||||
debris->original_angle = std::atan2(DY, DX);
|
debris->original_angle = std::atan2(DY, DX);
|
||||||
debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F;
|
debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F;
|
||||||
debris->p1 = world_p1;
|
debris->p1 = WORLD_P1;
|
||||||
debris->p2 = world_p2;
|
debris->p2 = WORLD_P2;
|
||||||
|
|
||||||
// Direcció radial (desde el centro hacia el segment)
|
// Direcció radial (desde el centro hacia el segment)
|
||||||
Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro);
|
Vec2 direccio = computeExplosionDirection(WORLD_P1, WORLD_P2, centro);
|
||||||
|
|
||||||
// Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte +
|
// Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte +
|
||||||
// velocitat de la bala escalada per BULLET_IMPULSE_FACTOR).
|
// velocitat de la bala escalada per BULLET_IMPULSE_FACTOR).
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ namespace Effects {
|
|||||||
// Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR, independent de
|
// Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR, independent de
|
||||||
// velocitat_objecte. Permet que els trossos "salten amb la força de la bala"
|
// velocitat_objecte. Permet que els trossos "salten amb la força de la bala"
|
||||||
// encara que el cos sigui pesat i amb prou feines es mogui.
|
// encara que el cos sigui pesat i amb prou feines es mogui.
|
||||||
|
// - piece_scale: multiplicador de la longitud de cada fragment al spawn
|
||||||
|
// (per defecte 1.0). Útil per a debris "parcial" d'impactes no letals
|
||||||
|
// en enemics HP>1 (trossos petits, com d'esquerda).
|
||||||
void explode(const std::shared_ptr<Graphics::Shape>& shape,
|
void explode(const std::shared_ptr<Graphics::Shape>& shape,
|
||||||
const Vec2& centro,
|
const Vec2& centro,
|
||||||
float angle,
|
float angle,
|
||||||
@@ -66,7 +69,8 @@ namespace Effects {
|
|||||||
float lifetime = Defaults::Physics::Debris::TEMPS_VIDA,
|
float lifetime = Defaults::Physics::Debris::TEMPS_VIDA,
|
||||||
float friction = Defaults::Physics::Debris::ACCELERACIO,
|
float friction = Defaults::Physics::Debris::ACCELERACIO,
|
||||||
int segment_multiplier = 1,
|
int segment_multiplier = 1,
|
||||||
const Vec2& bullet_impulse_velocity = {.x = 0.0F, .y = 0.0F});
|
const Vec2& bullet_impulse_velocity = {.x = 0.0F, .y = 0.0F},
|
||||||
|
float piece_scale = 1.0F);
|
||||||
|
|
||||||
// Actualitzar todos los fragments active
|
// Actualitzar todos los fragments active
|
||||||
void update(float delta_time);
|
void update(float delta_time);
|
||||||
@@ -103,7 +107,7 @@ namespace Effects {
|
|||||||
-> std::vector<std::pair<Vec2, Vec2>>;
|
-> std::vector<std::pair<Vec2, Vec2>>;
|
||||||
// Inicialitza un debris en un slot lliure i el deixa actiu. Retorna
|
// Inicialitza un debris en un slot lliure i el deixa actiu. Retorna
|
||||||
// false si el pool está ple (la cridadora ha d'aturar el bucle).
|
// false si el pool está ple (la cridadora ha d'aturar el bucle).
|
||||||
auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity) -> bool;
|
auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity, float piece_scale) -> bool;
|
||||||
static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular);
|
static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular);
|
||||||
static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual);
|
static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -133,6 +133,9 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|||||||
invulnerability_timer_ = cfg.spawn.invulnerability_duration;
|
invulnerability_timer_ = cfg.spawn.invulnerability_duration;
|
||||||
brightness_ = cfg.spawn.invulnerability_brightness_start;
|
brightness_ = cfg.spawn.invulnerability_brightness_start;
|
||||||
|
|
||||||
|
health_ = cfg.health;
|
||||||
|
flash_timer_ = 0.0F;
|
||||||
|
|
||||||
is_active_ = true;
|
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) {
|
if (invulnerability_timer_ > 0.0F) {
|
||||||
invulnerability_timer_ -= delta_time;
|
invulnerability_timer_ -= delta_time;
|
||||||
invulnerability_timer_ = std::max(invulnerability_timer_, 0.0F);
|
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() {
|
void Enemy::destroy() {
|
||||||
@@ -203,6 +219,7 @@ void Enemy::destroy() {
|
|||||||
wounded_timer_ = 0.0F;
|
wounded_timer_ = 0.0F;
|
||||||
wound_expired_this_frame_ = false;
|
wound_expired_this_frame_ = false;
|
||||||
last_hit_by_ = 0xFF;
|
last_hit_by_ = 0xFF;
|
||||||
|
flash_timer_ = 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::hurt(uint8_t shooter_id) {
|
void Enemy::hurt(uint8_t shooter_id) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/defaults/enemies.hpp"
|
||||||
#include "core/entities/entity.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
#include "game/entities/enemy_ai.hpp"
|
#include "game/entities/enemy_ai.hpp"
|
||||||
@@ -16,10 +17,11 @@ class Ship;
|
|||||||
|
|
||||||
// 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)
|
||||||
SQUARE = 1, // Square perseguidor (tracks ship)
|
SQUARE = 1, // Square perseguidor (tracks ship)
|
||||||
PINWHEEL = 2, // Molinillo agresivo (rápido, girando)
|
PINWHEEL = 2, // Molinillo agresivo (rápido, girando)
|
||||||
STAR = 3 // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag)
|
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.
|
// 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; }
|
void consumeWoundExpired() { wound_expired_this_frame_ = false; }
|
||||||
[[nodiscard]] auto getLastHitBy() const -> uint8_t { return last_hit_by_; }
|
[[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.
|
// Aplica un impulso (cambio inmediato de velocidad mass-aware) al cuerpo físico.
|
||||||
void applyImpulse(const Vec2& impulse);
|
void applyImpulse(const Vec2& impulse);
|
||||||
|
|
||||||
@@ -154,6 +170,16 @@ class Enemy : public Entities::Entity {
|
|||||||
bool wound_expired_this_frame_{false};
|
bool wound_expired_this_frame_{false};
|
||||||
uint8_t last_hit_by_{0xFF};
|
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
|
// Métodos privados
|
||||||
void updateAnimation(float delta_time);
|
void updateAnimation(float delta_time);
|
||||||
void updatePulse(float delta_time);
|
void updatePulse(float delta_time);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace {
|
|||||||
if (s == "square") { return EnemyType::SQUARE; }
|
if (s == "square") { return EnemyType::SQUARE; }
|
||||||
if (s == "pinwheel") { return EnemyType::PINWHEEL; }
|
if (s == "pinwheel") { return EnemyType::PINWHEEL; }
|
||||||
if (s == "star") { return EnemyType::STAR; }
|
if (s == "star") { return EnemyType::STAR; }
|
||||||
|
if (s == "big_pentagon") { return EnemyType::BIG_PENTAGON; }
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +178,24 @@ namespace {
|
|||||||
return true;
|
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> {
|
auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> {
|
||||||
if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
|
if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
|
||||||
if (s == "destroy") { return EnemyActionType::DESTROY; }
|
if (s == "destroy") { return EnemyActionType::DESTROY; }
|
||||||
if (s == "add_score") { return EnemyActionType::ADD_SCORE; }
|
if (s == "add_score") { return EnemyActionType::ADD_SCORE; }
|
||||||
if (s == "create_debris") { return EnemyActionType::CREATE_DEBRIS; }
|
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 == "create_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
|
||||||
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
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;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +352,13 @@ namespace {
|
|||||||
out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
|
out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
|
||||||
out.movement.proximity_distance = legacy.proximity_distance;
|
out.movement.proximity_distance = legacy.proximity_distance;
|
||||||
break;
|
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)) {
|
if (e.contains("on_hit") && !parseActionList(e["on_hit"], name, "on_hit", out.on_hit)) {
|
||||||
return false;
|
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") &&
|
if (e.contains("on_hurt_end") &&
|
||||||
!parseActionList(e["on_hurt_end"], name, "on_hurt_end", out.on_hurt_end)) {
|
!parseActionList(e["on_hurt_end"], name, "on_hurt_end", out.on_hurt_end)) {
|
||||||
return false;
|
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 (!parseSpawn(node, cfg.name, cfg.spawn)) { return std::nullopt; }
|
||||||
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
||||||
if (!parseScore(node, cfg.name, cfg.score)) { 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 (!parseEvents(node, cfg.name, cfg.events)) { return std::nullopt; }
|
||||||
if (!parseAi(node, cfg.name, cfg.ai_type, cfg.behavior, cfg.ai)) { 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;
|
SpawnCfg spawn;
|
||||||
ColorsCfg colors;
|
ColorsCfg colors;
|
||||||
int score;
|
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;
|
EnemyEventConfig events;
|
||||||
EnemyAiConfig ai;
|
EnemyAiConfig ai;
|
||||||
|
|
||||||
|
|||||||
@@ -11,18 +11,22 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum class EnemyEventType : uint8_t {
|
enum class EnemyEventType : uint8_t {
|
||||||
ON_HIT, // Impactat per una bala
|
ON_HIT, // Impactat per una bala
|
||||||
ON_HURT_END, // Timer wounded ha expirat aquest frame
|
ON_NO_HEALTH, // health ha arribat a 0 o menys aquest frame (via DECREASE_HEALTH)
|
||||||
ON_DESTROY, // L'acció destroy s'està executant (efectes col·laterals)
|
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 {
|
enum class EnemyActionType : uint8_t {
|
||||||
SET_HURT, // Entra estat wounded (o destrueix si ja era wounded)
|
SET_HURT, // Entra estat wounded (o destrueix si ja era wounded)
|
||||||
DESTROY, // Dispara on_destroy + desactiva físicament
|
DESTROY, // Dispara on_destroy + desactiva físicament
|
||||||
ADD_SCORE, // Suma config.score al shooter + floating score
|
ADD_SCORE, // Suma config.score al shooter + floating score
|
||||||
CREATE_DEBRIS, // Explosió de debris amb herència de velocitat
|
CREATE_DEBRIS, // Explosió de debris amb herència de velocitat
|
||||||
CREATE_FIREWORKS, // Burst radial de firework
|
CREATE_DEBRIS_PARTIAL, // Debris de xip parcial (trossos a escala 0.3, per hits HP>1)
|
||||||
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
|
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 {
|
struct EnemyAction {
|
||||||
@@ -31,6 +35,7 @@ struct EnemyAction {
|
|||||||
|
|
||||||
struct EnemyEventConfig {
|
struct EnemyEventConfig {
|
||||||
std::vector<EnemyAction> on_hit;
|
std::vector<EnemyAction> on_hit;
|
||||||
|
std::vector<EnemyAction> on_no_health;
|
||||||
std::vector<EnemyAction> on_hurt_end;
|
std::vector<EnemyAction> on_hurt_end;
|
||||||
std::vector<EnemyAction> on_destroy;
|
std::vector<EnemyAction> on_destroy;
|
||||||
|
|
||||||
@@ -38,6 +43,8 @@ struct EnemyEventConfig {
|
|||||||
switch (event) {
|
switch (event) {
|
||||||
case EnemyEventType::ON_HIT:
|
case EnemyEventType::ON_HIT:
|
||||||
return on_hit;
|
return on_hit;
|
||||||
|
case EnemyEventType::ON_NO_HEALTH:
|
||||||
|
return on_no_health;
|
||||||
case EnemyEventType::ON_HURT_END:
|
case EnemyEventType::ON_HURT_END:
|
||||||
return on_hurt_end;
|
return on_hurt_end;
|
||||||
case EnemyEventType::ON_DESTROY:
|
case EnemyEventType::ON_DESTROY:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ EnemyConfig EnemyRegistry::pentagon_config;
|
|||||||
EnemyConfig EnemyRegistry::square_config;
|
EnemyConfig EnemyRegistry::square_config;
|
||||||
EnemyConfig EnemyRegistry::pinwheel_config;
|
EnemyConfig EnemyRegistry::pinwheel_config;
|
||||||
EnemyConfig EnemyRegistry::star_config;
|
EnemyConfig EnemyRegistry::star_config;
|
||||||
|
EnemyConfig EnemyRegistry::big_pentagon_config;
|
||||||
bool EnemyRegistry::loaded = false;
|
bool EnemyRegistry::loaded = false;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -38,10 +39,11 @@ auto EnemyRegistry::loadAll() -> bool {
|
|||||||
const bool OK = loadOne("pentagon", EnemyType::PENTAGON, pentagon_config) &&
|
const bool OK = loadOne("pentagon", EnemyType::PENTAGON, pentagon_config) &&
|
||||||
loadOne("square", EnemyType::SQUARE, square_config) &&
|
loadOne("square", EnemyType::SQUARE, square_config) &&
|
||||||
loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_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;
|
loaded = OK;
|
||||||
if (OK) {
|
if (OK) {
|
||||||
std::cout << "[EnemyRegistry] 4 configuracions d'enemic carregades.\n";
|
std::cout << "[EnemyRegistry] 5 configuracions d'enemic carregades.\n";
|
||||||
}
|
}
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
@@ -60,6 +62,8 @@ auto EnemyRegistry::get(EnemyType type) -> const EnemyConfig& {
|
|||||||
return pinwheel_config;
|
return pinwheel_config;
|
||||||
case EnemyType::STAR:
|
case EnemyType::STAR:
|
||||||
return star_config;
|
return star_config;
|
||||||
|
case EnemyType::BIG_PENTAGON:
|
||||||
|
return big_pentagon_config;
|
||||||
}
|
}
|
||||||
std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n";
|
std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n";
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ class EnemyRegistry {
|
|||||||
static EnemyConfig square_config;
|
static EnemyConfig square_config;
|
||||||
static EnemyConfig pinwheel_config;
|
static EnemyConfig pinwheel_config;
|
||||||
static EnemyConfig star_config;
|
static EnemyConfig star_config;
|
||||||
|
static EnemyConfig big_pentagon_config;
|
||||||
static bool loaded;
|
static bool loaded;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,17 +129,21 @@ namespace StageSystem {
|
|||||||
|
|
||||||
// Weighted random selection based on distribution
|
// Weighted random selection based on distribution
|
||||||
int rand_val = std::rand() % 100;
|
int rand_val = std::rand() % 100;
|
||||||
|
const auto& d = config_->distribucio;
|
||||||
|
|
||||||
if (std::cmp_less(rand_val, config_->distribucio.pentagon)) {
|
if (std::cmp_less(rand_val, d.pentagon)) {
|
||||||
return EnemyType::PENTAGON;
|
return EnemyType::PENTAGON;
|
||||||
}
|
}
|
||||||
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
|
if (rand_val < d.pentagon + d.cuadrado) {
|
||||||
return EnemyType::SQUARE;
|
return EnemyType::SQUARE;
|
||||||
}
|
}
|
||||||
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado + config_->distribucio.molinillo) {
|
if (rand_val < d.pentagon + d.cuadrado + d.molinillo) {
|
||||||
return EnemyType::PINWHEEL;
|
return EnemyType::PINWHEEL;
|
||||||
}
|
}
|
||||||
return EnemyType::STAR;
|
if (rand_val < d.pentagon + d.cuadrado + d.molinillo + d.star) {
|
||||||
|
return EnemyType::STAR;
|
||||||
|
}
|
||||||
|
return EnemyType::BIG_PENTAGON;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
|
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ namespace StageSystem {
|
|||||||
|
|
||||||
// Distribució de type de enemigos (percentatges)
|
// Distribució de type de enemigos (percentatges)
|
||||||
struct DistribucioEnemics {
|
struct DistribucioEnemics {
|
||||||
uint8_t pentagon; // 0-100
|
uint8_t pentagon; // 0-100
|
||||||
uint8_t cuadrado; // 0-100
|
uint8_t cuadrado; // 0-100
|
||||||
uint8_t molinillo; // 0-100
|
uint8_t molinillo; // 0-100
|
||||||
uint8_t star{0}; // 0-100 (opcional al YAML; default 0 per compat amb stages antics)
|
uint8_t star{0}; // 0-100 (opcional al YAML; default 0 per compat amb stages antics)
|
||||||
// Suma ha de ser 100, validat en StageLoader
|
uint8_t big_pentagon{0}; // 0-100 (opcional; enemic gegant HP=10)
|
||||||
|
// Suma ha de ser 100, validat en StageLoader
|
||||||
};
|
};
|
||||||
|
|
||||||
// Multiplicadors de dificultat
|
// Multiplicadors de dificultat
|
||||||
@@ -60,7 +61,7 @@ namespace StageSystem {
|
|||||||
// el tipo; basta con confirmar que no es 0 (sentinela "sin asignar").
|
// el tipo; basta con confirmar que no es 0 (sentinela "sin asignar").
|
||||||
return stage_id >= 1 &&
|
return stage_id >= 1 &&
|
||||||
total_enemies > 0 && total_enemies <= 200 &&
|
total_enemies > 0 && total_enemies <= 200 &&
|
||||||
distribucio.pentagon + distribucio.cuadrado + distribucio.molinillo + distribucio.star == 100;
|
distribucio.pentagon + distribucio.cuadrado + distribucio.molinillo + distribucio.star + distribucio.big_pentagon == 100;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -171,11 +171,12 @@ namespace StageSystem {
|
|||||||
dist.pentagon = yaml["pentagon"].get_value<uint8_t>();
|
dist.pentagon = yaml["pentagon"].get_value<uint8_t>();
|
||||||
dist.cuadrado = yaml["cuadrado"].get_value<uint8_t>();
|
dist.cuadrado = yaml["cuadrado"].get_value<uint8_t>();
|
||||||
dist.molinillo = yaml["molinillo"].get_value<uint8_t>();
|
dist.molinillo = yaml["molinillo"].get_value<uint8_t>();
|
||||||
// 'star' és opcional per compatibilitat amb stages antics (default 0).
|
// 'star' i 'big_pentagon' són opcionals per compatibilitat amb stages antics (default 0).
|
||||||
dist.star = yaml.contains("star") ? yaml["star"].get_value<uint8_t>() : 0;
|
dist.star = yaml.contains("star") ? yaml["star"].get_value<uint8_t>() : 0;
|
||||||
|
dist.big_pentagon = yaml.contains("big_pentagon") ? yaml["big_pentagon"].get_value<uint8_t>() : 0;
|
||||||
|
|
||||||
// Validar que suma 100
|
// Validar que suma 100
|
||||||
int sum = dist.pentagon + dist.cuadrado + dist.molinillo + dist.star;
|
int sum = dist.pentagon + dist.cuadrado + dist.molinillo + dist.star + dist.big_pentagon;
|
||||||
if (sum != 100) {
|
if (sum != 100) {
|
||||||
std::cerr << "[StageLoader] Error: distribució no suma 100 (suma=" << sum << ")" << '\n';
|
std::cerr << "[StageLoader] Error: distribució no suma 100 (suma=" << sum << ")" << '\n';
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ namespace Systems::EnemyEvents {
|
|||||||
ctx.floating_score_manager.crear(POINTS, enemy.getCenter());
|
ctx.floating_score_manager.crear(POINTS, enemy.getCenter());
|
||||||
}
|
}
|
||||||
|
|
||||||
void doCreateDebris(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet) {
|
// Helper compartit per CREATE_DEBRIS i CREATE_DEBRIS_PARTIAL: única
|
||||||
|
// crida a explode(), paràmetres alineats; només canvia piece_scale
|
||||||
|
// (1.0 = explosió completa, 0.3 = xip d'esquerda).
|
||||||
|
void spawnDebrisForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet, float piece_scale) {
|
||||||
constexpr float SPEED_EXPLOSIO = 80.0F;
|
constexpr float SPEED_EXPLOSIO = 80.0F;
|
||||||
const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
|
const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
|
||||||
Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
||||||
@@ -44,7 +47,8 @@ namespace Systems::EnemyEvents {
|
|||||||
Defaults::Physics::Debris::ENEMY_LIFETIME,
|
Defaults::Physics::Debris::ENEMY_LIFETIME,
|
||||||
Defaults::Physics::Debris::ENEMY_FRICTION,
|
Defaults::Physics::Debris::ENEMY_FRICTION,
|
||||||
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER,
|
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER,
|
||||||
BULLET_VEL);
|
BULLET_VEL,
|
||||||
|
piece_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void doCreateFireworks(Systems::Collision::Context& ctx, const Enemy& enemy) {
|
void doCreateFireworks(Systems::Collision::Context& ctx, const Enemy& enemy) {
|
||||||
@@ -88,6 +92,13 @@ namespace Systems::EnemyEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& action : actions) {
|
for (const auto& action : actions) {
|
||||||
|
// Si una acció prèvia d'aquest chain ha destruït l'enemic
|
||||||
|
// (típicament DECREASE_HEALTH→ON_NO_HEALTH→SET_HURT-wounded→DESTROY),
|
||||||
|
// saltem la resta — no té sentit aplicar APPLY_IMPULSE o FLASH a un
|
||||||
|
// cos ja inactiu.
|
||||||
|
if (!enemy.isActive()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case EnemyActionType::SET_HURT:
|
case EnemyActionType::SET_HURT:
|
||||||
if (enemy.isWounded()) {
|
if (enemy.isWounded()) {
|
||||||
@@ -106,7 +117,10 @@ namespace Systems::EnemyEvents {
|
|||||||
doAddScore(ctx, enemy, shooter_id);
|
doAddScore(ctx, enemy, shooter_id);
|
||||||
break;
|
break;
|
||||||
case EnemyActionType::CREATE_DEBRIS:
|
case EnemyActionType::CREATE_DEBRIS:
|
||||||
doCreateDebris(ctx, enemy, bullet);
|
spawnDebrisForEnemy(ctx, enemy, bullet, 1.0F);
|
||||||
|
break;
|
||||||
|
case EnemyActionType::CREATE_DEBRIS_PARTIAL:
|
||||||
|
spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE);
|
||||||
break;
|
break;
|
||||||
case EnemyActionType::CREATE_FIREWORKS:
|
case EnemyActionType::CREATE_FIREWORKS:
|
||||||
doCreateFireworks(ctx, enemy);
|
doCreateFireworks(ctx, enemy);
|
||||||
@@ -116,6 +130,15 @@ namespace Systems::EnemyEvents {
|
|||||||
doApplyImpulse(enemy, bullet);
|
doApplyImpulse(enemy, bullet);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case EnemyActionType::DECREASE_HEALTH:
|
||||||
|
enemy.decrementHealth(shooter_id);
|
||||||
|
if (enemy.getHealth() <= 0) {
|
||||||
|
dispatchEvent(ctx, enemy, EnemyEventType::ON_NO_HEALTH, shooter_id, bullet);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnemyActionType::FLASH:
|
||||||
|
enemy.triggerFlash();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user