feat(enemy): sistema d'events declaratius via YAML
This commit is contained in:
@@ -53,3 +53,16 @@ colors:
|
|||||||
wounded: [255, 220, 60] # Daurat (parpelleig al rebre impacte)
|
wounded: [255, 220, 60] # Daurat (parpelleig al rebre impacte)
|
||||||
|
|
||||||
score: 100
|
score: 100
|
||||||
|
|
||||||
|
events:
|
||||||
|
# Comportament clàssic: dos impactes per matar (set_hurt entra wounded;
|
||||||
|
# el segon hit detecta wounded i destrueix automàticament).
|
||||||
|
on_hit:
|
||||||
|
- action: apply_impulse
|
||||||
|
- action: set_hurt
|
||||||
|
on_hurt_end:
|
||||||
|
- action: destroy
|
||||||
|
on_destroy:
|
||||||
|
- action: add_score
|
||||||
|
- action: create_debris
|
||||||
|
- action: create_fireworks
|
||||||
|
|||||||
@@ -53,3 +53,14 @@ colors:
|
|||||||
wounded: [255, 220, 60]
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
score: 200
|
score: 200
|
||||||
|
|
||||||
|
events:
|
||||||
|
on_hit:
|
||||||
|
- action: apply_impulse
|
||||||
|
- action: set_hurt
|
||||||
|
on_hurt_end:
|
||||||
|
- action: destroy
|
||||||
|
on_destroy:
|
||||||
|
- action: add_score
|
||||||
|
- action: create_debris
|
||||||
|
- action: create_fireworks
|
||||||
|
|||||||
@@ -53,3 +53,14 @@ colors:
|
|||||||
wounded: [255, 220, 60]
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
score: 150
|
score: 150
|
||||||
|
|
||||||
|
events:
|
||||||
|
on_hit:
|
||||||
|
- action: apply_impulse
|
||||||
|
- action: set_hurt
|
||||||
|
on_hurt_end:
|
||||||
|
- action: destroy
|
||||||
|
on_destroy:
|
||||||
|
- action: add_score
|
||||||
|
- action: create_debris
|
||||||
|
- action: create_fireworks
|
||||||
|
|||||||
@@ -53,3 +53,13 @@ colors:
|
|||||||
wounded: [255, 220, 60]
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
score: 100
|
score: 100
|
||||||
|
|
||||||
|
events:
|
||||||
|
# STAR: mor al primer impacte, sense passar per wounded.
|
||||||
|
on_hit:
|
||||||
|
- action: apply_impulse
|
||||||
|
- action: destroy
|
||||||
|
on_destroy:
|
||||||
|
- action: add_score
|
||||||
|
- action: create_debris
|
||||||
|
- action: create_fireworks
|
||||||
|
|||||||
@@ -177,6 +177,79 @@ namespace {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
|
||||||
|
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseActionList(const fkyaml::node& list_node, const std::string& enemy_name, const char* event_name, std::vector<EnemyAction>& out) -> bool {
|
||||||
|
if (!list_node.is_sequence()) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: '" << event_name << "' ha de ser una llista a "
|
||||||
|
<< enemy_name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& item : list_node) {
|
||||||
|
if (!item.contains("action")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: entrada sense 'action' a " << event_name
|
||||||
|
<< " (" << enemy_name << ")\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto STR = item["action"].get_value<std::string>();
|
||||||
|
const auto PARSED = actionTypeFromString(STR);
|
||||||
|
if (!PARSED) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: acció desconeguda '" << STR << "' a "
|
||||||
|
<< event_name << " (" << enemy_name << ")\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.push_back({*PARSED});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults: replica el flux hardcoded actual (set_hurt → destroy → score+debris+fireworks).
|
||||||
|
void fillLegacyDefaults(EnemyEventConfig& events) {
|
||||||
|
events.on_hit = {{EnemyActionType::SET_HURT}};
|
||||||
|
events.on_hurt_end = {{EnemyActionType::DESTROY}};
|
||||||
|
events.on_destroy = {
|
||||||
|
{EnemyActionType::ADD_SCORE},
|
||||||
|
{EnemyActionType::CREATE_DEBRIS},
|
||||||
|
{EnemyActionType::CREATE_FIREWORKS},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseEvents(const fkyaml::node& node, const std::string& name, EnemyEventConfig& out) -> bool {
|
||||||
|
if (!node.contains("events")) {
|
||||||
|
fillLegacyDefaults(out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto& e = node["events"];
|
||||||
|
if (e.contains("on_hit") && !parseActionList(e["on_hit"], name, "on_hit", out.on_hit)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (e.contains("on_hurt_end") &&
|
||||||
|
!parseActionList(e["on_hurt_end"], name, "on_hurt_end", out.on_hurt_end)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (e.contains("on_destroy") &&
|
||||||
|
!parseActionList(e["on_destroy"], name, "on_destroy", out.on_destroy)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Validació: destroy no pot aparèixer dins on_destroy (recursió infinita).
|
||||||
|
for (const auto& a : out.on_destroy) {
|
||||||
|
if (a.type == EnemyActionType::DESTROY) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: 'destroy' no pot aparèixer dins 'on_destroy' a "
|
||||||
|
<< name << " (recursió infinita)\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||||
@@ -194,6 +267,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; }
|
||||||
|
if (!parseEvents(node, cfg.name, cfg.events)) { return std::nullopt; }
|
||||||
|
|
||||||
return cfg;
|
return cfg;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
#include "game/entities/enemy.hpp" // EnemyType
|
#include "game/entities/enemy.hpp" // EnemyType
|
||||||
|
#include "game/entities/enemy_event.hpp"
|
||||||
|
|
||||||
struct EnemyConfig {
|
struct EnemyConfig {
|
||||||
struct ShapeCfg {
|
struct ShapeCfg {
|
||||||
@@ -98,6 +99,7 @@ struct EnemyConfig {
|
|||||||
SpawnCfg spawn;
|
SpawnCfg spawn;
|
||||||
ColorsCfg colors;
|
ColorsCfg colors;
|
||||||
int score;
|
int score;
|
||||||
|
EnemyEventConfig events;
|
||||||
|
|
||||||
// Parseja un descriptor d'enemic. expected_ai_type valida que ai_type del
|
// Parseja un descriptor d'enemic. expected_ai_type valida que ai_type del
|
||||||
// YAML coincideix amb el tipus que el caller espera (segons el directori).
|
// YAML coincideix amb el tipus que el caller espera (segons el directori).
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// enemy_event.hpp - Sistema declaratiu d'events i accions per a enemics
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Cada enemic descriu al seu YAML què passa quan rep un event (on_hit,
|
||||||
|
// on_hurt_end, on_destroy) com a llista d'accions. El motor només dispatcha;
|
||||||
|
// el comportament viu a les dades.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#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)
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EnemyAction {
|
||||||
|
EnemyActionType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EnemyEventConfig {
|
||||||
|
std::vector<EnemyAction> on_hit;
|
||||||
|
std::vector<EnemyAction> on_hurt_end;
|
||||||
|
std::vector<EnemyAction> on_destroy;
|
||||||
|
|
||||||
|
[[nodiscard]] auto getActions(EnemyEventType event) const -> const std::vector<EnemyAction>& {
|
||||||
|
switch (event) {
|
||||||
|
case EnemyEventType::ON_HIT:
|
||||||
|
return on_hit;
|
||||||
|
case EnemyEventType::ON_HURT_END:
|
||||||
|
return on_hurt_end;
|
||||||
|
case EnemyEventType::ON_DESTROY:
|
||||||
|
return on_destroy;
|
||||||
|
}
|
||||||
|
return on_hit; // unreachable
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -9,61 +9,11 @@
|
|||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
#include "game/constants.hpp"
|
#include "game/constants.hpp"
|
||||||
#include "game/entities/bullet_config.hpp"
|
#include "game/entities/bullet_config.hpp"
|
||||||
#include "game/entities/enemy_config.hpp"
|
#include "game/systems/enemy_event_dispatcher.hpp"
|
||||||
|
|
||||||
namespace Systems::Collision {
|
namespace Systems::Collision {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t NO_SHOOTER = 0xFF;
|
|
||||||
|
|
||||||
// Mata al enemy con explosión: floating score, debris con velocity heredada,
|
|
||||||
// sonido. Si shooter_id ≠ NO_SHOOTER, suma puntos a ese jugador.
|
|
||||||
// CRUCIAL: leer velocity/datos ANTES de destruir() (que zera la velocity).
|
|
||||||
void explodeNow(Context& ctx, Enemy& enemy, uint8_t shooter_id) {
|
|
||||||
const Vec2 ENEMY_POS = enemy.getCenter();
|
|
||||||
const Vec2 ENEMY_VEL = enemy.getVelocityVector();
|
|
||||||
const float BRIGHTNESS = enemy.getBrightness();
|
|
||||||
const auto SHAPE = enemy.getShape();
|
|
||||||
|
|
||||||
const int POINTS = enemy.getConfig().score;
|
|
||||||
const SDL_Color COLOR = enemy.getConfig().colors.normal;
|
|
||||||
const SDL_Color WOUNDED_COLOR = enemy.getConfig().colors.wounded;
|
|
||||||
|
|
||||||
if (shooter_id != NO_SHOOTER) {
|
|
||||||
ctx.score_per_player[shooter_id] += POINTS;
|
|
||||||
}
|
|
||||||
ctx.floating_score_manager.crear(POINTS, ENEMY_POS);
|
|
||||||
|
|
||||||
enemy.destroy();
|
|
||||||
|
|
||||||
constexpr float SPEED_EXPLOSIO = 80.0F; // px/s (explosión suave)
|
|
||||||
const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
|
||||||
ctx.debris_manager.explode(
|
|
||||||
SHAPE,
|
|
||||||
ENEMY_POS,
|
|
||||||
0.0F, // angle (rotación interna del enemy)
|
|
||||||
1.0F, // escala
|
|
||||||
SPEED_EXPLOSIO,
|
|
||||||
BRIGHTNESS,
|
|
||||||
INHERITED_VEL,
|
|
||||||
0.0F, // sense herència angular: evita que els 5 trossos curvin en bloc
|
|
||||||
0.0F, // sin herencia visual
|
|
||||||
Defaults::Sound::EXPLOSION,
|
|
||||||
COLOR,
|
|
||||||
Defaults::Physics::Debris::ENEMY_LIFETIME,
|
|
||||||
Defaults::Physics::Debris::ENEMY_FRICTION,
|
|
||||||
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);
|
|
||||||
|
|
||||||
// Firework burst radial des del centro de l'enemic (efecte adicional al debris).
|
|
||||||
// Línia blanca + halo daurat (WOUNDED) per a feel d'espurnes.
|
|
||||||
ctx.firework_manager.spawn(ENEMY_POS,
|
|
||||||
Defaults::FX::Firework::DEFAULT_COLOR,
|
|
||||||
Defaults::FX::Firework::SPEED,
|
|
||||||
Defaults::FX::Firework::N_POINTS,
|
|
||||||
Defaults::FX::Firework::INITIAL_BRIGHTNESS,
|
|
||||||
/*glow=*/true,
|
|
||||||
WOUNDED_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trenca una bala en debris (8 fragments de l'octàgon) + so HIT + desactiva.
|
// Trenca una bala en debris (8 fragments de l'octàgon) + so HIT + desactiva.
|
||||||
// S'invoca des de qualsevol desactivació de bala (impacte amb enemic, amb jugador,
|
// S'invoca des de qualsevol desactivació de bala (impacte amb enemic, amb jugador,
|
||||||
@@ -102,26 +52,11 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *** COLISIÓN bullet → enemy ***
|
// *** COLISIÓN bullet → enemy ***
|
||||||
// Empuje físico cuasi-realista: el impulse és el moment de la bala
|
// La cadena d'efectes (impulse, hurt, destroy, debris, score...) viu
|
||||||
// (m·v) multiplicat pel factor de transferència. Direcció = vector
|
// al YAML de l'enemic via la secció `events:`. Aquí només dispatchem.
|
||||||
// velocity de la bala (cap a on viatjava).
|
Systems::EnemyEvents::dispatchEvent(ctx, enemy, EnemyEventType::ON_HIT, bullet.getOwnerId(), &bullet);
|
||||||
const Vec2 IMPULSE = bullet.getBody().velocity *
|
|
||||||
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
|
|
||||||
enemy.applyImpulse(IMPULSE);
|
|
||||||
|
|
||||||
const uint8_t SHOOTER = bullet.getOwnerId();
|
|
||||||
|
|
||||||
if (enemy.isWounded()) {
|
|
||||||
// Segundo impacto sobre enemy ya herido → muerte instantánea,
|
|
||||||
// puntos al nuevo shooter.
|
|
||||||
explodeNow(ctx, enemy, SHOOTER);
|
|
||||||
} else {
|
|
||||||
// Primer impacto → entra en estado herido (explosión diferida).
|
|
||||||
enemy.hurt(SHOOTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
breakBullet(ctx.debris_manager, bullet);
|
breakBullet(ctx.debris_manager, bullet);
|
||||||
break; // Una bala impacta a un enemy y muere
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +67,7 @@ namespace Systems::Collision {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
enemy.consumeWoundExpired();
|
enemy.consumeWoundExpired();
|
||||||
explodeNow(ctx, enemy, enemy.getLastHitBy());
|
Systems::EnemyEvents::dispatchEvent(ctx, enemy, EnemyEventType::ON_HURT_END, enemy.getLastHitBy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// enemy_event_dispatcher.cpp - Implementació del dispatcher d'events d'enemic
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#include "game/systems/enemy_event_dispatcher.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "core/defaults.hpp"
|
||||||
|
#include "core/types.hpp"
|
||||||
|
#include "game/entities/bullet.hpp"
|
||||||
|
#include "game/entities/bullet_config.hpp"
|
||||||
|
#include "game/entities/enemy_config.hpp"
|
||||||
|
|
||||||
|
namespace Systems::EnemyEvents {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr uint8_t NO_SHOOTER = 0xFF;
|
||||||
|
|
||||||
|
void doAddScore(Systems::Collision::Context& ctx, const Enemy& enemy, uint8_t shooter) {
|
||||||
|
const int POINTS = enemy.getConfig().score;
|
||||||
|
if (shooter != NO_SHOOTER) {
|
||||||
|
ctx.score_per_player[shooter] += POINTS;
|
||||||
|
}
|
||||||
|
ctx.floating_score_manager.crear(POINTS, enemy.getCenter());
|
||||||
|
}
|
||||||
|
|
||||||
|
void doCreateDebris(Systems::Collision::Context& ctx, const Enemy& enemy) {
|
||||||
|
constexpr float SPEED_EXPLOSIO = 80.0F;
|
||||||
|
const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
|
||||||
|
Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
||||||
|
ctx.debris_manager.explode(
|
||||||
|
enemy.getShape(),
|
||||||
|
enemy.getCenter(),
|
||||||
|
0.0F,
|
||||||
|
1.0F,
|
||||||
|
SPEED_EXPLOSIO,
|
||||||
|
enemy.getBrightness(),
|
||||||
|
INHERITED_VEL,
|
||||||
|
0.0F,
|
||||||
|
0.0F,
|
||||||
|
Defaults::Sound::EXPLOSION,
|
||||||
|
enemy.getConfig().colors.normal,
|
||||||
|
Defaults::Physics::Debris::ENEMY_LIFETIME,
|
||||||
|
Defaults::Physics::Debris::ENEMY_FRICTION,
|
||||||
|
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void doCreateFireworks(Systems::Collision::Context& ctx, const Enemy& enemy) {
|
||||||
|
ctx.firework_manager.spawn(enemy.getCenter(),
|
||||||
|
Defaults::FX::Firework::DEFAULT_COLOR,
|
||||||
|
Defaults::FX::Firework::SPEED,
|
||||||
|
Defaults::FX::Firework::N_POINTS,
|
||||||
|
Defaults::FX::Firework::INITIAL_BRIGHTNESS,
|
||||||
|
/*glow=*/true,
|
||||||
|
enemy.getConfig().colors.wounded);
|
||||||
|
}
|
||||||
|
|
||||||
|
void doApplyImpulse(Enemy& enemy, const Bullet* bullet) {
|
||||||
|
if (bullet == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Vec2 IMPULSE = bullet->getBody().velocity *
|
||||||
|
(bullet->getBody().mass * bullet->getConfig().physics.impact_momentum_factor);
|
||||||
|
enemy.applyImpulse(IMPULSE);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) {
|
||||||
|
const auto& actions = enemy.getConfig().events.getActions(event);
|
||||||
|
for (const auto& action : actions) {
|
||||||
|
switch (action.type) {
|
||||||
|
case EnemyActionType::SET_HURT:
|
||||||
|
if (enemy.isWounded()) {
|
||||||
|
// Segon hit sobre wounded → mort immediata (regla 2-hits).
|
||||||
|
dispatchEvent(ctx, enemy, EnemyEventType::ON_DESTROY, shooter_id, bullet);
|
||||||
|
enemy.destroy();
|
||||||
|
} else {
|
||||||
|
enemy.hurt(shooter_id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnemyActionType::DESTROY:
|
||||||
|
dispatchEvent(ctx, enemy, EnemyEventType::ON_DESTROY, shooter_id, bullet);
|
||||||
|
enemy.destroy();
|
||||||
|
break;
|
||||||
|
case EnemyActionType::ADD_SCORE:
|
||||||
|
doAddScore(ctx, enemy, shooter_id);
|
||||||
|
break;
|
||||||
|
case EnemyActionType::CREATE_DEBRIS:
|
||||||
|
doCreateDebris(ctx, enemy);
|
||||||
|
break;
|
||||||
|
case EnemyActionType::CREATE_FIREWORKS:
|
||||||
|
doCreateFireworks(ctx, enemy);
|
||||||
|
break;
|
||||||
|
case EnemyActionType::APPLY_IMPULSE:
|
||||||
|
doApplyImpulse(enemy, bullet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Systems::EnemyEvents
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// enemy_event_dispatcher.hpp - Executa les accions YAML d'un event d'enemic
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Mira la llista d'EnemyAction associada a l'event al config de l'enemic i les
|
||||||
|
// executa una per una. L'acció DESTROY dispara recursivament ON_DESTROY abans
|
||||||
|
// de desactivar físicament l'enemic (el parser garanteix que ON_DESTROY no
|
||||||
|
// conté DESTROY, evitant recursió infinita).
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "game/entities/enemy_event.hpp"
|
||||||
|
#include "game/systems/collision_system.hpp"
|
||||||
|
|
||||||
|
namespace Systems::EnemyEvents {
|
||||||
|
|
||||||
|
// shooter_id: id del jugador que ha disparat (0xFF = sense atribució).
|
||||||
|
// bullet: punter opcional a la bala que ha causat l'event (usat per APPLY_IMPULSE);
|
||||||
|
// nullptr per a events no derivats d'una bala (on_hurt_end).
|
||||||
|
void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet = nullptr);
|
||||||
|
|
||||||
|
} // namespace Systems::EnemyEvents
|
||||||
Reference in New Issue
Block a user