feat(enemy): els enemics poden disparar bales via tick d'IA
This commit is contained in:
@@ -6,8 +6,12 @@
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/types.hpp"
|
||||
#include "game/constants.hpp"
|
||||
#include "game/entities/bullet.hpp"
|
||||
#include "game/entities/bullet_config.hpp"
|
||||
#include "game/entities/bullet_registry.hpp"
|
||||
#include "game/entities/enemy.hpp"
|
||||
#include "game/entities/enemy_ai.hpp"
|
||||
#include "game/entities/enemy_config.hpp"
|
||||
@@ -165,31 +169,106 @@ namespace Systems::EnemyAi {
|
||||
}
|
||||
}
|
||||
|
||||
// SHOOT: cerca slot lliure a ctx.bullets i el dispara amb el bullet config
|
||||
// referenciat per nom (lazy-load via BulletRegistry). Angle segons aim_mode +
|
||||
// jitter. owner_id = ENEMY_OWNER_BASE + enemy_index per al filtre d'autoimpacte.
|
||||
void doShoot(Systems::Collision::Context& ctx, const Enemy& enemy, std::size_t enemy_index, const AiTickAction& action) {
|
||||
if (action.bullet_config_name.empty()) {
|
||||
return;
|
||||
}
|
||||
const BulletConfig* cfg = BulletRegistry::get(action.bullet_config_name);
|
||||
if (cfg == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Cerca slot dins la zona reservada per a enemics: així no robem
|
||||
// slots als pools de player (que iteren [0, MAX_BULLETS) i [MAX_BULLETS, 2*MAX_BULLETS)).
|
||||
Bullet* slot = nullptr;
|
||||
constexpr std::size_t START = Defaults::Entities::ENEMY_BULLET_START_IDX;
|
||||
constexpr std::size_t END = START + Defaults::Entities::MAX_ENEMY_BULLETS;
|
||||
for (std::size_t i = START; i < END; ++i) {
|
||||
if (!ctx.bullets[i].isActive()) {
|
||||
slot = &ctx.bullets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot == nullptr) {
|
||||
return; // pool d'enemic ple
|
||||
}
|
||||
|
||||
float angle = 0.0F;
|
||||
if (action.aim_mode == AimMode::AIMED) {
|
||||
const Vec2* target = findNearestShipPosition(enemy);
|
||||
if (target == nullptr) {
|
||||
// Sense ship viu: degrada a random per no congelar el dispar.
|
||||
angle = randFloat01() * 2.0F * Constants::PI;
|
||||
} else {
|
||||
const Vec2 TO = *target - enemy.getCenter();
|
||||
// angle=0 apunta amunt (eix Y negatiu SDL): atan2 + PI/2.
|
||||
angle = std::atan2(TO.y, TO.x) + (Constants::PI / 2.0F);
|
||||
}
|
||||
} else {
|
||||
angle = randFloat01() * 2.0F * Constants::PI;
|
||||
}
|
||||
if (action.jitter_rad > 0.0F) {
|
||||
angle += (randFloat01() - 0.5F) * 2.0F * action.jitter_rad;
|
||||
}
|
||||
|
||||
const auto OWNER = static_cast<uint8_t>(Defaults::Entities::ENEMY_OWNER_BASE + enemy_index);
|
||||
slot->fire(enemy.getCenter(), angle, OWNER, action.bullet_speed, cfg);
|
||||
}
|
||||
|
||||
void runMovement(Enemy& enemy, float delta_time) {
|
||||
switch (enemy.getConfig().ai.movement.type) {
|
||||
case MovementType::ZIGZAG:
|
||||
case MovementType::WANDER:
|
||||
// WANDER reusa la mecànica de canvi de direcció probabilístic;
|
||||
// l'única diferència és semàntica i el tunning dels paràmetres.
|
||||
moveZigzag(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::TRACKING:
|
||||
moveTracking(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::RECTILINEAR_PROXIMITY:
|
||||
moveRectilinearProximity(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::CHASE:
|
||||
moveChase(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::FLEE:
|
||||
moveFlee(enemy, delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void tick(Enemy& enemy, float delta_time) {
|
||||
void move(Enemy& enemy, float delta_time) {
|
||||
if (!enemy.isActive() || enemy.isWounded()) {
|
||||
return;
|
||||
}
|
||||
switch (enemy.getConfig().ai.movement.type) {
|
||||
case MovementType::ZIGZAG:
|
||||
case MovementType::WANDER:
|
||||
// WANDER reusa la mecànica de canvi de direcció probabilístic;
|
||||
// l'única diferència és semàntica i el tunning dels paràmetres.
|
||||
moveZigzag(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::TRACKING:
|
||||
moveTracking(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::RECTILINEAR_PROXIMITY:
|
||||
moveRectilinearProximity(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::CHASE:
|
||||
moveChase(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::FLEE:
|
||||
moveFlee(enemy, delta_time);
|
||||
break;
|
||||
runMovement(enemy, delta_time);
|
||||
}
|
||||
|
||||
void tick(Systems::Collision::Context& ctx, Enemy& enemy, std::size_t enemy_index, float delta_time) {
|
||||
if (!enemy.isActive() || enemy.isWounded()) {
|
||||
return;
|
||||
}
|
||||
runMovement(enemy, delta_time);
|
||||
|
||||
// Accions periòdiques: decrementa timer, dispara quan ≤0.
|
||||
auto& timers = enemy.getAiTickTimers();
|
||||
const auto& actions = enemy.getConfig().ai.tick;
|
||||
for (std::size_t i = 0; i < actions.size() && i < timers.size(); ++i) {
|
||||
timers[i] -= delta_time;
|
||||
if (timers[i] > 0.0F) {
|
||||
continue;
|
||||
}
|
||||
timers[i] = actions[i].interval;
|
||||
switch (actions[i].type) {
|
||||
case AiActionType::SHOOT:
|
||||
doShoot(ctx, enemy, enemy_index, actions[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user