R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
CollisionSystem.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** RTYPE
4** File description:
5** CollisionSystem
6*/
7
8#include "CollisionSystem.hpp"
9#include "../../Components/Enemy.hpp"
10#include "../../Components/IComponent.hpp"
11#include "../../Components/OrbitalModule.hpp"
12#include "../../Components/Projectile.hpp"
13#include "../../Components/Wall.hpp"
15
16namespace ecs {
20 void CollisionSystem::update(Registry &registry, [[maybe_unused]] float deltaTime) {
21 auto entities = registry.getEntitiesWithMask(this->getComponentMask());
22 std::vector<std::uint32_t> entitiesVec(entities.begin(), entities.end());
23 std::vector<std::uint32_t> projectilesToDestroy;
24
25 // Collect entities to destroy after collision processing
26 std::vector<Address> entitiesToDestroy;
27
28 for (size_t i = 0; i < entitiesVec.size(); ++i) {
29 for (size_t j = i + 1; j < entitiesVec.size(); ++j) {
30 auto entity1 = entitiesVec[i];
31 auto entity2 = entitiesVec[j];
32
33 // Check if entities still exist and have all required components
34 // (might have been marked for destruction or modified by other systems)
35 if (!registry.hasComponent<Transform>(entity1) ||
36 !registry.hasComponent<Transform>(entity2) || !registry.hasComponent<Collider>(entity1) ||
37 !registry.hasComponent<Collider>(entity2)) {
38 continue;
39 }
40
41 auto &transform1 = registry.getComponent<Transform>(entity1);
42 auto &collider1 = registry.getComponent<Collider>(entity1);
43 auto &transform2 = registry.getComponent<Transform>(entity2);
44 auto &collider2 = registry.getComponent<Collider>(entity2);
45
46 if (!canCollide(collider1.getLayer(), collider1.getMask(), collider2.getLayer(),
47 collider2.getMask())) {
48 continue;
49 }
50
51 if (checkAABB(transform1.getPosition(), collider1.getSize(), collider1.getOffset(),
52 transform2.getPosition(), collider2.getSize(), collider2.getOffset())) {
53 // Check for wall collisions
54 bool entity1IsWall = registry.hasComponent<Wall>(entity1);
55 bool entity2IsWall = registry.hasComponent<Wall>(entity2);
56 bool entity1IsPlayer = registry.hasComponent<Player>(entity1);
57 bool entity2IsPlayer = registry.hasComponent<Player>(entity2);
58 bool entity1IsCollectible = registry.hasComponent<Collectible>(entity1);
59 bool entity2IsCollectible = registry.hasComponent<Collectible>(entity2);
60 bool entity1IsOrbitalModule = registry.hasComponent<OrbitalModule>(entity1);
61 bool entity2IsOrbitalModule = registry.hasComponent<OrbitalModule>(entity2);
62 bool entity1IsEnemy = registry.hasComponent<Enemy>(entity1);
63 bool entity2IsEnemy = registry.hasComponent<Enemy>(entity2);
64 bool entity1IsProjectile = registry.hasComponent<Projectile>(entity1);
65 bool entity2IsProjectile = registry.hasComponent<Projectile>(entity2);
66
67 // Handle Player-Wall collision: block player movement
68 if (entity1IsPlayer && entity2IsWall) {
69 resolveWallCollision(entity1, entity2, transform1, collider1, transform2, collider2,
70 registry);
71 } else if (entity2IsPlayer && entity1IsWall) {
72 resolveWallCollision(entity2, entity1, transform2, collider2, transform1, collider1,
73 registry);
74 }
75
76 // Handle Orbital Module - Enemy collision (module damages enemy)
77 if (entity1IsOrbitalModule && entity2IsEnemy) {
78 handleModuleEnemyCollision(entity1, entity2, registry);
79 } else if (entity2IsOrbitalModule && entity1IsEnemy) {
80 handleModuleEnemyCollision(entity2, entity1, registry);
81 }
82
83 // Handle Orbital Module - Enemy Projectile collision (module blocks projectile)
84 if (entity1IsOrbitalModule && entity2IsProjectile) {
85 handleModuleProjectileCollision(entity1, entity2, registry, entitiesToDestroy);
86 } else if (entity2IsOrbitalModule && entity1IsProjectile) {
87 handleModuleProjectileCollision(entity2, entity1, registry, entitiesToDestroy);
88 }
89
90 // Handle player-collectible pickup
91 if ((entity1IsPlayer && entity2IsCollectible) ||
92 (entity2IsPlayer && entity1IsCollectible)) {
93 LOG_DEBUG("[COLLISION] Player-Collectible collision detected: E", entity1, " & E",
94 entity2);
95 }
96
97 if (entity1IsPlayer && entity2IsCollectible) {
98 handlePickup(entity1, entity2, registry, entitiesToDestroy);
99 } else if (entity2IsPlayer && entity1IsCollectible) {
100 handlePickup(entity2, entity1, registry, entitiesToDestroy);
101 }
102
103 // Handle projectile-entity collisions (damage system)
104 handleProjectileCollision(registry, entity1, entity2, entitiesToDestroy);
105 }
106 }
107 }
108
109 // Destroy collected entities after all collision processing
110 for (Address addr : entitiesToDestroy) {
111 if (registry.hasComponent<Transform>(addr)) { // Check if still exists
112 registry.destroyEntity(addr);
113 }
114 }
115 }
116
117 void CollisionSystem::handlePickup(Address playerAddr, Address collectibleAddr, Registry &registry,
118 std::vector<Address> &entitiesToDestroy) {
119 // Verify both entities still exist
120 if (!registry.hasComponent<Collectible>(collectibleAddr)) {
121 return;
122 }
123
124 if (!registry.hasComponent<Player>(playerAddr)) {
125 return;
126 }
127
128 try {
129 Collectible &collectible = registry.getComponent<Collectible>(collectibleAddr);
130
131 LOG_INFO("[PICKUP] Player ", playerAddr, " collected item at entity ", collectibleAddr);
132
133 // Apply collectible effects
134 if (collectible.grantsBuff()) {
135 // Add buff to player
136 if (!registry.hasComponent<Buff>(playerAddr)) {
137 registry.setComponent<Buff>(playerAddr, Buff());
138 }
139
140 Buff &buff = registry.getComponent<Buff>(playerAddr);
141 buff.addBuff(collectible.getBuffType(), collectible.getDuration(), collectible.getValue());
142
143 // Log the buff type
144 const char *buffName = "Unknown";
145 switch (collectible.getBuffType()) {
147 buffName = "SpeedBoost";
148 break;
150 buffName = "DamageBoost";
151 break;
153 buffName = "FireRateBoost";
154 break;
155 case BuffType::Shield:
156 buffName = "Shield";
157 break;
159 buffName = "HealthRegen";
160 break;
162 buffName = "MultiShot";
163 break;
165 buffName = "DoubleShot";
166 break;
168 buffName = "TripleShot";
169 break;
171 buffName = "PiercingShot";
172 break;
174 buffName = "HomingShot";
175 break;
177 buffName = "MaxHealthIncrease";
178 break;
179 }
180 float duration = collectible.getDuration();
181 if (duration > 0.0f) {
182 LOG_INFO(" ✓ Applied buff: ", buffName, " (duration: ", duration,
183 "s, value: ", collectible.getValue(), ")");
184 } else {
185 LOG_INFO(" ✓ Applied PERMANENT upgrade: ", buffName, " (value: ", collectible.getValue(),
186 ")");
187 }
188
189 // For permanent max health increase, apply immediately
190 if (collectible.getBuffType() == BuffType::MaxHealthIncrease) {
191 if (registry.hasComponent<Health>(playerAddr)) {
192 Health &health = registry.getComponent<Health>(playerAddr);
193 int increase = static_cast<int>(collectible.getValue());
194 health.setMaxHealth(health.getMaxHealth() + increase);
195 health.setCurrentHealth(health.getCurrentHealth() + increase);
196 }
197 }
198 }
199
200 if (collectible.restoresHealth()) {
201 if (registry.hasComponent<Health>(playerAddr)) {
202 Health &health = registry.getComponent<Health>(playerAddr);
203 int oldHealth = health.getCurrentHealth();
204 int newHealth = oldHealth + collectible.getHealthRestore();
205 health.setCurrentHealth(std::min(newHealth, health.getMaxHealth()));
206 LOG_INFO(" ✓ Restored health: ", oldHealth, " -> ", health.getCurrentHealth(), " (+",
207 collectible.getHealthRestore(), ")");
208 }
209 }
210
211 if (collectible.awardsScore()) {
212 if (registry.hasComponent<Player>(playerAddr)) {
213 Player &player = registry.getComponent<Player>(playerAddr);
214 int oldScore = player.getScore();
215 player.setScore(oldScore + collectible.getScoreValue());
216 LOG_INFO(" ✓ Awarded score: ", oldScore, " -> ", player.getScore(), " (+",
217 collectible.getScoreValue(), ")");
218 }
219 }
220
221 // Mark collectible for destruction (will be destroyed after collision loop)
222 entitiesToDestroy.push_back(collectibleAddr);
223 } catch (const std::exception &e) {
224 // Entity might have been destroyed during collision processing
225 // This is not critical, just skip this pickup
226 return;
227 }
228 }
229
231 Registry &registry) {
232 // Get module damage value
233 if (!registry.hasComponent<OrbitalModule>(moduleAddr)) {
234 return;
235 }
236
237 const OrbitalModule &module = registry.getComponent<OrbitalModule>(moduleAddr);
238 int damage = module.getDamage();
239
240 // Apply damage to enemy
241 if (registry.hasComponent<Health>(enemyAddr)) {
242 Health &enemyHealth = registry.getComponent<Health>(enemyAddr);
243 int oldHealth = enemyHealth.getCurrentHealth();
244 enemyHealth.setCurrentHealth(oldHealth - damage);
245
246 LOG_DEBUG("[COLLISION] Orbital module E", moduleAddr, " hit enemy E", enemyAddr, " for ", damage,
247 " damage (", oldHealth, " -> ", enemyHealth.getCurrentHealth(), ")");
248
249 // If enemy is dead, it will be handled by HealthSystem
250 }
251 }
252
254 Registry &registry,
255 std::vector<Address> &entitiesToDestroy) {
256 // Check if projectile is from enemy
257 if (!registry.hasComponent<Projectile>(projectileAddr)) {
258 return;
259 }
260
261 const Projectile &projectile = registry.getComponent<Projectile>(projectileAddr);
262
263 // Only block enemy projectiles
264 if (!projectile.isFriendly()) {
265 LOG_DEBUG("[COLLISION] Orbital module E", moduleAddr, " blocked enemy projectile E",
266 projectileAddr);
267
268 // Destroy the projectile
269 entitiesToDestroy.push_back(projectileAddr);
270 }
271 }
272
277 const Collider::Vector2 &offset1, const Transform::Vector2 &pos2,
278 const Collider::Vector2 &size2, const Collider::Vector2 &offset2) const {
279
280 // Position is the CENTER of the entity, so we need to offset by half the size
281 float left1 = pos1.x + offset1.x - (size1.x / 2.0f);
282 float right1 = left1 + size1.x;
283 float top1 = pos1.y + offset1.y - (size1.y / 2.0f);
284 float bottom1 = top1 + size1.y;
285
286 float left2 = pos2.x + offset2.x - (size2.x / 2.0f);
287 float right2 = left2 + size2.x;
288 float top2 = pos2.y + offset2.y - (size2.y / 2.0f);
289 float bottom2 = top2 + size2.y;
290
291 return !(right1 < left2 || left1 > right2 || bottom1 < top2 || top1 > bottom2);
292 }
293
298 Transform &playerTransform, Collider &playerCollider,
299 Transform &wallTransform, Collider &wallCollider,
300 Registry &registry) {
301 (void)wallAddr;
302 (void)playerTransform;
303 (void)playerCollider;
304 (void)wallTransform;
305 (void)wallCollider;
306
307 // Walls are instant death - deal massive damage to kill player
308 LOG_INFO("[COLLISION] Player touched wall - instant death!");
309
310 // Deal 9999 damage to ensure instant death
311 if (registry.hasComponent<Health>(playerAddr)) {
312 Health &health = registry.getComponent<Health>(playerAddr);
313 health.setCurrentHealth(0);
314 }
315 }
316
320 bool CollisionSystem::canCollide(std::uint32_t layer1, std::uint32_t mask1, std::uint32_t layer2,
321 std::uint32_t mask2) const {
322 return (mask1 & layer2) && (mask2 & layer1);
323 }
324
335 void CollisionSystem::handleProjectileCollision(Registry &registry, std::uint32_t entity1,
336 std::uint32_t entity2,
337 std::vector<Address> &entitiesToDestroy) {
338 // Check which entity is the projectile
339 bool entity1IsProjectile = registry.hasComponent<Projectile>(entity1);
340 bool entity2IsProjectile = registry.hasComponent<Projectile>(entity2);
341
342 // If neither or both are projectiles, no damage to apply
343 if (!entity1IsProjectile && !entity2IsProjectile) {
344 return;
345 }
346
347 // Determine projectile and target
348 std::uint32_t projectileAddr = entity1IsProjectile ? entity1 : entity2;
349 std::uint32_t targetAddr = entity1IsProjectile ? entity2 : entity1;
350
351 // Skip if projectile is already marked for destruction (prevents double hits)
352 for (Address addr : entitiesToDestroy) {
353 if (addr == projectileAddr) {
354 return; // Already processed this projectile
355 }
356 }
357
358 // Get projectile component
359 if (!registry.hasComponent<Projectile>(projectileAddr)) {
360 return;
361 }
362
363 Projectile &projectile = registry.getComponent<Projectile>(projectileAddr);
364
365 // Check if target can receive damage
366 if (!registry.hasComponent<Health>(targetAddr)) {
367 return; // Target has no health, nothing to damage
368 }
369
370 // Determine if this is a valid hit based on projectile type
371 bool targetIsEnemy = registry.hasComponent<Enemy>(targetAddr);
372 bool targetIsPlayer = registry.hasComponent<Player>(targetAddr);
373
374 // Friendly projectiles hit enemies only
375 if (projectile.isFriendly() && !targetIsEnemy) {
376 return;
377 }
378
379 // Enemy projectiles hit players only
380 if (!projectile.isFriendly() && !targetIsPlayer) {
381 return;
382 }
383
384 // Apply damage
385 Health &targetHealth = registry.getComponent<Health>(targetAddr);
386 int damage = static_cast<int>(projectile.getDamage());
387
388 bool damageApplied = targetHealth.takeDamage(damage);
389
390 if (damageApplied) {
391 // Log the hit
392 if (targetIsEnemy) {
393 LOG_INFO("[PROJECTILE HIT] Player projectile (E", projectileAddr, ") hit enemy (E",
394 targetAddr, ") for ", damage, " damage. HP: ", targetHealth.getCurrentHealth(), "/",
395 targetHealth.getMaxHealth());
396 } else if (targetIsPlayer) {
397 LOG_INFO("[PROJECTILE HIT] Enemy projectile (E", projectileAddr, ") hit player (E",
398 targetAddr, ") for ", damage, " damage. HP: ", targetHealth.getCurrentHealth(), "/",
399 targetHealth.getMaxHealth());
400 }
401
402 // Mark projectile for destruction (unless it's a piercing shot)
403 // TODO: Check for PiercingShot buff when implementing advanced projectile types
404 entitiesToDestroy.push_back(projectileAddr);
405 }
406 }
407
409 return (1ULL << getComponentType<Transform>()) | (1ULL << getComponentType<Collider>());
410 }
411} // namespace ecs
#define LOG_INFO(...)
Definition Logger.hpp:181
#define LOG_DEBUG(...)
Definition Logger.hpp:180
Component managing active buffs on an entity.
Definition Buff.hpp:58
void addBuff(BuffType type, float duration, float value)
Add a new buff.
Definition Buff.hpp:81
Component for items that can be picked up by players.
bool grantsBuff() const
Check if this grants a buff.
bool awardsScore() const
Check if this awards score.
bool restoresHealth() const
Check if this restores health.
float getDuration() const
Get buff duration.
BuffType getBuffType() const
Get buff type (if applicable)
int getHealthRestore() const
Get health restore amount.
float getValue() const
Get buff value.
int getScoreValue() const
Get score value.
Component for collision detection and physics interactions.
Definition Collider.hpp:21
void resolveWallCollision(Address playerAddr, Address wallAddr, Transform &playerTransform, Collider &playerCollider, Transform &wallTransform, Collider &wallCollider, Registry &registry)
Resolves collision between player and wall by instantly killing the player.
ComponentMask getComponentMask() const override
Gets the component mask for this system.
void handleProjectileCollision(Registry &registry, std::uint32_t entity1, std::uint32_t entity2, std::vector< Address > &entitiesToDestroy)
Handle projectile collision with other entities.
void handleModuleProjectileCollision(Address moduleAddr, Address projectileAddr, Registry &registry, std::vector< Address > &entitiesToDestroy)
Handle collision between orbital module and projectile.
void handlePickup(Address playerAddr, Address collectibleAddr, Registry &registry, std::vector< Address > &entitiesToDestroy)
Handle collision between player and collectible.
void handleModuleEnemyCollision(Address moduleAddr, Address enemyAddr, Registry &registry)
Handle collision between orbital module and enemy.
bool canCollide(std::uint32_t layer1, std::uint32_t mask1, std::uint32_t layer2, std::uint32_t mask2) const
Checks if two entities can collide based on layers.
void update(Registry &registry, float deltaTime) override
Detects and handles collisions between entities.
bool checkAABB(const Transform::Vector2 &pos1, const Collider::Vector2 &size1, const Collider::Vector2 &offset1, const Transform::Vector2 &pos2, const Collider::Vector2 &size2, const Collider::Vector2 &offset2) const
Checks AABB collision between two entities.
Component identifying an entity as an enemy with AI behavior.
Definition Enemy.hpp:20
Component representing entity health and invincibility.
Definition Health.hpp:20
bool takeDamage(int amount)
Apply damage to the entity. Respects invincibility frames - no damage taken if invincible.
Definition Health.hpp:94
int getCurrentHealth() const
Get current health points.
Definition Health.hpp:44
void setCurrentHealth(int health)
Set current health.
Definition Health.hpp:74
int getMaxHealth() const
Get maximum health points.
Definition Health.hpp:50
void setMaxHealth(int health)
Set maximum health.
Definition Health.hpp:68
Component for entities that orbit around a parent entity (like drones in Isaac).
Component identifying an entity as a player with game statistics.
Definition Player.hpp:20
void setScore(int score)
Set player's score.
Definition Player.hpp:53
int getScore() const
Get player's score.
Definition Player.hpp:35
Component for projectile entities (bullets, missiles, etc.).
bool isFriendly() const
Check if projectile is friendly.
float getDamage() const
Get damage value.
Manages entities, their signatures and component type registrations.
Definition Registry.hpp:68
void destroyEntity(Address address)
Remove an entity and its Signature from the registry.
Definition Registry.cpp:56
std::vector< Address > getEntitiesWithMask(Signature requiredMask)
Get all entities matching a specific component mask.
Definition Registry.cpp:79
T & getComponent(Address address)
Get a component from an entity.
bool hasComponent(Address address)
Check if an entity has a specific component.
void setComponent(Address address, const T &component)
Set/add a component to an entity with its data.
Component representing position, rotation and scale in 2D space.
Definition Transform.hpp:20
Component for static or destructible walls/obstacles.
Definition Wall.hpp:20
Maximum number of distinct component types supported by the Registry.
Definition GameLogic.hpp:26
@ PiercingShot
Projectiles pierce through enemies.
@ TripleShot
Fire three projectiles at once.
@ DamageBoost
Increases weapon damage.
@ HomingShot
Projectiles home towards enemies.
@ FireRateBoost
Increases fire rate.
@ MaxHealthIncrease
Permanently increase max health.
@ Shield
Temporary invincibility.
@ HealthRegen
Regenerates health over time.
@ SpeedBoost
Increases movement speed.
@ MultiShot
Shoot in multiple directions.
@ DoubleShot
Fire two projectiles at once.
std::uint32_t Address
Type used to represent an entity address/ID.
std::uint64_t ComponentMask
Type alias for component bitmask.
Definition ISystem.hpp:24
2D vector for size and offset.
Definition Collider.hpp:27
float y
Y component.
Definition Collider.hpp:29
float x
X component.
Definition Collider.hpp:28
2D vector structure for positions and scales.
Definition Transform.hpp:26
float x
X coordinate.
Definition Transform.hpp:27
float y
Y coordinate.
Definition Transform.hpp:28