Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
EnemyAISystem.cpp
Go to the documentation of this file.
1
9
10#include <algorithm>
11#include <cmath>
12#include <limits>
13#include <unordered_map>
14
15namespace rtp::server
16{
18 // Public API
20
22 : _registry(registry) {}
23
24 void EnemyAISystem::update(float dt)
25 {
26 _time += dt;
27
28 std::unordered_map<uint32_t, float> frontX;
29 {
30 auto players = _registry.zipView<
34 >();
35 for (auto&& [tf, type, room] : players) {
36 if (type.type != net::EntityType::Player) {
37 continue;
38 }
39 auto it = frontX.find(room.id);
40 if (it == frontX.end() || tf.position.x > it->second) {
41 frontX[room.id] = tf.position.x;
42 }
43 }
44 }
45
46 auto transformsRes = _registry.get<ecs::components::Transform>();
47 auto velocitiesRes = _registry.get<ecs::components::Velocity>();
49 auto typesRes = _registry.get<ecs::components::EntityType>();
50 auto roomsRes = _registry.get<ecs::components::RoomId>();
51 if (!transformsRes || !velocitiesRes || !patternsRes || !typesRes || !roomsRes) {
52 return;
53 }
54 auto &transforms = transformsRes->get();
55 auto &velocities = velocitiesRes->get();
56 auto &patterns = patternsRes->get();
57 auto &types = typesRes->get();
58 auto &rooms = roomsRes->get();
59
60 std::unordered_map<uint32_t, std::vector<std::pair<ecs::Entity, Vec2f>>> bosses;
61 for (auto entity : transforms.entities()) {
62 if (!types.has(entity) || !rooms.has(entity)) {
63 continue;
64 }
65 const auto &type = types[entity];
66 const auto &room = rooms[entity];
67 if (type.type == net::EntityType::Boss) {
68 bosses[room.id].push_back({entity, transforms[entity].position});
69 }
70 }
71
72 constexpr float minAheadDefault = 250.0f;
73 constexpr float minAheadBoss = 500.0f;
74 constexpr float anchorDefault = 900.0f;
75 constexpr float anchorBoss = 1100.0f;
76 constexpr float followGain = 2.0f;
77
78 static std::unordered_map<ecs::Entity, ecs::Entity> shieldBoss;
79 static std::unordered_map<ecs::Entity, Vec2f> shieldOffsets;
80
81 for (auto entity : transforms.entities()) {
82 if (!velocities.has(entity) || !patterns.has(entity) ||
83 !types.has(entity) || !rooms.has(entity)) {
84 continue;
85 }
86
87 auto &tf = transforms[entity];
88 auto &vel = velocities[entity];
89 auto &pat = patterns[entity];
90 auto &type = types[entity];
91 auto &room = rooms[entity];
92 if (type.type != net::EntityType::Scout &&
93 type.type != net::EntityType::Tank &&
94 type.type != net::EntityType::Boss &&
95 type.type != net::EntityType::BossShield) {
96 continue;
97 }
98
99 if (type.type == net::EntityType::BossShield) {
100 auto itBosses = bosses.find(room.id);
101 if (itBosses == bosses.end() || itBosses->second.empty()) {
102 continue; // no boss to follow
103 }
104
105 ecs::Entity bossEntity = ecs::NullEntity;
106 Vec2f bossPos{};
107 bool bossFound = false;
108
109 auto linkIt = shieldBoss.find(entity);
110 if (linkIt != shieldBoss.end()) {
111 bossEntity = linkIt->second;
112 for (const auto& [bEntity, bPos] : itBosses->second) {
113 if (bEntity == bossEntity) {
114 bossPos = bPos;
115 bossFound = true;
116 break;
117 }
118 }
119 }
120
121 if (bossEntity == ecs::NullEntity || !bossFound) {
122 float bestDist2 = std::numeric_limits<float>::max();
123 for (const auto& [bEntity, bPos] : itBosses->second) {
124 const float dx = bPos.x - tf.position.x;
125 const float dy = bPos.y - tf.position.y;
126 const float dist2 = dx * dx + dy * dy;
127 if (dist2 < bestDist2) {
128 bestDist2 = dist2;
129 bossEntity = bEntity;
130 bossPos = bPos;
131 bossFound = true;
132 }
133 }
134 shieldBoss[entity] = bossEntity;
135 shieldOffsets[entity] = tf.position - bossPos;
136 }
137
138 auto offIt = shieldOffsets.find(entity);
139 if (offIt == shieldOffsets.end()) {
140 shieldOffsets[entity] = tf.position - bossPos;
141 offIt = shieldOffsets.find(entity);
142 }
143
144 const Vec2f targetPos = bossPos + offIt->second;
145 const Vec2f delta = targetPos - tf.position;
146 const float maxSpeed = std::max(60.0f, pat.speed);
147 vel.direction.x = std::clamp(delta.x * 2.5f, -maxSpeed, maxSpeed);
148 vel.direction.y = std::clamp(delta.y * 2.5f, -maxSpeed, maxSpeed);
149 continue; // skip default handling
150 }
151
152 if (pat.pattern != ecs::components::Patterns::Kamikaze &&
153 tf.position.x < frontX[room.id] - 100.0f) {
154 continue;
155 }
156 if (pat.pattern == ecs::components::Patterns::Kamikaze) {
157 auto it = frontX.find(room.id);
158 if (it != frontX.end()) {
159 const float dx = it->second - tf.position.x;
160 vel.direction.x = std::clamp(dx * followGain, -pat.speed, pat.speed);
161 } else {
162 vel.direction.x = 0.f;
163 }
164 } else {
165 const bool isBoss = (type.type == net::EntityType::Boss);
166 const float minAhead = isBoss ? minAheadBoss : minAheadDefault;
167 const float anchorX = isBoss ? anchorBoss : anchorDefault;
168
169 float targetX = anchorX;
170 auto it = frontX.find(room.id);
171 if (it != frontX.end()) {
172 targetX = std::max(anchorX, it->second + minAhead);
173 }
174 const float dx = targetX - tf.position.x;
175 const float maxXSpeed = std::max(60.0f, pat.speed);
176 const float desiredX = std::clamp(dx * followGain, -maxXSpeed, maxXSpeed);
177 vel.direction.x = desiredX;
178 }
179
180 switch (pat.pattern) {
182 vel.direction.y = 0.f;
183 break;
184
186 vel.direction.y = pat.amplitude * std::sin(_time * pat.frequency);
187 break;
188
190 const float zigzagPeriod = 2.0f;
191 const float phase = std::fmod(_time * pat.frequency, zigzagPeriod);
192 vel.direction.y = (phase < zigzagPeriod / 2.0f) ? pat.amplitude : -pat.amplitude;
193 break;
194 }
195
197 const float angle = _time * pat.frequency;
198 const float circleX = pat.amplitude * std::cos(angle);
199 const float circleY = pat.amplitude * std::sin(angle);
200 vel.direction.x += circleX * 4.0f;
201 vel.direction.y = circleY * 2.0f;
202 break;
203 }
204
206 float nearestDist = std::numeric_limits<float>::max();
207 Vec2f nearestPlayerPos = {tf.position.x, tf.position.y};
208
209 auto players = _registry.zipView<
213 >();
214
215 for (auto&& [pTf, pType, pRoom] : players) {
216 if (pType.type == net::EntityType::Player && pRoom.id == room.id) {
217 const float dx = pTf.position.x - tf.position.x;
218 const float dy = pTf.position.y - tf.position.y;
219 const float dist = std::sqrt(dx * dx + dy * dy);
220 if (dist < nearestDist) {
221 nearestDist = dist;
222 nearestPlayerPos = pTf.position;
223 }
224 }
225 }
226 const float dx = nearestPlayerPos.x - tf.position.x;
227 const float dy = nearestPlayerPos.y - tf.position.y;
228 const float distance = std::sqrt(dx * dx + dy * dy);
229
230 if (distance > 0.1f) {
231 vel.direction.x = (dx / distance) * pat.speed;
232 vel.direction.y = (dy / distance) * pat.speed;
233 } else {
234 vel.direction.x = 0.f;
235 vel.direction.y = 0.f;
236 }
237 break;
238 }
239
241 vel.direction.y = 0.f;
242 break;
243
244 default:
245 break;
246 }
247 }
248 }
249} // namespace rtp::server
Represents an entity in the ECS (Entity-Component-System) architecture.
Definition Entity.hpp:63
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
auto zipView(this Self &self)
ecs::Registry & _registry
Reference to the entity registry.
EnemyAISystem(ecs::Registry &registry)
Constructor for EnemyAISystem.
void update(float dt) override
Update enemy AI system logic for one frame.
float _time
Elapsed time for AI calculations.
@ StraightLine
Moves in a straight line.
@ Static
Remains stationary.
@ SineWave
Moves in a sine wave pattern.
@ Circular
Moves in a circular pattern.
@ ZigZag
Moves in a zigzag pattern.
@ Kamikaze
Moves directly towards the player.
constexpr Entity NullEntity
Definition Entity.hpp:109
File : GameManager.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue....
Component representing a movement pattern.
Component representing a network identifier for an entity.
Definition RoomId.hpp:22
Component representing position, rotation, and scale of an entity.
Definition Transform.hpp:23
Component representing a 2D velocity.
Definition Velocity.hpp:17