Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
CollisionSystem.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** Air-Trap
4** File description:
5**
6 * CollisionSystem
7*/
8
10
11#include "RType/Logger.hpp"
13
14#include <algorithm>
15#include <cmath>
16#include <cstdlib>
17#include <cstring>
18#include <vector>
19
20namespace rtp::server
21{
22 namespace {
23 int getKillScore(net::EntityType type)
24 {
25 switch (type) {
26 case net::EntityType::Scout: return 10;
27 case net::EntityType::Tank: return 25;
28 case net::EntityType::Boss: return 100;
29 case net::EntityType::Boss2: return 200;
30 case net::EntityType::Obstacle: return 5;
31 case net::EntityType::ObstacleSolid: return 15;
32 default: return 0;
33 }
34 }
35 } // namespace
36
38 // Public API
41 RoomSystem &roomSystem,
42 NetworkSyncSystem &networkSync)
43 : _registry(registry)
44 , _roomSystem(roomSystem)
45 , _networkSync(networkSync)
46 {
47 }
48
50 {
51 (void)dt;
52
53 auto transformsRes = _registry.get<ecs::components::Transform>();
55 auto typesRes = _registry.get<ecs::components::EntityType>();
56 auto roomsRes = _registry.get<ecs::components::RoomId>();
57 auto healthRes = _registry.get<ecs::components::Health>();
59 auto powerupRes = _registry.get<ecs::components::Powerup>();
60 auto damageRes = _registry.get<ecs::components::Damage>();
61
62 if (!transformsRes ||
63 !boxesRes ||
64 !typesRes ||
65 !roomsRes ||
66 !healthRes ||
67 !speedRes ||
68 !powerupRes ||
69 !damageRes) {
70 return;
71 }
72
73 auto &transforms = transformsRes->get();
74 auto &boxes = boxesRes->get();
75 auto &types = typesRes->get();
76 auto &rooms = roomsRes->get();
77 auto &healths = healthRes->get();
78 auto &speeds = speedRes->get();
79 auto &powerups = powerupRes->get();
80 auto &damages = damageRes->get();
81
82 // Optional components - only some entities have them
83 auto velocitiesRes = _registry.get<ecs::components::Velocity>();
84 auto *velocities = velocitiesRes ? &velocitiesRes->get() : nullptr;
85
86 auto shieldsRes = _registry.get<ecs::components::Shield>();
87 auto doubleFiresRes = _registry.get<ecs::components::DoubleFire>();
88
89 std::unordered_set<uint32_t> removed;
90 std::vector<std::pair<ecs::Entity, uint32_t>> pending;
91 std::vector<ecs::Entity> players;
92 std::vector<ecs::Entity> enemies;
93 std::vector<ecs::Entity> obstacles;
94 std::vector<ecs::Entity> bullets;
95 std::vector<ecs::Entity> enemyBullets;
96 std::vector<ecs::Entity> powerupEntities;
97
98 auto markForDespawn = [&](ecs::Entity entity, uint32_t roomId) {
99 if (removed.find(entity.index()) != removed.end()) {
100 return;
101 }
102 removed.insert(entity.index());
103 pending.emplace_back(entity, roomId);
104 };
105
106 auto updatePlayerScore = [&](uint32_t roomId, ecs::Entity playerEntity, int delta) {
107 if (delta == 0 || playerEntity == ecs::NullEntity) {
108 return;
109 }
110 auto room = _roomSystem.getRoom(roomId);
111 if (!room) {
112 return;
113 }
114 const auto playersInRoom = room->getPlayers();
115 for (const auto &player : playersInRoom) {
116 if (!player) {
117 continue;
118 }
119 if (player->getEntityId() != static_cast<uint32_t>(playerEntity.index())) {
120 continue;
121 }
122 player->addScore(delta);
124 net::ScoreUpdatePayload payload{player->getScore()};
125 packet << payload;
127 break;
128 }
129 };
130
131 auto sendPlayerHealth = [&](uint32_t roomId, ecs::Entity playerEntity, const ecs::components::Health &health) {
132 auto room = _roomSystem.getRoom(roomId);
133 if (!room) {
134 return;
135 }
136 const auto playersInRoom = room->getPlayers();
137 for (const auto &player : playersInRoom) {
138 if (!player) {
139 continue;
140 }
141 if (player->getEntityId() != static_cast<uint32_t>(playerEntity.index())) {
142 continue;
143 }
145 net::HealthUpdatePayload payload{health.currentHealth, health.maxHealth};
146 packet << payload;
148 break;
149 }
150 };
151
152 auto sendGameOverIfNeeded = [&](const std::shared_ptr<Room> &room) {
153 if (!room) {
154 return;
155 }
156 if (room->getState() != Room::State::InGame) {
157 return;
158 }
159 if (room->hasActivePlayers()) {
160 return;
161 }
162
163 room->forceFinishGame();
164
165 const auto playersInRoom = room->getPlayers();
166 if (playersInRoom.empty()) {
167 return;
168 }
169
170 std::string bestName;
171 int bestScore = 0;
172 bool bestSet = false;
173
174 for (const auto &player : playersInRoom) {
175 if (!player) {
176 continue;
177 }
178 const int score = player->getScore();
179 if (!bestSet || score > bestScore) {
180 bestSet = true;
181 bestScore = score;
182 bestName = player->getUsername();
183 }
184 }
185
186 for (const auto &player : playersInRoom) {
187 if (!player) {
188 continue;
189 }
191 net::GameOverPayload payload{};
192 std::strncpy(payload.bestPlayer, bestName.c_str(), sizeof(payload.bestPlayer) - 1);
193 payload.bestPlayer[sizeof(payload.bestPlayer) - 1] = '\0';
194 payload.bestScore = bestScore;
195 payload.playerScore = player->getScore();
196 packet << payload;
198 }
199 };
200
201 for (auto entity : types.entities()) {
202 if (!types.has(entity)) {
203 continue;
204 }
205 const auto type = types[entity].type;
206 if (type == net::EntityType::Player) {
207 if (transforms.has(entity) &&
208 boxes.has(entity) &&
209 rooms.has(entity) &&
210 healths.has(entity) &&
211 speeds.has(entity)) {
212 players.push_back(entity);
213 }
214 } else if (type ==
216 type ==
218 type ==
220 type ==
222 type ==
224 if (transforms.has(entity) &&
225 boxes.has(entity) &&
226 rooms.has(entity) &&
227 healths.has(entity)) {
228 enemies.push_back(entity);
229 }
230 } else if (type ==
233 if (transforms.has(entity) &&
234 boxes.has(entity) &&
235 rooms.has(entity) &&
236 healths.has(entity)) {
237 obstacles.push_back(entity);
238 }
239 } else if (type ==
242 if (transforms.has(entity) &&
243 boxes.has(entity) &&
244 rooms.has(entity) &&
245 damages.has(entity)) {
246 bullets.push_back(entity);
247 }
248 } else if (type == net::EntityType::EnemyBullet) {
249 if (transforms.has(entity) &&
250 boxes.has(entity) &&
251 rooms.has(entity) &&
252 damages.has(entity)) {
253 enemyBullets.push_back(entity);
254 }
255 } else if (type ==
260 if (transforms.has(entity) &&
261 boxes.has(entity) &&
262 rooms.has(entity) &&
263 powerups.has(entity)) {
264 powerupEntities.push_back(entity);
265 }
266 }
267 }
268
269 for (auto player : players) {
270 auto &ptf = transforms[player];
271 auto &pbox = boxes[player];
272 auto &proom = rooms[player];
273 auto &health = healths[player];
274 auto &speed = speeds[player];
275 auto *pvel = (velocities && velocities->has(player)) ? &(*velocities)[player] : nullptr;
276
277 for (auto powerEntity : powerupEntities) {
278 if (removed.find(powerEntity.index()) != removed.end()) {
279 continue;
280 }
281 if (rooms[powerEntity].id != proom.id) {
282 continue;
283 }
284
285 const auto &tf = transforms[powerEntity];
286 const auto &box = boxes[powerEntity];
287 if (!overlaps(ptf, pbox, tf, box)) {
288 continue;
289 }
290
291 const auto &powerup = powerups[powerEntity];
292 log::info("=== Power-up collision! Type: {} ===", static_cast<int>(powerup.type));
293
294 if (powerup.type == ecs::components::PowerupType::Heal) {
295 // Red powerup - heal 1 heart (only if not at max health)
296 int oldHealth = health.currentHealth;
297 if (health.currentHealth < health.maxHealth) {
298 health.currentHealth = std::min(
299 health.maxHealth,
300 health.currentHealth + 1);
301 sendPlayerHealth(proom.id, player, health);
302 log::info("✚ HEAL: {} HP → {} HP (max: {})", oldHealth, health.currentHealth, health.maxHealth);
303 } else {
304 log::info("✚ HEAL: Already at max health ({}/{}), not applied", health.currentHealth, health.maxHealth);
305 }
306 } else if (powerup.type == ecs::components::PowerupType::Speed) {
307 speed.multiplier =
308 std::max(speed.multiplier, powerup.value);
309 speed.boostRemaining =
310 std::max(speed.boostRemaining, powerup.duration);
311 log::info("⚡ SPEED: Multiplier={}, Duration={}s", speed.multiplier, speed.boostRemaining);
312 } else if (powerup.type == ecs::components::PowerupType::DoubleFire) {
313 // Cyan powerup - double fire for 20 seconds
314 if (doubleFiresRes) {
315 auto& doubleFires = doubleFiresRes->get();
316 if (!doubleFires.has(player)) {
318 log::info("🔫 DOUBLE FIRE: Added component (20s duration)");
319 } else {
320 doubleFires[player].remainingTime = 20.0f; // Reset timer
321 log::info("🔫 DOUBLE FIRE: Reset timer to 20s");
322 }
323 } else {
324 log::warning("DoubleFire component storage not available!");
325 }
326 } else if (powerup.type == ecs::components::PowerupType::Shield) {
327 // Green powerup - shield that absorbs 1 hit
328 if (shieldsRes) {
329 auto& shields = shieldsRes->get();
330 if (!shields.has(player)) {
332 log::info("🛡️ SHIELD: Added component (1 charge)");
333 } else {
334 shields[player].charges = 1; // Reset to 1 charge
335 log::info("🛡️ SHIELD: Reset to 1 charge");
336 }
337 } else {
338 log::warning("Shield component storage not available!");
339 }
340 }
341
342 log::info("Despawning power-up entity {}", powerEntity.index());
343 markForDespawn(powerEntity, proom.id);
344 }
345 }
346
347 for (auto player : players) {
348 auto &ptf = transforms[player];
349 auto &pbox = boxes[player];
350 auto &proom = rooms[player];
351 auto *pvel = (velocities && velocities->has(player)) ? &(*velocities)[player] : nullptr;
352
353 for (auto obstacle : obstacles) {
354 if (rooms[obstacle].id != proom.id) {
355 continue;
356 }
357 const auto &otf = transforms[obstacle];
358 const auto &obox = boxes[obstacle];
359 if (!overlaps(ptf, pbox, otf, obox)) {
360 continue;
361 }
362
363 const float px = ptf.position.x;
364 const float py = ptf.position.y;
365 const float pw = pbox.width;
366 const float ph = pbox.height;
367
368 const float ox = otf.position.x;
369 const float oy = otf.position.y;
370 const float ow = obox.width;
371 const float oh = obox.height;
372
373 const float overlapX1 = (px + pw) - ox;
374 const float overlapX2 = (ox + ow) - px;
375 const float overlapY1 = (py + ph) - oy;
376 const float overlapY2 = (oy + oh) - py;
377
378 const float minOverlapX =
379 (overlapX1 < overlapX2) ? overlapX1 : -overlapX2;
380 const float minOverlapY =
381 (overlapY1 < overlapY2) ? overlapY1 : -overlapY2;
382
383 if (std::abs(minOverlapX) < std::abs(minOverlapY)) {
384 ptf.position.x -= minOverlapX;
385 if (pvel) {
386 pvel->direction.x = 0.0f;
387 }
388 } else {
389 ptf.position.y -= minOverlapY;
390 if (pvel) {
391 pvel->direction.y = 0.0f;
392 }
393 }
394 }
395 }
396
397 for (auto bullet : bullets) {
398 if (removed.find(bullet.index()) != removed.end()) {
399 continue;
400 }
401 const auto &btf = transforms[bullet];
402 const auto &bbox = boxes[bullet];
403 const auto &broom = rooms[bullet];
404 const auto &damage = damages[bullet];
405
406 auto boomerResLocal = _registry.get<ecs::components::Boomerang>();
407 for (auto enemy : enemies) {
408 if (rooms[enemy].id != broom.id) {
409 continue;
410 }
411 const auto &etf = transforms[enemy];
412 const auto &ebox = boxes[enemy];
413 auto &health = healths[enemy];
414 if (!overlaps(btf, bbox, etf, ebox)) {
415 continue;
416 }
417
418 // Check if enemy is a Boss and if there are shields protecting it
419 if (types[enemy].type == net::EntityType::Boss) {
420 // Count living BossShields in the same room
421 int shieldCount = 0;
422 for (auto potentialShield : enemies) {
423 if (rooms[potentialShield].id == broom.id &&
424 types[potentialShield].type == net::EntityType::BossShield &&
425 healths[potentialShield].currentHealth > 0) {
426 shieldCount++;
427 }
428 }
429
430 // If shields exist, boss is protected - don't take damage
431 if (shieldCount > 0) {
432 bool isBoomer = (boomerResLocal && boomerResLocal->get().has(bullet));
433 if (!isBoomer) {
434 // Non-boomerang bullets despawn on shield
435 markForDespawn(bullet, broom.id);
436 break;
437 } else {
438 // Boomerang should not be destroyed by boss shields; skip damaging
439 continue;
440 }
441 }
442 }
443
444 health.currentHealth -= damage.amount;
445 bool isBoomer = (boomerResLocal && boomerResLocal->get().has(bullet));
446 if (!isBoomer) {
447 markForDespawn(bullet, broom.id);
448 }
449 if (health.currentHealth <= 0) {
450 const int award = getKillScore(types[enemy].type);
451 updatePlayerScore(broom.id, damage.sourceEntity, award);
452 // Enemy died - chance to drop power-up
453 const int dropChance = std::rand() % 100;
454 if (dropChance < 30) { // 30% chance to drop
455 spawnPowerup(etf.position, broom.id, dropChance);
456 }
457 markForDespawn(enemy, broom.id);
458 }
459 break;
460 }
461
462 if (removed.find(bullet.index()) != removed.end()) {
463 continue;
464 }
465
466 for (auto obstacle : obstacles) {
467 if (rooms[obstacle].id != broom.id) {
468 continue;
469 }
470 const auto &otf = transforms[obstacle];
471 const auto &obox = boxes[obstacle];
472 auto &health = healths[obstacle];
473 if (!overlaps(btf, bbox, otf, obox)) {
474 continue;
475 }
476
477 if (types[obstacle].type == net::EntityType::Obstacle) {
478 health.currentHealth -= damage.amount;
479 }
480 bool isBoomerObs = (boomerResLocal && boomerResLocal->get().has(bullet));
481 if (!isBoomerObs) {
482 markForDespawn(bullet, broom.id);
483 }
484 if (types[obstacle].type ==
486 health.currentHealth <= 0) {
487 markForDespawn(obstacle, broom.id);
488 }
489 break;
490 }
491 }
492
493 // Handle boomerang returning to owner: recover ammo and despawn
494 if (auto boomResCheck = _registry.get<ecs::components::Boomerang>()) {
495 auto &boomers = boomResCheck->get();
496 for (auto bullet : bullets) {
497 if (removed.find(bullet.index()) != removed.end()) continue;
498 if (!boomers.has(bullet)) continue;
499 auto &b = boomers[bullet];
500 if (!b.returning) continue;
501
502 const auto &btf = transforms[bullet];
503 const auto &bbox = boxes[bullet];
504
505 // find owner entity in players list
506 for (auto player : players) {
507 if (rooms[player].id != rooms[bullet].id) continue;
508 if (static_cast<uint32_t>(player.index()) != b.ownerIndex) continue;
509 const auto &ptf = transforms[player];
510 const auto &pbox = boxes[player];
511 if (!overlaps(ptf, pbox, btf, bbox)) continue;
512
513 // Recover ammo for owner
514 if (auto ammoRes = _registry.get<ecs::components::Ammo>()) {
515 auto &ammos = ammoRes->get();
516 if (ammos.has(player)) {
517 if (ammos[player].max > 0) {
518 ammos[player].current = static_cast<uint16_t>(std::min<int>(ammos[player].max, ammos[player].current + 1));
519 }
520 ammos[player].dirty = true;
521
522 // send ammo update packet to owner
524 net::AmmoUpdatePayload payload{};
525 payload.current = ammos[player].current;
526 payload.max = ammos[player].max;
527 payload.isReloading = static_cast<uint8_t>(ammos[player].isReloading ? 1 : 0);
528 payload.cooldownRemaining = ammos[player].isReloading ? (ammos[player].reloadCooldown - ammos[player].reloadTimer) : 0.0f;
529 packet << payload;
530 if (auto netRes = _registry.get<ecs::components::NetworkId>()) {
531 auto &nets = netRes->get();
532 if (nets.has(player)) {
534 }
535 }
536 }
537 }
538
539 markForDespawn(bullet, rooms[bullet].id);
540 break;
541 }
542 }
543 }
544
545 for (auto bullet : enemyBullets) {
546 if (removed.find(bullet.index()) != removed.end()) {
547 continue;
548 }
549 const auto &btf = transforms[bullet];
550 const auto &bbox = boxes[bullet];
551 const auto &broom = rooms[bullet];
552 const auto &damage = damages[bullet];
553
554 for (auto player : players) {
555 if (rooms[player].id != broom.id) {
556 continue;
557 }
558 const auto &ptf = transforms[player];
559 const auto &pbox = boxes[player];
560 auto &health = healths[player];
561 if (!overlaps(btf, bbox, ptf, pbox)) {
562 continue;
563 }
564
565 // Check if player has shield
566 bool shieldBlocked = false;
567 if (shieldsRes) {
568 auto& shields = shieldsRes->get();
569 if (shields.has(player) && shields[player].charges > 0) {
570 // Shield absorbs the hit
571 shields[player].charges--;
572 if (shields[player].charges <= 0) {
574 log::info("Player shield depleted");
575 } else {
576 log::info("Player shield blocked attack ({} charges left)", shields[player].charges);
577 }
578 shieldBlocked = true;
579 }
580 }
581
582 if (!shieldBlocked && !_invincibleMode) {
583 // No shield and not invincible - take damage
584 health.currentHealth =
585 std::max(0, health.currentHealth - damage.amount);
586 sendPlayerHealth(broom.id, player, health);
587 }
588 markForDespawn(bullet, broom.id);
589
590 if (!shieldBlocked && !_invincibleMode && health.currentHealth <= 0) {
591 auto room = _roomSystem.getRoom(broom.id);
592 if (room) {
593 const auto playersInRoom = room->getPlayers();
594 for (const auto &roomPlayer : playersInRoom) {
595 if (!roomPlayer) {
596 continue;
597 }
598 if (roomPlayer->getEntityId() == static_cast<uint32_t>(player.index())) {
599 room->setPlayerType(roomPlayer->getId(), Room::PlayerType::Spectator);
600 roomPlayer->setEntityId(0);
601 _networkSync.unbindSession(roomPlayer->getId());
602 break;
603 }
604 }
605 sendGameOverIfNeeded(room);
606 }
607 markForDespawn(player, broom.id);
608 }
609 break;
610 }
611 }
612
613 for (const auto &[entity, roomId] : pending) {
614 despawn(entity, roomId);
615 }
616 }
617
619 // Private API
621
625 const ecs::components::BoundingBox &bb) const
626 {
627 const float ax1 = a.position.x;
628 const float ay1 = a.position.y;
629 const float ax2 = a.position.x + ab.width;
630 const float ay2 = a.position.y + ab.height;
631
632 const float bx1 = b.position.x;
633 const float by1 = b.position.y;
634 const float bx2 = b.position.x + bb.width;
635 const float by2 = b.position.y + bb.height;
636
637 return ax1 < bx2 && ax2 > bx1 && ay1 < by2 && ay2 > by1;
638 }
639
640 void CollisionSystem::despawn(const ecs::Entity &entity, uint32_t roomId)
641 {
642 log::debug("despawn() called for entity {}, roomId {}", entity.index(), roomId);
643 auto transformRes = _registry.get<ecs::components::Transform>();
644 auto typeRes = _registry.get<ecs::components::EntityType>();
646 if (!transformRes || !typeRes || !netRes) {
647 log::debug("despawn() early return - missing component arrays");
648 return;
649 }
650
651 auto &transforms = transformRes->get();
652 auto &types = typeRes->get();
653 auto &nets = netRes->get();
654 if (!transforms.has(entity) ||
655 !types.has(entity) ||
656 !nets.has(entity)) {
657 return;
658 }
659
660 const auto &transform = transforms[entity];
661 const auto &type = types[entity];
662 const auto &net = nets[entity];
663
664 auto room = _roomSystem.getRoom(roomId);
665 if (!room) {
666 _registry.kill(entity);
667 return;
668 }
669
670 const auto players = room->getPlayers();
671 if (players.empty()) {
672 _registry.kill(entity);
673 return;
674 }
675
677 net::EntityDeathPayload payload{};
678 payload.netId = net.id;
679 payload.type = static_cast<uint8_t>(type.type);
680 payload.position = transform.position;
681 packet << payload;
682
683 log::debug("Sending EntityDeath packet for netId={}, type={} to {} players",
684 net.id, static_cast<int>(type.type), players.size());
685
686 for (const auto &player : players) {
687 _networkSync.sendPacketToSession(player->getId(), packet,
689 }
690
691 log::debug("Killing entity {} from registry", entity.index());
692 _registry.kill(entity);
693 }
694
695 void CollisionSystem::spawnPowerup(const Vec2f& position, uint32_t roomId, int dropRoll)
696 {
697 auto entity = _registry.spawn();
698 if (!entity) {
699 log::error("Failed to spawn power-up entity");
700 return;
701 }
702
703 // Determine power-up type based on drop roll (0-29 range for 30% drop)
705 net::EntityType netType;
706
707 if (dropRoll < 10) {
708 // 0-9: Red - Health (33% of drops)
711 } else if (dropRoll < 20) {
712 // 10-19: Yellow/White - Double Fire (33% of drops)
715 } else {
716 // 20-29: Green - Shield (33% of drops)
719 }
720
721 auto e = entity.value();
722
723 // Add components
724 _registry.add<ecs::components::Transform>(e, position, 0.0f, Vec2f{1.0f, 1.0f});
725 _registry.add<ecs::components::Velocity>(e, ecs::components::Velocity{Vec2f{-1.0f, 0.0f}, 30.0f}); // Move left slowly
729 _registry.add<ecs::components::Powerup>(e, type, 1.0f, 0.0f);
730
731 // Assign network ID
732 static uint32_t nextId = 1000;
734
735 log::info("Spawned power-up type {} at ({}, {})", static_cast<int>(type), position.x, position.y);
736
737 // Notify clients
738 auto room = _roomSystem.getRoom(roomId);
739 if (!room) {
740 return;
741 }
742
743 const auto players = room->getPlayers();
744 if (players.empty()) {
745 return;
746 }
747
749 net::EntitySpawnPayload payload{};
750 payload.netId = nextId - 1;
751 payload.type = static_cast<uint8_t>(netType);
752 payload.posX = position.x;
753 payload.posY = position.y;
754 packet << payload;
755
756 for (const auto &player : players) {
758 }
759 }
760}
Logger declaration with support for multiple log levels.
Network packet implementation for R-Type protocol.
Represents an entity in the ECS (Entity-Component-System) architecture.
Definition Entity.hpp:63
constexpr std::uint32_t index(void) const
auto spawn(void) -> std::expected< Entity, rtp::Error >
Definition Registry.cpp:51
void kill(Entity entity)
Definition Registry.cpp:73
auto add(Entity entity, Args &&...args) -> std::expected< std::reference_wrapper< T >, rtp::Error >
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
void remove(Entity entity) noexcept
Network packet with header and serializable body.
Definition Packet.hpp:471
void spawnPowerup(const Vec2f &position, uint32_t roomId, int dropRoll)
Spawn a power-up at given position.
void update(float dt) override
Update system logic for one frame.
RoomSystem & _roomSystem
Reference to the RoomSystem.
void despawn(const ecs::Entity &entity, uint32_t roomId)
Handle collision between two entities.
NetworkSyncSystem & _networkSync
Reference to the NetworkSyncSystem.
CollisionSystem(ecs::Registry &registry, RoomSystem &roomSystem, NetworkSyncSystem &networkSync)
Constructor for CollisionSystem.
bool _invincibleMode
Debug: players are invincible.
ecs::Registry & _registry
Reference to the ECS registry.
bool overlaps(const ecs::components::Transform &a, const ecs::components::BoundingBox &ab, const ecs::components::Transform &b, const ecs::components::BoundingBox &bb) const
Check if two entities overlap based on their transforms and bounding boxes.
System to handle network-related operations on the server side.
void sendPacketToSession(uint32_t sessionId, const net::Packet &packet, net::NetworkMode mode)
Send a packet to a specific session.
void unbindSession(uint32_t sessionId)
System to handle room-related operations on the server side.
std::shared_ptr< Room > getRoom(uint32_t roomId)
Get a room by its ID.
@ Spectator
Spectator player.
Definition Room.hpp:64
@ InGame
Game in progress.
Definition Room.hpp:43
File : Network.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue.hajjar-llauquen@epit...
PowerupType
Supported powerup types.
Definition Powerup.hpp:16
constexpr Entity NullEntity
Definition Entity.hpp:109
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
void debug(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a debug message.
EntityType
Types of entities in the game.
Definition Packet.hpp:144
@ AmmoUpdate
Ammo update notification.
@ GameOver
Game over summary.
@ HealthUpdate
Player health update.
@ EntitySpawn
Entity spawn notification.
@ ScoreUpdate
Player score update.
@ EntityDeath
Entity death notification.
File : GameManager.hpp License: MIT Author : Elias Josué HAJJAR LLAUQUEN elias-josue....
Ammo tracking for weapons.
Definition Ammo.hpp:18
Marks a projectile as a boomerang and stores state for return logic.
Definition Boomerang.hpp:16
float height
Height of the rectangle.
float width
Width of the rectangle.
Component representing damage value.
Definition Damage.hpp:16
Component for double fire powerup.
Component representing an entity's health.
Definition Health.hpp:15
Component representing movement speed with temporary boosts.
Component representing a network identifier for an entity.
Definition NetworkId.hpp:22
Component representing a powerup pickup.
Definition Powerup.hpp:27
Component representing a network identifier for an entity.
Definition RoomId.hpp:22
Component representing a shield that absorbs damage.
Definition Shield.hpp:16
Component representing position, rotation, and scale of an entity.
Definition Transform.hpp:23
Vec2f position
X and Y coordinates.
Definition Transform.hpp:24
Component representing a 2D velocity.
Definition Velocity.hpp:17
Ammo update notification data Server OpCode.
Definition Packet.hpp:374
uint16_t current
Current ammo.
Definition Packet.hpp:375
Entity death notification data.
Definition Packet.hpp:362
uint32_t netId
Network entity identifier.
Definition Packet.hpp:363
Entity spawn notification data.
Definition Packet.hpp:348
uint32_t netId
Network entity identifier.
Definition Packet.hpp:349
Game over summary data.
Definition Packet.hpp:405
Player health update data.
Definition Packet.hpp:415
Player score update notification data.
Definition Packet.hpp:397