r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
EnemyAISystem.cpp
Go to the documentation of this file.
1///
2/// @file EnemyAISystem.cpp
3/// @brief Implementation of enemy AI and behavior system
4/// @namespace gme
5///
6
8#include "Utils/Logger.hpp"
10#include <algorithm>
11#include <chrono>
12#include <cmath>
13
14namespace gme
15{
17 : m_registry(registry), m_entityManager(entityManager), m_aggressiveness(1.0f), m_shootChance(0.0f, 1.0f)
18 {
19 auto seed = static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count());
20 m_rng.seed(seed);
21
22 utl::Logger::log("EnemyAISystem: Initialized", utl::LogLevel::INFO);
23 }
24
25 void EnemyAISystem::update(ecs::Registry &registry, float deltaTime)
26 {
27 (void)registry; // Use member registry instead
28 const auto &enemies = m_entityManager.getEnemies();
29
30 for (const auto &[enemyId, enemyEntity] : enemies)
31 {
32 const auto *metadata = m_entityManager.getEntityMetadata(enemyId);
33 if (!metadata || !metadata->isActive)
34 continue;
35
36 // Update AI based on enemy type
37 switch (metadata->type)
38 {
40 updateBasicEnemyAI(enemyId, enemyEntity, deltaTime);
41 break;
43 updateAdvancedEnemyAI(enemyId, enemyEntity, deltaTime);
44 break;
46 updateBossAI(enemyId, enemyEntity, deltaTime);
47 break;
48 default:
49 break;
50 }
51
52 // Clamp enemy to screen bounds
53 clampToScreen(enemyEntity);
54 }
55 }
56
57 void EnemyAISystem::updateBasicEnemyAI(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
58 {
59 auto *velocity = m_registry.getComponent<ecs::Velocity>(enemy);
60 if (!velocity)
61 return;
62
63 // Basic enemies move straight left
64 velocity->x = -200.0f;
65 velocity->y = 0.0f;
66
67 // Initialize movement timer if not present
68 if (m_enemyMovementTimers.find(enemyId) == m_enemyMovementTimers.end())
69 {
70 m_enemyMovementTimers[enemyId] = 0.0f;
71 }
72 m_enemyMovementTimers[enemyId] += deltaTime;
73
74 // Try to shoot occasionally
75 tryShoot(enemyId, enemy, deltaTime);
76 }
77
78 void EnemyAISystem::updateAdvancedEnemyAI(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
79 {
80 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
81 if (!transform)
82 return;
83
84 // Initialize movement timer if not present
85 if (m_enemyMovementTimers.find(enemyId) == m_enemyMovementTimers.end())
86 {
87 m_enemyMovementTimers[enemyId] = 0.0f;
88 }
89 m_enemyMovementTimers[enemyId] += deltaTime;
90
91 // Advanced enemies use sine wave movement
92 applySineWaveMovement(enemyId, enemy, deltaTime, 1.5f, 100.0f);
93
94 // Check distance to nearest player
95 float distanceToPlayer = getDistanceToNearestPlayer(transform->x, transform->y);
96
97 // If player is close, become more aggressive
98 if (distanceToPlayer < 400.0f)
99 {
100 applyAggressiveMovement(enemy, deltaTime, 100.0f);
101 }
102
103 // Try to shoot more frequently than basic enemies
104 tryShoot(enemyId, enemy, deltaTime);
105 }
106
107 void EnemyAISystem::updateBossAI(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
108 {
109 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
110 auto *velocity = m_registry.getComponent<ecs::Velocity>(enemy);
111 auto *enemyComp = m_registry.getComponent<ecs::Enemy>(enemy);
112
113 if (!transform || !velocity || !enemyComp)
114 return;
115
116 // Initialize timers if not present
117 if (m_enemyMovementTimers.find(enemyId) == m_enemyMovementTimers.end())
118 {
119 m_enemyMovementTimers[enemyId] = 0.0f;
120 }
121 if (m_bossSpreadTimers.find(enemyId) == m_bossSpreadTimers.end())
122 {
123 m_bossSpreadTimers[enemyId] = 0.0f;
124 }
125 m_enemyMovementTimers[enemyId] += deltaTime;
126 m_bossSpreadTimers[enemyId] += deltaTime;
127
128 // Boss has multiple phases based on health
129 float healthPercent = enemyComp->health / enemyComp->max_health;
130
131 if (healthPercent > 0.66f)
132 {
133 // Phase 1: Move slowly and shoot occasionally
134 velocity->x = -30.0f;
135 applySineWaveMovement(enemyId, enemy, deltaTime, 0.5f, 150.0f);
136
137 // Reduce shoot cooldown
138 enemyComp->shoot_cooldown = 1.5f;
139 }
140 else if (healthPercent > 0.33f)
141 {
142 // Phase 2: Move faster with zigzag pattern
143 applyZigzagMovement(enemyId, enemy, deltaTime);
144
145 // Shoot more frequently
146 enemyComp->shoot_cooldown = 1.0f;
147 }
148 else
149 {
150 // Phase 3: Aggressive movement and rapid fire
151 velocity->x = -50.0f;
152 applyAggressiveMovement(enemy, deltaTime, 80.0f);
153
154 // Rapid fire
155 enemyComp->shoot_cooldown = 0.5f;
156 }
157
158 // Boss always tries to shoot
159 tryShoot(enemyId, enemy, deltaTime);
160
161 // Additional boss-specific behavior
162 // Shoot multiple projectiles in a spread pattern when health is low
163 if (healthPercent < 0.33f && m_bossSpreadTimers[enemyId] >= 1.0f)
164 {
165 // Spread pattern: shoot at angles
166 for (int i = -1; i <= 1; i++)
167 {
168 if (i == 0)
169 continue; // Skip center (handled by normal shoot)
170
171 float angle = i * 15.0f * (3.14159f / 180.0f); // Convert to radians
172 float vx = -300.0f * std::cos(angle);
173 float vy = -300.0f * std::sin(angle);
174
176 transform->y, vx, vy);
177 }
178 m_bossSpreadTimers[enemyId] = 0.0f;
179 }
180 }
181
182 void EnemyAISystem::applySineWaveMovement(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime,
183 float frequency, float amplitude)
184 {
185 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
186 auto *velocity = m_registry.getComponent<ecs::Velocity>(enemy);
187
188 if (!transform || !velocity)
189 return;
190
191 // Base horizontal movement
192 float baseVelocityX = velocity->x;
193 if (baseVelocityX == 0.0f)
194 baseVelocityX = -150.0f;
195
196 // Use per-enemy timer instead of static variable
197 float time = m_enemyMovementTimers[enemyId];
198 float sineValue = std::sin(time * frequency);
199 velocity->y = sineValue * amplitude;
200 velocity->x = baseVelocityX;
201 }
202
203 void EnemyAISystem::applyAggressiveMovement(ecs::Entity enemy, float deltaTime, float speed)
204 {
205 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
206 auto *velocity = m_registry.getComponent<ecs::Velocity>(enemy);
207
208 if (!transform || !velocity)
209 return;
210
211 // Find nearest player
212 ecs::Entity nearestPlayer = findNearestPlayer(transform->x, transform->y);
213 if (nearestPlayer == ecs::INVALID_ENTITY)
214 {
215 // No player found, move left by default
216 velocity->x = -speed;
217 velocity->y = 0.0f;
218 return;
219 }
220
221 auto *playerTransform = m_registry.getComponent<ecs::Transform>(nearestPlayer);
222 if (!playerTransform)
223 return;
224
225 // Calculate direction to player
226 float dx = playerTransform->x - transform->x;
227 float dy = playerTransform->y - transform->y;
228 float distance = std::sqrt(dx * dx + dy * dy);
229
230 if (distance > 0.01f)
231 {
232 // Normalize and apply speed
233 velocity->x = (dx / distance) * speed * m_aggressiveness;
234 velocity->y = (dy / distance) * speed * m_aggressiveness;
235 }
236 }
237
238 void EnemyAISystem::applyZigzagMovement(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
239 {
240 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
241 auto *velocity = m_registry.getComponent<ecs::Velocity>(enemy);
242
243 if (!transform || !velocity)
244 return;
245
246 // Use per-enemy timer instead of static variable
247 float zigzagTime = m_enemyMovementTimers[enemyId];
248
249 // Zigzag pattern: change direction every second
250 float period = 1.5f;
251 float phase = std::fmod(zigzagTime, period * 2.0f);
252
253 velocity->x = -150.0f;
254 velocity->y = (phase < period) ? 100.0f : -100.0f;
255 }
256
257 void EnemyAISystem::tryShoot(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
258 {
259 auto *enemyComp = m_registry.getComponent<ecs::Enemy>(enemy);
260 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
261
262 if (!enemyComp || !transform)
263 return;
264
265 // Update shot timer
266 enemyComp->last_shot_time += deltaTime;
267
268 // Check if can shoot
269 if (!canShoot(enemy))
270 return;
271
272 // Random chance to shoot (to add variety)
273 float randomValue = m_shootChance(m_rng);
274 if (randomValue > 0.3f) // 70% chance to NOT shoot (30% chance to shoot)
275 return;
276
277 // Check if there's a player to shoot at
278 float distanceToPlayer = getDistanceToNearestPlayer(transform->x, transform->y);
279 if (distanceToPlayer > 1500.0f) // Don't shoot if no player is nearby
280 return;
281
282 // Shoot projectile towards the left (player direction)
283 float projectileVx = -400.0f;
284 float projectileVy = 0.0f;
285
286 // For more intelligent enemies, shoot towards nearest player
287 ecs::Entity nearestPlayer = findNearestPlayer(transform->x, transform->y);
288 if (nearestPlayer != ecs::INVALID_ENTITY)
289 {
290 auto *playerTransform = m_registry.getComponent<ecs::Transform>(nearestPlayer);
291 if (playerTransform)
292 {
293 float dx = playerTransform->x - transform->x;
294 float dy = playerTransform->y - transform->y;
295 float distance = std::sqrt(dx * dx + dy * dy);
296
297 // Only shoot towards the left (player direction) - don't shoot backwards
298 if (distance > 0.01f && dx < 0.0f)
299 {
300 // Normalize and apply projectile speed
301 float projectileSpeed = 500.0f;
302 projectileVx = (dx / distance) * projectileSpeed;
303 projectileVy = (dy / distance) * projectileSpeed;
304 }
305 else if (dx >= 0.0f)
306 {
307 // Player is behind, don't shoot
308 return;
309 }
310 }
311 }
312
313 m_entityManager.createEnemyProjectile(enemyId, transform->x - 10.0f, transform->y, projectileVx, projectileVy);
314
315 // Reset shot timer
316 enemyComp->last_shot_time = 0.0f;
317
318 // Shooting logged only for debugging (too verbose at INFO level)
319 // utl::Logger::log("EnemyAISystem: Enemy " + std::to_string(enemyId) + " fired projectile");
320 }
321
323 {
324 auto *enemyComp = m_registry.getComponent<ecs::Enemy>(enemy);
325 if (!enemyComp)
326 return false;
327
328 return enemyComp->last_shot_time >= enemyComp->shoot_cooldown;
329 }
330
332 {
333 const auto &players = m_entityManager.getPlayers();
334
335 ecs::Entity nearestPlayer = ecs::INVALID_ENTITY;
336 float nearestDistance = std::numeric_limits<float>::max();
337
338 for (const auto &[playerId, playerEntity] : players)
339 {
340 auto *transform = m_registry.getComponent<ecs::Transform>(playerEntity);
341 if (!transform)
342 continue;
343
344 float dx = transform->x - x;
345 float dy = transform->y - y;
346 float distance = std::sqrt(dx * dx + dy * dy);
347
348 if (distance < nearestDistance)
349 {
350 nearestDistance = distance;
351 nearestPlayer = playerEntity;
352 }
353 }
354
355 return nearestPlayer;
356 }
357
358 float EnemyAISystem::getDistanceToNearestPlayer(float x, float y) const
359 {
360 const auto &players = m_entityManager.getPlayers();
361
362 float nearestDistance = std::numeric_limits<float>::max();
363
364 for (const auto &[playerId, playerEntity] : players)
365 {
366 auto *transform = m_registry.getComponent<ecs::Transform>(playerEntity);
367 if (!transform)
368 continue;
369
370 float dx = transform->x - x;
371 float dy = transform->y - y;
372 float distance = std::sqrt(dx * dx + dy * dy);
373
374 if (distance < nearestDistance)
375 {
376 nearestDistance = distance;
377 }
378 }
379
380 return nearestDistance;
381 }
382
384 {
385 auto *transform = m_registry.getComponent<ecs::Transform>(enemy);
386 if (!transform)
387 return;
388
389 const float MARGIN = 50.0f;
390
391 // Clamp to screen bounds with margin
392 transform->x = std::max(-MARGIN, std::min(transform->x, utl::GameConfig::Server::SCREEN_WIDTH + MARGIN));
393 transform->y = std::max(MARGIN, std::min(transform->y, utl::GameConfig::Server::SCREEN_HEIGHT - MARGIN));
394 }
395
396} // namespace gme
Server-side enemy AI and behavior system for R-Type.
Configuration constants for the multiplayer game.
This file contains the Logger class.
Class for managing entities and their components.
Definition Registry.hpp:25
T * getComponent(Entity e)
Definition Registry.hpp:71
float getDistanceToNearestPlayer(float x, float y) const
Calculate distance to nearest player.
std::unordered_map< std::uint32_t, float > m_bossSpreadTimers
Boss spread shot timing per boss ID.
std::unordered_map< std::uint32_t, float > m_enemyMovementTimers
Movement timing per enemy ID.
EnemyAISystem(ecs::Registry &registry, EntityManager &entityManager)
Constructor.
void updateAdvancedEnemyAI(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
Update AI logic for advanced enemy type.
void applySineWaveMovement(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime, float frequency=2.0f, float amplitude=50.0f)
Apply sine wave vertical movement pattern.
void tryShoot(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
Attempt to shoot projectile at player.
ecs::Registry & m_registry
ECS registry reference.
void applyAggressiveMovement(ecs::Entity enemy, float deltaTime, float speed=150.0f)
Apply aggressive pursuit movement toward nearest player.
bool canShoot(ecs::Entity enemy) const
Check if enemy is able to shoot (cooldown and component checks)
float m_aggressiveness
Global aggressiveness multiplier (affects pursuit speed)
EntityManager & m_entityManager
Entity manager for spawning projectiles.
void update(ecs::Registry &registry, float deltaTime) override
Update the enemy AI system (called each frame)
void updateBasicEnemyAI(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
Update AI logic for basic enemy type.
ecs::Entity findNearestPlayer(float x, float y) const
Find the nearest player entity to a given position.
std::uniform_real_distribution< float > m_shootChance
Random distribution for shoot probability.
void clampToScreen(ecs::Entity enemy)
Clamp enemy position to visible screen bounds.
std::mt19937 m_rng
Random number generator for shooting patterns.
void updateBossAI(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
Update AI logic for boss enemy type.
void applyZigzagMovement(std::uint32_t enemyId, ecs::Entity enemy, float deltaTime)
Apply zigzag diagonal movement pattern.
Central entity lifecycle manager for the R-Type game server.
const std::unordered_map< std::uint32_t, ecs::Entity > & getEnemies() const
Get all enemy entities.
std::uint32_t getNetworkIdForEntity(ecs::Entity entity) const
Get network ID for an entity handle.
EntityMetadata * getEntityMetadata(std::uint32_t networkId)
Get entity metadata by network ID.
const std::unordered_map< std::uint32_t, ecs::Entity > & getPlayers() const
Get all player entities.
ecs::Entity createEnemyProjectile(std::uint32_t enemyId, float x, float y, float vx, float vy)
Create an enemy projectile entity.
static void log(const std::string &message, const LogLevel &logLevel)
Definition Logger.hpp:51
std::uint32_t Entity
Definition Entity.hpp:13
constexpr Entity INVALID_ENTITY
Definition Entity.hpp:14
@ ENEMY_ADVANCED
Advanced enemy type (complex behavior)
@ ENEMY_BASIC
Basic enemy type (simple behavior)
@ BOSS
Boss enemy (high health, special patterns)
constexpr float SCREEN_WIDTH
constexpr float SCREEN_HEIGHT
float last_shot_time