From 0dddd1283bfada3f3ebde802bab0234e814f7cad Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Thu, 31 Aug 2023 19:55:16 +1000 Subject: [PATCH 01/37] Created new TowerUpgraderComponent class, which listens for an event to make an upgrade to the tower. No upgrade types do anything yet. --- .../tower/TowerUpgraderComponent.java | 32 +++++++++++++++++++ .../tower/TowerUpgradeComponentTest.java | 30 +++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java create mode 100644 source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java 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 new file mode 100644 index 000000000..a88209178 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java @@ -0,0 +1,32 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; + +/** + * Listens for an event from the popup menu to upgrade + * the turret entity this component is attached to. + */ +public class TowerUpgraderComponent extends Component { + public enum UPGRADE { + ATTACK, MAXHP, FIRERATE + } + + @Override + public void create() { + super.create(); + entity.getEvents().addListener("upgradeTower", this::upgradeTower); + } + + /** + * Determines which type of upgrade to perform based on arguments provided by the event trigger. + * @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 + */ + void upgradeTower(UPGRADE upgradeType, int value) { + switch (upgradeType) { + case ATTACK -> {/*Not implemented yet*/} + case MAXHP -> {/*Not implemented yet either*/} + case FIRERATE -> {/*Not implemented at the present moment*/} + } + } +} diff --git a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java new file mode 100644 index 000000000..df008d2ed --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java @@ -0,0 +1,30 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.tower.TowerUpgraderComponent; +import com.csse3200.game.input.InputService; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.events.EventHandler; +import com.csse3200.game.events.listeners.EventListener2; +import com.csse3200.game.extensions.GameExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import static org.mockito.Mockito.*; + +@ExtendWith(GameExtension.class) +class TowerUpgradeComponentTest { + + @BeforeEach + @Test + void doesNotCrash() { + Entity entity = new Entity(); + InputService inputService = new InputService(); + ServiceLocator.registerInputService(inputService); + TowerUpgraderComponent towerUpgraderComponent = spy(TowerUpgraderComponent.class); + entity.addComponent(towerUpgraderComponent); + entity.create(); + entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.ATTACK, 0); + verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.ATTACK, 0); + } +} From 9cde24aece2a55cfa79a3870fa96f524b08ef15e Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Thu, 31 Aug 2023 20:19:21 +1000 Subject: [PATCH 02/37] Added capabilty for TowerUpgraderComponent to increase the the base attack stat in the CombatStatsComponent of the same entity. --- .../tower/TowerUpgraderComponent.java | 14 +++++++++++- .../tower/TowerUpgradeComponentTest.java | 22 +++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) 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 a88209178..de078bff4 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 @@ -1,5 +1,6 @@ package com.csse3200.game.components.tower; +import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.Component; /** @@ -19,14 +20,25 @@ public void create() { /** * Determines which type of upgrade to perform based on arguments provided by the event trigger. + * * @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 */ void upgradeTower(UPGRADE upgradeType, int value) { switch (upgradeType) { - case ATTACK -> {/*Not implemented yet*/} + case ATTACK -> {upgradeTowerAttack(value);} case MAXHP -> {/*Not implemented yet either*/} case FIRERATE -> {/*Not implemented at the present moment*/} } } + + /** + * Increases the tower's attack stat. + * + * @param increase The amount that the attack stat should increase by. + */ + void upgradeTowerAttack(int increase) { + int oldAttack = getEntity().getComponent(CombatStatsComponent.class).getBaseAttack(); + getEntity().getComponent(CombatStatsComponent.class).setBaseAttack(oldAttack + increase); + } } diff --git a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java index df008d2ed..5397a8184 100644 --- a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java +++ b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java @@ -1,30 +1,30 @@ package com.csse3200.game.components.tower; -import com.csse3200.game.components.tower.TowerUpgraderComponent; -import com.csse3200.game.input.InputService; -import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.entities.Entity; -import com.csse3200.game.events.EventHandler; -import com.csse3200.game.events.listeners.EventListener2; import com.csse3200.game.extensions.GameExtension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(GameExtension.class) class TowerUpgradeComponentTest { + Entity entity; @BeforeEach + void beforeEach() {entity = new Entity();} + @Test - void doesNotCrash() { - Entity entity = new Entity(); - InputService inputService = new InputService(); - ServiceLocator.registerInputService(inputService); + void increaseAttackStat() { TowerUpgraderComponent towerUpgraderComponent = spy(TowerUpgraderComponent.class); + CombatStatsComponent combatStatsComponent = new CombatStatsComponent(100,10); entity.addComponent(towerUpgraderComponent); + entity.addComponent(combatStatsComponent); entity.create(); - entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.ATTACK, 0); - verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.ATTACK, 0); + entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.ATTACK, 10); + verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.ATTACK, 10); + assertEquals(20, combatStatsComponent.getBaseAttack()); } } From 98c3dc545b0943bdbb9c108cdb04d6855a6a3576 Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Sun, 3 Sep 2023 18:04:55 +1000 Subject: [PATCH 03/37] Fixed filename typo --- .../com/csse3200/game/entities/factories/ProjectileFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java index ff861fdb4..83f2093b9 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java @@ -24,7 +24,7 @@ public class ProjectileFactory { private static final NPCConfigs configs = - FileLoader.readClass(NPCConfigs.class, "configs/NPCS.json"); + FileLoader.readClass(NPCConfigs.class, "configs/NPCs.json"); /** * Creates a fireball Entity. From c75f7826cb40af19b9502c0e6049bb1988e6e901 Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Sun, 3 Sep 2023 18:33:32 +1000 Subject: [PATCH 04/37] Implemented the upgrading of a tower's max health, including changes to CombatStatsComponent to alter the fullHealth variable. --- .../game/components/CombatStatsComponent.java | 21 ++++++++++++++++++- .../tower/TowerUpgraderComponent.java | 13 +++++++++++- ...t.java => TowerUpgraderComponentTest.java} | 14 ++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) rename source/core/src/test/com/csse3200/game/components/tower/{TowerUpgradeComponentTest.java => TowerUpgraderComponentTest.java} (63%) diff --git a/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java b/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java index a860b6547..5b1ef208b 100644 --- a/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java +++ b/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java @@ -28,7 +28,7 @@ public class CombatStatsComponent extends Component { private static final Logger logger = LoggerFactory.getLogger(CombatStatsComponent.class); private int health; private int baseAttack; - private final int fullHealth; + private int fullHealth; private String state; private ArrayList drops; private ArrayList closeRangeAbilities; @@ -99,6 +99,25 @@ public void addHealth(int health) { changeState(); } + /** + * Returns the entity's fullHealth value (note that this does not influence the ability to set its actual health) + * + * @return The entity's fullHealth variable + */ + public int getMaxHealth() { + return fullHealth; + } + + /** + * Sets the entity's fullHealth variable. + * Intended for when the entity's maximum health must be changed after creation, like upgrading a turret's HP. + * + * @param newMaxHealth The new value fullHealth should be set to + */ + public void setMaxHealth(int newMaxHealth) { + fullHealth = newMaxHealth; + } + /** * Returns the entity's base attack damage. * 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 de078bff4..12d2b4660 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 @@ -27,7 +27,7 @@ public void create() { void upgradeTower(UPGRADE upgradeType, int value) { switch (upgradeType) { case ATTACK -> {upgradeTowerAttack(value);} - case MAXHP -> {/*Not implemented yet either*/} + case MAXHP -> {upgradeTowerMaxHealth(value);} case FIRERATE -> {/*Not implemented at the present moment*/} } } @@ -41,4 +41,15 @@ void upgradeTowerAttack(int increase) { int oldAttack = getEntity().getComponent(CombatStatsComponent.class).getBaseAttack(); getEntity().getComponent(CombatStatsComponent.class).setBaseAttack(oldAttack + increase); } + + /** + * Increases the tower's maximum health, and restores the tower's health to the new maximum. + * + * @param increase The amount that the max health stat should increase by. + */ + void upgradeTowerMaxHealth(int increase) { + int oldMaxHealth = getEntity().getComponent(CombatStatsComponent.class).getMaxHealth(); + getEntity().getComponent(CombatStatsComponent.class).setMaxHealth(oldMaxHealth + increase); + getEntity().getComponent(CombatStatsComponent.class).setHealth(oldMaxHealth + increase); + } } diff --git a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java similarity index 63% rename from source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java rename to source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java index 5397a8184..a72c89184 100644 --- a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgradeComponentTest.java +++ b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.*; @ExtendWith(GameExtension.class) -class TowerUpgradeComponentTest { +class TowerUpgraderComponentTest { Entity entity; @BeforeEach @@ -27,4 +27,16 @@ void increaseAttackStat() { verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.ATTACK, 10); assertEquals(20, combatStatsComponent.getBaseAttack()); } + + @Test + void increaseMaxHealthStat() { + TowerUpgraderComponent towerUpgraderComponent = spy(TowerUpgraderComponent.class); + CombatStatsComponent combatStatsComponent = new CombatStatsComponent(100,10); + entity.addComponent(towerUpgraderComponent); + entity.addComponent(combatStatsComponent); + entity.create(); + entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.MAXHP, 50); + verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.MAXHP, 50); + assertEquals(150, combatStatsComponent.getMaxHealth()); + } } From 051b6b236678c7dc7e6ec3179405e993f806a4e4 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 4 Sep 2023 13:49:11 +1000 Subject: [PATCH 05/37] added new methods and config files for tower1 and tower2 --- .../main/com/csse3200/game/entities/configs/Tower1Config.java | 2 ++ .../main/com/csse3200/game/entities/configs/Tower2Config.java | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java diff --git a/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java b/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java new file mode 100644 index 000000000..730e7c610 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java @@ -0,0 +1,2 @@ +package com.csse3200.game.entities.configs;public class Tower1Config { +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java b/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java new file mode 100644 index 000000000..fb7f8a560 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java @@ -0,0 +1,2 @@ +package com.csse3200.game.entities.configs;public class Tower2Config { +} From 71079eea006e3187b8e69d2322e264c86c8e1dfa Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 4 Sep 2023 13:51:23 +1000 Subject: [PATCH 06/37] Added the modified files --- source/core/assets/configs/tower.json | 10 +++++++ .../game/entities/configs/Tower1Config.java | 7 ++++- .../game/entities/configs/Tower2Config.java | 7 ++++- .../entities/configs/baseTowerConfigs.java | 2 ++ .../game/entities/factories/TowerFactory.java | 29 ++++++++++++++++--- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/source/core/assets/configs/tower.json b/source/core/assets/configs/tower.json index 004dda33c..d5d255ff7 100644 --- a/source/core/assets/configs/tower.json +++ b/source/core/assets/configs/tower.json @@ -13,5 +13,15 @@ "health": 20, "baseAttack": 0, "cost": 1 + }, + "tower1": { + "health": 10, + "baseAttack": 10, + "cost": 10 + }, + "tower2": { + "health": 10, + "baseAttack": 10, + "cost": 10 } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java b/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java index 730e7c610..9c8f844c7 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java @@ -1,2 +1,7 @@ -package com.csse3200.game.entities.configs;public class Tower1Config { +package com.csse3200.game.entities.configs; + +public class Tower1Config { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java b/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java index fb7f8a560..b471e8166 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java @@ -1,2 +1,7 @@ -package com.csse3200.game.entities.configs;public class Tower2Config { +package com.csse3200.game.entities.configs; + +public class Tower2Config { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java index 13c6fe19b..1e3998e56 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java @@ -7,4 +7,6 @@ public class baseTowerConfigs { public WeaponTowerConfig weapon = new WeaponTowerConfig(); public WallTowerConfig wall = new WallTowerConfig(); public IncomeTowerConfig income = new IncomeTowerConfig(); + public Tower1Config tower1 = new Tower1Config(); + public Tower2Config tower2 = new Tower2Config(); } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 77b29bb45..72d9f7941 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,5 +1,7 @@ package com.csse3200.game.entities.factories; +import com.csse3200.game.entities.Weapon; +import com.csse3200.game.entities.configs.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.badlogic.gdx.graphics.Texture; @@ -13,14 +15,10 @@ import com.csse3200.game.components.tower.TowerAnimationController; import com.csse3200.game.components.tasks.CurrencyTask; import com.csse3200.game.entities.Entity; -import com.csse3200.game.entities.configs.WallTowerConfig; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.components.ColliderComponent; import com.csse3200.game.physics.components.HitboxComponent; import com.csse3200.game.physics.components.PhysicsComponent; -import com.csse3200.game.entities.configs.WeaponTowerConfig; -import com.csse3200.game.entities.configs.IncomeTowerConfig; -import com.csse3200.game.entities.configs.baseTowerConfigs; import com.csse3200.game.files.FileLoader; import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.rendering.TextureRenderComponent; @@ -121,6 +119,29 @@ public static Entity createWeaponTower() { return weapon; } + + public static Entity createWeaponTower1() { + Entity weaponTower = createBaseTower(); + Tower1Config config = configs.tower1; + + weaponTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new CostComponent(config.cost)); + + return weaponTower; + } + + public static Entity createWeaponTower2() { + Entity weaponTower = createBaseTower(); + Tower2Config config = configs.tower2; + + weaponTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))); + + return weaponTower; + } + /** * Creates a generic tower entity to be used as a base entity by more specific tower creation methods. * @return entity From 75f49a845b07a51e42617b716fb1e4e7659ffc38 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Tue, 5 Sep 2023 16:03:32 +1000 Subject: [PATCH 07/37] Created TNTTowerConfigs to store configuration for TNTTower and modified tower.json and baseTowerConfigs --- source/core/assets/configs/tower.json | 5 +++++ .../game/entities/configs/TNTTowerConfigs.java | 10 ++++++++++ .../game/entities/configs/baseTowerConfigs.java | 1 + 3 files changed, 16 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java diff --git a/source/core/assets/configs/tower.json b/source/core/assets/configs/tower.json index 004dda33c..fe44c4d17 100644 --- a/source/core/assets/configs/tower.json +++ b/source/core/assets/configs/tower.json @@ -13,5 +13,10 @@ "health": 20, "baseAttack": 0, "cost": 1 + }, + "TNTTower": { + "health": 10, + "baseAttack": 5, + "cost": 1 } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java new file mode 100644 index 000000000..2dbd2e53d --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java @@ -0,0 +1,10 @@ +package com.csse3200.game.entities.configs; + +/** + * Defines a basic set of properties stored in entities config files to be loaded by Entity Factories. + */ +public class TNTTowerConfigs { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java index 13c6fe19b..d0c920d0c 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java @@ -7,4 +7,5 @@ public class baseTowerConfigs { public WeaponTowerConfig weapon = new WeaponTowerConfig(); public WallTowerConfig wall = new WallTowerConfig(); public IncomeTowerConfig income = new IncomeTowerConfig(); + public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); } \ No newline at end of file From a939d2472eb6733b23aefa67a57315dfc7c4496f Mon Sep 17 00:00:00 2001 From: Mohamad Date: Tue, 5 Sep 2023 16:04:15 +1000 Subject: [PATCH 08/37] Created the TNTTower entity in the TowerFactory class --- .../game/entities/factories/TowerFactory.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 04dab2a54..ae929b8d4 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,5 +1,6 @@ package com.csse3200.game.entities.factories; +import com.csse3200.game.entities.configs.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.badlogic.gdx.graphics.Texture; @@ -13,14 +14,10 @@ import com.csse3200.game.components.tower.TowerAnimationController; import com.csse3200.game.components.tasks.CurrencyTask; import com.csse3200.game.entities.Entity; -import com.csse3200.game.entities.configs.WallTowerConfig; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.components.ColliderComponent; import com.csse3200.game.physics.components.HitboxComponent; import com.csse3200.game.physics.components.PhysicsComponent; -import com.csse3200.game.entities.configs.WeaponTowerConfig; -import com.csse3200.game.entities.configs.IncomeTowerConfig; -import com.csse3200.game.entities.configs.baseTowerConfigs; import com.csse3200.game.files.FileLoader; import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.rendering.TextureRenderComponent; @@ -87,6 +84,17 @@ public static Entity createWallTower() { return wall; } + public static Entity createTNTTower() { + Entity TNTTower = createBaseTower(); + TNTTowerConfigs config = configs.TNTTower; + + TNTTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new CostComponent(config.cost)); + + return TNTTower; + } + /** * Creates a weaponry tower that shoots at mobs - This will most likely need to be extended From 3ebc05571a660e0e0041ba7a6e0fdf8ef0f9785b Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 6 Sep 2023 13:36:25 +1000 Subject: [PATCH 09/37] renamed tower1 to fireTower and tower2 with stunTower in relevant files --- source/core/assets/configs/tower.json | 4 +-- ...Tower1Config.java => FireTowerConfig.java} | 2 +- ...Tower2Config.java => StunTowerConfig.java} | 2 +- .../entities/configs/baseTowerConfigs.java | 4 +-- .../game/entities/factories/TowerFactory.java | 25 ++++++++----------- 5 files changed, 16 insertions(+), 21 deletions(-) rename source/core/src/main/com/csse3200/game/entities/configs/{Tower1Config.java => FireTowerConfig.java} (80%) rename source/core/src/main/com/csse3200/game/entities/configs/{Tower2Config.java => StunTowerConfig.java} (80%) diff --git a/source/core/assets/configs/tower.json b/source/core/assets/configs/tower.json index d5d255ff7..b11cf27c6 100644 --- a/source/core/assets/configs/tower.json +++ b/source/core/assets/configs/tower.json @@ -14,12 +14,12 @@ "baseAttack": 0, "cost": 1 }, - "tower1": { + "fireTower": { "health": 10, "baseAttack": 10, "cost": 10 }, - "tower2": { + "stunTower": { "health": 10, "baseAttack": 10, "cost": 10 diff --git a/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java b/source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java similarity index 80% rename from source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java rename to source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java index 9c8f844c7..7e697040b 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/Tower1Config.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java @@ -1,6 +1,6 @@ package com.csse3200.game.entities.configs; -public class Tower1Config { +public class FireTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; diff --git a/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java b/source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java similarity index 80% rename from source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java rename to source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java index b471e8166..fc711e70f 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/Tower2Config.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java @@ -1,6 +1,6 @@ package com.csse3200.game.entities.configs; -public class Tower2Config { +public class StunTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; diff --git a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java index 1e3998e56..f7549d4f4 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java @@ -7,6 +7,6 @@ public class baseTowerConfigs { public WeaponTowerConfig weapon = new WeaponTowerConfig(); public WallTowerConfig wall = new WallTowerConfig(); public IncomeTowerConfig income = new IncomeTowerConfig(); - public Tower1Config tower1 = new Tower1Config(); - public Tower2Config tower2 = new Tower2Config(); + public FireTowerConfig fireTower = new FireTowerConfig(); + public StunTowerConfig stunTower = new StunTowerConfig(); } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 72d9f7941..595965d72 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,10 +1,6 @@ package com.csse3200.game.entities.factories; -import com.csse3200.game.entities.Weapon; import com.csse3200.game.entities.configs.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; @@ -120,26 +116,25 @@ public static Entity createWeaponTower() { } - public static Entity createWeaponTower1() { - Entity weaponTower = createBaseTower(); - Tower1Config config = configs.tower1; - - weaponTower + public static Entity createFireTower() { + Entity fireTower = createBaseTower(); + FireTowerConfig config = configs.fireTower; + fireTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(new CostComponent(config.cost)); - return weaponTower; + return fireTower; } - public static Entity createWeaponTower2() { - Entity weaponTower = createBaseTower(); - Tower2Config config = configs.tower2; + public static Entity createStunTower() { + Entity stunTower = createBaseTower(); + StunTowerConfig config = configs.stunTower; - weaponTower + stunTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent((new CostComponent(config.cost))); - return weaponTower; + return stunTower; } /** From 82715be1ede319429f42b286891ccd361d1ca004 Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Wed, 6 Sep 2023 13:58:52 +1000 Subject: [PATCH 10/37] Added fire rate upgrade functionality to TowerUpgraderComponent, also added functionality to TowerCombatTask for supporting fire rate upgrades. --- .../components/tasks/TowerCombatTask.java | 44 ++++++++++++++++++- .../tower/TowerUpgraderComponent.java | 6 ++- .../tower/TowerUpgraderComponentTest.java | 33 +++++++++++--- 3 files changed, 75 insertions(+), 8 deletions(-) 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()); + } } From 705c3e24bd409dc98778a3f359462e3ddeac7742 Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Wed, 6 Sep 2023 14:33:46 +1000 Subject: [PATCH 11/37] TowerUpgraderComponent is now added to tower entities when they are created. --- .../com/csse3200/game/entities/factories/TowerFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 77b29bb45..366b0c0f1 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,5 +1,6 @@ package com.csse3200.game.entities.factories; +import com.csse3200.game.components.tower.TowerUpgraderComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.badlogic.gdx.graphics.Texture; @@ -116,7 +117,8 @@ public static Entity createWeaponTower() { .addComponent(new CostComponent(config.cost)) .addComponent(aiTaskComponent) .addComponent(animator) - .addComponent(new TowerAnimationController()); + .addComponent(new TowerAnimationController()) + .addComponent(new TowerUpgraderComponent()); return weapon; From 23d4e6e588f65be742a7989bc54e80b257dba204 Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Wed, 6 Sep 2023 15:01:19 +1000 Subject: [PATCH 12/37] Added repair capability to TowerUpgraderComponent --- .../components/tower/TowerUpgraderComponent.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 789f31d39..07ead7edc 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 @@ -10,7 +10,7 @@ */ public class TowerUpgraderComponent extends Component { public enum UPGRADE { - ATTACK, MAXHP, FIRERATE + ATTACK, MAXHP, FIRERATE, REPAIR } @Override @@ -24,13 +24,14 @@ public void create() { * 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 + * @param value How much the upgrade should change the tower's stats, if applicable */ void upgradeTower(UPGRADE upgradeType, int value) { switch (upgradeType) { case ATTACK -> {upgradeTowerAttack(value);} case MAXHP -> {upgradeTowerMaxHealth(value);} case FIRERATE -> {getEntity().getEvents().trigger("addFireRate", value);} + case REPAIR -> {repairTower();} } } @@ -54,4 +55,12 @@ void upgradeTowerMaxHealth(int increase) { getEntity().getComponent(CombatStatsComponent.class).setMaxHealth(oldMaxHealth + increase); getEntity().getComponent(CombatStatsComponent.class).setHealth(oldMaxHealth + increase); } + + /** + * Restores the tower's health to its maximum health. + */ + void repairTower() { + int maxHealth = getEntity().getComponent(CombatStatsComponent.class).getMaxHealth(); + getEntity().getComponent(CombatStatsComponent.class).setHealth(maxHealth); + } } From 5207c70ef99507751660ea5cda607d8c97364072 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Wed, 6 Sep 2023 23:11:42 +1000 Subject: [PATCH 13/37] Added png and atlas file for TNT_Tower --- .../core/assets/images/towers/TNTTower.atlas | 104 ++++++++++++++++++ source/core/assets/images/towers/TNTTower.png | Bin 0 -> 8486 bytes 2 files changed, 104 insertions(+) create mode 100644 source/core/assets/images/towers/TNTTower.atlas create mode 100644 source/core/assets/images/towers/TNTTower.png diff --git a/source/core/assets/images/towers/TNTTower.atlas b/source/core/assets/images/towers/TNTTower.atlas new file mode 100644 index 000000000..d74b23a05 --- /dev/null +++ b/source/core/assets/images/towers/TNTTower.atlas @@ -0,0 +1,104 @@ + +TNTTower.png +size: 1024, 128 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +default + rotate: false + xy: 773, 104 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 0 +dig + rotate: false + xy: 2, 21 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 4 +dig + rotate: false + xy: 751, 104 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 1 +dig + rotate: false + xy: 2, 2 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 3 +dig + rotate: false + xy: 24, 21 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 0 +dig + rotate: false + xy: 751, 85 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 5 +dig + rotate: false + xy: 24, 2 + size: 20, 17 + orig: 20, 17 + offset: 0, 0 + index: 2 +explode + rotate: false + xy: 2, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 2 +explode + rotate: false + xy: 109, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 4 +explode + rotate: false + xy: 216, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 1 +explode + rotate: false + xy: 323, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 6 +explode + rotate: false + xy: 430, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 3 +explode + rotate: false + xy: 537, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 0 +explode + rotate: false + xy: 644, 40 + size: 105, 81 + orig: 105, 81 + offset: 0, 0 + index: 5 diff --git a/source/core/assets/images/towers/TNTTower.png b/source/core/assets/images/towers/TNTTower.png new file mode 100644 index 0000000000000000000000000000000000000000..bec8f52681c511acf5274a83c1802a4af4dcb162 GIT binary patch literal 8486 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 Date: Wed, 6 Sep 2023 23:24:12 +1000 Subject: [PATCH 14/37] Added listeners for TNT_Tower animations in TNTAnimationController --- .../tower/TNTAnimationController.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java diff --git a/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java new file mode 100644 index 000000000..03153cd31 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java @@ -0,0 +1,49 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to TNTTower entity's state and plays the animation when one + * of the events is triggered. + */ +public class TNTAnimationController extends Component { + AnimationRenderComponent animator; + + /** + * Creation call for a TNTAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations and sounds. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("digStart", this::animateDig); + entity.getEvents().addListener("defaultStart", this::animateDefault); + entity.getEvents().addListener("explodeStart",this::animateExplode); + + } + + /** + * Triggers the "dig" animation for the entity. + * This method should be invoked when the entity enters the digging state. + */ + void animateDig() { + animator.startAnimation("dig"); + } + + /** + * Triggers the "default" animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { animator.startAnimation("default");} + + /** + * Triggers the "explode" animation for the entity. + * This method should be invoked when the entity enters the explosion state. + */ + void animateExplode() { animator.startAnimation("explode"); + + } + +} From 6fb7ebef9a08e210ffccc579cf21b509c866458b Mon Sep 17 00:00:00 2001 From: Mohamad Date: Wed, 6 Sep 2023 23:25:51 +1000 Subject: [PATCH 15/37] Added AIComponent and AnimationRenderComponent to TNT_Tower --- .../game/entities/factories/TowerFactory.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index ae929b8d4..93aa93794 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,5 +1,9 @@ package com.csse3200.game.entities.factories; +import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.tasks.TNTTowerCombatTask; +import com.csse3200.game.components.tower.TNTAnimationController; +import com.csse3200.game.components.tower.TNTDamageComponent; import com.csse3200.game.entities.configs.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +37,18 @@ public class TowerFactory { private static final int COMBAT_TASK_PRIORITY = 2; private static final int WEAPON_TOWER_MAX_RANGE = 40; + private static final int TNT_TOWER_MAX_RANGE = 6; + private static final int TNT_TOWER_RANGE = 5; + private static final int TNT_KNOCK_BACK_FORCE = 10; private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; + private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; + private static final String DEFAULT_ANIM = "default"; + private static final float DEFAULT_SPEED= 0.2f; + private static final String DIG_ANIM = "dig"; + private static final float DIG_SPEED = 0.2f; + private static final String EXPLODE_ANIM = "explode"; + private static final float EXPLODE_SPEED = 0.2f; private static final String IDLE_ANIM = "idle"; private static final float IDLE_SPEED = 0.3f; private static final String DEPLOY_ANIM = "deploy"; @@ -84,13 +98,37 @@ public static Entity createWallTower() { return wall; } + + /** + * Create a type of TNT that explodes once it detects a mob within a certain range. + * Upon detonation, the TNT will apply both knock-back and health damage to the affected mobs + * @return entity + */ public static Entity createTNTTower() { Entity TNTTower = createBaseTower(); TNTTowerConfigs config = configs.TNTTower; + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new TNTTowerCombatTask(COMBAT_TASK_PRIORITY, TNT_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(TNT_ATLAS, TextureAtlas.class)); + + animator.addAnimation(DIG_ANIM, DIG_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(DEFAULT_ANIM,DEFAULT_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(EXPLODE_ANIM,EXPLODE_SPEED, Animation.PlayMode.NORMAL); + TNTTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent(new CostComponent(config.cost)); + .addComponent(new CostComponent(config.cost)) + .addComponent(new TNTDamageComponent(PhysicsLayer.NPC,TNT_KNOCK_BACK_FORCE,TNT_TOWER_RANGE)) + .addComponent(aiTaskComponent) + .addComponent(animator) + .addComponent(new TNTAnimationController()); + + TNTTower.getComponent(AnimationRenderComponent.class).scaleEntity(); return TNTTower; } From b05f0880ab109621cdfa9ac7c649ceec8ff0c340 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Wed, 6 Sep 2023 23:28:20 +1000 Subject: [PATCH 16/37] Added a function to PhysicsMovementComponent which pauses the update procedure for a certain duration in order to apply knock-back on moving entities --- .../components/PhysicsMovementComponent.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/source/core/src/main/com/csse3200/game/physics/components/PhysicsMovementComponent.java b/source/core/src/main/com/csse3200/game/physics/components/PhysicsMovementComponent.java index 9acbf4f64..e1f88cb1d 100644 --- a/source/core/src/main/com/csse3200/game/physics/components/PhysicsMovementComponent.java +++ b/source/core/src/main/com/csse3200/game/physics/components/PhysicsMovementComponent.java @@ -12,6 +12,7 @@ public class PhysicsMovementComponent extends Component implements MovementController { private static final Logger logger = LoggerFactory.getLogger(PhysicsMovementComponent.class); private Vector2 maxSpeed = Vector2Utils.ONE; + private float skipMovementTime = 0f; // in seconds, for knockback private PhysicsComponent physicsComponent; private Vector2 targetPosition; @@ -24,12 +25,24 @@ public void create() { @Override public void update() { + if (skipMovementTime > 0) { + skipMovementTime -= 0.02f; + return; + } if (movementEnabled && targetPosition != null) { Body body = physicsComponent.getBody(); updateDirection(body); } } + /** + * Applies knock-back to the entity by disabling its movement for a specified duration. + * @param duration The time (in seconds) for which the entity's movement will be disabled. + */ + public void applyKnockback(float duration) { + this.skipMovementTime = duration; + } + /** * Enable/disable movement for the controller. Disabling will immediately set velocity to 0. * From b095e0841cc26af776fb57816c98cecd91ed281c Mon Sep 17 00:00:00 2001 From: Mohamad Date: Wed, 6 Sep 2023 23:29:33 +1000 Subject: [PATCH 17/37] Implemented the AOE effect for TNTTower but can still be used for other types of entities --- .../components/tower/TNTDamageComponent.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java diff --git a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java new file mode 100644 index 000000000..c019b92fd --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java @@ -0,0 +1,142 @@ +package com.csse3200.game.components.tower; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.physics.box2d.Body; +import com.badlogic.gdx.physics.box2d.Fixture; +import com.badlogic.gdx.utils.Array; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.Component; +import com.csse3200.game.components.maingame.MainGameActions; +import com.csse3200.game.components.tasks.WaitTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.physics.BodyUserData; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +import com.csse3200.game.services.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class responsible for applying damage and knock-back to nearby entities when triggered. + * Utilizes HitboxComponent and CombatStatsComponent for functionality. + */ +public class TNTDamageComponent extends Component { + private static final Logger logger = LoggerFactory.getLogger(TNTDamageComponent.class); + private short targetLayer; + private float knockbackForce = 0f; + private float radius; + private CombatStatsComponent combatStats; + private HitboxComponent hitboxComponent; + + /** + * Default constructor for creating a component without knockback. + * + * @param targetLayer The physics layer of the target entities' collider. + */ + public TNTDamageComponent(short targetLayer) { + this.targetLayer = targetLayer; + } + + /** + * Overloaded constructor for creating a component with knockback and radius. + * + * @param targetLayer The physics layer of the target entities' collider. + * @param knockback The force of the knockback. + * @param radius The radius within which entities will be affected. + */ + public TNTDamageComponent(short targetLayer, float knockback, float radius) { + this.targetLayer = targetLayer; + this.knockbackForce = knockback; + this.radius = radius; + } + + /** + * Initializes the component and registers event listeners. + */ + @Override + public void create() { + entity.getEvents().addListener("TNTDamageStart", this::applyTNTDamage); + combatStats = entity.getComponent(CombatStatsComponent.class); + hitboxComponent = entity.getComponent(HitboxComponent.class); + } + + /** + * Scans all nearby entities within a given radius and applies damage and knockback to them. + */ + private void applyTNTDamage() { + // Fetch nearby entities + Array allEntities = ServiceLocator.getEntityService().getEntities(); + + for (int i = 0; i < allEntities.size; i++) { + Entity otherEntity = allEntities.get(i); + + if (entity == otherEntity) continue; // Skip the source entity + + Vector2 positionSource = entity.getPosition(); + Vector2 positionOther = otherEntity.getPosition(); + + if (positionSource.dst(positionOther) <= radius) { + HitboxComponent sourceHitbox = entity.getComponent(HitboxComponent.class); + HitboxComponent otherHitbox = otherEntity.getComponent(HitboxComponent.class); + + // Check for null components and log specifics + if (sourceHitbox == null || otherHitbox == null) { + if (sourceHitbox == null) { + logger.debug("Warning: Source Entity without HitboxComponent. Source Entity: " + entity); + } + if (otherHitbox == null) { + logger.debug("Warning: Other Entity without HitboxComponent. Other Entity: " + otherEntity); + } + continue; + } + + applyDamage(sourceHitbox.getFixture(), otherHitbox.getFixture()); + } + } + } + + + /** + * Applies damage and knockback to a specific entity based on their physics fixtures. + * + * @param me The fixture representing this entity. + * @param other The fixture representing the target entity. + */ + private void applyDamage(Fixture me, Fixture other) { + if (hitboxComponent.getFixture() != me) { + // Not triggered by hitbox, ignore + return; + } + + if (!PhysicsLayer.contains(targetLayer, other.getFilterData().categoryBits)) { + // Doesn't match our target layer, ignore + return; + } + + // Try to attack target. + Entity target = ((BodyUserData) other.getBody().getUserData()).entity; + CombatStatsComponent targetStats = target.getComponent(CombatStatsComponent.class); + if (targetStats != null) { + targetStats.hit(combatStats); + } + + // apply knock-back + PhysicsComponent physicsComponent = target.getComponent(PhysicsComponent.class); + PhysicsMovementComponent movementComponent = target.getComponent(PhysicsMovementComponent.class); + + if (physicsComponent != null && knockbackForce > 0f) { + // Disable regular movement temporarily + if (movementComponent != null) { + movementComponent.applyKnockback(0.5f); // Disable movement for 0.5 seconds + } + + Body targetBody = physicsComponent.getBody(); + Vector2 direction = target.getCenterPosition().sub(entity.getCenterPosition()).nor(); + Vector2 impulse = direction.scl(knockbackForce); + targetBody.applyLinearImpulse(impulse, targetBody.getWorldCenter(), true); + } + } +} \ No newline at end of file From d2ddb2dc4aac9b660c7860d2369a7355cbe6aecd Mon Sep 17 00:00:00 2001 From: Mohamad Date: Wed, 6 Sep 2023 23:33:01 +1000 Subject: [PATCH 18/37] The TNTTowerCombatTask runs the AI for the TNTTower class. It has a bug at the moment where when one entity gets disposed, all other TNT_Towers lose their animation component --- .../components/tasks/TNTTowerCombatTask.java | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java new file mode 100644 index 000000000..c55a92450 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java @@ -0,0 +1,173 @@ +package com.csse3200.game.components.tasks; + + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * The TNTTowerCombatTask runs the AI for the TNTTower class. The tower will scan for targets in a straight line + * from its center point until a point at (x + maxRange, y), where x,y are the coordinates of the tower's center + * position. This component should be added to an AiTaskComponent attached to the tower instance. + */ +public class TNTTowerCombatTask extends DefaultTask implements PriorityTask { + // Constants + private static final int INTERVAL = 1; // time interval to scan for enemies in seconds + private static final short TARGET = PhysicsLayer.NPC; // The type of targets that the tower will detect + // the following four constants are the event names that will be triggered in the state machine + private static final String DIG = "digStart"; + private static final String EXPLOSION = "explodeStart"; + private static final String DEFAULT = "defaultStart"; + private static final String DAMAGE = "TNTDamageStart"; + + + // class attributes + private final int priority; // The active priority this task will have + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); // initial placeholder value - will be overwritten + private final Vector2 maxRangePosition = new Vector2(); + private final PhysicsEngine physics; + private final GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + private boolean readToDelete = false; + + private enum STATE { + IDLE, EXPLODE, REMOVE + } + private STATE towerState = STATE.IDLE; + + /** + * @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 + */ + public TNTTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + + } + + /** + * Starts the Task running, triggers the initial "defaultStart" event. + */ + @Override + public void start() { + super.start(); + // Set the tower's coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Default mode + owner.getEntity().getEvents().trigger(DEFAULT); + + endTime = timeSource.getTime() + (INTERVAL * 500); + } + + /** + * The update method is what is run every time the TaskRunner in the AiTaskComponent calls update(). + * triggers events depending on the presence or otherwise of targets in the detection range + */ + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * TNT tower state machine. Updates tower state by scanning for mobs, and + * triggers the appropriate events corresponding to the STATE enum. + */ + public void updateTowerState() { + // configure tower state depending on target visibility + + switch (towerState) { + case IDLE -> { + // targets detected in idle mode - start deployment + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(DIG); + towerState = STATE.EXPLODE; + } else { + owner.getEntity().getEvents().trigger(DEFAULT); + } + } + case EXPLODE -> { + owner.getEntity().getEvents().trigger(EXPLOSION); + owner.getEntity().getEvents().trigger(DAMAGE); + towerState = STATE.REMOVE; + } + case REMOVE -> { + readToDelete = true; + } + } + + + } + /** + * For stopping the running task + */ + @Override + public void stop() { + super.stop(); + } + + /** + * Returns the current priority of the task. + * @return active priority value if targets detected, inactive priority otherwise + */ + @Override + public int getPriority() { + + if (isReadyToDelete()) { + owner.getEntity().setFlagForDelete(true); + return -1; + } else { + return priority; + } + } + + /** + * Fetches the active priority of the Task if a target is visible. + * @return (int) active priority if a target is visible, -1 otherwise + */ + private int getActivePriority() { + + return !isTargetVisible() ? 0 : priority; + } + + /** + * Fetches the inactive priority of the Task if a target is not visible. + * @return (int) -1 if a target is not visible, active priority otherwise + */ + private int getInactivePriority() { + + return isTargetVisible() ? priority : 0; + } + + /** + * Uses a raycast to determine whether there are any targets in detection range + * @return true if a target is visible, false otherwise + */ + 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); + } + + private boolean isReadyToDelete() { + + return readToDelete; + } +} + + + + From 3ec0494901a047ca8787d7daa98bbda144410e96 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Wed, 6 Sep 2023 23:34:19 +1000 Subject: [PATCH 19/37] Spawned TNT_Tower in ForestGameArea and added the flag in the update function of Entity --- .../csse3200/game/areas/ForestGameArea.java | 20 +++++++++++++++++-- .../com/csse3200/game/entities/Entity.java | 6 +++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index c3e93e6ef..0b3fc6557 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -84,7 +84,8 @@ public class ForestGameArea extends GameArea { "images/background/building2.png", "images/iso_grass_3.png", "images/economy/scrap.png", - "images/towers/mine_tower.png" + "images/towers/mine_tower.png", + "images/towers/TNTTower.png" }; private static final String[] forestTextureAtlases = { "images/terrain_iso_grass.atlas", @@ -94,7 +95,8 @@ public class ForestGameArea extends GameArea { "images/towers/turret01.atlas", "images/mobs/xenoGruntRunning.atlas", "images/mobs/robot.atlas", - "images/mobs/rangeBossRight.atlas" + "images/mobs/rangeBossRight.atlas", + "images/towers/TNTTower.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -155,6 +157,8 @@ public void create() { bossKing1 = spawnBossKing1(); bossKing2 = spawnBossKing2(); + spawnTNTTower(); + playMusic(); } @@ -398,6 +402,18 @@ private void spawnWeaponTower() { } } + private void spawnTNTTower() { + GridPoint2 minPos = new GridPoint2(0, 0); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity weaponTower = TowerFactory.createTNTTower(); + spawnEntityAt(weaponTower, randomPos, true, true); + } + + } + private void playMusic() { Music music = ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class); diff --git a/source/core/src/main/com/csse3200/game/entities/Entity.java b/source/core/src/main/com/csse3200/game/entities/Entity.java index e1cf2735c..676292dfb 100644 --- a/source/core/src/main/com/csse3200/game/entities/Entity.java +++ b/source/core/src/main/com/csse3200/game/entities/Entity.java @@ -252,6 +252,10 @@ public void update() { for (Component component : createdComponents) { component.triggerUpdate(); } + if (isFlaggedForDelete) { + dispose(); + return; + } } /** @@ -281,7 +285,7 @@ public boolean getFlagForDelete() { * @param condition true to flag for deletion, false to unflag */ public void setFlagForDelete(boolean condition) { - isFlaggedForDelete = condition; + this.isFlaggedForDelete = condition; } @Override From f56ca5ffc7c039c8228f222a5aea21bcd1a3afe8 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Thu, 7 Sep 2023 15:10:03 +1000 Subject: [PATCH 20/37] Fixed the dispose functionality. I also had to comment out atlas.dispose in AnimationRenderComponent --- .../src/main/com/csse3200/game/areas/ForestGameArea.java | 6 +++--- .../game/components/tower/TNTAnimationController.java | 2 +- .../csse3200/game/entities/factories/ProjectileFactory.java | 2 +- .../csse3200/game/rendering/AnimationRenderComponent.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index 0b3fc6557..4623cb16d 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -136,9 +136,9 @@ public void create() { displayUI(); spawnTerrain(); - spawnBuilding1(); - spawnBuilding2(); - spawnMountains(); +// spawnBuilding1(); +// spawnBuilding2(); +// spawnMountains(); player = spawnPlayer(); playMusic(); diff --git a/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java index 03153cd31..1f092dab5 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/TNTAnimationController.java @@ -8,7 +8,7 @@ * of the events is triggered. */ public class TNTAnimationController extends Component { - AnimationRenderComponent animator; + private AnimationRenderComponent animator; /** * Creation call for a TNTAnimationController, fetches the animationRenderComponent that this controller will diff --git a/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java index 33d596599..cb02aa1fa 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java @@ -44,7 +44,7 @@ public static Entity createFireBall(Entity target, Vector2 destination, Vector2 .addComponent(new ColliderComponent().setSensor(true)) // This is the component that allows the projectile to damage a specified target. - .addComponent(new TouchAttackComponent(PhysicsLayer.PLAYER, 1.5f, true)) + .addComponent(new TouchAttackComponent(PhysicsLayer.NPC, 1.5f, true)) .addComponent(new CombatStatsComponent(config.health, config.baseAttack)); projectile diff --git a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java index 2c403e26b..8f52f18bb 100644 --- a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java +++ b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java @@ -179,7 +179,7 @@ protected void draw(SpriteBatch batch) { @Override public void dispose() { - atlas.dispose(); + // atlas.dispose(); super.dispose(); } } From 0b5452e2112c807ccc7bcd6da1f4f4b38223e148 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Thu, 7 Sep 2023 15:38:08 +1000 Subject: [PATCH 21/37] fixed bug (#104) --- .../com/csse3200/game/rendering/AnimationRenderComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java index 8f52f18bb..0143567f3 100644 --- a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java +++ b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java @@ -179,7 +179,7 @@ protected void draw(SpriteBatch batch) { @Override public void dispose() { - // atlas.dispose(); + // atlas.dispose(); // this has to be disabled to keep the atlas file for other entities that rely on it super.dispose(); } } From e46afceb4568bed0f8ecfab601988b2641757b6d Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 7 Sep 2023 19:07:37 +1000 Subject: [PATCH 22/37] created the FireTowerAnimationController, FireTowerCombatTask and completed the createFireTower method in TowerFactory. --- .../images/towers/fire_tower_atlas.atlas | 83 ++++++++++++ .../assets/images/towers/fire_tower_atlas.png | Bin 0 -> 8037 bytes .../csse3200/game/areas/ForestGameArea.java | 4 + .../components/tasks/FireTowerCombatTask.java | 126 ++++++++++++++++++ .../tower/FireTowerAnimationController.java | 42 ++++++ .../game/entities/factories/TowerFactory.java | 28 +++- 6 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 source/core/assets/images/towers/fire_tower_atlas.atlas create mode 100644 source/core/assets/images/towers/fire_tower_atlas.png create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java create mode 100644 source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java diff --git a/source/core/assets/images/towers/fire_tower_atlas.atlas b/source/core/assets/images/towers/fire_tower_atlas.atlas new file mode 100644 index 000000000..80fa90a9f --- /dev/null +++ b/source/core/assets/images/towers/fire_tower_atlas.atlas @@ -0,0 +1,83 @@ + +fire_tower_atlas.png +size: 2048, 256 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +attac_prep + rotate: false + xy: 2, 2 + size: 159, 150 + orig: 159, 150 + offset: 0, 0 + index: 2 +attack + rotate: false + xy: 467, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 1 +attack + rotate: false + xy: 923, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 3 +attack + rotate: false + xy: 1075, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 0 +attack + rotate: false + xy: 1379, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 2 +attack_prep + rotate: false + xy: 315, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 0 +idle + rotate: false + xy: 163, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 1 +attack_prep + rotate: false + xy: 163, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 1 +idle + rotate: false + xy: 619, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 3 +idle + rotate: false + xy: 771, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 0 +idle + rotate: false + xy: 1227, 2 + size: 150, 150 + orig: 150, 150 + offset: 0, 0 + index: 2 diff --git a/source/core/assets/images/towers/fire_tower_atlas.png b/source/core/assets/images/towers/fire_tower_atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..cbdce58d1a7ef6309bca48b921303229e0648956 GIT binary patch literal 8037 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 literal 0 HcmV?d00001 diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index c3e93e6ef..d1aedf83c 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -68,6 +68,7 @@ public class ForestGameArea extends GameArea { "images/towers/turret.png", "images/towers/turret01.png", "images/towers/turret_deployed.png", + "images/towers/fire_tower_atlas.png", "images/background/building2.png", "images/mobs/robot.png", "images/mobs/Attack_1.png", @@ -92,6 +93,7 @@ public class ForestGameArea extends GameArea { "images/ghostKing.atlas", "images/towers/turret.atlas", "images/towers/turret01.atlas", + "images/towers/fire_tower_atlas.atlas", "images/mobs/xenoGruntRunning.atlas", "images/mobs/robot.atlas", "images/mobs/rangeBossRight.atlas" @@ -393,7 +395,9 @@ private void spawnWeaponTower() { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity weaponTower = TowerFactory.createWeaponTower(); Entity wallTower = TowerFactory.createWallTower(); + Entity fireTower = TowerFactory.createFireTower(); spawnEntityAt(weaponTower, randomPos, true, true); + spawnEntityAt(fireTower, randomPos, true, true); spawnEntityAt(wallTower, new GridPoint2(randomPos.x + 3, randomPos.y), true, true); } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java new file mode 100644 index 000000000..d2c2361f9 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -0,0 +1,126 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * The FireTowerCombatTask runs the AI for the FireTower class. The tower implementing this task will scan for enemies + * in a straight line from the current position to a maxRange, and change the state of the tower. + */ +public class FireTowerCombatTask extends DefaultTask implements PriorityTask { + //constants + private static final int INTERVAL = 1; //time interval to scan for enemies in seconds + private static final short TARGET = PhysicsLayer.NPC; //the type of targets this tower will detect + //The constants are names of events that will be triggered in the state machine + private static final String IDLE = "startIdle"; + private static final String PREP_ATTACK = "startAttackPrep"; + private static final String ATTACK = "startAttack"; + + //Class attributes + private final int priority; + private final float maxRange; + + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + private enum STATE { + IDLE, PREP_ATTACK, ATTACK + } + private STATE towerState = STATE.IDLE; + + /** + * Starts the task running, triggers the initial 'IDLE' event + */ + public FireTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + @Override + public void start() { + super.start(); + // get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + //default to idle state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 500); + } + + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + public void updateTowerState() { + switch (towerState) { + case IDLE -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(PREP_ATTACK); + towerState = STATE.PREP_ATTACK; + } + } + case PREP_ATTACK -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + case ATTACK -> { + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createFireBall(owner.getEntity(), + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.75), + (float) (owner.getEntity().getPosition().y + 0.75)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + } + } + + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + private int getActivePriority() { + return !isTargetVisible() ? 0 : priority; + } + + private int getInactivePriority() { + return isTargetVisible() ? priority : 0; + } + + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java new file mode 100644 index 000000000..297476ec0 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java @@ -0,0 +1,42 @@ +package com.csse3200.game.components.tower; + +import com.badlogic.gdx.audio.Sound; +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +public class FireTowerAnimationController extends Component{ + //Event name constants + private static final String IDLE = "startIdle"; + private static final String PREP_ATTACK = "startAttackPrep"; + private static final String ATTACK = "startAttack"; + + //animation name constants + private static final String IDLE_ANIM = "idle"; + private static final String PREP_ATTACK_ANIM = "prepAttack"; + private static final String ATTACK_ANIM = "attack"; + //here we can add the sounds for the implemented animations + + AnimationRenderComponent animator; + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(IDLE, this::animateIdle); + entity.getEvents().addListener(PREP_ATTACK, this::animatePrepAttack); + entity.getEvents().addListener(ATTACK, this::animateAttack); + } + + void animateIdle() { + animator.startAnimation(IDLE_ANIM); + } + + void animatePrepAttack() { + animator.startAnimation(PREP_ATTACK_ANIM); + } + + void animateAttack() { + animator.startAnimation(ATTACK_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index b0ade3ab2..c290e4212 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,5 +1,7 @@ package com.csse3200.game.entities.factories; +import com.csse3200.game.components.tasks.FireTowerCombatTask; +import com.csse3200.game.components.tower.FireTowerAnimationController; import com.csse3200.game.entities.configs.*; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; @@ -32,6 +34,7 @@ public class TowerFactory { private static final int WEAPON_TOWER_MAX_RANGE = 40; private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; + private static final String FIRE_TURRET_ATLAS = "images/towers/fire_tower_atlas.atlas"; private static final String IDLE_ANIM = "idle"; private static final float IDLE_SPEED = 0.3f; private static final String DEPLOY_ANIM = "deploy"; @@ -40,7 +43,13 @@ public class TowerFactory { private static final float STOW_SPEED = 0.2f; private static final String FIRE_ANIM = "firing"; private static final float FIRE_SPEED = 0.25f; - private static final int INCOME_INTERVAL = 300; + private static final String FIRE_TOWER_IDLE_ANIM = "idle"; + private static final float FIRE_TOWER_IDLE_SPEED = 0.3f; + private static final String FIRE_TOWER_PREP_ATTACK_ANIM = "prepAttack"; + private static final float FIRE_TOWER_PREP_ATTACK_SPEED = 0.2f; + private static final String FIRE_TOWER_ATTACK_ANIM = "attack"; + private static final float FIRE_TOWER_ATTACK_SPEED = 0.25f; + private static final int INCOME_INTERVAL = 300; private static final int INCOME_TASK_PRIORITY = 1; private static final baseTowerConfigs configs = @@ -119,9 +128,24 @@ public static Entity createWeaponTower() { public static Entity createFireTower() { Entity fireTower = createBaseTower(); FireTowerConfig config = configs.fireTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new FireTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(FIRE_TURRET_ATLAS, TextureAtlas.class)); + animator.addAnimation(FIRE_TOWER_IDLE_ANIM, FIRE_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(FIRE_TOWER_PREP_ATTACK_ANIM, FIRE_TOWER_PREP_ATTACK_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(FIRE_TOWER_ATTACK_ANIM, FIRE_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); + fireTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent(new CostComponent(config.cost)); + .addComponent(new CostComponent(config.cost)) + .addComponent(aiTaskComponent) + .addComponent(animator) + .addComponent(new FireTowerAnimationController()); return fireTower; } From 4bbef11b0e2692b6742274125f7052f95f3502ae Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 8 Sep 2023 01:20:12 +1000 Subject: [PATCH 23/37] Added files StunTowerCombatTask, StunTowerAnimationController, stun_tower.png, stun_tower.atlas and finished the base functionality for createStunTower method in TowerFactory. --- .../assets/images/towers/stun_tower.atlas | 118 ++++++++++++ .../core/assets/images/towers/stun_tower.png | Bin 0 -> 6954 bytes .../csse3200/game/areas/ForestGameArea.java | 178 +++++++++--------- .../components/tasks/StunTowerCombatTask.java | 109 +++++++++++ .../tower/StunTowerAnimationController.java | 35 ++++ .../game/entities/factories/TowerFactory.java | 32 +++- 6 files changed, 381 insertions(+), 91 deletions(-) create mode 100644 source/core/assets/images/towers/stun_tower.atlas create mode 100644 source/core/assets/images/towers/stun_tower.png create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java create mode 100644 source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java diff --git a/source/core/assets/images/towers/stun_tower.atlas b/source/core/assets/images/towers/stun_tower.atlas new file mode 100644 index 000000000..feeea736b --- /dev/null +++ b/source/core/assets/images/towers/stun_tower.atlas @@ -0,0 +1,118 @@ + +stun_tower.png +size: 1024, 64 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +attack + rotate: false + xy: 2, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 4 +attack + rotate: false + xy: 136, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 9 +attack + rotate: false + xy: 203, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 1 +attack + rotate: false + xy: 270, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 6 +attack + rotate: false + xy: 404, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 3 +attack + rotate: false + xy: 538, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 8 +attack + rotate: false + xy: 672, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 0 +attack + rotate: false + xy: 739, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 5 +attack + rotate: false + xy: 873, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 2 +attack + rotate: false + xy: 940, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 7 +idle + rotate: false + xy: 69, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 1 +idle + rotate: false + xy: 337, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 3 +idle + rotate: false + xy: 471, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 0 +idle + rotate: false + xy: 605, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 5 +idle + rotate: false + xy: 605, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 4 +idle + rotate: false + xy: 806, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 2 diff --git a/source/core/assets/images/towers/stun_tower.png b/source/core/assets/images/towers/stun_tower.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5c0d4f6c69a165cf03552b2a3c8e24e284fac5 GIT binary patch literal 6954 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 fixedPositions = new ArrayList<>(); //Generating ArrayList - - fixedPositions.add(new GridPoint2(5, 8)); - fixedPositions.add(new GridPoint2(12, 4)); - fixedPositions.add(new GridPoint2(20, 10)); - fixedPositions.add(new GridPoint2(33, 17)); - - for (GridPoint2 fixedPos : fixedPositions) { - Entity tree = ObstacleFactory.createMountain(); - spawnEntityAt(tree, fixedPos, true, false); - } - } +// private void spawnBuilding1() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building1 = ObstacleFactory.createBuilding1(); +// spawnEntityAt(building1, randomPos, true, false); +// } +// } +// private void spawnBuilding2() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building2 = ObstacleFactory.createBuilding2(); +// spawnEntityAt(building2, randomPos, true, false); +// } +// } + +// private void spawnMountains() { +// ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList +// +// fixedPositions.add(new GridPoint2(5, 8)); +// fixedPositions.add(new GridPoint2(12, 4)); +// fixedPositions.add(new GridPoint2(20, 10)); +// fixedPositions.add(new GridPoint2(33, 17)); +// +// for (GridPoint2 fixedPos : fixedPositions) { +// Entity tree = ObstacleFactory.createMountain(); +// spawnEntityAt(tree, fixedPos, true, false); +// } +// } private Entity spawnPlayer() { Entity newPlayer = PlayerFactory.createPlayer(); @@ -324,34 +326,34 @@ private void spawnXenoGrunts() { } } - private Entity spawnGhostKing() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 0); - GridPoint2 randomPos - = RandomUtils.random(minPos, maxPos); - // = new GridPoint2(26, 26); - Entity ghostKing = NPCFactory.createGhostKing(player); - spawnEntityAt(ghostKing, randomPos, true, true); - return ghostKing; - - } - - private Entity spawnBossKing2() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - - for (int i = 0; i < NUM_BOSS; i++) { - int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate - int randomY = MathUtils.random(0, maxPos.y); - GridPoint2 randomPos = new GridPoint2(fixedX, randomY); - bossKing2 = BossKingFactory.createBossKing2(player); - spawnEntityAt(bossKing2, - randomPos, - true, - false); - } - return bossKing2; - } +// private Entity spawnGhostKing() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 0); +// GridPoint2 randomPos +// = RandomUtils.random(minPos, maxPos); +// // = new GridPoint2(26, 26); +// Entity ghostKing = NPCFactory.createGhostKing(player); +// spawnEntityAt(ghostKing, randomPos, true, true); +// return ghostKing; +// +// } + +// private Entity spawnBossKing2() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BOSS; i++) { +// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate +// int randomY = MathUtils.random(0, maxPos.y); +// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); +// bossKing2 = BossKingFactory.createBossKing2(player); +// spawnEntityAt(bossKing2, +// randomPos, +// true, +// false); +// } +// return bossKing2; +// } /** * Creates multiple projectiles that travel simultaneous. They all have same @@ -364,13 +366,13 @@ private Entity spawnBossKing2() { * @param speed The speed of the projectiles. * @param quantity The amount of projectiles to spawn. */ - private void spawnMultiProjectile(Vector2 position, Entity target, int direction, int space, Vector2 speed, int quantity) { - int half = quantity / 2; - for (int i = 0; i < quantity; i++) { - spawnProjectile(position, target, space * half, direction, speed); - --half; - } - } +// private void spawnMultiProjectile(Vector2 position, Entity target, int direction, int space, Vector2 speed, int quantity) { +// int half = quantity / 2; +// for (int i = 0; i < quantity; i++) { +// spawnProjectile(position, target, space * half, direction, speed); +// --half; +// } +// } /** * Returns projectile that can do an area of effect damage @@ -393,11 +395,13 @@ private void spawnWeaponTower() { for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity weaponTower = TowerFactory.createWeaponTower(); + //Entity weaponTower = TowerFactory.createWeaponTower(); Entity wallTower = TowerFactory.createWallTower(); Entity fireTower = TowerFactory.createFireTower(); - spawnEntityAt(weaponTower, randomPos, true, true); + Entity stunTower = TowerFactory.createStunTower(); + //spawnEntityAt(weaponTower, randomPos, true, true); spawnEntityAt(fireTower, randomPos, true, true); + spawnEntityAt(stunTower, randomPos, true, true); spawnEntityAt(wallTower, new GridPoint2(randomPos.x + 3, randomPos.y), true, true); } } @@ -440,16 +444,16 @@ public void dispose() { this.unloadAssets(); } - private void spawnScrap() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - - for (int i = 0; i < 50; i++) { - GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity scrap = DropFactory.createScrapDrop(); - spawnEntityAt(scrap, randomPos, true, false); - } - } +// private void spawnScrap() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < 50; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity scrap = DropFactory.createScrapDrop(); +// spawnEntityAt(scrap, randomPos, true, false); +// } +// } private void spawnIncome() { GridPoint2 minPos = new GridPoint2(0, 0); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java new file mode 100644 index 000000000..1c2cc5863 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -0,0 +1,109 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + + +public class StunTowerCombatTask extends DefaultTask implements PriorityTask { + //constants + private static final int INTERVAL = 1; + private static final short TARGET = PhysicsLayer.NPC; + //Following constants are names of events that will be triggered in the state machine + public static final String IDLE = "startIdle"; + public static final String ATTACK = "startAttack"; + + //Following are the class constants + private final int priority; + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + //enums for the state triggers + private enum STATE { + IDLE, ATTACK + } + private STATE towerState = STATE.IDLE; + + public StunTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + @Override + public void start() { + super.start(); + //get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + //set the default state to IDLE state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 5000); + } + + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + public void updateTowerState() { + switch (towerState) { + case IDLE -> { + if(isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } + } + case ATTACK -> { + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createFireBall(owner.getEntity(), + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.75), + (float) (owner.getEntity().getPosition().y + 0.75)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + } + } + + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + public int getActivePriority() { + return !isTargetVisible() ? 0 : priority; + } + + public int getInactivePriority() { + return isTargetVisible() ? priority : 0; + } + + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java new file mode 100644 index 000000000..8ff908b35 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java @@ -0,0 +1,35 @@ +package com.csse3200.game.components.tower; + +import com.badlogic.gdx.audio.Sound; +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +public class StunTowerAnimationController extends Component { + //Event name constants + private static final String IDLE = "startIdle"; + private static final String ATTACK = "startAttack"; + //animation name constants + private static final String IDLE_ANIM = "idle"; + private static final String ATTACK_ANIM = "attack"; + + //further sounds can be added for the tower attacks/movement + + AnimationRenderComponent animator; + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(IDLE, this::animateIdle); + entity.getEvents().addListener(ATTACK, this::animateAttack); + } + + void animateIdle() { + animator.startAnimation(IDLE_ANIM); + } + + void animateAttack() { + animator.startAnimation(ATTACK_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index c290e4212..90edc11af 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,7 +1,9 @@ package com.csse3200.game.entities.factories; import com.csse3200.game.components.tasks.FireTowerCombatTask; +import com.csse3200.game.components.tasks.StunTowerCombatTask; import com.csse3200.game.components.tower.FireTowerAnimationController; +import com.csse3200.game.components.tower.StunTowerAnimationController; import com.csse3200.game.entities.configs.*; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; @@ -22,6 +24,8 @@ import com.csse3200.game.rendering.TextureRenderComponent; import com.csse3200.game.services.ServiceLocator; +import java.util.ServiceConfigurationError; + /** * Factory to create a tower entity. * @@ -34,7 +38,8 @@ public class TowerFactory { private static final int WEAPON_TOWER_MAX_RANGE = 40; private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; - private static final String FIRE_TURRET_ATLAS = "images/towers/fire_tower_atlas.atlas"; + private static final String FIRE_TOWER_ATLAS = "images/towers/fire_tower_atlas.atlas"; + private static final String STUN_TOWER_ATLAS = "images/towers/stun_tower.atlas"; private static final String IDLE_ANIM = "idle"; private static final float IDLE_SPEED = 0.3f; private static final String DEPLOY_ANIM = "deploy"; @@ -49,7 +54,11 @@ public class TowerFactory { private static final float FIRE_TOWER_PREP_ATTACK_SPEED = 0.2f; private static final String FIRE_TOWER_ATTACK_ANIM = "attack"; private static final float FIRE_TOWER_ATTACK_SPEED = 0.25f; - private static final int INCOME_INTERVAL = 300; + private static final String STUN_TOWER_IDLE_ANIM = "idle"; + private static final float STUN_TOWER_IDLE_SPEED = 0.33f; + private static final String STUN_TOWER_ATTACK_ANIM = "attack"; + private static final float STUN_TOWER_ATTACK_SPEED = 0.12f; + private static final int INCOME_INTERVAL = 300; private static final int INCOME_TASK_PRIORITY = 1; private static final baseTowerConfigs configs = @@ -135,7 +144,7 @@ public static Entity createFireTower() { AnimationRenderComponent animator = new AnimationRenderComponent( ServiceLocator.getResourceService() - .getAsset(FIRE_TURRET_ATLAS, TextureAtlas.class)); + .getAsset(FIRE_TOWER_ATLAS, TextureAtlas.class)); animator.addAnimation(FIRE_TOWER_IDLE_ANIM, FIRE_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); animator.addAnimation(FIRE_TOWER_PREP_ATTACK_ANIM, FIRE_TOWER_PREP_ATTACK_SPEED, Animation.PlayMode.NORMAL); animator.addAnimation(FIRE_TOWER_ATTACK_ANIM, FIRE_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); @@ -147,6 +156,7 @@ public static Entity createFireTower() { .addComponent(animator) .addComponent(new FireTowerAnimationController()); + fireTower.setScale(3, 3); return fireTower; } @@ -154,10 +164,24 @@ public static Entity createStunTower() { Entity stunTower = createBaseTower(); StunTowerConfig config = configs.stunTower; + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new StunTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(STUN_TOWER_ATLAS, TextureAtlas.class)); + animator.addAnimation(STUN_TOWER_IDLE_ANIM, STUN_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(STUN_TOWER_ATTACK_ANIM, STUN_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); + stunTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent((new CostComponent(config.cost))); + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent) + .addComponent(animator) + .addComponent(new StunTowerAnimationController()); + stunTower.setScale(1.5f, 1.5f); return stunTower; } From bfee9421af85f4a9f3e97d7788af39c66a51646e Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 02:07:29 +1000 Subject: [PATCH 24/37] Added the png and atlas file for the DroidTower --- .../assets/images/towers/DroidTower.atlas | 251 ++++++++++++++++++ .../core/assets/images/towers/DroidTower.png | Bin 0 -> 11118 bytes 2 files changed, 251 insertions(+) create mode 100644 source/core/assets/images/towers/DroidTower.atlas create mode 100644 source/core/assets/images/towers/DroidTower.png diff --git a/source/core/assets/images/towers/DroidTower.atlas b/source/core/assets/images/towers/DroidTower.atlas new file mode 100644 index 000000000..47c4d1626 --- /dev/null +++ b/source/core/assets/images/towers/DroidTower.atlas @@ -0,0 +1,251 @@ + +DroidTower.png +size: 1024, 64 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +attackDown + rotate: false + xy: 72, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +attackDown + rotate: false + xy: 212, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +attackDown + rotate: false + xy: 457, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +attackDown + rotate: false + xy: 597, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +attackDown + rotate: false + xy: 737, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +attackUp + rotate: false + xy: 247, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +attackUp + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +attackUp + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +death + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +idle + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +attackUp + rotate: false + xy: 632, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +attackUp + rotate: false + xy: 842, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 2, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 177, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 6 +death + rotate: false + xy: 387, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 8 +death + rotate: false + xy: 527, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 5 +death + rotate: false + xy: 702, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 7 +goDown + rotate: false + xy: 37, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 37, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +goUp + rotate: false + xy: 37, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +goDown + rotate: false + xy: 282, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +goUp + rotate: false + xy: 282, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +goDown + rotate: false + xy: 562, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 5 +goDown + rotate: false + xy: 772, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +goUp + rotate: false + xy: 142, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 142, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +goDown + rotate: false + xy: 142, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +goUp + rotate: false + xy: 352, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +death + rotate: false + xy: 352, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +goDown + rotate: false + xy: 352, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +walk + rotate: false + xy: 107, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +walk + rotate: false + xy: 317, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +walk + rotate: false + xy: 492, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +walk + rotate: false + xy: 667, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +walk + rotate: false + xy: 807, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 diff --git a/source/core/assets/images/towers/DroidTower.png b/source/core/assets/images/towers/DroidTower.png new file mode 100644 index 0000000000000000000000000000000000000000..6fab7ed2da337ae247805579a17a3a8fa260ea3c GIT binary patch literal 11118 zcmd^l^;?r~`1TM1X%!`YQt%NX; z&H=)Ae!lN}9Pj_|9>=qL`(e-VJlA=}eO~8%g}>BNp{8J_0002gYO2q50Dzl>p8)w= zq=fY&1<54*O*mfO~|=OX4>`>tqss_k!@+T9CC2fugd%zI158Pd*h z!B>3-;aY{Axi_i9gMzwmzK@H5hEv)nL}b{AB((q3u@OqatcGQtUT7eHAK?yVw09T0 zC6ML1yWhUa3^xqB=NxbEh^zhP6^;y?n8{D~=AHr}RNS^bF$pH_+aR8?smlbJaYjJW zk9MWqFDN0Z4#EGuGM-Ta1U0~)o3FN=Kfn_QUFB_q-SSYc(QakQv6hUXmQ;iD^I22> z{%oD}g(OIF+Qy2!mi)Ks&$VFG>vyYfjB|zbe*eB2c2K_4dZzY zxZ!H-=HVYom+2dAy+0`d29VIutUnHKR-a`<>jU%yh%9D~Pd-IkGLozoE?Vmjx?NDM zR7F+N8t_nA-yAtyOS8Z>42pe!K#O?a-QN?}8y;Y*UgE#>1sXtv%m>_f z-{lC&?P**#*}@wmJ2<-j5TpvvD(wybwtn6IFvzkcnS4BTkg8)NK9XG` z+i{__a*`F`rT|YIaBj=I9&_QYJ;<9&vTjXTv$FL#erbDP?nNZu-8;by(hGd~*{6BE z!Vt?~SYb}p(aXin+a~?q@(m>*^!v+A()bGLP}d2r;8sVHxZvSp;#d5DHZiByI!1st zaT`vfMcqMr2l$60C0)$z^_^+tyytM<>ZN~JBIT}hj?CMAo8Ps!brk7jjT=mTSLE0x z(uQqCxNXwkRwyn=5P`43(A8Bljm!1bD^&000E}1B^jei#^O!>pHdAlyTVG0=NQngd zr#I|2;`@#IOUS|3VrUcU046#tgW|c;@7=NX?l&9~`0J}vjCvqL3?=8`DZDJA%jKP_ z92Jf(J`M2jf#v3yyiZrZ!(F3W>4B_3C(c6`MM~z5+VzvB;foy%ukWtQgMmwB zZ%qrGp`1}~(!$VkKOd$A*HfZXUIzF}$dhRu}_ z(9LaWa+Uo{i?Okw{W3c)HqAxE$Mj*QEQv{t#pjG`hEU@ zIi+(;Rb=nw+z8gQ<;&M)F2fQ9sh!5dl=fBjlS@h@-zx391DrPZ=T@&Ki_lT&ha-}1 z=0j~evYAgx9jKqbD%H1B)*%sb{y<%bC@ zmfIB8bX+gHBjqAl%Yt;@yF*3i>L(!Q@9}V)!(g%?`IY_5L+XCm`i{QyLEw3c>7tu` z{)&CECPpNj?xnuEVPaq#`d+1g=B4h0IP(kH{)xxa9zW*-IsETPd9@08;L%ER&}w>2 zRJahnsFGfiM%QuknEl&tNJTgKD}(3dLKmRBo4Jy zh6XNB$H?)m5z(41H1BwF^ANk6XZX7I($#LWtLAqer*m8Ws6522>X1qr@O3dX^kg6{ zZcR7^ylG4s-SzQT%5M_S<&Sar)X{v)edFidFOd5WaZ5r~^Y8WtaEi!I7mF&iaOR7D zvetJyoO~R8^l8iOsyHObBgBiQdjKcP5c8%7oG3*h#vY%|Q%Qqm(Et0;y2v>Tkm4<3 zAVUdo&+C)B?Ybt4i!2Wy!ip2(piBILuc=So1#Ig66r+aMKb96K<>N``$~Qi|7dwg~`oZJh zO=+hA4Jsy^EALcKK8YE&_F;WNO&3@X*b6C*=_`~I78@;bIa{z<>d{{Ek;M;RpYtAu zT6(l)$!*}S!M!5$rhwBdOtzs+x+jre>$-_6N+QJ0%J*@>HpAGQi^ZR_)tF|Q-c`P5 z)J##_jb~mvmxa&T#R8}f4i=WRy8m)iQDv}AXD6z4#1~}NcHfGQ9roHWn=#VPjam#H zh777jWkFM?tE=0mJj$!}}m`%zO_}eLdN*HACiNUbnBxF6J?dn1CT)25IXvEo22RmD^MegVpz! zxS^sYG4p$L8X}B6F)l@^*gnS>-&r`Y?_D-vEV*_G7QMpXCcfpea8h3x(3H{;ALYiQ8JLznZ(C|9}xDOVk($g>o=%uDmu%UI{- z;j!oGiNMz<)vMjaTk~GyLgngbNdfj0+Uh1Qz-!gOGXxhof1xI;ToX{dEpHn|6CYCp zEL4+AIwt49*tgJu7tl{cvmRcm2rZU)OpA z7_&P*m}+gnb%x1-6pbDkZc2?rJ_mb8%eKBvx3>**ul_C@81wTjEf>c1!cPa531PWk%?u6gfGbLv)>G z=KaY;dYDy6PhTf$+o>}G1rQ9wuwSe!@c_XFVB=cnyvVS6^w}+Zw_$9fGu)FLSm?A= zBU7?cg^n{QK@@8D#%iuS&Mi8HaO4(wV?H*X{a(C~8 zLPB>}ajbnYrhK^V9Ot5FZ2|b(K|WUg?RmGvsHY!EH!f%^-Eo#9->!f9ojS3KsIp45 zRuTEBY9qZ>qROIXLp>LhU7All?@dp7+wDXqFT68cU%q+eB5aVb3j+N00!qck)Q^lkMn^3uo1bnzi2JbCL?@&QiDOaXenLrhqXD7%=L%upd-*Rxvz zk7vF*!A}yul=3-R-LB*_?HbTjX?Ucba^;6|7=CIv1tUti_y}zO+8muBm_#phvTQAJ zr5Z2H#v{z-U22PcuCQX7RrdmG`{(=?E`A?d^eqXaE@)nP%c0V^6G)<@r9 zhji_KO1(v}5Ff?mh-{m2Q&~SPsLzWru4QbExpB4AEPUD(G3nZmg5Tv^rU{Zf&vbYn z`;+g)_}dHzS`!FCw7xHb+g;5x;zEDQ+bvzy)g8Ae(PJs-3moyPNRK-pPN4rO5n(we z!PNd(p$hd00ljQxn7PmvFl(X&?1s8J70@zqIdu&b{}68!nRH20!y*fTXT+i2^top2 z(lu3yUJVXF$N6NIj$fK3SH)2?pusGDySvLTum`Ql6n(L9rG$aUW`A~ zB`C&jY=p5`ZXmhhtT0$SqAaDG*aMl+*vHH@6x9(UEAGWtR5dC%YbH?-2? z7pJB$UuwOzQWG%;{(K5!G~zZ^Q`I-?8TrK7bRJ#R751B@iVbf?ANrmqXUrv^n7JDr z*S$sl8usoJ_i?Es{^$+}0#rOAf&l$5Lbxw_V_6QmmjR?#N!sU{dGFqXW>uI}Y@B)q z;tPyXYLrEM{tC*_yx@jreS`v4ky1<()^&PZco-5tmQb9`S6jUjGJ>FsTmrY25y#4+ z2=D9Evy(QP3bC3BE9RjGnV$VGC`m(#Q2kXrSpB{pScHvX>GFi5LZZ~76qfO7O)*6J zd=>K*2~gu>5f{Iehu$wfFY-CW1z+fxxe*t7fry+?BaB$wMb0O6AtUb zA3ooolOSyzrhu#Pqxv?9BznokWm4*UAY23Lb-LP$-)scK4XOT4m$}yX-f6C82~@rF zrxV*x)5qVBRhN!t7DM_=wOK<)$x$6?19FN6sqNW-FSo}xa$hA=Q&8RKtCE*2pja<+ zOdmZtYI<(RDG&n-qUp}HbF_WlQ~29?AT%DiDqJd*X&~i_V$6%l-yM1|1$kZxqXfwL z97_ko_Fqkk>xs2QsfLZ{Hf7!B`XrA1F}B(o*Zmj!OJO$mpV!|a-(uRH<0X)}CKme3 zUFdsi4B*7=9_@RxA}P)(psVgq-4?mvO4k;^3$oDDs-H6adKx8(^wrdXhf-B%MWd^> zsz5~Laj@ZaNWcw222j%H;*cXB^y*PkG(*1Dr7}XdX@R5#k^BBCprXMu$lP$Lrh*f( zQ0~&d$z;sLr0md3{x$P!gG!N7(2Tet8|D45C_@pmUugF;A#`+1o^ltNhlQN}U>d2W zdM5Ev%?6V$6VY1NyB++&9Cn*wCbM+^hKz|hjAQm@p=@Z3Rj<(~Jz(!JP!Tp-y2S#6 z*JhQn6+ux)R?runk;1cPUYt(+$ED7ALNO>-vP*b_vR+a?R}(YbX&fLQCOZ_1rt&Ss z*=6{)i1_ER9ICw`qp9*vq{q?z010Bd{K1Kn>xtsKz1V0@PpTqYjr|+5BXozF>0S&* z#w8sdHe%2J+%+M)47_UX*oo;siocmEeoFV(yjy(4ePb`QG^Zvx-Vz;=mgTKhMFLnX zJ$stt_;KSw09E!t47q_AHo@SkloUxXWyYp5H$XGelW(VCdozh)_wiXcBdjwB;M*Q| zwdn6aaka88%Jd0^VrnU11RHeT2)cOp1jx*KQS2cZPXqiBmjaf8v9RiPgRIL{SW~p? zziG6j(aZTv>p4J<{RCJ{Q73w%e(g8v_q|uYPTlr1jT^X z5vgKF9}&9s3nY`1_nCD6d`M#}Qyil^pzd#u$`Sf!2Tf+$=`(^IX7AAvJbWyyAii`} zVWSxlASX2h2<*-gvoY$?ix!pDA!B79xjp`@mti-rAbmBN-x#tjqLb6Bc&M0R z$4C5%@#uw|s<|GJeFq6<4KYx*JxUr?nA>11QxX8Q>@B+o!$)Bq;_u&Ur6^ZsiLlzD zRWT!y8KF-Dn2co))(Gt8kdk$-6I9$4837^NBTObW}sn)o%~yAWE|0QsG<4u*4+ z^+b<|NpTmGc$9@XJY8X7YMz{o=JR^SB&(zTT6SJeNS07sJ7BCCNs-}xhT~>{TlzAu zXEuyxOY}qxXWsIq(IV-Qq~=}g&su` z<9=tNg>J9mE)aK~vP;!F;{lrLgr`tu>nf?IlLWP*Vdym$sp@JrWK0@;S#S(3?A9hn zw@IuOgm;A@*5yNh5HP@|#X>=}GqXk%(|hFm;Nr3!J$^DUU3Yf$5j`TQi4LkiLjU^D z1RPX^RS4+eNC(WVWXp*y4jZbdP#JrrtUmW|(}C#-aBrH9!ieMzxXXQP4j9Wnx_OLj?27?mb+? z*tp~_HXjEoC!x$iPwbMlUX7!)1|o4`XQ}jn=npCJ3Wwn`VdyyXdrLlB!fD!28#A3) znPPa0$SBt^d^r>Du%%}OeG;e{CR3t@zwLRnhf3&~nxGrYgKw5cQ5AUpZ!o^HC$b>u zHr4I%eG&OgpXXa1_D7@?<4F0=j=u8+Fj1qH5Bonmt@w>twha~W#oH)u?rSot_AvIt z-t0@4DGqxsCJ8KcQ(veG%c(q=+LTXX!FZWVir}6!rl9;9>8n-dlta`*W>?ob#Y!<-E!a zfq4)MRg7vb@ocFEuRuj7C0uX$@f_Q`XAD0B;-?-)I~Fa>s~3;vX!G4{12KhF8sD1?anMlI>=%!zuKw@NV_w#f?^A{^~v&u%wOi9WE$mG z#lVH8MZ%x+RmPqhh#8eVW17XRqB7r0SrJAyTwT?2F8t!k;ODH?f4aCjlS^Qw{ zn3m3F7i!R+FypJU{VQ};5 zh;onLivN1emkxJwse#Oz{=|vb4t`dwQYX>2oK8{y7y|5T-N7?9u$d9NXfpI*uvYu1 zc+Di2mJtiOi)}xg!p3${W=|=%=d5;L!fgo!fqheFdq_Zl?)wdx_-{DKf1Y z?kE17=h}k%`eCw3M+MVU;Gz`OoOgl4w0rUkF@P}69Xep3F;GdF`|w0kzQ;WYc8e!E z*x~m6b(|j}swzrnz%+GoBf+5BFsCCn&wWN{ap*x@N~;tFY~M3i;!UZMB_#mCX+bmD zKp)NBy8pT~3z2bq#rm~s$JMwS++Ju#MYh}+;+8qDf;(FdIo3r3e$o@2WAAPfhaN?K zjEVOZk7jsmU@_|RetWTb7RS3n2i_PJQKyDyR70%uMD_lmkU~J=Xna3fn+}(Zx5pWV zn`&SBSsT`i>ia)z=<`p~N5B_n?4Z?^iCuuSbmP*jzgC#zt+yEzjSI-4S*~ZNM8xRb48kOKs>zisVGv zi2ZSj)_Ac_1!j$Avob@rr9w7pmgW!=z-gd4RKZ`~JV5d-Oz}JD?jP-{IOheQ?-28r zGqVwrT;IRl={7&Uh75r`WWnt4UTx>ZiznGEw3WlvlK+6f8n(cWd&y?=rTkVQU|c46 zTVQ+Ypo*ng(ELyPElK*{PMZ2iSkIon;A`bP9|F<3@nCG-r@x1Bi-1EYEUN$Iah4;; zed1(bU8QA_)0`LodbIcV4e{`%S4C@ZV1{prI*4>Ep@@^^p|cE*lu6G zJE29o$@n62_3PGBb(iV0$U*ItJOARN_kfdSPJy{{!0(Y|08ZA+Hak>*RH3JBc8BjHdW#~6N|8&5mIV)5c#%d_#70!}!B%X7ShkvZ6>_1N6aMY8GY827>ZMX1p zOWd^r@^o_Lurkf3W2l?KiMLBT=O?Sp3yVXHv(Vkj%cBX@BdyL&bdRmLEYs9H@M!QIbrq3cs&NPzgxdC@yh)B>26pYY$5!?S{=xcG z*-D)Bl1=cr#H#=4m-;)RNPDyHXod{E&A@Nae}*&(^A;Sk^u5<_m_ZMcd4qPcQI$LT zAn@~$(zHTk7PGron1h@}Hj`fd*sQ5duK4VZ+nvK0yfMwKP9dnzy*0+?qdMYd<>OrJ zX^us7)%*f6$WGzuObV#Q1+7^VO9&d)Y6)@M7U1^eG&&FQNi6QFmLY9zI{`bfIvAs* zqjoUYS}shM3H=S9Gow;18oOu4YQLdD{Qja|&7~?I$3Gqw(x4*fTsmJQ2KbHq+ata+ zKi0Ao)Ekv(^5+!%luZ2ks`hEkd-4)EbQb^4#Uzmu5=4<1%ffWgq(rliyj5jIb5sx-jFXaT+`!dy~w%&H-&iPXTW z4J%NbE$M$iW13_+6j%S?6Dn=U>cJBKp~L&4;qj)vc1fAf*Nd6-dw-`%68GGUefutxj#uDQ|$;dS|2VYh1A-_F6yCYh3kR{SCX$ zLTt^McNZc-q>V~Jv{ys;N%Y&|v84~8fxlcLqQmoArLH;ZVMKsnfmK%KPYF5Ene?ff zBX!`X?|@hyhe_Z;vFM54qtQ$7^FJR5rT!u)E=4MUyi4vpF`X~i%q~G}S&8{%-#t!X zR%10-n?1u^urJ8`t+Ss>Fh7#yNv7xAB+nDNuQB1kwi>6DJ5vh&LHQ*kWFC1%YAqsV zDxEq0z1J`1Uz)re%$BGth~t@h$fJV}Qt~@#3=j(-Q)j7Bzj)5{n+iTq9}~?)5zEjA zZZDAVxR8!bOs=Tbv^X=a2W@=bOo^ul-D}Q~O6qc)JN}fWHiz^`$SMjk8d;ta-~a83 z@|@E-<-3hD(sWvq$S@DBRtX(twtCnRt~HAnYJcR0$*AAWl+>d_p*pecoBW~xsN!vD z_WSf+s~efwG^;gz89qVnGO7&*NOCIJVo@?J?ji#>5X1n!QoH|#YwKR$6i2A42P35LCP*B zv8CQBp#21YkBtX`3jK0$Ctv-nS@_nG zRo1_&dUCQxD7ZG&?EJ&j=TCH3H)NPt=1;ObK*U-pK%~J~1pem7<+Yr5w5ipKjb`I> z+{d5VywJK3(=Wnyr}S3AJp<*UQ_b!?@cTnb?|2CB#)*l;$9xgEHH{6xD^!{jOi)V&h8+gi_{lmNw)JF)j~W7`>sEad(UPt7Z|d1Fqg zy~zMkV|tYDMfRxgwpyk?Nc68Iy8bNZ$2XOgEzCzs-%bHtZ79{z?j`o$BV$<*p~-%Wcs7eh zRigsV^vkb&gGs$bq)O%H?3__%g%BT)jv=>a-4WFc!4-;c2O%s1uXsCN-FS5is!H7)6;3EVjM=lRQUR_B}P4>#5{*;NR{pRk8;A z3G9ct6hy_k{^gB~Gw$E1Pod?TJ{t{egI#xWMvF-AE3e7J@li3!b2Zc#8!n8@>wVWG zKp&iWxncsJ_2qDMi7eeGhIyhrTnXftGkKRJ?s;IuYY!rePv8%(2&h2It_FcbBQ>U* zZ_GcGx9Ik^c#LT!3TZh(_`XK_C z@}u5H+GZH;{$J?-JJ%>bBY|+PF!NpsebRPm-mY7)tyy-ib?N}=_mg=uJGSZ|rFjeR z%pgMaw~5JUYc2(0MRf+Bk)6QTUu0}Bp{lH{Lmg%I`uWT?hZX}WUn;A_x&T>uJ{cp% z7WDs|RE5xC??D7o$c2WVl>~eKgjo$wkt}mal-s#j49mS})%6-I6~f-NRm2de?_z20i?3 z{)a3wWC(G(I6=vM|F4rNjCYG+q*L6cEzNHdZGlae(55YRO+WUXS-;@EV&Xfse&`?_V`ZOc)_401KC zGVTqadaW#|#RO#cUsWVGzSXON)uYVaPFpQpKRC>?l)gyOpD6LWNVyt+aIu$rXZg~kOK*331~YpELY$82?mkN+G@mg*+k8Qh%j8x`htp2e+E_uOfo1%B~O6i_ZD_*R=lTqsIA?mkbk2c828;$ z(62m}=D9wf%FrLq^PUF&c9{)m*1?Q~52#ZFj!X z_l*rB_?u2^c!n9xxCu}TG>%`C)=wdvaK26Q7saY~`56SkR( zg8nvUZ^OEY)mpit>`xbMCKmth1d6@%B$ShZ&uV5<8j2VT?2m!4x~3-Z3rxsXOO#EE z$To2X?0pvu8Vi_f2>NDJ7%UM<|jrHa7jEzL??bBo}UQiVqU04#E( zk7|8|spIIeoMZ>Uq*a6BdTii6oEd>CKIf?Y(k?g#cb!aTat?wPHM>LO?(_y#TQq5c#z1C|}LA68)P6A+X z#aM!V?--{*ugJzO9A4?DK3V;CNh9|I|56PfVR{gb>!*L=&^5Hsw>&X$cb-%^_<`C6 z0!c3t+Hlnsn=q#BFD-f1d)OCGxvTBPHnVF64Q?rY_D50u(@Q@g**y0Od-9_DV??ES zA1x0;Q@NXl5It^J?s7Y}pm9sk>k|QQ8OXkz8JMJF=`7k{b8ysX%pxtyA-?9afE|+~ z0LW4ob23YBPWe{iqPckS{dM z4WGfck=uzM(LcYwF#x(}OR}#TNMf`xdk)*pY-$QqSi&^I`UQG06_QKt)J?e<_07i( zvP?47>aoyobDZO!eQq0N>mhnd6x5?JNC30r5YF7JKPNS_8%P-ZS-3%%`!Wn*tJYwO z=JjgEwwD!D3^v(%e#CxS1CbZ$8~itG`EO|Q|MSaw?V4zj>v^?; H#fSd^VmRi+ literal 0 HcmV?d00001 From d7870fd9aa8ac9da4f904e9266502e81a9ee6fd4 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 02:09:06 +1000 Subject: [PATCH 25/37] Added all the configurations for the DroidTower --- source/core/assets/configs/tower.json | 5 +++++ .../game/entities/configs/DroidTowerConfig.java | 10 ++++++++++ .../game/entities/configs/baseTowerConfigs.java | 1 + 3 files changed, 16 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java diff --git a/source/core/assets/configs/tower.json b/source/core/assets/configs/tower.json index fe44c4d17..d8a86a549 100644 --- a/source/core/assets/configs/tower.json +++ b/source/core/assets/configs/tower.json @@ -18,5 +18,10 @@ "health": 10, "baseAttack": 5, "cost": 1 + }, + "DroidTower": { + "health": 10, + "baseAttack": 5, + "cost": 1 } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java new file mode 100644 index 000000000..18ce675d5 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java @@ -0,0 +1,10 @@ +package com.csse3200.game.entities.configs; + +/** + * Defines a basic set of properties stored in entities config files to be loaded by Entity Factories. + */ +public class DroidTowerConfig { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java index d0c920d0c..e76f7f138 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java @@ -8,4 +8,5 @@ public class baseTowerConfigs { public WallTowerConfig wall = new WallTowerConfig(); public IncomeTowerConfig income = new IncomeTowerConfig(); public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); + public DroidTowerConfig DroidTower = new DroidTowerConfig(); } \ No newline at end of file From 1863d8063ee6b56043526d237f67637c0f5acc96 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 02:10:04 +1000 Subject: [PATCH 26/37] Created the DroidTower entity and added some of the components to it --- .../game/entities/factories/TowerFactory.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 93aa93794..452f53a61 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -38,7 +38,7 @@ public class TowerFactory { private static final int COMBAT_TASK_PRIORITY = 2; private static final int WEAPON_TOWER_MAX_RANGE = 40; private static final int TNT_TOWER_MAX_RANGE = 6; - private static final int TNT_TOWER_RANGE = 5; + private static final int TNT_TOWER_RANGE = 6; private static final int TNT_KNOCK_BACK_FORCE = 10; private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; @@ -133,6 +133,27 @@ public static Entity createTNTTower() { return TNTTower; } + /** + * This robotic unit is programmed to detect mobs within its vicinity and fire projectiles at them. + * The droid has the capability to switch its aim from high to low positions, thereby providing a versatile attack strategy. + * When it detects a mob, the droid releases a projectile that inflicts both physical damage and a slow-down effect on the target. + * @return entity + */ + public static Entity createDroidTower() { + Entity DroidTower = createBaseTower(); + DroidTowerConfig config = configs.DroidTower; + + + DroidTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new CostComponent(config.cost)) + .addComponent(new TNTAnimationController()); + + DroidTower.getComponent(AnimationRenderComponent.class).scaleEntity(); + + return DroidTower; + } + /** * Creates a weaponry tower that shoots at mobs - This will most likely need to be extended From 71e40d4bfe82145d3b9e321e4327ae675de884ce Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 02:37:00 +1000 Subject: [PATCH 27/37] Created the DroidAnimationController which listens to events relevant to DroidTower's animations --- .../tower/DroidAnimationController.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java diff --git a/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java new file mode 100644 index 000000000..e1fb8f098 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java @@ -0,0 +1,86 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to DroidTower entity's state and plays the animation when one + * of the events is triggered. + */ +public class DroidAnimationController extends Component { + private AnimationRenderComponent animator; + + /** + * Creation call for a DroidAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations and sounds. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("walkStart", this::animateWalk); + entity.getEvents().addListener("idleStart", this::animateDefault); + entity.getEvents().addListener("goUpStart",this::animateGoUp); + entity.getEvents().addListener("goDownStart",this::animateGoDown); + entity.getEvents().addListener("attackUpStart",this::animateAttackUp); + entity.getEvents().addListener("attackDownStart",this::animateAttackDown); + entity.getEvents().addListener("deathStart",this::animateDeath); + + } + + /** + * Initiates the walking animation for the robot. + * This method should be invoked when the robot is moving but not in combat. + */ + void animateWalk() { + animator.startAnimation("walk"); + } + + /** + * Starts the animation sequence for switching aim from above. + * Use this method when the robot is preparing to target mobs after aiming from below. + */ + void animateGoUp() { + animator.startAnimation("goUp"); + } + + /** + * Activates the animation sequence for switching aim from below. + * Use this method when the robot is preparing to target mobs after aiming from above. + */ + void animateGoDown() { + animator.startAnimation("goDown"); + } + + /** + * Triggers the animation for firing projectiles from an elevated aim. + * Invoke this method when the robot engages with mobs and aiming above. + */ + void animateAttackUp() { + animator.startAnimation("attackUp"); + } + + /** + * Starts the animation sequence for firing projectiles from a lowered aim. + * Use this method when the robot engages with mobs and aiming below. + */ + void animateAttackDown() { + animator.startAnimation("attackDown"); + } + + /** + * Triggers the robot's death animation. + * This method should be invoked when the robot's health reaches zero. + */ + void animateDeath() { + animator.startAnimation("death"); + } + + + /** + * Triggers the "default" animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { animator.startAnimation("default");} + +} From 406027b8535664ea4031e626115f7f90b7f45e9e Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Fri, 8 Sep 2023 13:21:33 +1000 Subject: [PATCH 28/37] Averts divide by zero edge case if a tower's firerate is reduced to 0 shots per second. --- .../game/components/tasks/TowerCombatTask.java | 6 +++++- .../tower/TowerUpgraderComponentTest.java | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) 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 b426f70ae..74614dc98 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 @@ -203,7 +203,11 @@ private boolean isTargetVisible() { private void changeFireRateInterval(int perMinute) { float oldFireSpeed = 1/fireRateInterval; float newFireSpeed = oldFireSpeed + perMinute/60f; - fireRateInterval = 1/newFireSpeed; + if (newFireSpeed == 0) { + fireRateInterval = 0; + } else { + fireRateInterval = 1 / newFireSpeed; + } } /** 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 f9ab79901..2533eb7e1 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 @@ -62,4 +62,20 @@ void increaseFireRate() { verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, 60); assertEquals(0.5, towerCombatTask.getFireRateInterval()); } + + @Test + void divideByZeroDefaultToZero() { + 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., towerCombatTask.getFireRateInterval()); + } } From bd6a871a625328495f9240df07d3aa48a6036bf4 Mon Sep 17 00:00:00 2001 From: MajorDzaster Date: Fri, 8 Sep 2023 13:37:24 +1000 Subject: [PATCH 29/37] TowerUpgraderComponent is a component of all tower entities, rather than just weapon towers. --- .../com/csse3200/game/entities/factories/TowerFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 366b0c0f1..f2ce8f2e9 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -117,8 +117,7 @@ public static Entity createWeaponTower() { .addComponent(new CostComponent(config.cost)) .addComponent(aiTaskComponent) .addComponent(animator) - .addComponent(new TowerAnimationController()) - .addComponent(new TowerUpgraderComponent()); + .addComponent(new TowerAnimationController()); return weapon; @@ -132,7 +131,8 @@ public static Entity createBaseTower() { Entity tower = new Entity() .addComponent(new ColliderComponent()) .addComponent(new HitboxComponent().setLayer(PhysicsLayer.OBSTACLE)) // TODO: we might have to change the names of the layers - .addComponent(new PhysicsComponent().setBodyType(BodyType.StaticBody)); + .addComponent(new PhysicsComponent().setBodyType(BodyType.StaticBody)) + .addComponent(new TowerUpgraderComponent()); return tower; } From 0afcd0cdcc292a5436805f3cd232a5204671e902 Mon Sep 17 00:00:00 2001 From: Ahmad Abu-Aysha <111224176+The-AhmadAA@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:58:55 +1000 Subject: [PATCH 30/37] Update TowerCombatTask.java updated to return without editing firerate if division by zero is attempted. --- .../com/csse3200/game/components/tasks/TowerCombatTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74614dc98..1719b4712 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 @@ -204,7 +204,7 @@ private void changeFireRateInterval(int perMinute) { float oldFireSpeed = 1/fireRateInterval; float newFireSpeed = oldFireSpeed + perMinute/60f; if (newFireSpeed == 0) { - fireRateInterval = 0; + return; } else { fireRateInterval = 1 / newFireSpeed; } From 5549e1291979338e37138395e439abb73e94acc9 Mon Sep 17 00:00:00 2001 From: Ahmad Abu-Aysha <111224176+The-AhmadAA@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:02:20 +1000 Subject: [PATCH 31/37] Update TowerUpgraderComponentTest.java Fixed failing test after previous edit --- .../game/components/tower/TowerUpgraderComponentTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2533eb7e1..703a1299b 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 @@ -64,7 +64,7 @@ void increaseFireRate() { } @Test - void divideByZeroDefaultToZero() { + void divideByZeroDefaultToIgnore() { entity.addComponent(towerUpgraderComponent); AITaskComponent aiTaskComponent = new AITaskComponent(); ServiceLocator.registerPhysicsService(mock(PhysicsService.class)); @@ -76,6 +76,6 @@ void divideByZeroDefaultToZero() { entity.create(); entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, -60); verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, -60); - assertEquals(0., towerCombatTask.getFireRateInterval()); + assertEquals(1., towerCombatTask.getFireRateInterval()); } } From 4441a3b3fc5ba6fdd0de150cec75bf10be7fe1fa Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 8 Sep 2023 14:24:27 +1000 Subject: [PATCH 32/37] Corrected animation lengths for fireTowers and stunTowers. --- .../images/towers/fire_tower_atlas.atlas | 92 ++++++++-------- .../assets/images/towers/fire_tower_atlas.png | Bin 8037 -> 6334 bytes .../assets/images/towers/stun_tower.atlas | 98 +++++++++--------- .../core/assets/images/towers/stun_tower.png | Bin 6954 -> 7075 bytes .../csse3200/game/areas/ForestGameArea.java | 11 +- .../components/tasks/FireTowerCombatTask.java | 4 +- .../components/tasks/StunTowerCombatTask.java | 4 +- .../game/entities/factories/TowerFactory.java | 5 +- 8 files changed, 109 insertions(+), 105 deletions(-) diff --git a/source/core/assets/images/towers/fire_tower_atlas.atlas b/source/core/assets/images/towers/fire_tower_atlas.atlas index 80fa90a9f..7c9ce2206 100644 --- a/source/core/assets/images/towers/fire_tower_atlas.atlas +++ b/source/core/assets/images/towers/fire_tower_atlas.atlas @@ -1,83 +1,83 @@ fire_tower_atlas.png -size: 2048, 256 +size: 1024, 64 format: RGBA8888 filter: Nearest, Nearest repeat: none -attac_prep - rotate: false - xy: 2, 2 - size: 159, 150 - orig: 159, 150 - offset: 0, 0 - index: 2 attack rotate: false - xy: 467, 2 - size: 150, 150 - orig: 150, 150 + xy: 122, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 1 attack rotate: false - xy: 923, 2 - size: 150, 150 - orig: 150, 150 + xy: 302, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 3 attack rotate: false - xy: 1075, 2 - size: 150, 150 - orig: 150, 150 + xy: 422, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 0 attack rotate: false - xy: 1379, 2 - size: 150, 150 - orig: 150, 150 + xy: 602, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 2 -attack_prep - rotate: false - xy: 315, 2 - size: 150, 150 - orig: 150, 150 - offset: 0, 0 - index: 0 idle rotate: false - xy: 163, 2 - size: 150, 150 - orig: 150, 150 - offset: 0, 0 - index: 1 -attack_prep - rotate: false - xy: 163, 2 - size: 150, 150 - orig: 150, 150 + xy: 62, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 1 idle rotate: false - xy: 619, 2 - size: 150, 150 - orig: 150, 150 + xy: 182, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 3 idle rotate: false - xy: 771, 2 - size: 150, 150 - orig: 150, 150 + xy: 362, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 0 idle rotate: false - xy: 1227, 2 - size: 150, 150 - orig: 150, 150 + xy: 542, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 2 +prep_attack + rotate: false + xy: 2, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 1 +prep_attack + rotate: false + xy: 242, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 0 +prep_attack + rotate: false + xy: 482, 2 + size: 58, 58 + orig: 58, 58 offset: 0, 0 index: 2 diff --git a/source/core/assets/images/towers/fire_tower_atlas.png b/source/core/assets/images/towers/fire_tower_atlas.png index cbdce58d1a7ef6309bca48b921303229e0648956..a8c5cc3ee2af7ab5d8115c7d0930244de40b6dcd 100644 GIT binary patch literal 6334 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 literal 8037 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 diff --git a/source/core/assets/images/towers/stun_tower.atlas b/source/core/assets/images/towers/stun_tower.atlas index feeea736b..f0033a197 100644 --- a/source/core/assets/images/towers/stun_tower.atlas +++ b/source/core/assets/images/towers/stun_tower.atlas @@ -7,112 +7,112 @@ repeat: none attack rotate: false xy: 2, 2 - size: 65, 45 - orig: 65, 45 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 4 attack rotate: false - xy: 136, 2 - size: 65, 45 - orig: 65, 45 + xy: 116, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 9 attack rotate: false - xy: 203, 2 - size: 65, 45 - orig: 65, 45 + xy: 173, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 1 attack rotate: false - xy: 270, 2 - size: 65, 45 - orig: 65, 45 + xy: 230, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 6 attack rotate: false - xy: 404, 2 - size: 65, 45 - orig: 65, 45 + xy: 344, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 3 attack rotate: false - xy: 538, 2 - size: 65, 45 - orig: 65, 45 + xy: 458, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 8 attack rotate: false - xy: 672, 2 - size: 65, 45 - orig: 65, 45 + xy: 572, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 0 attack rotate: false - xy: 739, 2 - size: 65, 45 - orig: 65, 45 + xy: 629, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 5 attack rotate: false - xy: 873, 2 - size: 65, 45 - orig: 65, 45 + xy: 743, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 2 attack rotate: false - xy: 940, 2 - size: 65, 45 - orig: 65, 45 + xy: 800, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 7 idle rotate: false - xy: 69, 2 - size: 65, 45 - orig: 65, 45 + xy: 59, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 1 idle rotate: false - xy: 337, 2 - size: 65, 45 - orig: 65, 45 + xy: 287, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 3 idle rotate: false - xy: 471, 2 - size: 65, 45 - orig: 65, 45 + xy: 401, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 0 idle rotate: false - xy: 605, 2 - size: 65, 45 - orig: 65, 45 + xy: 515, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 index: 5 idle rotate: false - xy: 605, 2 - size: 65, 45 - orig: 65, 45 + xy: 686, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 - index: 4 + index: 2 idle rotate: false - xy: 806, 2 - size: 65, 45 - orig: 65, 45 + xy: 857, 2 + size: 55, 45 + orig: 55, 45 offset: 0, 0 - index: 2 + index: 4 diff --git a/source/core/assets/images/towers/stun_tower.png b/source/core/assets/images/towers/stun_tower.png index 0d5c0d4f6c69a165cf03552b2a3c8e24e284fac5..025b35a4c77b083e3575c76a7ebd8547028f1531 100644 GIT binary patch literal 7075 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�e6j{}&rohLn+IZcC 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 literal 6954 zcmbVRc{tSF`yQfXvL!P@WQk&yCQG5ro2`_kRkAe9$UcceGJ|4lL!-Px+gMsK*0Rf3 z$5J%5ERkXC*%=Ij`Axmw<OdTXU}mya#*y8{H@0a>>{hYskgtxg6Rv=tXK{ zQlHPP{gY0WNmkJ`U#HU1OIjvYPj7`%i|~4QGrI$46@H|PnRGN!StFk-Y~^6{G*ZyL z=7U!{RjU;WQWagrv}Xo9;q%3jugvxv3Mqz*|1Xz=!k?1B8dw*9M7fr-|Cw!#fj(8| z|1_nm?AS5?>GBft1TOE(f4cBBJub-E!00EX%y-S*1H*=Jo+Vg2vJYSAzg^J1=vEl) zwct#^XO;CbLtJ#K`2oOSEf_%D@VtYTyCvqI9%AcT6)p;XPOjVv$$sAjfzl|SaCyvRuKbP;i>f^(@nlL z3O#~<6Px>uzs7qf;AAN|r2LAWsrhiw`oR>yfkGfr5Xboab1D5-XukXA=BLm=`sQs& zppQ2QFct{e0s;Z(LJWTCrsQIAg50Q9?fTE>G~vyTJ>=&5j&kfA7`x(qJ`%f#FTIW6 zH)z)V$P#pIEQ+J2_oQKSPA!E+9nydH~p__d%4 z(!bJg*{NA;pWG$|YNyb>+f~y5pUTm3Ybn~L|Z+1VYMFn!D zX97Q4IbMDOrgO+W>C24tg`e#G6(23PjTu2{VW|t%w~1F||Ml%-LCI75s>0{3&^%Sm zdm7WYl`qVaZ@OV?ESmrF+)3HBF7AE4;UdV0C58ZxGT#YPZ7jPyZZew8Lu{BR5pE31;y?yw5eH#`Y(0HbqYfs=SR0piZ_DW zM3R-;i>kcI^Q0>oq27WoQyk2@7j-e0H%Zb84QkLGJkWTmy07x2_bscu+dSS_$urC? zP+%Zt3&Qe~BucF8T#i(YXo|i9UUZTnc7_lGxknhR4{c+E>ZA@vJS}F4ulVSypM^#h z3{KA+hPgMU_~o5>zqW_-4-Cjejo zfyT(b!zn2Hy!~sR1rqur(+MDe@Ek^Q*r-KiBU$q5bl|;LnOmjF>m;f+;&g<;6KgPZ zEiMpVyT69(hA)vO|F8!foh1=s25%OOJ~%oDw7IsUyt$)^l7Rwd=b+m?bxicfqN3z2 zrmIGU(4_}h$h8gKwEqT8&+=M@Rx{X-YNMmag-Y2z2xjK^N*wk$B(QkInSJ_AfKgNQ zg_kj9&D&2Y(D7Fuc@+&h9h=@a2M2phLbcSQ+?PlLJ#TAzC-d{>kDx-T61ILa#>hKK zAU<@e5^ULLxaDP^JSmNo5%)B!zwhTok?;!^)_r%6_?w@A31x$TQfHsr`TIU|lpW9s zlIL0+=v-#KK0XCctxn6!1@3Wyv&WSCydGre5&j&ysspkI+xsh~{ySmIxp0_hb;&mpv zG>WH2)5lk$D0AVU%Bqh6@$`#Me1~7GWF3dWX0ID9^Fh}_ z4#}#JP)I9_X9dX;+EUhG=di6+-)<2JK4(?mQ%HC#1HYnV=Z|P8h;=JR1L!#|omnAv z@eR=4KXGdm8Efr?X>2fY=kGM#58~OBf7&v$uJYNh5wH2JIsn@Cv_d>J$txS?K2#%k zdqWg(Q>eM*DiphI-MQz`V-q7r$F{ndC?I}1WUCtm=iK;2ACa!pb%#uT zfA8~XB}Px@ya9VFG3o_SqvyW1^sw#Q*6`N1T8EC2&j&(*Cwp?`3G8;l zqj+8}PNEMY{bGh%xkGT306+QY%VInj+euc-W${eZ78xZM;^&~Cos|I323b%bI;TwK zPR<*sLjl8|uTH-5&pS4-i94^RuMju#`AgZ9kVH>jLyWdV+#GP!a2&03-_e0Z_7 zI5U^If9{l_j}JbOh2Nukl4Sr()+dIeM<3{C62$CgIkP$S>Aey$(TZ=*@t8bQnZY6QvLWzxRXzn~GPORqK((M$Sb0%c)6$ANVf!yVnoiYyivlgFkD> z?46zb)ZT01*fFz6GX`%c?dCzYLgFt+5i3f2TY|UEK5=(Qucv&&{=r<&^3em~M$ej0 zrh1&Z#X5D@GVa%|V~r-~oYO%BY?+L{e)Xfsxcz?ukUdY!wgs@iqOhR-So6Injgx#H z>3uKmQtEnCu2~P3O`7a7^ExJ!z9oEz+kqKaA9I(R)&v=cx4CJs+ z_6LGOdQZFXJd;xvom$7)hietk-8m#=EQLs7PvsK7lRG zJ}&iUJkQa<+3wSB&LkMAX6W4%*~pQU^yF?@O>c8Mjtr<*!~iK0$@aVR-VgNc77I{f z7oECTzVdcyT4K4{(p;#I?l)WH^d~a18-_K(`LetE{Ovg-9NlS@U z5x3&;M%^PH#27-!z*%+Y-9tA9k?Ub_=#4@3k;9GKKLqRld=yLkgmrm zI;By*qE+y4Flb|JOADdsSnU$Ay$~1H)$r?Zk4uNFtO4|W8(4TP@aIlb$>@(#f)z1s z0+?a4%$~9s9hjkRt8R;iZf(hEQp+5v!_t?LAIs>g@pI}4$<2+Cw(hl%5}$frQXjC+ zxo}P7#LpybF9-zO$VSWmcuiS(;m#wVoO5?wTCm$JF{B5z+V8RQ)mt>fqU8F032>?; zXv3FThqn=U6Z0*TV3Q~+gRFoL%+sVLE|Jusv!gPI5h9R0;%TWRf1o?9E&Zep&er@T zTwn_Gday@b=uCdS>g!P3sO!`1lekCZhn&X89`UPJofHpqoL7uF`5Gq#g^PxzMXyeviS$J{4KId+ZfEhtkX}u&!NCepK&)!I|7_jX+k_wm@65OZ^FFJ zU(>iF=~TFwuRb*rcoqcEa}eOk*7zgcb?L#*JBhIRE9CFq4vE~E1qc3lphE*eSGON+ zsfM%db1IDFSm93Cydzjeayd(t-Pgp(AOvRB4WU}FAPX1DdVRopPhm;Skn~w-L-zel zNm+gtcDgH9y7v}H(lr|o4PmeC6y2bB*Y2F)Y(!rARx~B%yk(5@J>$dT@N6@ z2*~5kCFBW)I=sG^ zO7_x`D#?a$x<`{yQl_X37@fVVG3t<7h1hCTiaOL#&*qGwZ8DX$c2A1h`$nJ@EUIhe zGWn&HWDvYF-!roP1>S#Mq=Cx$UT?aM?`c8T>6;!m#XV#lgS~hn;fe1u+-`=fYZT-! z;uvl+VEnh&xoF8kp&<71HfyLYzM$uq(9E$3>{4}2H*+!*uu+FV$#T4p0bp%7r&hRvh+>SyNksc+31WsP+z3QW@izyLlD5LFmIGK z8AH0bjnjAt6@%-;ce*T$)Q?pn%YSCl=_k&$f^{vL&XbFI(fztDJgael@3HhEY zrL`@8oKJ-*UyB78_~b4=i^bRdQ2Lpip0qN9@%r!{8*f-y;ux>XD>>Vj$S#wEN&L)v z_$6KA0bqp7i91AH$g-C#E)Kdjdrz-+eU6TmxkSu(?(3Q(mlExD-Q10=9kthPD-ykH zly6{~dSzc`u}K&0s;9vj^u+iL1uz=@1DEE=(4I!(9Hxr<8eJ_MsdssyBeN4}W-`iX zCi0gOXH~fJ-FLeO&2GBBfttkwbaGskOkJbtS{c}<7IDQG-vU{1rTcrMYxeLm1a`~z zf(0Q_vS=$2i`rkpc^fCB(b6YO6ny9s1LF1+V4ER?$seI-CsIM8tI8{gqMtJYIYm{D zZmL}+Hk9iHDgL%X5iy1gOujZ>v72&0<33Q(6gM34h*q6nR*FY`7cITGov$Ne0k+C@ z_p&*IHr=$ITMSc6Z)w=z=eTtS*I~k+gkcS>!Mssf%kuE?T4-h{4&4fHjtOazEtNlg z#o@wI=nFS5XIo}kMi}O8)EdR(O5x9!(u=iaQ&AT+d#`pi1pWOw_4;6Z%|}M543SWN ztWOZaHoCz#tYh#Y|Lb}w-ybc|KfaTE*GC_Z3v{|@y|5%+l{P16r1`XwnPZtY5hS`k zT>|BAfY;629oU`M_%Ox~Xb_1lnu0_23p}y26>!`2XF`Rw zLEgQWl-y|hnX=wl23K+HhqtWv5Wx?khT7L2J2|4`2oCN{u{pzmG6o3mM(FKMyFh$j zuIc}=@F|!YAkRQ@7c*QJN)AX;gX^W2iuV>%JQ$}$#d$bp$FyZ-B`1Mh@Ia*B-bQ|# z(e)bKNCAVdz<-e5w4wCf<&L}NwnfmX>WRso#BI)l%&8>@gjP4M7Qt*rBa1t5xUv;1 zo({x_N<@p`f4N(({at0zuIl>llK;bZl_@fy@~pjjCDN-+e_b@ z&t?yUIsKLg&H1qs(K$tQ!An6-^<}_X0&RTmz9C63YWl6;+LtMP?E_ZTgiUqUD{om5 z62V&k57_J?w-@jts~o?*JNfb72^)2MH(I(o)AA>K+NF$)TERS(v2^6qy=O*P>b~jr zUPK*_W>t~vo!@V~ygLEzoio$7GEmQ+4O$ylYe~rHC16tc9gZHDvdJ9Xnbp3KRW+$i z!2FAH4+TRvCb(aGmTKt+G$U37?WL!?{h&R=(*FtZv->E&YM1RR!rA~+bfBO4trU4} zACr3_SM=n`5@F&8=FD$^rof0h&wMdhwW>qIeGJw@T^#mxYZPeB+vAZ2pa6wM{@7{2 zdg;$gd$6nHB3DP4S@j=iZ+V&J(z%nedy8u^v}=X13%Qy{F;bxCrfEb(B*S^CzM_n` zj#i{24}uT*tZ!tDof=V5z%5<=7{o0o`0}S(%E&ivp7@VyBaKE`w*A%s3w{cnWZhx~@2LnAaF==^M@>A0X|)Q!+lzK<8=xhb7X- z`!>{gC2c;0rR4yyu^DJ4BuWLDsau~XwY%T7EjXHgH5F6olV7Wo=AZBn?Q}pnQn+(m z+UA#?uE^XbLdx44Qol-4|G=OfF8PYxKaXdt1TOb~wdR31|FpF1^Ba5A&!Z60@6jSD zQ=hplU+b3mx1&wY*k&G*&+mLsAbxlvJM*$7i1@W-oZ?Gqifh2t+TAv9W($oI^&H71 z-%GoAsdb4Qkr#G6z)v@D!M4IJ87>X;o)Jkyy`vHUWk9)d;g#6rqhVuA<2T#_%vc!s98n6O;frk zdA+!P($|v+Mp5rT1J)Ex*T|>Mp_Sp+4QP6(fKDZ{kZl1y)tje_J{=t^h7B{bFzX{# zTe~9c)XoTuXb#H~xckMNe2&x-AM^QEJ~_;pPT ze`!$CZ(hkMKPeeahgX=ly<~78Vjl;@6jXc{RNtgtzZRGiGOm48)z+)%kTasI1cYyI zV0S#QXAxjbW*en=EE45CV5Sf9En(MB%GlWzz1GK^yQ3Vms!uN3FU!AX=Ry?(c#Re( zq!Ud(+PJUm+Fhicy09Xx`Uj&e%;gN)&X}iLI?I-7({iZU5B1R1xN?tb{Uyx1GD z>W7;CHCbiWj^$h-TEkpl`(a9Fx@o-0O!d1X&WwDwt&$=+Fai52hxDn- z6j#Av;k`pBAfuS4qG;t_LC%n?E1`rqlstV{)q@0%C3QWKSc=|23$P{~{lryZ4XsI4 zH!tFos{K|>T7U2umx|m&2|Mg_xv(#hvw-ljE2v$WTK%-;3b>t)radJed^)`UQErRD z#_lTzG(Wk8<-McDyKR>iy)~KkY0VT%h$T|neeJI`?zSB2uzcx?+&+HFM;09$1%jFn z@tihlYW|ySZ9G#wuChYA8Od8HQTJWn!?#S>vW-aI1h;}+V*A$6a>`FPz0aZ|vcnul znRTp5-oD1UnVg0B3FOR?kjZ>f@Kx6G_tmj4=*@vPZ`UE&xvPaHH55uv%b;O!bWATg zV~!S|>~2Ria8p{~ekm4NC~KClPMmyeLEb94F9k+;I2XrPpX~8|7aZ>E%D@;~wnHt4 z($AO;lr?Yh_KWuCazVUBJ+D&Y4`z$<|KZ6WKhr3P6AzxBEMWyayqbU==^Vc8E)z4+-0Hro8l0cyTH zoeKi7w4Lz;pAjMVN5PRf-pPWYg95Q}*;~;vv`ooH+IacX{9fyTcWf&)NdJdZSuL1; zHM#@i2Wcn-=uwNi+{3>udXsTB#Hi<_Ax|*YAs(kzE6&KKl}R(DbtNt}80?lp?=rXe z>yTlO+NDTEPp7j;qn;bbRU_oUnf39-;sxwpK>$nN8PA6?95oc)qWuhNDQ?>yTP|*6 zCCDHGA)GN?_paZjB#CaPW?S}Ozq|U6{o Date: Fri, 8 Sep 2023 15:19:02 +1000 Subject: [PATCH 33/37] Added javadocs to StunTowerAnimationController, FireTowerAnimationController and mehtod in towerFactory classes. --- .../components/tasks/FireTowerCombatTask.java | 28 +++++++++++++++++ .../components/tasks/StunTowerCombatTask.java | 30 +++++++++++++++++++ .../tower/FireTowerAnimationController.java | 16 ++++++++++ .../tower/StunTowerAnimationController.java | 13 ++++++++ .../game/entities/factories/TowerFactory.java | 9 ++++++ 5 files changed, 96 insertions(+) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java index 560790ce8..eff97273f 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -50,6 +50,9 @@ public FireTowerCombatTask(int priority, float maxRange) { timeSource = ServiceLocator.getTimeSource(); } + /** + * starts this task and triggers the IDLE animation + */ @Override public void start() { super.start(); @@ -62,6 +65,9 @@ public void start() { endTime = timeSource.getTime() + (INTERVAL * 500); } + /** + * this method is called everytime state of the tower needs to be changed. + */ @Override public void update() { if (timeSource.getTime() >= endTime) { @@ -70,6 +76,9 @@ public void update() { } } + /** + * finite state machine for the FireTower. Detects mobs in a straight line and changes the state of the tower. + */ public void updateTowerState() { switch (towerState) { case IDLE -> { @@ -103,23 +112,42 @@ public void updateTowerState() { } } + /** + * stops the current animation. + */ public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); } + /** + * gets the priority for the current task. + * @return (int) active priority if target is visible and inactive priority otherwise + */ public int getPriority() { return !isTargetVisible() ? 0 : priority; } + /** + * not currently used. + * @return the priority for this task + */ private int getActivePriority() { return !isTargetVisible() ? 0 : priority; } + /** + * not currently used. + * @return + */ private int getInactivePriority() { return isTargetVisible() ? priority : 0; } + /** + * detects targets from the centre of the tower to maxRange in a straight line. + * @return true if mobs are present and false otherwise. + */ public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java index 830add348..b4fb3d802 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -12,6 +12,10 @@ import com.csse3200.game.services.ServiceLocator; +/** + * The StunTowerCombatTask runs the AI for the StunTower class. The tower scans for mobs and targets in a straight line + * from its centre coordinate and executes the trigger phrases for animations depending on the current state of tower. + */ public class StunTowerCombatTask extends DefaultTask implements PriorityTask { //constants private static final int INTERVAL = 1; @@ -36,6 +40,10 @@ private enum STATE { } private STATE towerState = STATE.IDLE; + /** + * @param priority Task priority when targets are detected (0 when nothing is present) + * @param maxRange Maximum effective range of the StunTower. + */ public StunTowerCombatTask(int priority, float maxRange) { this.priority = priority; this.maxRange = maxRange; @@ -43,6 +51,9 @@ public StunTowerCombatTask(int priority, float maxRange) { timeSource = ServiceLocator.getTimeSource(); } + /** + * Starts the task running and starts the Idle animation + */ @Override public void start() { super.start(); @@ -55,6 +66,10 @@ public void start() { endTime = timeSource.getTime() + (INTERVAL * 5000); } + /** + * updates the current state of the tower based on the current state of the game. If enemies are detected, attack + * state is activated and otherwise idle state remains. + */ public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); @@ -62,6 +77,10 @@ public void update() { } } + /** + * This method acts is the state machine for StunTower. Relevant animations are triggered based on relevant state + * of the game. If enemies are detected, state of the tower is changed to attack state. + */ public void updateTowerState() { switch (towerState) { case IDLE -> { @@ -86,11 +105,18 @@ public void updateTowerState() { } } + /** + * stops the current animation and switches back the state of the tower to IDLE. + */ public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); } + /** + * returns the current priority of the task + * @return (int) active priority if target is visible and inactive priority otherwise + */ public int getPriority() { return !isTargetVisible() ? 0 : priority; } @@ -103,6 +129,10 @@ public int getInactivePriority() { return isTargetVisible() ? priority : 0; } + /** + * Searches for enemies/mobs in a straight line from the centre of the tower to maxRange in a straight line. + * @return true if targets are detected, false otherwise + */ public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } diff --git a/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java index 297476ec0..358d7a3a6 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java @@ -5,6 +5,10 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.ServiceLocator; +/** + * Listens for events relevant to a weapon tower state. + * Each event will have a trigger phrase and have a certain animation attached. + */ public class FireTowerAnimationController extends Component{ //Event name constants private static final String IDLE = "startIdle"; @@ -19,6 +23,9 @@ public class FireTowerAnimationController extends Component{ AnimationRenderComponent animator; + /** + * Create method for FireTowerAnimationController. + */ @Override public void create() { super.create(); @@ -28,14 +35,23 @@ public void create() { entity.getEvents().addListener(ATTACK, this::animateAttack); } + /** + * Starts the idle animation + */ void animateIdle() { animator.startAnimation(IDLE_ANIM); } + /** + * starts the prep_attack animation + */ void animatePrepAttack() { animator.startAnimation(PREP_ATTACK_ANIM); } + /** + * starts the attack animation + */ void animateAttack() { animator.startAnimation(ATTACK_ANIM); } diff --git a/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java index 8ff908b35..fa4868c4c 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java @@ -5,6 +5,9 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.ServiceLocator; +/** + * Listens to triggers phrases and executes the required animations. + */ public class StunTowerAnimationController extends Component { //Event name constants private static final String IDLE = "startIdle"; @@ -17,6 +20,10 @@ public class StunTowerAnimationController extends Component { AnimationRenderComponent animator; + /** + * Creation method for StunTowerAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations. + */ @Override public void create() { super.create(); @@ -25,10 +32,16 @@ public void create() { entity.getEvents().addListener(ATTACK, this::animateAttack); } + /** + * Starts the idle animation + */ void animateIdle() { animator.startAnimation(IDLE_ANIM); } + /** + * starts the attack animation + */ void animateAttack() { animator.startAnimation(ATTACK_ANIM); } diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index d10c6c367..70b1f8b14 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -136,10 +136,15 @@ public static Entity createWeaponTower() { } + /** + * Creates the FireTower entity which shoots at mobs traversing in a straight line. + * @return FireTower entity with relevant components. + */ public static Entity createFireTower() { Entity fireTower = createBaseTower(); FireTowerConfig config = configs.fireTower; + //Component that handles triggering events and animations AITaskComponent aiTaskComponent = new AITaskComponent() .addTask(new FireTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); @@ -161,6 +166,10 @@ public static Entity createFireTower() { return fireTower; } + /** + * Creates the StunTower entity which shoots at mobs traversing in a straight line. + * @return StunTower entity with relevant components. + */ public static Entity createStunTower() { Entity stunTower = createBaseTower(); StunTowerConfig config = configs.stunTower; From 46da325e4ef565e95fea8d82770ef729c4cf6660 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 15:58:24 +1000 Subject: [PATCH 34/37] Adjusted the TNT effect so that it only affects target on the same lane --- .../com/csse3200/game/components/tower/TNTDamageComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java index c019b92fd..ee220433f 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java +++ b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java @@ -78,7 +78,7 @@ private void applyTNTDamage() { Vector2 positionSource = entity.getPosition(); Vector2 positionOther = otherEntity.getPosition(); - if (positionSource.dst(positionOther) <= radius) { + if (positionSource.dst(positionOther) <= radius && positionSource.y -3 <= positionOther.y && positionSource.y +3 >= positionOther.y) { HitboxComponent sourceHitbox = entity.getComponent(HitboxComponent.class); HitboxComponent otherHitbox = otherEntity.getComponent(HitboxComponent.class); From 490cfdfe135e6909347f1807170bf679c973388f Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 16:01:53 +1000 Subject: [PATCH 35/37] Added the combat task for Droid tower which triggers animations according to game events --- .../components/tasks/DroidCombatTask.java | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java new file mode 100644 index 000000000..8512c2221 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java @@ -0,0 +1,183 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * The DroidCombatTask runs the AI for the DroidTower class. The tower will scan for targets in a straight line + * from its center point until a point at (x + maxRange, y), where x,y are the cooridinates of the tower's center + * position. This component should be added to an AiTaskComponent attached to the tower instance. + */ +public class DroidCombatTask extends DefaultTask implements PriorityTask { + // Constants + private static final int INTERVAL = 1; // time interval to scan for enemies in seconds + private static final short TARGET = PhysicsLayer.NPC; // The type of targets that the tower will detect + // the following four constants are the event names that will be triggered in the state machine + private static final String GO_UP = "goUpStart"; + private static final String GO_DOWN = "goDownStart"; + private static final String ATTACK_UP = "attackUpStart"; + private static final String ATTACK_DOWN = "attackDownStart"; + private static final String WALK = "walkStart"; + private static final String DEATH = "deathStart"; + private static final String IDLE = "idleStart"; + + + // class attributes + private final int priority; // The active priority this task will have + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); // initial placeholder value - will be overwritten + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + private enum STATE { + IDLE, UP, DOWN, SHOOT_UP, SHOOT_DOWN, WALK, DIE + } + private STATE towerState = STATE.WALK; + + /** + * @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 + */ + public DroidCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the Task running, triggers the initial "idleStart" event. + */ + @Override + public void start() { + super.start(); + // Set the tower's coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Default to idle mode + owner.getEntity().getEvents().trigger(WALK); + + endTime = timeSource.getTime() + (INTERVAL * 500); + } + + /** + * The update method is what is run every time the TaskRunner in the AiTaskComponent calls update(). + * triggers events depending on the presence or otherwise of targets in the detection range + */ + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * Droid tower state machine. Updates tower state by scanning for mobs, and + * triggers the appropriate events corresponding to the STATE enum. + */ + public void updateTowerState() { + // configure tower state depending on target visibility + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DIE) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DIE; + return; + } + switch (towerState) { + case WALK -> { + owner.getEntity().getEvents().trigger(WALK); + towerState = STATE.IDLE; + } + case IDLE -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_UP); + towerState = STATE.DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + } + } + case SHOOT_DOWN -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_DOWN); + towerState = STATE.UP; + } else { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.UP; + } + } + case SHOOT_UP -> { + if (isTargetVisible()) { + + owner.getEntity().getEvents().trigger(ATTACK_UP); + towerState = STATE.DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + case DOWN -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(GO_DOWN); + towerState = STATE.SHOOT_DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + case UP -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.SHOOT_UP; + } else { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.IDLE; + + + } + } + case DIE -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) + owner.getEntity().setFlagForDelete(true); + } + } + } + /** + * For stopping the running task + */ + @Override + public void stop() { + super.stop(); +// owner.getEntity().getEvents().trigger(STOW); + } + + /** + * Returns the current priority of the task. + * @return active priority value if targets detected, inactive priority otherwise + */ + @Override + public int getPriority() { + return isTargetVisible() ? priority : 0; + } + + /** + * Uses a raycast to determine whether there are any targets in detection range + * @return true if a target is visible, false otherwise + */ + 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); + } +} From 8e804de640804176bbc79e013da3e443a3ce8ab6 Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 16:03:53 +1000 Subject: [PATCH 36/37] Added components to the Droid tower --- .../game/entities/factories/TowerFactory.java | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 452f53a61..8b0ea7579 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,7 +1,9 @@ package com.csse3200.game.entities.factories; import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.tasks.DroidCombatTask; import com.csse3200.game.components.tasks.TNTTowerCombatTask; +import com.csse3200.game.components.tower.DroidAnimationController; import com.csse3200.game.components.tower.TNTAnimationController; import com.csse3200.game.components.tower.TNTDamageComponent; import com.csse3200.game.entities.configs.*; @@ -43,7 +45,15 @@ public class TowerFactory { private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; + private static final String DROID_ATLAS = "images/towers/DroidTower.atlas"; + private static final float DROID_SPEED = 0.25f; private static final String DEFAULT_ANIM = "default"; + private static final String WALK_ANIM = "walk"; + private static final String DEATH_ANIM = "death"; + private static final String GO_UP = "goUp"; + private static final String GO_DOWN = "goDown"; + private static final String SHOOT_UP = "attackUp"; + private static final String SHOOT_DOWN = "attackDown"; private static final float DEFAULT_SPEED= 0.2f; private static final String DIG_ANIM = "dig"; private static final float DIG_SPEED = 0.2f; @@ -143,13 +153,30 @@ public static Entity createDroidTower() { Entity DroidTower = createBaseTower(); DroidTowerConfig config = configs.DroidTower; + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new DroidCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(DROID_ATLAS, TextureAtlas.class)); + + animator.addAnimation(IDLE_ANIM, DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(SHOOT_UP,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(SHOOT_DOWN,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(WALK_ANIM,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(DEATH_ANIM,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(GO_UP,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(GO_DOWN,DROID_SPEED, Animation.PlayMode.NORMAL); + + DroidTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(new CostComponent(config.cost)) - .addComponent(new TNTAnimationController()); - - DroidTower.getComponent(AnimationRenderComponent.class).scaleEntity(); + .addComponent(new DroidAnimationController()) + .addComponent(animator) + .addComponent(aiTaskComponent); return DroidTower; } From 7ca10b5aff423a6ac9c301c081a755177cd41cfa Mon Sep 17 00:00:00 2001 From: Mohamad Date: Fri, 8 Sep 2023 16:04:17 +1000 Subject: [PATCH 37/37] Spawned Droid Tower to the game --- .../csse3200/game/areas/ForestGameArea.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index 4623cb16d..d6443a9b5 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -85,7 +85,8 @@ public class ForestGameArea extends GameArea { "images/iso_grass_3.png", "images/economy/scrap.png", "images/towers/mine_tower.png", - "images/towers/TNTTower.png" + "images/towers/TNTTower.png", + "images/towers/DroidTower.png" }; private static final String[] forestTextureAtlases = { "images/terrain_iso_grass.atlas", @@ -96,7 +97,8 @@ public class ForestGameArea extends GameArea { "images/mobs/xenoGruntRunning.atlas", "images/mobs/robot.atlas", "images/mobs/rangeBossRight.atlas", - "images/towers/TNTTower.atlas" + "images/towers/TNTTower.atlas", + "images/towers/DroidTower.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -144,20 +146,21 @@ public void create() { playMusic(); // Types of projectile - spawnAoeProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f), 1); - spawnProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f)); - spawnMultiProjectile(new Vector2(0, 10), player, towardsMobs, 20, new Vector2(2f, 2f), 7); +// spawnAoeProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f), 1); +// spawnProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f)); +// spawnMultiProjectile(new Vector2(0, 10), player, towardsMobs, 20, new Vector2(2f, 2f), 7); spawnXenoGrunts(); spawnGhosts(); spawnWeaponTower(); - spawnIncome(); - spawnScrap(); +// spawnIncome(); +// spawnScrap(); bossKing1 = spawnBossKing1(); bossKing2 = spawnBossKing2(); spawnTNTTower(); + spawnDroidTower(); playMusic(); } @@ -414,6 +417,18 @@ private void spawnTNTTower() { } + private void spawnDroidTower() { + GridPoint2 minPos = new GridPoint2(0, 0); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity weaponTower = TowerFactory.createDroidTower(); + spawnEntityAt(weaponTower, randomPos, true, true); + } + + } + private void playMusic() { Music music = ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class);