diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java index bae13b0fd..b426f70ae 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java @@ -10,6 +10,7 @@ import com.csse3200.game.physics.raycast.RaycastHit; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import static java.lang.Math.round; /** * The TowerCombatTask runs the AI for the WeaponTower class. The tower will scan for targets in a straight line @@ -28,6 +29,7 @@ public class TowerCombatTask extends DefaultTask implements PriorityTask { // class attributes private final int priority; // The active priority this task will have + private float fireRateInterval; // time interval to fire projectiles at enemies in seconds private final float maxRange; private Vector2 towerPosition = new Vector2(10, 10); // initial placeholder value - will be overwritten private final Vector2 maxRangePosition = new Vector2(); @@ -48,6 +50,20 @@ private enum STATE { public TowerCombatTask(int priority, float maxRange) { this.priority = priority; this.maxRange = maxRange; + this.fireRateInterval = 1; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * @param priority Task priority when targets are detected (0 when nothing detected). Must be a positive integer. + * @param maxRange Maximum effective range of the weapon tower. This determines the detection distance of targets + * @param fireRate The number of times per second this tower should fire its weapon + */ + public TowerCombatTask(int priority, float maxRange, float fireRate) { + this.priority = priority; + this.maxRange = maxRange; + this.fireRateInterval = 1/fireRate; physics = ServiceLocator.getPhysicsService().getPhysics(); timeSource = ServiceLocator.getTimeSource(); } @@ -63,6 +79,8 @@ public void start() { this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); // Default to idle mode owner.getEntity().getEvents().trigger(IDLE); + // Set up listener to change firerate + owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); endTime = timeSource.getTime() + (INTERVAL * 500); } @@ -75,7 +93,11 @@ public void start() { public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); - endTime = timeSource.getTime() + (INTERVAL * 1000); + if (towerState == STATE.FIRING) { + endTime = timeSource.getTime() + round(fireRateInterval * 1000); + } else { + endTime = timeSource.getTime() + (INTERVAL * 1000); + } } } @@ -172,4 +194,24 @@ private boolean isTargetVisible() { // If there is an obstacle in the path to the max range point, mobs visible. return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + + /** + * Increases the fireRateInterval, changing how frequently the turret fires. Will decrease if the argument is negative. + * + * @param perMinute The number of times per minute the turret's fire rate should increase + */ + private void changeFireRateInterval(int perMinute) { + float oldFireSpeed = 1/fireRateInterval; + float newFireSpeed = oldFireSpeed + perMinute/60f; + fireRateInterval = 1/newFireSpeed; + } + + /** + * Function for getting the turret's fire rate. + * + * @return The fireRateInterval variable + */ + public float getFireRateInterval() { + return fireRateInterval; + } } diff --git a/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java b/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java index 12d2b4660..789f31d39 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java +++ b/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java @@ -2,6 +2,7 @@ import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.Component; +import static java.lang.Math.round; /** * Listens for an event from the popup menu to upgrade @@ -20,15 +21,16 @@ public void create() { /** * Determines which type of upgrade to perform based on arguments provided by the event trigger. + * Note: The fire rate upgrade is in shots per minute. * * @param upgradeType An enum indicating the type of upgrade to do - * @param value How much the upgrade should change the tower's stats, where applicable + * @param value How much the upgrade should change the tower's stats */ void upgradeTower(UPGRADE upgradeType, int value) { switch (upgradeType) { case ATTACK -> {upgradeTowerAttack(value);} case MAXHP -> {upgradeTowerMaxHealth(value);} - case FIRERATE -> {/*Not implemented at the present moment*/} + case FIRERATE -> {getEntity().getEvents().trigger("addFireRate", value);} } } diff --git a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java index a72c89184..f9ab79901 100644 --- a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java +++ b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java @@ -1,8 +1,13 @@ package com.csse3200.game.components.tower; +import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.tasks.TowerCombatTask; import com.csse3200.game.entities.Entity; import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -12,14 +17,18 @@ @ExtendWith(GameExtension.class) class TowerUpgraderComponentTest { Entity entity; + TowerUpgraderComponent towerUpgraderComponent; + CombatStatsComponent combatStatsComponent; @BeforeEach - void beforeEach() {entity = new Entity();} + void beforeEach() { + entity = new Entity(); + towerUpgraderComponent = spy(TowerUpgraderComponent.class); + combatStatsComponent = new CombatStatsComponent(100,10); + } @Test void increaseAttackStat() { - TowerUpgraderComponent towerUpgraderComponent = spy(TowerUpgraderComponent.class); - CombatStatsComponent combatStatsComponent = new CombatStatsComponent(100,10); entity.addComponent(towerUpgraderComponent); entity.addComponent(combatStatsComponent); entity.create(); @@ -30,8 +39,6 @@ void increaseAttackStat() { @Test void increaseMaxHealthStat() { - TowerUpgraderComponent towerUpgraderComponent = spy(TowerUpgraderComponent.class); - CombatStatsComponent combatStatsComponent = new CombatStatsComponent(100,10); entity.addComponent(towerUpgraderComponent); entity.addComponent(combatStatsComponent); entity.create(); @@ -39,4 +46,20 @@ void increaseMaxHealthStat() { verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.MAXHP, 50); assertEquals(150, combatStatsComponent.getMaxHealth()); } + + @Test + void increaseFireRate() { + entity.addComponent(towerUpgraderComponent); + AITaskComponent aiTaskComponent = new AITaskComponent(); + ServiceLocator.registerPhysicsService(mock(PhysicsService.class)); + ServiceLocator.registerTimeSource(mock(GameTime.class)); + TowerCombatTask towerCombatTask = new TowerCombatTask(10, 10, 1); + aiTaskComponent.addTask(towerCombatTask); + entity.addComponent(aiTaskComponent); + towerCombatTask.start(); + entity.create(); + entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, 60); + verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, 60); + assertEquals(0.5, towerCombatTask.getFireRateInterval()); + } }