R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
WeaponSystem.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** RTYPE
4** File description:
5** WeaponSystem
6*/
7
8#include "WeaponSystem.hpp"
9#include <cmath>
19
20namespace ecs {
24 void WeaponSystem::update(Registry &registry, float deltaTime) {
25 auto entities = registry.getEntitiesWithMask(this->getComponentMask());
26
27 for (auto entityId : entities) {
28 auto &weapon = registry.getComponent<Weapon>(entityId);
29
30 // Update cooldown
31 weapon.setCooldown(std::max(0.0F, weapon.getCooldown() - deltaTime));
32
33 // Check if this is an enemy (simple auto-fire, no charging)
34 bool isEnemy = registry.hasComponent<Enemy>(entityId);
35
36 if (isEnemy) {
37 // Enemy auto-fire: shoot when cooldown is ready
38 if (weapon.shouldShoot() && weapon.getCooldown() <= 0.0F) {
39 fireWeapon(registry, entityId, false); // isFriendly = false for enemies
40 }
41 } else {
42 // Player charging system
43 if (weapon.shouldShoot() && weapon.getCooldown() <= 0.0F) {
44 // Start or continue charging
45 if (!weapon.isCharging()) {
46 weapon.setCharging(true);
47 weapon.setChargeLevel(0.0F);
48 }
49
50 // Accumulate charge
51 float newCharge = weapon.getChargeLevel() + (weapon.getChargeRate() * deltaTime);
52 weapon.setChargeLevel(newCharge);
53
54 // Cap at full charge (1.0)
55 if (weapon.getChargeLevel() >= 1.0F) {
56 weapon.setChargeLevel(1.0F);
57 }
58 } else if (!weapon.shouldShoot() && weapon.isCharging()) {
59 // Button released - fire charged shot
60 float chargeLevel = weapon.getChargeLevel();
61
62 // Fire the charged shot
63 bool isFriendly = true; // Player weapon
64 fireChargedShot(registry, entityId, chargeLevel, isFriendly);
65
66 // Reset charge state
67 weapon.setCharging(false);
68 weapon.setChargeLevel(0.0F);
69
70 // Set cooldown
71 float fireRate = weapon.getFireRate();
72 if (fireRate > 0.0F) {
73 weapon.setCooldown(1.0F / fireRate);
74 } else {
75 weapon.setCooldown(1.0F / 7.0F); // Fallback: 7 shots/sec
76 }
77 } else if (!weapon.shouldShoot() && !weapon.isCharging()) {
78 // Not shooting and not charging - reset charge
79 weapon.setChargeLevel(0.0F);
80 }
81 }
82 }
83 }
84
85 std::uint32_t WeaponSystem::fireWeapon(Registry &registry, std::uint32_t ownerId, bool isFriendly) {
86 // Get weapon data from owner
87 if (!registry.hasComponent<Weapon>(ownerId)) {
88 return 0;
89 }
90
91 auto &weapon = registry.getComponent<Weapon>(ownerId);
92 float damage = weapon.getDamage();
93 float baseSpeed = 500.0f; // Default projectile speed
94
95 // Check for multishot buffs
96 int shotCount = 1;
97
98 if (registry.hasComponent<Buff>(ownerId)) {
99 const Buff &buff = registry.getComponent<Buff>(ownerId);
100
101 // Check all multishot buffs and keep the highest
102 if (buff.hasBuff(BuffType::MultiShot)) {
103 shotCount = 5; // MultiShot fires 5 projectiles in a spread
104 }
105 if (buff.hasBuff(BuffType::TripleShot) && shotCount < 3) {
106 shotCount = 3;
107 }
108 if (buff.hasBuff(BuffType::DoubleShot) && shotCount < 2) {
109 shotCount = 2;
110 }
111 }
112
113 // Fire multiple shots if multishot buff is active
114 if (shotCount > 1) {
115 fireMultipleShots(registry, ownerId, damage, baseSpeed, isFriendly, false, shotCount);
116 return 0; // Return 0 as we created multiple projectiles
117 }
118
119 // Single shot - create projectile
120 Transform projectileTransform = calculateProjectileTransform(registry, ownerId, isFriendly);
121 Velocity projectileVelocity = calculateProjectileVelocity(baseSpeed, isFriendly);
122
123 return createProjectile(registry, ownerId, projectileTransform, projectileVelocity, damage,
124 isFriendly, false);
125 }
126
127 Velocity WeaponSystem::calculateProjectileVelocity(float baseSpeed, bool isFriendly) {
128 // Friendly projectiles go right, enemy projectiles go left
129 float dirX = isFriendly ? 1.0F : -1.0F;
130 return Velocity(dirX, 0.0F, baseSpeed);
131 }
132
134 bool isFriendly) {
135 // Get owner's position
136 Transform defaultTransform(0.0F, 0.0F);
137
138 if (registry.hasComponent<Transform>(ownerId)) {
139 auto &ownerTransform = registry.getComponent<Transform>(ownerId);
140 auto ownerPos = ownerTransform.getPosition();
141
142 // Friendly spawns to the right, enemy spawns to the left
143 float offsetX = isFriendly ? 40.0F : -40.0F;
144 return Transform(ownerPos.x + offsetX, ownerPos.y);
145 }
146
147 return defaultTransform;
148 }
149
150 std::uint32_t WeaponSystem::fireChargedShot(Registry &registry, std::uint32_t ownerId, float chargeLevel,
151 bool isFriendly) {
152 // Check if charge is sufficient (minimum 50% charge)
153 const float CHARGE_THRESHOLD = 0.5F;
154
155 if (chargeLevel < CHARGE_THRESHOLD) {
156 // Fire normal shot if not charged enough
157 return fireWeapon(registry, ownerId, isFriendly);
158 }
159
160 // Get weapon data from owner
161 if (!registry.hasComponent<Weapon>(ownerId)) {
162 return 0;
163 }
164
165 auto &weapon = registry.getComponent<Weapon>(ownerId);
166
167 // Calculate charge-based multipliers
168 float damageMultiplier = 1.0F + chargeLevel * 1.5F; // Up to 2.5x at full charge
169 float speedMultiplier = 1.0F + chargeLevel * 0.5F; // Up to 1.5x at full charge
170
171 float chargedDamage = weapon.getDamage() * damageMultiplier;
172 float chargedSpeed = 500.0f * speedMultiplier; // Default projectile speed with charge multiplier
173
174 // Check for multishot buffs
175 int shotCount = 1;
176
177 if (registry.hasComponent<Buff>(ownerId)) {
178 const Buff &buff = registry.getComponent<Buff>(ownerId);
179
180 // Check all multishot buffs and keep the highest
181 if (buff.hasBuff(BuffType::MultiShot)) {
182 shotCount = 5;
183 }
184 if (buff.hasBuff(BuffType::TripleShot) && shotCount < 3) {
185 shotCount = 3;
186 }
187 if (buff.hasBuff(BuffType::DoubleShot) && shotCount < 2) {
188 shotCount = 2;
189 }
190 }
191
192 // Fire multiple shots if multishot buff is active
193 if (shotCount > 1) {
194 fireMultipleShots(registry, ownerId, chargedDamage, chargedSpeed, isFriendly, true, shotCount);
195 return 0;
196 }
197
198 // Single charged shot
199 Transform projectileTransform = calculateProjectileTransform(registry, ownerId, isFriendly);
200 Velocity projectileVelocity = calculateProjectileVelocity(chargedSpeed, isFriendly);
201
202 return createProjectile(registry, ownerId, projectileTransform, projectileVelocity, chargedDamage,
203 isFriendly, true);
204 }
205
207 return (1ULL << getComponentType<Weapon>()) | (1ULL << getComponentType<Transform>());
208 }
209
210 std::uint32_t WeaponSystem::createProjectile(Registry &registry, std::uint32_t ownerId,
211 const Transform &transform, const Velocity &velocity,
212 float damage, bool isFriendly, bool isCharged) {
213 auto projectileId = registry.newEntity();
214
215 registry.setComponent(projectileId, transform);
216 registry.setComponent(projectileId, velocity);
217 registry.setComponent(projectileId, Projectile(damage, 10.0f, ownerId, isFriendly));
218
219 // Add Collider for collision detection
220 // Player projectile: layer 4 (1<<2), Enemy projectile: layer 8 (1<<3)
221 uint32_t layer = isFriendly ? 4 : 8;
222 registry.setComponent(projectileId, Collider(10.0f, 10.0f, 0.0f, 0.0f, layer, 0xFFFFFFFF, false));
223
224 // Add projectile animations
226 registry.setComponent(projectileId, bulletAnimations);
227
228 // Select animation and sprite based on shot type
229 std::string animationName = isCharged ? "charged_projectile_1" : "projectile_fly";
230 ecs::Rectangle projRect = {267, 84, 17, 13};
231 float scale = isCharged ? 2.5f : 2.0f;
232
233 registry.setComponent(projectileId, ecs::Animation(animationName, true, true));
234 ecs::Sprite projSprite("Projectiles", projRect, scale, 0.0f, false, false, 0);
235 registry.setComponent(projectileId, projSprite);
236
237 // Reset weapon cooldown
238 if (registry.hasComponent<Weapon>(ownerId)) {
239 auto &weapon = registry.getComponent<Weapon>(ownerId);
240 float fireRate = weapon.getFireRate();
241 if (fireRate > 0.0F) {
242 weapon.setCooldown(1.0F / fireRate);
243 }
244 }
245
246 // Invoke callback if set
247 if (_projectileCreatedCallback && projectileId != 0) {
248 auto pos = transform.getPosition();
249 auto dir = velocity.getDirection();
250 float speed = velocity.getSpeed();
251 _projectileCreatedCallback(projectileId, ownerId, pos.x, pos.y, dir.x, dir.y, speed,
252 static_cast<int>(damage), isFriendly);
253 }
254
255 return projectileId;
256 }
257
258 void WeaponSystem::fireMultipleShots(Registry &registry, std::uint32_t ownerId, float damage, float speed,
259 bool isFriendly, bool isCharged, int shotCount) {
260 if (!registry.hasComponent<Transform>(ownerId)) {
261 return;
262 }
263
264 // Calculate angle spread based on shot count
265 float angleSpread = 0.0f;
266 float startAngle = 0.0f;
267
268 switch (shotCount) {
269 case 2: // DoubleShot
270 angleSpread = 15.0f; // 15 degrees apart
271 startAngle = -7.5f; // Center the spread
272 break;
273 case 3: // TripleShot
274 angleSpread = 15.0f;
275 startAngle = -15.0f;
276 break;
277 case 5: // MultiShot
278 angleSpread = 15.0f;
279 startAngle = -30.0f;
280 break;
281 default:
282 angleSpread = 10.0f;
283 startAngle = -(angleSpread * (shotCount - 1)) / 2.0f;
284 break;
285 }
286
287 Transform baseTransform = calculateProjectileTransform(registry, ownerId, isFriendly);
288
289 // Fire each projectile with different angle
290 for (int i = 0; i < shotCount; i++) {
291 float angle = startAngle + (i * angleSpread);
292 float angleRad = angle * 3.14159f / 180.0f;
293
294 // Calculate velocity with angle (invert for enemies)
295 float baseDir = isFriendly ? 1.0F : -1.0F;
296 float dirX = baseDir * std::cos(angleRad);
297 float dirY = std::sin(angleRad);
298 Velocity projectileVelocity(dirX, dirY, speed);
299
300 // Slightly offset Y position for visual variety
301 Transform projectileTransform = baseTransform;
302 auto pos = projectileTransform.getPosition();
303 projectileTransform.setPosition(pos.x, pos.y + (i - shotCount / 2) * 5.0f);
304
305 createProjectile(registry, ownerId, projectileTransform, projectileVelocity, damage, isFriendly,
306 isCharged);
307 }
308 }
309} // namespace ecs
Component containing all available animations for an entity.
Component managing current animation playback state.
Definition Animation.hpp:21
Component managing active buffs on an entity.
Definition Buff.hpp:58
bool hasBuff(BuffType type) const
Check if entity has a specific buff.
Definition Buff.hpp:114
Component for collision detection and physics interactions.
Definition Collider.hpp:21
Component identifying an entity as an enemy with AI behavior.
Definition Enemy.hpp:20
Component for projectile entities (bullets, missiles, etc.).
Manages entities, their signatures and component type registrations.
Definition Registry.hpp:68
std::vector< Address > getEntitiesWithMask(Signature requiredMask)
Get all entities matching a specific component mask.
Definition Registry.cpp:79
Address newEntity()
Create and register a new entity, returning its Address.
Definition Registry.cpp:47
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 a visual sprite from a texture.
Definition Sprite.hpp:32
Component representing position, rotation and scale in 2D space.
Definition Transform.hpp:20
Vector2 getPosition() const
Get the position vector.
Definition Transform.hpp:61
void setPosition(float posX, float posY)
Set the position.
Definition Transform.hpp:80
Component representing movement direction and speed.
Definition Velocity.hpp:20
Vector2 getDirection() const
Get the direction vector.
Definition Velocity.hpp:45
float getSpeed() const
Get the movement speed.
Definition Velocity.hpp:51
std::uint32_t fireChargedShot(Registry &registry, std::uint32_t ownerId, float chargeLevel, bool isFriendly)
Fire a charged shot if charge is sufficient, otherwise fire normal shot.
void update(Registry &registry, float deltaTime) override
Manages weapon cooldowns for all entities.
std::uint32_t fireWeapon(Registry &registry, std::uint32_t ownerId, bool isFriendly)
Fire a weapon from an entity, spawning a projectile.
ProjectileCreatedCallback _projectileCreatedCallback
std::uint32_t createProjectile(Registry &registry, std::uint32_t ownerId, const Transform &transform, const Velocity &velocity, float damage, bool isFriendly, bool isCharged)
Create a single projectile with specified properties.
ComponentMask getComponentMask() const override
Gets the component mask for this system.
void fireMultipleShots(Registry &registry, std::uint32_t ownerId, float damage, float speed, bool isFriendly, bool isCharged, int shotCount)
Fire multiple projectiles based on multishot buff.
Velocity calculateProjectileVelocity(float baseSpeed, bool isFriendly)
Calculate projectile initial velocity based on owner's velocity.
Transform calculateProjectileTransform(Registry &registry, std::uint32_t ownerId, bool isFriendly)
Calculate projectile spawn position based on owner position.
Component for entities capable of shooting projectiles.
Definition Weapon.hpp:28
float getDamage() const
Get damage value.
Definition Weapon.hpp:68
float getFireRate() const
Get fire rate.
Definition Weapon.hpp:50
void setCooldown(float cooldown)
Set cooldown timer.
Definition Weapon.hpp:98
ecs::AnimationSet createPlayerBulletAnimations()
Create player bullet animations.
Maximum number of distinct component types supported by the Registry.
Definition GameLogic.hpp:26
@ TripleShot
Fire three projectiles at once.
@ MultiShot
Shoot in multiple directions.
@ DoubleShot
Fire two projectiles at once.
std::uint64_t ComponentMask
Type alias for component bitmask.
Definition ISystem.hpp:24
Rectangle structure defining a region in a texture.
Definition Sprite.hpp:18