Files
orni-attack/source/game/systems/enemy_event_dispatcher.cpp
T

147 lines
6.1 KiB
C++

// 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());
}
// 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;
const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
const Vec2 BULLET_VEL = (bullet != nullptr) ? bullet->getBody().velocity : Vec2{};
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,
BULLET_VEL,
piece_scale);
}
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);
// Pre-scan: aquest event matarà l'enemic? Si sí, l'impuls de la bala
// va directament als debris (via doCreateDebris) i NO s'aplica al cos
// — així evitem el "double-count" on els trossos hereten la velocitat
// del cos (boostat per la bala) I a més el seu propi impuls de bala.
// Regla: el bullet impacta al cos O als trossos, mai a tots dos.
bool will_die = false;
for (const auto& action : actions) {
if (action.type == EnemyActionType::DESTROY) {
will_die = true;
break;
}
if (action.type == EnemyActionType::SET_HURT && enemy.isWounded()) {
will_die = true;
break;
}
}
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) {
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:
spawnDebrisForEnemy(ctx, enemy, bullet, 1.0F);
break;
case EnemyActionType::CREATE_DEBRIS_PARTIAL:
spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE);
break;
case EnemyActionType::CREATE_FIREWORKS:
doCreateFireworks(ctx, enemy);
break;
case EnemyActionType::APPLY_IMPULSE:
if (!will_die) {
doApplyImpulse(enemy, bullet);
}
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;
}
}
}
} // namespace Systems::EnemyEvents