From 685341e8a1c3cdf4715fbc1667b013b82e6c69d0 Mon Sep 17 00:00:00 2001 From: Thivan W Date: Sun, 1 Oct 2023 21:14:25 +1000 Subject: [PATCH 01/31] added FireworksTower config --- .../game/entities/configs/FireworksTowerConfig.java | 7 +++++++ .../csse3200/game/entities/configs/baseTowerConfigs.java | 1 + .../com/csse3200/game/entities/factories/TowerFactory.java | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java diff --git a/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java new file mode 100644 index 000000000..6e24e2e6f --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java @@ -0,0 +1,7 @@ +package com.csse3200.game.entities.configs; + +public class FireworksTowerConfig { + 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 968f4ec93..c34261327 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 @@ -11,4 +11,5 @@ public class baseTowerConfigs { public StunTowerConfig stunTower = new StunTowerConfig(); public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); public DroidTowerConfig DroidTower = new DroidTowerConfig(); + public FireworksTowerConfig fireworksTower = new FireworksTowerConfig(); } \ 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 1341746fd..8108e2e05 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 @@ -304,6 +304,13 @@ public static Entity createStunTower() { return stunTower; } + public static Entity createFireworksTower() { + Entity fireworksTower = createBaseTower(); + FireworksTowerConfig config = configs.fireworksTower; + + return fireworksTower; + } + /** * Creates a generic tower entity to be used as a base entity by more specific tower creation methods. * @return entity From aff253f6600ea22431db42f4c0e8fd16b933177b Mon Sep 17 00:00:00 2001 From: Thivan W Date: Sun, 1 Oct 2023 21:44:39 +1000 Subject: [PATCH 02/31] Added FireworksTowerCombatTask --- .../tasks/FireworksTowerCombatTask.java | 159 ++++++++++++++++++ .../game/entities/factories/TowerFactory.java | 5 + 2 files changed, 164 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java new file mode 100644 index 000000000..1a125d2b4 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -0,0 +1,159 @@ +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.components.ProjectileEffects; +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 FireworksTowerCombatTask runs the AI for the FireworksTower class. The tower scans for mobs and targets in a + * straight line from its centre coordinate and executes the trigger phrases for animations depeending on the current + * state of tower. + */ +public class FireworksTowerCombatTask extends DefaultTask implements PriorityTask { + // constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + private static final short TARGET = PhysicsLayer.NPC; + private static final String IDLE = "startIdle"; + public static final String PREP_ATTACK = "startAttackPrep"; + public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; + + // 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(); + + public enum STATE { + IDLE, ATTACK, DEATH + } + public 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 FireworksTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @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); + } + + /** + * 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(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * 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() { + + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + 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.createSplitFireWorksFireball(PhysicsLayer.NPC, + // double check if this is the correct projectile to call + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), + 3); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), + (float) (owner.getEntity().getPosition().y + 0.25)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + } + } + + /** + * Returns the state that the tower is currently in + * @return this.towerState + */ + public STATE getState() { + return this.towerState; + } + + /** + * 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; + } + + /** + * 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/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 8108e2e05..559a53c81 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 @@ -6,6 +6,7 @@ import com.csse3200.game.entities.configs.*; import com.csse3200.game.components.tasks.FireTowerCombatTask; import com.csse3200.game.components.tasks.StunTowerCombatTask; +import com.csse3200.game.components.tasks.FireworksTowerCombatTask; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; @@ -308,6 +309,10 @@ public static Entity createFireworksTower() { Entity fireworksTower = createBaseTower(); FireworksTowerConfig config = configs.fireworksTower; + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new FireworksTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + return fireworksTower; } From 3833226f8823ef35691a83692e50a454a1e42ecf Mon Sep 17 00:00:00 2001 From: Thivan W Date: Sun, 1 Oct 2023 21:51:49 +1000 Subject: [PATCH 03/31] Added all components apart from animations to the FireworksTower --- .../game/entities/factories/TowerFactory.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 559a53c81..e1082d9ab 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 @@ -312,7 +312,22 @@ public static Entity createFireworksTower() { AITaskComponent aiTaskComponent = new AITaskComponent() .addTask(new FireworksTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + // NEED TO MAKE FIREWORKS_TOWER_ATLAS +// AnimationRenderComponent animator = +// new AnimationRenderComponent( +// ServiceLocator.getResourceService() +// .getAsset(FIREWORKS_TOWER_ATLAS, TextureAtlas.class)); + fireworksTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + // NEED TO ADD ANIMATIONS +// .addComponent(animator) +// .addComponent(new FireworksTowerAnimationController()); + + fireworksTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(fireworksTower, 0.5f, 0.5f); return fireworksTower; } From a0a26c489d2084f16a2b30cc20ffa670c27db33b Mon Sep 17 00:00:00 2001 From: Thivan W Date: Sun, 1 Oct 2023 23:49:55 +1000 Subject: [PATCH 04/31] Added PierceTower to TowerFactory --- .../tasks/FireworksTowerCombatTask.java | 1 - .../tasks/PierceTowerCombatTask.java | 158 ++++++++++++++++++ .../entities/configs/PierceTowerConfig.java | 7 + .../entities/configs/baseTowerConfigs.java | 1 + .../game/entities/factories/TowerFactory.java | 27 +++ 5 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/PierceTowerConfig.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java index 1a125d2b4..8cf074a70 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -27,7 +27,6 @@ public class FireworksTowerCombatTask extends DefaultTask implements PriorityTas // The type of targets this tower will detect private static final short TARGET = PhysicsLayer.NPC; private static final String IDLE = "startIdle"; - public static final String PREP_ATTACK = "startAttackPrep"; public static final String ATTACK = "startAttack"; public static final String DEATH = "startDeath"; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java new file mode 100644 index 000000000..61e79eec3 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -0,0 +1,158 @@ +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.components.ProjectileEffects; +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 PierceTowerCombatTask runs the AI for the PierceTower class. The tower scans for mobs and targets in a straight + * line from its centre coordinate and executes the trigger phrases for animations depeending on the current state of + * tower. + */ +public class PierceTowerCombatTask extends DefaultTask implements PriorityTask { + // constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + private static final short TARGET = PhysicsLayer.NPC; + private static final String IDLE = "startIdle"; + public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; + + // 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(); + + public enum STATE { + IDLE, ATTACK, DEATH + } + public 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 PierceTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @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); + } + + /** + * 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(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * 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() { + + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + 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); + // NEED TO CHANGE THAT + Entity newProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, + // double check if this is the correct projectile to call + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), + 3); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), + (float) (owner.getEntity().getPosition().y + 0.25)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + } + } + + /** + * Returns the state that the tower is currently in + * @return this.towerState + */ + public STATE getState() { + return this.towerState; + } + + /** + * 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; + } + + /** + * 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/entities/configs/PierceTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/PierceTowerConfig.java new file mode 100644 index 000000000..8baf1cb52 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/PierceTowerConfig.java @@ -0,0 +1,7 @@ +package com.csse3200.game.entities.configs; + +public class PierceTowerConfig { + 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 c34261327..5f83abf8e 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 @@ -12,4 +12,5 @@ public class baseTowerConfigs { public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); public DroidTowerConfig DroidTower = new DroidTowerConfig(); public FireworksTowerConfig fireworksTower = new FireworksTowerConfig(); + public PierceTowerConfig pierceTower = new PierceTowerConfig(); } \ 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 e1082d9ab..3653f9458 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 @@ -7,6 +7,7 @@ import com.csse3200.game.components.tasks.FireTowerCombatTask; import com.csse3200.game.components.tasks.StunTowerCombatTask; import com.csse3200.game.components.tasks.FireworksTowerCombatTask; +import com.csse3200.game.components.tasks.PierceTowerCombatTask; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; @@ -305,6 +306,10 @@ public static Entity createStunTower() { return stunTower; } + /** + * Creates the FireworksTower entity which shoots at mobs traversing in a straight line. + * @return FireworksTower entity with relevant components. + */ public static Entity createFireworksTower() { Entity fireworksTower = createBaseTower(); FireworksTowerConfig config = configs.fireworksTower; @@ -331,6 +336,28 @@ public static Entity createFireworksTower() { return fireworksTower; } + /** + * Creates the PierceTower entity which shoots at mobs traversing in a straight line. + * @return PierceTower entity with relevant components. + */ + public static Entity createPierceTower() { + Entity pierceTower = createBaseTower(); + PierceTowerConfig config = configs.pierceTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new PierceTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + pierceTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + // ADD ANIMATION COMPONENTS + + pierceTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(pierceTower, 0.5f, 0.5f); + return pierceTower; + } + /** * Creates a generic tower entity to be used as a base entity by more specific tower creation methods. * @return entity From 0386c95b0e584ccc6c790e0eda3c69bd27a98efe Mon Sep 17 00:00:00 2001 From: Thivan W Date: Mon, 2 Oct 2023 00:12:16 +1000 Subject: [PATCH 05/31] Added ricochet tower and made modifications to pierce and fireworks towers for cohesion --- .../tasks/FireworksTowerCombatTask.java | 1 + .../tasks/PierceTowerCombatTask.java | 7 +- .../tasks/RicochetTowerCombatTask.java | 155 ++++++++++++++++++ .../entities/configs/RicochetTowerConfig.java | 7 + .../entities/configs/baseTowerConfigs.java | 1 + .../game/entities/factories/TowerFactory.java | 27 +++ 6 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/RicochetTowerConfig.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java index 8cf074a70..997c42ce4 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -26,6 +26,7 @@ public class FireworksTowerCombatTask extends DefaultTask implements PriorityTas private static final int INTERVAL = 1; // The type of targets this tower will detect private static final short TARGET = PhysicsLayer.NPC; + //Following constants are names of events that will be triggered in the state machine private static final String IDLE = "startIdle"; public static final String ATTACK = "startAttack"; public static final String DEATH = "startDeath"; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java index 61e79eec3..379f60800 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -106,11 +106,8 @@ public void updateTowerState() { towerState = STATE.IDLE; } else { owner.getEntity().getEvents().trigger(ATTACK); - // NEED TO CHANGE THAT - Entity newProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, - // double check if this is the correct projectile to call - new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), - 3); + Entity newProjectile = ProjectileFactory.createPierceFireBall(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), (float) (owner.getEntity().getPosition().y + 0.25)); ServiceLocator.getEntityService().register(newProjectile); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java new file mode 100644 index 000000000..d057c1ab0 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java @@ -0,0 +1,155 @@ +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.components.ProjectileEffects; +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 RicochetTowerCombatTask runs the AI for the RicochetTower 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 RicochetTowerCombatTask extends DefaultTask implements PriorityTask { + //constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + 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"; + public static final String DEATH = "startDeath"; + + // 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 + public enum STATE { + IDLE, ATTACK, DEATH + } + public 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 RicochetTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @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); + } + + /** + * 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(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * 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() { + + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + 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.createRicochetFireball(PhysicsLayer.NPC, + // NEED TO DO USER TESTING TO FIGURE OUT THE BOUNCE COUNT + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), 3); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), + (float) (owner.getEntity().getPosition().y + 0.25)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + } + } + + public STATE getState() { + return this.towerState; + } + + /** + * 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; + } + + /** + * 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/entities/configs/RicochetTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/RicochetTowerConfig.java new file mode 100644 index 000000000..3a637c7e2 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/RicochetTowerConfig.java @@ -0,0 +1,7 @@ +package com.csse3200.game.entities.configs; + +public class RicochetTowerConfig { + 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 5f83abf8e..9a1d62c47 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 @@ -13,4 +13,5 @@ public class baseTowerConfigs { public DroidTowerConfig DroidTower = new DroidTowerConfig(); public FireworksTowerConfig fireworksTower = new FireworksTowerConfig(); public PierceTowerConfig pierceTower = new PierceTowerConfig(); + public RicochetTowerConfig ricochetTower = new RicochetTowerConfig(); } \ 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 3653f9458..4a85e83ab 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 @@ -8,6 +8,7 @@ import com.csse3200.game.components.tasks.StunTowerCombatTask; import com.csse3200.game.components.tasks.FireworksTowerCombatTask; import com.csse3200.game.components.tasks.PierceTowerCombatTask; +import com.csse3200.game.components.tasks.RicochetTowerCombatTask; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; @@ -347,6 +348,8 @@ public static Entity createPierceTower() { AITaskComponent aiTaskComponent = new AITaskComponent() .addTask(new PierceTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + // ADD AnimationRenderComponent + pierceTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent((new CostComponent(config.cost))) @@ -358,6 +361,30 @@ public static Entity createPierceTower() { return pierceTower; } + /** + * Creates the RicochetTower entity which shoots at mobs traversing in a straight line. + * @return RicochetTower entity with relevant components. + */ + public static Entity createRicochetTower() { + Entity ricochetTower = createBaseTower(); + RicochetTowerConfig config = configs.ricochetTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new RicochetTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + // ADD AnimationRenderComponent + + ricochetTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + // ADD ANIMATION COMPONENTS + + ricochetTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(ricochetTower, 0.5f, 0.5f); + return ricochetTower; + } + /** * Creates a generic tower entity to be used as a base entity by more specific tower creation methods. * @return entity From cacf4fc6ce3068e31ee7d39705cff24da8bdf77e Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Mon, 2 Oct 2023 07:04:15 +1000 Subject: [PATCH 06/31] made the tower fireworks from created to working state the tower is currently spawned in and is using the fireworks projectile and the tower combat has been modified for the charging start and end states created animation controller, atlas files, sprite sheet for the animations.(current tower state : almost finished) --- .../images/towers/fireworks_tower.atlas | 488 ++++++++++++++++++ .../assets/images/towers/fireworks_tower.png | Bin 0 -> 44667 bytes .../csse3200/game/areas/ForestGameArea.java | 22 +- .../tasks/FireworksTowerCombatTask.java | 58 ++- .../FireworksTowerAnimationController.java | 66 +++ .../entities/configs/HealTowerConfig.java | 7 + .../entities/configs/baseTowerConfigs.java | 1 + .../game/entities/factories/TowerFactory.java | 50 +- 8 files changed, 659 insertions(+), 33 deletions(-) create mode 100644 source/core/assets/images/towers/fireworks_tower.atlas create mode 100644 source/core/assets/images/towers/fireworks_tower.png create mode 100644 source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java diff --git a/source/core/assets/images/towers/fireworks_tower.atlas b/source/core/assets/images/towers/fireworks_tower.atlas new file mode 100644 index 000000000..c298ab6d7 --- /dev/null +++ b/source/core/assets/images/towers/fireworks_tower.atlas @@ -0,0 +1,488 @@ +fireworks_tower.png +size: 1700, 560 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Charge + rotate: false + xy: 0, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 100, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 200, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 300, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 400, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 500, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 600, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 700, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 800, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 900, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge + rotate: false + xy: 1000, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 1100, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1100, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1200, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1300, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1400, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1500, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1600, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 0, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 100, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 200, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 300, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 400, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 500, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 600, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 700, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 800, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 900, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 1000, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 1100, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 1200, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Charge_end + rotate: false + xy: 1300, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1400, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1500, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1600, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 0, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 100, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 200, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 300, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 400, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 500, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 600, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 700, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 800, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 900, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1000, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1100, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1200, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1300, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1400, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1500, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1600, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 0, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 100, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 200, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 300, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 400, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 500, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 600, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 700, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 800, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 900, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1000, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1100, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1200, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1300, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1400, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1500, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1600, 420 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/fireworks_tower.png b/source/core/assets/images/towers/fireworks_tower.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d1a6d2964bd5fd604d0a1b22d97f4be3c63281 GIT binary patch literal 44667 zcmdqJc|28Z_diaOXrLr2Y%*k?QpvE%bP$O`W}?VY84B5`C=qQ#rZSYF3`K(>bBL3O zOc^sB^L&iwn9lFo>b{@nbAO-b^ZWbzbzX<#oV~AcU2DD9TJLp&&Y#2WVK~e{MMbqo zQ$tmsifTtH71cJ0ojbrg{q_vL;1B&x4P!Sds$I<3lbY&TJR21i7nP>!nTwuXQ@{F9 z_I9uSjSDKgEWq^V zzx3^qC>7OM67|-HcV9{dRdY}&`Te={Vz8!&lF0ARsHn~h@;8yGzU=?2o#O$T?n>G-AhJFK3q?%iPhec2AzO~als)d4lii~Ug?)phjp}&0H(^?7Xp0n zp9#L8Qsa-*xtZ*wUi$jYwriGa@0a?W&Em%m&Y77%u~Dl$Nd95*XMrldJ=9#Zc4an@ zoERb-YN`p+X3Temi>hT9RzJOc~~lcEY;_YmZA z9rpabNXb#c__VQrdQ_uKb>y{`rZ3-4RKMvJd$?rXcKG)dhbvT1&P$MYNlL61ULNz& znH1Cb9*kP=xSwWD0c{+XZM`*J!0SD7^l{(mX(#^P5b@D&IiULuI#$XM^eL8uYGZ0$Av#B zFz`Q0a{(J_>}HSn_6PKj?n&8L{fO7Pz4Vmi7`@0)Gg+V_;}OePZy}e?Q7N)dk&9d| z+#FGHY1tCH>+|BUU&{4vexonaHOj_{S=oKnxYN@~$6IsBR96F?B(+>J`4Y}r;8GT- zsjM#77*bIkXrKv9SdThl*eGGA8>cOPZ11W=`U_ zG(pZOwf6Je$SND!5C~4TRepdz8{{dKKYk-)#2+QTEFJV`gg@ifGz=41f9>$PMl6&G z6~1JR9EhD_g1UrZl6?qw!ML#r((n^DIR1rqx_Qv;{l~z}Q2&?R!lq?9>J%5Q;M^;F z$Qo!UOIk?IL#t#4{S-4R|K16o)MMK7{JPB$K?SLQgW8fkS3? zYS9b#^$u6hfyHm<`~1MGN1QJt415u@!rY?c1CmY=GePDAbC{IggwJ3#y4{tzP!JsI z@pfAV3^%CiSwmrr^Lq7x$`|F3c=wI2uq53st-M0b2;x_pg@JE<>$e7Ku5PS0rZ6dc zlKa-_5;QpA9lPI=(mU~Y_XePceVnRYKCSjg9y`vl148PH&^j0P?7rU3p~06w+Fs99 z;Ggcd(+JkYqoRRMelX=*bs4?JPh642uohZt$evSP`qSXi;cqx8y<`DCXviNe@`*2J z@mM9MTFy%L?s**Oq+=M4-sBIKtxmrWe+fjw7-9ZuTwo;krGd_yzOeO!ibBos@U@_A zy0N_WxkAf&R(Gp$p)i9x7t%V!6BnQ*&+Zxal=sHzbEJNos|atq+_)l`wv318sHmN5 z0B9|4aUxAXEoT$ijCxEQX%Tm(P>d8Cbd)~84}Z-w#%&H%WWGwP9vw`xi>U$&STo-= z?$}&TlQ!({&k)chOeSluY7BDK)-`g>$o2Y=#VvM;Qw9}_!bFMO@Q^<;Cn*S9Z1y@1 z49}Jxd~?weRN&u*PO5Z;+2?#Nsp7@O}_vpWl~o8I?oyX zeAIogz|-EtVBNJRk5vj{S<}$HZMFxI9_NuGwq`}1J3Zi!#u3AH#O^5Q-P$y?quho` zvHZtQXQy#1pYzZFeI%ujyfQN*^mXsM`E9&-dWc0geXeP5 zHtads9yjVa2u9JrI-THK(l@`UJ=j18wJ4W6FEv-AC6m46OIO)z@Q-$PnA~uJg7wlO zWi$;loy%Nq_XnRN>ZzO)P=v%|I7im*yoF8Wb1&?kGFTUo?bW$Ko#gwr)M7>2Y3-C% z+VBrC{8D(ln#>eK_qDG3?>{qibj?@j$!imC$JX{4Se=lVXls2_3%ocbqW`*v{&;Bh;x?G!g8cU%%^#2w<;DKr;;vn_9c zM!{zcNBS%VnU6BRG%Q>Hav_a$$0zP+9$A*#&0rPHCH;3sMIS=Pay))k$hV%3(vA4H-N%lC!-aIPXZwm`S0vflRJ6Z2< z)2P}LO&z_LgXhQWyGlJqYFbR#9O3D}#`$}@^$w^(l+oGU>p)+ujunftAFdF}h~`$j z5q}{kXLq~ZF`tcSmmAIph}2{*2xodDQlo6*r zJGOlVdfKJYhfPv?{I!Cv522g*jz$C)dV+GxF~JUVKxxZn{hXH#uXBl)Cw~UIc$A=h z;8tS#xfO2BHE5UfyR9kQ4@q$ErZktF)ZYm)lWC32m}uvKrSAEo>Lk{qMa-E{AT`mx%HygHYjr5ZH?JQ$0rpJrA|nDpH= zWN+vTu1fkfbJ*;qHB@^9%GU;w8XX`|))NxxH5Ts@@X_Y)4zE5TTW)w3OPt{1 zHuLiqrPLcdJr}->+de+Rd!L-Umy$i_KMpK2}D8IQ1Iw()_1yeQ78>}6Bbc5)E zBp=ypo}wwJHSN6ccr6e;mCU5PUMP-aTzERMKYaJ~kN&-!EZu&mDN%Dqeq>x`As_jx zGx9J!dO@|2gvF|u?kqw%6+c*uRQd56%tmzoz zaEel21D;9@?_&2lkzE|o_V8{KyuUsS6@J=7mRTCq!dFE>Zyx67Tz&qCMW3>+OJP&) z$T)D@fZqF}lY!%r`6e3Jq@QQ$DFXx)G{*-uLD=K~M3DjTA3bQ}+LdM7d{U0S9w)bo3da~?7MEl%0Y8CE ze_H5_kun|gSe_9jhrS7YRU}bC-$!qm&$2pIk<%Yh3*W4nC%!;OY<4naYiIZ1??$-_ zRNx$r43)w8`VTbibWU_9Q7RY<`U-vJa+lc0?3>fv6WSwqlt-K5QIB5JI-jB#)WIh; zr!NDba+TbPs_WS4npQCaMbD| z$Zmqo#&q7h#VILBO~y}n?_)W;x+hXi-*IXd%52v@6t^x5nadbHbtt1~bm^*PjQFHJ zt`QZ{QfuK#gJjVWB=y)WaylO0*M}&wE9S>f@)(yb636&IlIj7f($Hd}7j(3OM3slLp_Nt<)+z*Ch4ziPi{TgruC}l<5*M7;W+Q*9>Pc-867J9lL zVz`jDINK$>K}TYHZhK$M_`!vy)N6^TJ9Plcd0pt4^!KH36KbhkMq#%uf{I?I`9@&W z>FN2V!M4+p6E+*`Chb+bBYTo`>vvgr4muKRa{QXy&PJZ0^LcAYDWtOhj0g5_=2`Aw zihKmU2tfJDx8Po{8*f*JiUV$rov{OJ^278wy6BK-?qA1i0c;iJC6~byl&)RR?>a&Y zXHLar&4(v`8MLV+5M0>B+A?k6Czs|q~qi@zRkVu@?oV^A~I6s*7B&=bjRRJF7?b67vLbs_&S|d zJ?&n-3*s2DEM#jeca_W*h4*th^%)JVq@ZZ4kRUk_VM0Q_0RP$`Qpq22=dZ^TT{aEP z+!1bgb?wvqcPMlQ*t$3;@pgQ3^$nt==ms7j{#yroLWbN88e+`I6%(Etf#ishk+XNI zHFnJ}1YMJEks2xYOdV~%62r`&EKh6!?#vntf8#L^%an%SP#s#mb8&{JxNm(}CQ(z7 zwm?Im4%hg0ZU3>4Gdw;_?g_+j^NsM5C&x1kWIx98o*d>mB5VvD*D7MlCD(MSucBll zu^NDPb2pv@MLvn-_u0L=0eqW#hU9niFQlPKJHP$WV!n>~KCLq9InHIa%?nSYEd1yf z_$Ei&E1L_iEbtL(yfhd1Y{(Lp`VPK{pV+b#Z`2~Sy_z?pMRbb2XU}z%&su!~e@ETc zRycZ=mc|{dkJ<}fdf1za0-ISSH~aP$d>gFq4QNM^%B`^!3}}f0vu-92M;tqRKxwe1 zJL|Y!>xr+2f5ho19GhGSf3x0U@KpAA|Az6)&yn^~UvRgDKRESCX8CAdfGZm^D+RuJ zU3NFz6~G;EPEsKLZ6hvowih8dNmH~s1hpQ}sbwA#d33;7&=9xzp1gRLJKj(5$7|ZPs=3Ze*)*IAYt^OACBK=%Jaqy7_R6@Z+};?M(E8g&UapxMpwoppD7M*>6JX zapKp|$jCiw5|+)u>m@fVd(TrOPifA~i*5e$_9*Jzf5>C@SEqsKR8ZO|k2z}XQ*!f; zcT(L@-@SX!X6moD>7siTZs;Ry&@M{_J$cNt{3QEAC64dh-kDZm=bVViS@|$!P6y(> zm)&&mt8QFlTfmibur}NH(erO%DWB?*BX-3RPmB|hK4djb*&@>+2Uzwa-zgJ#bLe@S zC`%x@IBQBVe^-FqX}tCg7h^qG=Axe%Byy|FcA#=$__5%gXfREDx3RYO zA$r}3ZSKinGFKJJlj84~;Jn5_tLA`CHsIaIxP6s+RW0{HlQ(ig4??nwFXa}sx`o{@ z^!JJYY1O>#RKQFdDNd9ug;x)zoVS<15X`uiyK*IVn|0K5)Hdsa$fFjS^WjEZ@x~C# zu)j)mAo6Zwf$!r~asa90(@h=ZqJ*VXCi=igH-r@$IjfZ*eyE%7@N@BuWYMT3qp_5V zKbSc$oPr((LaRvohrG~T?Ugmf?dmx{CVqG@o}?4u11=Yk#|M~PUeHZs zYY002xJY)7%eA!zyC)~%U^H7#gr;g7_L^ z#v1LM%C<);SW4{@mc#XgzP$|{FTG-!w0ENp75hRN{H$Uwr99R0l3y9qmmjmrBVe{dhU*F&0O^qVKbwmGk^OyW{{9BswPE z?47>Q^o{yPCww49oolK89w|MJ8aQBw7c23t!~RID_&#Vb#dWXhDtfXZCbtPM&3c9( zocS)uN#N$yR*?cDw@5AlUG*a6*gdjQi~GlpcMPTU?kzQv3`P&SkDc~^+w60Rug#~_ zJ6EUncJyG%i0|dEXLzCMLvzv7Dw3JcFz38m_5VLr*L+S1LDx?b?$ z2DhpQ4aDLxX7B~q`(;zxl}l(?Q)${DZ?U@}0^MPQLvpWwKJvOh#bn3zGlp~4H1M~V z=kI}d$dG^9=*F8oug%Qd_+CI(Y?6mv}@ke21`!xhJGxi z4Gqm9^{0%0u+0J=2$C)E_4_liE1AFyyhc)Rz-dd^Br#hvKNVwhPQqVV)2sbK+JfD2s}^B1S)8)kJH1~n6E zX#hqWyE$_c7z1Mv?&5-+6i3c}p$h^1gJ{O99^8g-YQyeR#!*HbQ-iD1Oxb}eKVt^FLF)9Cx=6zOy}}X{A#yjLX<8B%x-MjuO+wzE?DCD;iioj6-1l5w0xst z#46qQ3+bCDD>6sDwHWZ$$gc7!_H|$7eqK&~SN=%)^ifVPCm3;c^R9ZjxW5297&j1- z2Y)6hcIO?Iha~&3_}ZBoM0-5dyJ+VLt%27`C~48365l)XHRV*XqTj^QG|TC0Q0)al zT=~jU7)iqdSm{^IE1f&LaU779v>{Y<9kCYE_Qnj zuoALST{zC?jEsuzpMN#rRl|QN=ZHbIv)?NPFMG0L*)OjnLp-IbvPYofg{4<+dzVpn4~fbX^GJjv&_nbFatRCH z=NEi5mQSVFpYg876(c=fn`?#BEYBtzXTdDyh_gO{jN~m~j{UMOvzVvX zhe3f;h7p)a1OWg$;rKf~e27%NqcY#L0SKh{B*DbIPudWO0AO)C<(&gfl;zQEmntF= zOaf2q-08W}u+Ya?zMEVA4sd1Zw)UkaN&L(Pto}^=%q{W@@_sUT=ah5tQ?fW`ZPYvR zbH=I^MKcDdJim8oX;)6-PUy-u6*~(Q9b%7O4E6_BQ9*Rg!27*U&d2y~!KiQG&~SK3 zv`c}(^E47$4PJv|^{LM172$y8;=X3fNHNvW;i`TI&%GDhEtanAXt?Nj#rwhg4$qY{ z5VH8S1CF?Spo=zpUp}VnIJTPXrd`Pqf{~cA_4Z_79(*~W5xY4_L z?@FiQ(5nO+p0<~a1RnDPV+PU$o9$4>-qJ8s(1hbe{YLc43ElOX_Nq|IVnH{DdmM3} zGUgud?8V5qa3S0)`5rm|B<<}&>%~LU1{xk?$k6_!7Ynq<3XC|O4q!nB4pwlc;~Wg>4q6e< zW0$s)eprOE$@w92UnraBJt;JB*XM-M@;>7{mzCaxcbm&yp3`l*<;CV9gDEUz+-pU@ z{s45u2h+-SZHGai0puiZQLM{S6~M$|#p6yo#ERb|NBHJ`;L5XW+(_im6c>D8n<~4= zq&()aQ*Ota=V)o(%>@Eul?}>ys2A5o11Z!#pX&WM>T493$cmvpNdy7cTl>_{ry|GT zbFcEv8M@aFqg@sWcU<@|6f6Q?wY}9U0Uxwb*jL#$gQ5hU04BJwuyC?yR_H!=AO69p z$|v9AZhU9b)0wI^u^FCKLO1uFfyypfMfPD~Z;a znz#rPt*`hnV)*!vNQYGcI6BdMhSQ8F`4lbL>od^C%(F%s8oBB@^qzbf;1gGZ4R|lX z`JKbkJp^J+?0XXV1x%G}e-C|~D8H|l(r?n?xnze(4-xtsH+kzZ$eUEOu)b1NX`~x~ zWgSv*=+jxA@l!J2LQ(g7H+$AqkdRTH93`12Xxihx2{Cn|q#=CC&{qtSwmiDEpOTiB zkt=1YzOd;6pPO@J)40eT_p$MF3g)8P%jekLx$KG}Ix^CCMcjiU&P>!ia7?&-anSAn z^xDFb9z4f0yXKp|XB*%Iq0YTNgkO#cSB6qZ8i%v=kou{KbZx{|ZqcPH%LTE|S*-#( z)_tKh@|dhCiBCAGZaLiPhNfy6>aVyKIem@n(eFATjs z6OBdtmsP)D7KZ|Zc)nZHg3$fWD~`3sa@^a6_pFjUNiQ33Fs2`5d_Zy^i$4_g0G%Hb zS{xz?D09G9KXA2O#*%WczT;dIKI-z#H*Jz0tthfN!DBq}g@#F3*r_;QY9F+UYA(5q zl4k?Izqag(qM2`Uf@6JyW3aJ3!XiQ-@+7GbQf;A;+)Atk6kJ8@2 z_hAl~E(+3+FH+*|PS3v_K|#XXyH!P>44hBLWy*rJxgd$VM(g-t9=)>WsUTm>h3 zl<@YHQ56WilkD}7Hy}ACsLGe6aIW-ICGKo@Rza`X^s1+O%zgf?`$8r1H!)E zndEM`XTB+M3ubK%Zp(P9U&(fsYG&VP>6=}1*W79SBGrObab-<}Z)%dTxuK9iffVz5 zPVY`Y=f@2~zO8D^2AO#v0_sao7(w8RjJZA_MkhP~h9nm`f#eFq5vAQZW+r=p?{kac zB9fa@dI#iDZeq#DiCX3X();0=%dIp3@XKbZL*r3;gDP8rA?=*s;O{r0;BUg4nO@;- zt5DTyny?b5?^G#`P+)D*T&t(64TB@9evs`O@J)V8OYev?dFq2Il3Ly;bl}80^Qv~r z3WD0D3%!S-J$QV!Bl~TJ3$@<_;b%8;u&B((qA>KT4==6hbkt`2U+CpGj!6lHN4f-f z3NLvt_sp8)8$Mm6wy32<`Y9H@mn6~EUk(cyOVQ8^2Eo4}j@`9b(2a=L?z#Y?wci3k z(2c+ogvDLvnj%OV!FfRFe?j=6hLZ-CY%Nuht4K9FXC`_E1Vo3&R?5mI&#s!!0hf7XCM(t=J9_g}028NT(GNZw zApdfTD-$7fcqSOra5LWo;AbdmyDb=-QXBa%fk|MwfLxrxOx&Q&wm|~v{T2A|ayV?M zgN!x)8w$nNx@*KHThw=^cTJ}OKeI?)l{1fMrjt2#x!GI5*WD)N?73BM#SJIn7qMt6 zQiISxe2RpOR zASMsqZS(s-I_Iy%n>|mpjmb(Aw>UaFQz|zA4iNTCXuM(8(fdlI)ZExaeIEddvn6i) z!2vs=$bK83IjzAH+gGi{aHl#vu6U5Ac-|kA(+BA#ffq^XjaVG_!QlPB^~qmLjXQ!! zn}%tLn@*|}9gbZu21c1lc_%=fMP_4zYC!i}oN(e$)%Cu>)j^x_&f%V%GbeugzbZ!HxTjJ6 zZiCcM1H2{p;Y;HERt(p!t@ykjRfB@EY)W1n>T8s0#C?zrMsJfemKlasfHR-~GhneD zUO1w%HJkbnl(zb42H2c`6W~9SmgMw)e(-}|AFEr;i!{dhZ_THFOs-D1;uwd0r(bL^ z{~jcs3mQ*u1@4VAv0r7t~x#k_Tx%lzz zyIhi)i1zKTi5%6q1ra0)6MY>Lh`ttPDQM?Bd!`A2?8hc0)N<3AKn#Bqy?=`<^7rIH zLJ>&tGTAE<6IQM-j8+|k=f>?!v0(-*O)8>S1!d-e52u0o1Q_bf|1zlJeZm=TR(tIW-S~)Q zkal5S$m?Tp{-BI0@{i5@OMp zjVrR~_y(j6TP9LKu+9m>><})RJ6P`u83dG_+Z&5quWiZFza8M;T3V78PJVS|v6$oq zQGon0RW?wYjcZv~5^$`Q$4;G++cv*<`CG82uig9t^Q%Hv(^fp@fdgl2bXB84M>D^1 zPzAJ!7g#jmHpoL)6gzqk&SV_Zb>9TEqInHCf<^xVD|A{o?b)NP++6d=x-Q{=S}07a zBwxZK&!WSR9o1FOLDR}l6w)7r>jco|Yti$O$DS||WystaA{L)a++#fxzvgL}*0mS+0IB-B){g0=CfNK1q zHevVk%u+PuL|19FUb$GJ`sa5m(p`*i^LLVsB<^J_8N!|da-vmC>9I5F+&vPDc zs}AWMwGwbdZ5M{l0q*#cy+TfJ$4OZ~XN8W;TI@RIOD+KV^;M>TK@>$cv^JxGXLmafSBsBGJ`eI1PD=%V>hNDi)r!0LI>dsdY^uU^BRZFlRixNf zv~X;^lsS;E*i5ueTIAS}jjNAS$yO@iP4;Rx)E1(f=!Rd!hjBe@T zaz%78I&Cl|j-P{t-Xd&23~kC5dQE1cUyi?3iS(;mYy#-DxB3s)Pei4)ENh1ystzOss5}K;u^&ZVV`?nOA<|6UStZ(T@HTyf0 zKc?{W`Ai&moPLPbng)Yjts@gO`~{ca@pSDl=YfQueHObg{K@k2KhMra3aM}7PH}p; z5eH}oJ$!uLD#&W5;XsnywKJ7l8V}#lC{o1IhegK$p#GJ2G#H)#dN5{3S>kVk7sz>H z8P$JGB322~GnZ;+sa&iU_;Aq);F|;?n<6bzBV`dS1nEL?d-mYhdh(B%K@qCZ8^&EV^pfpcB3K#}F;-iQJw<&HJEz{=jf z)6&h3-wk);GHmlpUF&{N;|Xd>LCg1GI%gMI!%&3-z#9xso^A|gB>oT3_LrW(+t(X# zr?6h5?&Py6UA|c2=cj9TqbgNahN1RDo~mk*iA=+XqWm57zX$#cJ6-7*LfumO|6#l~ zs>wyR0q;t`Oaee;4VI*^t-=D*t<&^9x9qVoOSx4%xlwY(aYJn4Q|`6X+`pUsYcT)A z`T$`29PAq(k53bh+gwm2^21&Z$FsxH|Dn@=0gV)Apk+uY5kl6z_%-^eCGQVy6~=-8 z`z<>4ZA*_MCM;Z=yv^6&q!+KuxKAg@I7CT!KQc1#>q`YT_rDAf`#>)SEtE~;-G4{a z6w3!TPFRZv8`8A|dh$KZGjrA-_}AF}!>e@vQo`n4UpiwBg?a7v#QQ6=v-Frl;{DF( z=v7RfcI5+*mn~%7(nFe?P0sIK{9i@__SbnquXrpsRl$3Pbv02{{ey_ez7y%PB#gl_ zJNo$I&TjVI|9b-fq2BnV{K2okiKp?4+Lx!LcDCY&S~!{h_q%R=EYP_h4XBFVqBdCz z{Bc+Qc~bu8!TI*8?Nm)Av*l!$zvSsD#~wb%jb$G|Gy9)z1-R$$H-@G3PJP3OS0-P% z1cR8U{}yEcQnw({pB)CTze|RnI=5=|&R2PJfUxJ@)|J%o7C76w_IEGfzQb3aSYR~a zt)Gjvss~edOegHbuKeBeADsCIaY;0tox;ME4UsnET>tY5Y_eda;QGH>`+xB7$4av4 z`^W#9SUm$nz@PtwDu#Hds8;Q7(t$UWHmRu&bI*YPx6fhhaqz#DY^)po4;hdL61(33 zQ7!&vpHsCwzJDO|jGm3FFy#{>oUVH$j0HAx8LJ^Gi874W&qblSTW0n|hqr*9dF|$GaLTok9r2$@ z3|(B{L#0h_%H3T%9w}0FF*`qZqyjIfs0a|%V51trA2qIE-b3~3E#*gG;Xwr40_fwO zF(8iyv9a;$>~1q*hemq_G*o(} zAOrWKbje?<+cIb_box=(7)g{#VWSK4`>Hqu*+YeDu zVJ+6|&2Dv2WMp4l>4laWFM~&G>*A zMT~N!AqW$Mv9Z^5+;qOm=|54s@Nz*ZiY8KTCI`?*N<#hVBe5EW?u}fr?A%|gZgbRZ zmWM0>O}?a#PGTWwukqVC#5yo#w*bASaD;cjqrFJ@2AC z`>9|pbryaosvWvun+I2x(h-D}cC%T&Tr+vLNW(?B`<_+8w(7)qX!~$SC)PTRslD8Qa9c6u`ijgRv5=hp#BE?azdDrn-F=_DUZp@AvTreR*|yQpVe;j* z*pg?9)RbYtTQ3QBW0MjlWzM>^jMPS6lb!rV(lT{Kt{}Aba&|V_X#%vE{Dk>gRW6|E z@71{inD(mbO+HAaLn7zy;IDlG^hf^6sy3#|IKf+z842dkXE|O>dG~EJ;JVkeu;s=c zN`Bc%jlQ$g&hk{`z;%_)dm{^s&F@v_-Bq3+_Mc+5+R@ZzMo4Zyv9Y~=sheMG_sP3g zXZ==SY{x@#tPomE!o&QuwKw0#h`MVW&O8YZym;xLiyxqWIPyOJ(2kDxw|CYUVGnQ~ zghJ@ljQ>a-OFusq|Jx>kk3`Ok3M<=His!bUu&RsG95bc*Ju)j-y^w9-_zXb;n$i-Y zD{jUsi!YK0{XKU8Wro9)+4E-lNhmhtq|?(+`y78H9Rbn+|2PM}nSIVxKEa_B^IEHWw*xT`8Jp4%i##{^wszbrj=_{W#|Bk^ zXa#PTa7-nnvKCKnvwT9hxp8}i^~$oWg$W{Np}5BEeXqUW<`02?&$7$Ls2PA1`=Dj_ zx7m~}HeO&+xqw?N0oWDd9qf2@2P!0cL8I-iih)EQ`T0SuPxGhzIQ9%zkus&A$?-{x zR?uPMvV-swk0p9UJDL&D)qL12Lvmd!9t;bS4)9`QxTY^u7%O z#S%?R_lA;>QM>X7R~A;u-H59hA1?P4Em?m|8OzT8ieDFICAj0t0Hvx7mNo$v6lb%6 zb1jX$*9OR7cKx^bgFgh3>iWFF9i{E^I=}cKKjO1pod?0by`2Xm>%e_T(~j85`3RUw zbRSrv!0vPp&0f}$)bhPvA`Il4%~EIF)8o3nt$U__=i`KS&DXNw zn78e_)ZoX;%R<(W1(_-;=uUzJ3#NZWb%x)Pq?P><4Jjn@b zYUDTV*`1SOKj_a&(DxD9iSH3vdqg)2Fpn8I~!ql5P`kr31!^Y^ERM1bup7s zw3Khn<2VS-Gj3@TJ+Zcfsp{sJ*R3@BV<7Xfi*&`?Y*jZ$NBTUB=v&VIYR<`PpGGp{vWA}kn8AE4G< zS-z(whiji?vROur+Z@Cj*?fBQ z1)ok39$JBH;w?`#lN_F{5%<>^*{sbSxKT*R%Jk8mBsrMjN|C3hEv2S9v?xtP7eEsp z@UiVun>MK3Am0-ZiccC{)H^ zRw%uk!p5Tql*nE5(6S3w;ecpA!g4Z7K%{m7{b};8N40F?Ooerv)!Rj%Z@kk=+^Jqr zzs=9^-SYq(ein##rw1a%e7{x|)GD0nr3I2Jdi64czLD>o6=U!5;KRBQ*_PueVgw^SDX+EGNEf#%<)mAN3;NS3#C2SIU2GT$7 zR>&cgvEyqaLy)Hsmj3JXC8Q(}vbX{0$pi72^a>Epm~eUyly(i6YCxJ>T|(`zmm_C~ zpru@++R{dx_xu87LGFyN80GGIm1lwR^0_w|sb^&UgnQe9c#i=W9;<& zVre#BMK;rV;m>&KaQT<#tqzGoOAEt(E3L~HO1P)4Qz9LGPe1XvHId(zp2Ustnl_KM zJYRXgIOV!?f0$u4odehS0@=PA>_IeO7TL3^jeyK7hzA@pZ5w#`g0Yp5dVE?~F~$!w z;PwHaX2i$U?c)VnMX*kIqs@_=l%ol1`W(`q9AX&fBw3jGklaKtr*6vlaEk8ecO1=h z8xxiR*dy*YdxABW4|6yWY1y*DDiTOTxo*BsJZu_w>e^3SN3b0H$-V>c50CHT+~OFa zTJ}rPK9o74`XUw+;1DFti;tG?-<%MqXq<>aYc=cFct=&p0`Jep0+a5yCb*52PaX{tbcEE9_6dN)n%luceAG>Qvu%3YUKNFLiyy> z?_vi=!$P37L5Db6ZPlfDjMbO4Rv9BZ>*3rTX;HSUq`K^=eEzMd^qk^(r&&!{Xwa42 zV{evlH{Ou_m|Z4O@{6kGv-{qfF*7!?9}h@8L+R++_b$GVdM5MH!*yZ9OUl}HBfqpa z6q_{$rm@GMP0uEevCnm~w?^YD1KX+FS+!Y2Jqu}eEo{KL?(##I9bZ*G}ydqR@o>~*UXfY5J6V$^+-pGA2m_1YZ1D*0C*S`=%5Hy>mo0s*<=@aMpF ze@wUA1ka-m4Piy>c!Md0cz8%SU}l005=8e?G;W%em-^5|-(+3}@SW{Ga2KR@tZg76 zZC)L?5(KK!j^_1;*NO^Wm&!7$4XjSD-=mvY(bGR7yAK0o*kfD~6Q%QcsPSP_Py~G! z<46I8g+DM1c?;7AFLIUNR~HUo3S_DTWWKby+V9_|wFcU*@8jNH5zfkXi>wh51oHpi zB2!MRYA8yYbl~IcM?<3PT&8N9_7iwQepsaW$ah5cv0VRw2c_b*5y4$qt;~w6ZUo_t zLg0x2=pT~vBMf-Y7#iNv^p)E9slT`Yib5}XSS2Q!XSVef%zbGe1RVI}$%Sz~Cy?@z zWxnJ^mjg)oUKdv<6RmfU+1!~dETI^}r|%zLdh zw{sw4Fy0uN^Cghj<_^@=b_T6DKnw*}4f)SAV*ZpfQEw*NCq+a@n6$kW@c;L5CQ!8E zqnkh-Snzbl4Mgr-oxq~(K}P5DgSxg4FeMJ^w?Y6F9thvzPW(D$Wz>4pNbx7bo(eAL zENl6RJ4k$xQ|I{)X!dY^)oN_MsI-H<8(qGU;w6q8ic$vLJg4W=V+5CJ4fc>SN!0Q%QBned^`Ir7OBD_VtL=fB^4mnzo`jKGsAn~f>mNgH*J@E(0?M=Pcek$2IRg-ld$SE zF{uop`*u!pZ0alA%+hV`1IjN?KJ(*q@|a*;-j@U@rfYj8Of-Y4)87*aXYRue2u5<+ zIcoWAJvHUwHtT(mSAx18q{i^{o)iUZDri6&)h>%c%Yt&nOlrS@8@;~Zcp`v z_J=hSY^I0?7D&B=lmNvZqKUBiZMSrKI0R1WRp1_Mtn9SWfV*g+6Ah71CH$)YQemxU zH)u1s=`Mk)V(v{=I?joReab7nLG&1A)YLShhX9$EU12_ifgC&6ludmQ+H>bx7HTYX ze>dBu6EikHca%A=a8qBa3EZxV0Y8Rn} z`LwE>URV}limNMfPj#H%@MZZa@^K`07wnMwnfX%aT5R=ezt!C*L1Ef{rj0;=c7>Hl z>0yyoe!D=;cF6A2E_ht^#+NVxsIKJDCF(&VIYSV(tgou@W<2-KR|brOoh{Dv$EGl| zD(hXod^PrXFv)(D>M@!ylwrUn8Eb+t~ujH$YAJ z$w8a>9hU&|b=yr0I0_*eN_UQNfmBzV&7*BQq4_gd=+$2E@1`#d*j#T$_`Q!^H~aK+ z!SRE);C=KOyA`nlUoSbr9E{9yQxB+Mm%GiplPji$F@goX#GkM}7KC9^J?) zzLJ_107{LT$rKP(zog#r4h1=e0J%m#MP%K(Zy<$livjefRWPa-V$c!R><3E=j-Y4( zRP+BVFdE6(qTS%Ua`UP-TWwCTBUccI?_@U7mSf;>g1Y)-xUNaVSIWv zt6<6-kmdrhBOXf`3}>pmT`iGum>-&~2K%JEQ_fqA-}`wY#$O)vz0reDV5-EpnBYqx z7RxsZA9ue)QNLL^G5f~|uYa%QLnh8rLAa}*Kup;sCiHvhRo4DRrY`=pXAO|SNABkf zDl&2hL}q7@K_bw;oN>XZrWouRMIffsIYtVwX$$wC^jom@Tns!rKxqG2Tm}dU%mXR) zN~cQA_Fyv{vDZb|tn%=FgP*Ms>ss+7`(7ND23cLz z*dGQi5QB79Mr*0Z22&IWzao*`8ocHP{t}t|pnO{U?$c+CWuJ}$>Ly4}j{Mefq}Z41 zHrZ`g0f7-z_Q%zXuJE!<4=Mhb~v9ry4Me>-fGPWn2jp}rIj$hiI%@Uw+*jz0tXiLr=P z)-+g^v>u~OMf|_WY*H}i0j2#v8*@V8UY{-2un4UFz0vY7!mtW_X%nek?M#|$ItmEA z&!!dYEo&9AKxu9}ZjC`dh9FbezYB0|IZ3b;M%>jTFMjO1otnDRIo;+ZKi^Tpb*Kqn zk+h+S)ut4~x4;T6G+80GbeB5SAET?t5X(+H@x43I^jEb2h^%2;)7#ZGGH`Ps18|(B z7a{+MFg*YuP4+{Y+m6EOD5_1(>(%9xy1CvtJYyS%z64Du0fw`6Nw=-<<{vgzuKQM% z8WLMRShWuH22ew|NrDYvXRi9(0p)AvXCkr>0yVbG@FaCXL$U3Lm-4x=1M@C(K#{nF zl{Dgx!*}+)BiV}qD+Y?3W)wM{;=<4E#4Wi1U2t<^i3Jn@GeD*L!}NUI*;5l+0gVH2 z3@vDxHoV-3;i7j#S}cfqzP?zMZfv>Kzs%R}IPnZ{P%n z`8@)@1*twyQ@s+ajp!*ae?56Ng28GZmL&H$?c8`JA3Gv7i{WCR?gyx)=tDqT;h&%> zeyeNd^j|SEw4VtE*d6!|(C@U{U-~;U*713Ww6UO9>dLwL6r(hWm9>k`f6)a|!4;Q9 zUBB|l5SoAJ+OLWj`?9qH@K2{Y-OA5?d$CJVLpfhI_9DC7ngGKV7vaCAk5R2y$CQwv z%hTXao~vxlbe?<^-OY`$nwow{;_)AD0cieTs{P+y>FxW@k_x^S=hh<=KrJddEMq_W zWoY3yM|6$vZ;mLoApbp`n*ONJ@R24F6tK+RQ7pbF>T||>qnx#Kq94G^+u{$NSNH$_ z+#fc=pbwztwXND9bIa!0`?Gx>5+0V-i>`T#<=g=n1xD-;R@+K0G_sSI`R_x)Iug44 zPdX;xW_0uMmj(_3lTkF3TE5cKn7{3xjR5A{zZT|V)qiJIe#+mNEpC^E&ejQl^4#GB zj|SCnrvE*~E)aNim;U$_M;{F%nn=GDps|#9T4QzI?G}L0dbS3PYjx&5X-B8pU#M2vBgj9p~QHZinVvyW|5NQ4Oq*|L>=&00*>$r4${ zHufP|$JqDz-h%Q*mzSie`&7QxL@fSh6KOt{^F!7-y z4h8wntcnGzUj-Omj%WY2IfIGHdiiFk>{0VF;35xZP281nlCHrL=`PDZrrN1b@BCI> zIPkC~4XAo7 z+?wfR?vZBw&zJw^|F~;5^ztbx@4aBS(b2?k+fKEtyfv5#w%u@GKxyH9oRtsgbYx<@ z1C-gNd-v%592MzuMen5^D7(x!{-I;nl1D64b<8`d^jy@D$3_|hSXe_dvoViq5s0fpQC|QYTj&5 z3ZO$lE?|~UFG{ovZ7$Yz@XId= z7ig&Up9lVLN-0qlYVTP8MSBIr!l7>JbLH0Td0G_<>p*%A0<|->5mh!@7V-Mjzn}6H z62ZXm^4ex@-S5r&(?aRkwf}2gCNOW^ml+jZQx3f)nKKP_0I2UDQWPyr`J8iF+F9fO zdjA}F|I67ZW6$pZM(M0jr=O+0Fs7>a^1p2Q-*)uMIB%K~{f==t`_0$>(*w>qx6PDw zm}skUTd?Ix>1Cgb(`#Me{xhWqRC!telJ`^w$Z=iqtN0(%{|`B#qmTqm7t#vNZXWHs z2(zivXXKgMjJaV{$Oi3eWWqns8#_0cq=Gu9u#k;bR2A~YG0 zJa-CTKbO}zK)9pb+3|nu#e)~PlnWd@mKUj|KoaQ50c*#cPC}bqNL=WqA#L5WZ^l*H zrdtpHLgIpzp6Zu>mG~BZ`Zmd<7S!c+_BSMD+#vL5{9mhx!ju*vGD#CqJR-tc! zr5HA9=$iPXaF65P4!svH@8M!#E(l~xm+mSvuX8%r>ES(~|GAcbX!sK4_P{)Vd5=DR zSzht9>>qz_$&NAllgWS|NV*mrxwM}y2c!PpT=wGEvWZ^bJXpZO{4{D_0B_oaGZVop`og{Ge09vWRmY@ zvh4vy|J&a@mECDPk9OvNY312l z=f1M~JL`Ge!+x*w+lYz6*~g9V4yf*HlYJtuu1JIZZlw+9?-AS?OjE9(ul*96Hve&C z(hL(qD7lB6AdLT*8E~6jab{jUJ3Zmm7WyeEqnPi5*3KJeV-~90zOiXXDxgCBZ(JIw zHhQRk_nczjw23+DhCX}zsaKs>0}J*e)zgBvZa^=)y0PlWMv>C4Iqx*HWsh2Do92Hg zbS8)_4tf%<(sY|-7(v%N{-X`fnhpmtWMKC9NMahxGdZ0jbB9BxZ<2CE)RW2%kbK^P znvpG;7a?Wh&Iht>=M;$pp2ca2A0w*cq%M3tngq5?lzE#*Wu|b_=9QT%utrmpg?r2T zWA~$Hwp@75Djq9uW;lBN*&X4W1|3rLBhYp3ILoxb!7FMSr%nz(+!2QmrXbp8_X|Iw{=5w@H%legL7r|}lAV~!ed zhv__@=#*P@&=`?mDKSx^!SraXWvg$$wt61yL$qssCXFWkZlxt6loYaW$n;j9HCkYs zlYg+f9!)Go@KY}O7{^*RM~R;-mKdoh)Qxq3_BFrFKBo~du_0vbdi&93Q5!AIe)H(- zM#U-o2q&v%=Tc8ghyD+0nd8zD&`!@+{V3&OilActv4)*u2<-_>$X?$TzSJDym|;K% zie6GM6<`{L1sc*mJ=J7P)za7qP`MnhTQj23>Aev-Juq}`xv{K&Y>WFnJJEtz@i`)r zc#7?mms52Us@k%y$81)m zbbjodcqvcQFhUvHMfsR)B9BSy(_n9K4@(*3jBUu-gMXx*_4%&AEH24$l!<=)K7?ZL zACJ6JeKb^gD@?1mOEfa&!DTx)&3EJ=hS1V5ksq;YpnG3b|2^|UH1G1KbJQ09EqU6L zcuGv_s?t%ldVDNXqJhqK>t)UBaUw5dP+eVSubsd7C8k%!M*Ta} z@{F6uXD5%Uj4t*L9TY>p_T!6l%LrX=QB~^IY5O0U>-XyF-U?GSrnT-&Yc7p-7pl=< z53RIs-YiT93w=_?-<*H&jqKE- zq>A8@v&hu)!H;Q=y?A1x`&Y$W?H8u|T&?@Yj^JcF>+zTR3*8K@7Z%rW=O`le+8bzg}bW?p7H1L=k!_qE~NUc{A`; z&duIG#P**`HfTtIiR9c(mrtR6t~T~L+VjLj)21m&JN=yEwuX~&9CM6Nwv1y7pd@yB zSS5Jap`WpAGVq693;!iBkzF*x_Qfszufj}pJ*8;;T;zoW%D!8iz3m1!n+HGJ{f6c9 zhD9q5Tly_=bx5}vW5fb9!ZPD!z1>!aQ)R**8qh39*hIe!D|q{tOSc^|&)Qzw*J|@3 z-54t;eWH*rec*-PqEpS6KLiTZEe)YE`_|%5g}pqQ@QZH2_Q}2dxp*x9?x)FcF%DEx zS4G2$YHQ~;o5}Liq}9<)%bK(ygQF!z-Pv^?Y4g~Qocl%brCYqKJ$d_IMpz$BYSkAv zz*`kCzh$Qr|MryIK##~Hu9`BNY1??kw~hJM%Iw-|PA1wsX{(qzA-OY@<@qWl$7$_g zlcOxt6zQWD2aHIdcV=%fU zzoTS*xo?I`QEs-397Z!d;eE^;?KLjdRqB_gqW+k<&iW6xll%6;Ve#^Rr1`M>)Kqg+addW6b0)SK;-xRW6py~xVH2A+#$dOwDV7W1_@q+3d*?q{!7gikNEq54HnR+6s5L z3pQJIGzIFu^9>dYh)I;cFSU7pA>4)7P_x8Na{~C#937#Y7%T&sMfIhhu;5 z*vNff>Nyq5XZtjdA2*dWSo7M=xkemu1&iw)5y!#8x|?c|)nu|sdckNQQW&zST0Ecf zjhDtg?hxYe$!bFiXAZA7&`Ttm7hnQP0{yx!vz?sA!CWN!snpuYR;VN%)4Gq#ST#WB z6Xl%k%xRbRCgJ?$f0#WFdl3-V`x6TgnX1&n6>3$8xx}O-rdK7d8KSSz?zE9IndcCi z)9i0@q~N;;QC3CX%H^r^`(>t#%4QhrF<$}`$wt5L9Q7kMm$+X^Ok%*_=@0(jkvjKi zt)1kHHcQakXP{lFcm*t}sobHwEwM486%zz41I?OJ+7o|VHtkkc)EcuLCmzs``?7$s zt4gdSd_=FEq((fzLD+m&{jNh2Gv}BN`&5#t?1n4W1H7oo$gg7P%>K=ADy)ydlHPfG zE~wGOgg@y!PKxL{IBQwF&<4%4`7D{KjqxO3%n#*?WYZPW832f-As^|j+bpiUa}S(P zV`_$TM)|dhFb0+dmBc_M9Gc;(^d*ssT)V}xBr7u$^H1c&45X9iXc!zlAK@|XZZVxZ zCd0_nxV1)@gf4`Oxw$=nL(b?to@K`qng>e0E&pmEajpiv0ZCTNh*muUA>wdGn{&_$ z3=m=f&L}-;&XbV%l<+Z9A$3ZBi87U4sIy+f!$r!UQdl(BK2vNk@c3B7!HHHP;aX_C zRO{S@&D!f~Cm>$>4jj;K*yS&XG&0&n9IKpDcLkfBr&8^$IiwhU_^JS6>1f>NIX*sB z+==peJ}6e~fXTS~)4e`q5?{a&h=%jbEQbUec?Zp%j5@pUu|&`K3x# zyceX#`)%CBh)IoR>!p_A8&@g!MZS%^J+Uf*l`U^hGjThH#W5~vwW!G_vpA8m%S%PN zm@>lBDQECoqqMoV;YbnqoMdr=m(BNz-&i0jbMgX-7}?8+dPGUXr?fZ7n~*cZ6 zg*p)n)2(V3ymQ8oUM@)_7i!WbQM|OxJkzST$YQBe+6cu#%tSLt7{MxevoN!VSs>=p z_~I_}V@9RfgSIDF$Y{NKBj#}>6ui4!^FiU|c2zdq`6`WXm++jMq_86tBHWy!yR1db1X)1eb}~_8 z6@s|ZiCxtC$VPJeHmRli`-N0OY_I-7$ZA6r%$#)8)l|S8IU0O7JDEHCRm96} zWa-6{PU1WxpiV%cXZZ!^x2FVlcp(Q1k?~75H4HFp5J{sQsjoA|2ZjzUVHb508VR9 zM~OSxQ&Rp%nwtruunuZ`d=PZJ_^eypSpu#FA`Q8=;S(#wh}4!P;C@`}?p&;xS;yjB zQqqx5T9KI99}mE3w~UUc^-Y+a9pM?KwoDT7C-h^VlTg$j_I~0V(6%04U;H<$aY0oj4l|-A8gB>YQRG3SE))HAWheo05rabZtQ&nDkFo^b3s9ap@RZ7dF8auXs zBjEDV#kr+x?Kiq+4k+i<%;egi4pZIBb_(7Ogh2W6?5*&7o||Yk4Ys+x8jHk9qLtCA zb{qRSiI$-eWO7(Z^s`)^)CoFQX6KzRSavfU%EQ-}YxJ8PWq`|t!l8AXJ?sf#nN=-p*|nTzGDY5_MtIa0}t%!)UOzkidKCy&ZlwlO8 z{C;LQdN6SQkHoWjZu6Fx*IQj6u#U-!U;&q~FsUJ*oro!KRn%sp$PncTjHM zmFdd*Oj)6u*!xwqTETv))4e)hm9GqPhhIv~W1+2ymFdV(Q{FH8c72z6t!@#(qU|Dk zyV>s?gorpiDJloAOaC-=2BudM?U3z~QGEW61G)OlYrdLN8}r3FgrzrOEa608z?w@E zp?Xv}%^lmE{KrNFsm! z=IC^*Y8ipkeS?B?9Pbxz8@H8EsLb6H#V`tCFo&S~QOWpqc)~So#wPyavu+eE0(qV= z?+_P;`dwr8vmek(hLG9s(1p2_jzkLiR|p(4J6OR>Zdrz0gUfZ+qPyt};ZjpnY2`gNzrX^*8RfK@AsbI) zk1)+I2B(9NpCk(wK1WNB$6mXYz0o&nq88W@pX8ecdkFm=9!q8=V}khE0)>vwAD>Dv~Sq{x32+e0jW^&&Qq|58oOpGGZCh&yEV^C+qo3 zG4H)+fgEsm{bP((MT&sL?U@H|j-Jps^Q6EDWkuo?>&%B9$a(XnE^&UT&+&RVBLalL zvcn`VUySmX)et46@Xi71gwI^)i4!8t3~g*7t5L1#2Jb^hhHltrZZP#56Oe?xLjxO; zb-3&L@Cf%}hr~tWwe`)V2GaaW?Q(|{mO228ra0C?5-V#|#-?#XKXWO$U#2VX@V#D-MEgZ>|^UrVqzdPr&e)ylLBWWlxp2N}OiuFLyT& z!8mbr?swcrEU$iL`vl5EW&Pni{#>E8Hv{if0iG(VuG7*ezrQ_oR$4b(Z=E>{uOp< z6Znb;Z15*r#jatm%&fGKtu$~@V$h9et|2T> z$WB=?(Bb95(O{#}mL$zhNVfu-jc3u-6787^oB4|eERf-f8}wa-Y1UnN>z#v$GWy_< zFZ3+z8hY`Ewhqpr1LEIPG0{sE-*3U5qE6{sY8d>1W zKiD#f16!x-s*c7V?fYRdI^njd^F3m(Ulnf*-v{%|1HNX%cYy zzHtmEFLjT<2J}L}Gy2|xJ~V^ndB&BMbb`pED39F(zmmIW(x#{P$$Lqv1RS`?vR$8= z!(+6{$h_RVCtPNqXLpA;72QKgph#P3jmtVUWWb}J%a0d?D@IFOXf_GdhR*B2PcJ=3 zKg+8==yV*tRKcsig*6+ui$K9k%vSaG6P@>MR@c%f-76dy3iSe`)tk38tgCpf&f+o@ z+N54vf9MHKc63dWMy`pR?|UFRcZ@mL^~#4S*>62j_bsSD{qGLipq8{%sD#v-ho{yj z{E=OzK@#V4elwjcOqpLTnfJ4GF;$6Hb3gmf1wzj#|;)xxUgZ(eXoW-EC=SqX0q{A46mw zsO|H~FTiA!xm=C(IA^J<=o6@rwomo>E6@nKW<}R+s^swNSc}M0Q_|PPtXbH+y+oI& zLm?b+Oz{+;6F5DRzQoa!H_w1(iodolu+6{%SjO0jwyZlmHXb2hGxIuti=*motJN`~ zIaLz{)O>N!EoJ}E+IO0?gG1Iv^WdP=%bOgy!JFZ%($+2F)Io0A%-LF`_0ybG$TY+HnWb*D6gDaR!GkrSqb|yp==n#LF}|B&n_7|k ziW^(H;L493oD%Ng-r3N2&I?MTS{I_#Mq1L}1dT2Z+%SL4tzAl$F56tsaqHY#0u`^u z7|H%$3&H=)wiIaLe^eE|r~NWM8VvmdUYMIGxH|ZZ0avR%{7XH0Ji5z74E6B`72fp zGn<1qza=1(I5-h=lvE(+)dmGHe+l8 z(mbMr%#(CeM9aBv!yv~PmH$LYDVzj#8y7esaVsz1o8@HeAy9Mbo=cbN-6IG& zhUgxIrL_1U7YNP}c!Y!WkWr*B+YEdvdn&uWP=LCU76g_l1g4t6 zR3r{Zt4(z)Pf2DtRDFj@WW_|Rzqx14;C@PUaE}KBJ>_tDR;bRU+WwC*SU)(c1Dqq} z;_%@YNS`p>>t7Pzk>TJ&URJ2b>0-E~Q*fnv|6vHaakaYG4go7Cd(Zo^En?E@AVf9! zL%CHG&tAoFD}P)FJ)nSs=_m;I)0Hmd_5uww^_v8mUNxjiOkcU5g2LIOfU> z2u-%fg*lpBK|Lcpw?o(pk}J^WP~^3^u7b!2&*Bnf+yjsA_HmR5Zso}cp7T$ZTMCGe zOlKZMNH4uI^WIjve!>vtj(&L5?DNFy{ERNM4-EEvIvoV0N%S)lU=W=x=2nlH4ntz& zUH~wWkRWT!UiEr@gHEezLy??B7Je5)O;sM_-1 zQTFU=dkn1ob~QAndU^<`*6S}^ecF2{h#z}w?dO{*HZ@vI50^r z-hT=ct9p3uc@^m6-}uvbR(KJvxQfTI<9z9w()(~Uei-&h9hGM@(BJ$_&4z|(L}2)U z^+QKAalWHW(z$YjF{;zR;7;jO5&Sd`v_liHYPOjQ(42N_d^G+L}Pc?1vY}r*b26Kf7Y*rUaIQzILe5&Evpt)#%6+NLO?z&#rQNXf= zpbDXy(2lE~MV>>*;auvNk4-Go6T%>53|lyp>u$~mVKIB{!U=`*_n}7!I0w(grG_OP zR*XTt@?lQsqT2>~c14gM_Zi4REM%N%*{2Bgy}}FGCBb3ld8}m~#|BTJK0*LMvl1|0 zTZ(YIn{F(r#8IJX9iODm>(PK2>k|Is8K@vgm56 zc6M&ZU)dv7-cvlb7sm)!#UP4ffDXDe#zwAR6@rMK0K5v~UY2yyeF&ubW5Ple-N(E{ z3v0qLIqM1I7f0dWo48qqHk+1h2-{6&%WKiPxY64@24&sjwoAegR+j~d=d zl@le-;AjNLoI2@4s3cvqDSLGgR{=pCix%hT;Ej1+ui7)0(#-226B>JKWXG3MbTQ?| z9GnV{`Guow5QCa)S+n~Jy}8cO%B4B zI!+vehz8Nk#MbrWxCyvFKVuOY=DF3~RCgA#$0Z}y&AFkgD+sy7UkJx2S5XsFrd_*G z)ExZ8mZJH3x>W$Kcdz`K`Eoo2+U!2ryhp~D)<79KUyu??+~x& z=Pxrkb1Zpz`99+XY#*hH6=A@LOzViV^CiRAq?m?)wEPf%JD<`_C$QzJJgqSig0p7x zhEH%oSD9oWqEP9p7g}C={B}8mP|0<_5`1H3BlhF01$uoKA8?r(-KqDY+kWC%c&W-` zjJwvr$|FR}st`3obC(A4H&2Np^~@nh`6ZQBzO|Ih)-4ufHnd$nwiR=|az}>*)FhJj z^57<<04H49$J8DCo=xcvRU}dg^B2H8RC%@H`9Zqj6b+)bSpDYhJLUxx-CGCQeuT%AW8cErBXe_c|bVpX`!8RD+EIbIiJjvbJ zJ_g+4&11XMp!1~enbdUUlCcbm_(KCMJkpX8P2z>wi4R_Gzqb3>UrKY^1_UHWvcp;f z3($nktBt43YzD)&Jl6(rS(!_;-&9WW{iV)#=Kk9t=(KXC)O4|;UT=}Icpay!IRgc3 zZFn_l0ZB<+hqCH~>>{(*vzy;O{f`&_8nd=t`RJv{rRhyeLfpI(F^}s5iRLUMEs0s~ z&&*UBdLu6WZ!hb5uxY$t2t8llLUDI#@LlK`{OCM=@l%$GQ;FUZ9fS(@nmGV4*#_IU z%!T;ZvD%f-Bb+IlS%V%$uI?L|%t>MRkDq?$cRN%ASm88iD!trBa(?ey8`R!{sPa2sk?exuKs2B z82*e=cV69&{dTA&XD%_N-pFqD?fVFsDsAzd*tFXC;}Aaf%s&VI+tR!UDWp4MUuFzh zj$gRm(EmjUwf@XV$L3zY+uK?6x}miCKiBZLxC}Uf{H(Cwqorw6-z7!F57TVsPq`Yv{ zqwn6q<>|h*A1H_2RLy_c8=URYulrLSZj6a(W3OKPU3oyd$UTir8EfLUq0w$2$4Z;_ z?K)LImkT1wpYd{9Y9UkVRBO)qvNQ1LmmLhmU_R&Gd~Ikx@=mFsn4bYO>ZeC+z8bwjAX3~hwhy@S2V-7XcKLiG2ekZn2 zk5!IDlz9kDkv}i&QZmH(Z}ccl)cz=-zUZfc7kRwDay>G;zrPJ4A#nn*w`1Sg(p((~|7PrXr&`qa zNd5nHT?{zEH>q5Sj0ud1r1d=DuNaxQa@SZh-ozgrSE*`!=lK2gf7zE^x)Rf-uiQQ( zmmL^sopd_sa;J3TMdaj?Go2^&kduWwMrdR7%K!kDGBEw`OF@P~M6^ss?$c4FTE*)A z2V24|;UTL@X7 za0NQX7-VPa_?VghZO!m|wNF~C3WZtJmt6gBDuE^!#$(WvjFPm$&$py4u(gjOA{)Ez zTf6)h$=ek{li3Ik2DzfVUslhc`?69oblq8SsI9xgSTQ^^`EYl4<@EXV7yrtQ>fvBb zAJ)pXBiXz0#EB+mCpXXI0-F-6ltoWk^VcBh6TVDpjExzX@ zZ;RK$II|1?iNu(n`h z@j%*$w$s(d_nGF!sGOa68;$L}*?(tvNsFh3FYoQ3c5BT>Fz=^ms|2BU^;DVa)7OIZamV_M~@8r7QC}61@!C1L((~tiCd4FHBAY$R#|FY9c>-K6{qLKNQGQBjO1nNs z`wW1QmSImg7DahK8Y{0q{!<_Q&mU4sV_?71Gpa8nJcxZmWDpUe1}^xWnH zuuScpvRlKZHU~QnjDWoRp@Z}hw^N>fvsz$WbL)NCpir^%Uv0Ons{GZPRo{LhJRN-0ij%0mJ+pdv~BeNBsZ(hg4-hl~W45*5h&(Z*eI21JwB) z>K~@rAJGgc)W`m2{*CF~G`4(q>YK5lv8`7ea)~`1V{xie2lfd|`dyh|O0vaT?Jhel zKIZBZzOalmzl5pItJlu#jo=J^CJ=i#eL+CUf?6+>M{BR51qC z4J*`Zn~-uF4)X9v zYu#IRe(yZ#h7W4DREGEB6wd8Z7Cfe*##wGGz^^KRrPN!tMHJm$vsVorO+x+Fd}ib4 z0;ApJ6$JLyIm~F-hCkH`I}s(p9=`#(;7IOZlq_(IkA4# ztl-r#P$>02CabNmOL0g-`pAg9g>2ZC%R3i0H8Q1{uRY0+GxXebB$9y zQXL|pSK6}w1w&N6jV?Um%FMv-R-P;6zb_*w$)x3!@GzqJp*di(5$VC85+&^Hx@c(9 zpxb+CwYWH|(?yF{wp8zx1zg*myj4~-t>Llgc)2WZKlmgL^8ggvg`4b?`0TpyZ2NJ< z?o$&QC80f;z52B5hbHE4r;TOO<_5-F3LFxN)T>`<9hXT7&;C*9@(x)T8`;z8$k(N!_aqp@Q1z$U% zw}sjpF7HFD6;m&8WoXfy{D$sFq&zeHRf|xcT3U#!D_UfdPfyzAFY%TEn?|1wK45rn z8bGZ$#kPmZrAF3Of0ct(+zpZ0o#0CCaO6wD*i#%%TAD?LSSg`oO$udYHqh0vEd?K& zyDPv7JrDSY<1$6#QIa#Is3WisSN*@AcuQ&y-P%xzonjyJfA>&dc+}1#ro=LCXZj>g zf3!^6OatLg9qXc0RIi%i?u1EoSzAo0GvO0l&KLUL*JbzAm`4;JzR@=CuDK1=bq(nKZT7iFn^6d$)qf< z<%&_E&8y{0{=|k5jZu4AN`DD%M|{&n#^#f1kG1Vj)xLpu_MrV>`Xq20P2trVtA3(z zU}vB}iSXy4oZ(7KKgXS@Q#^+?SLXk@EeeaK$w#F{`PqJoF`tT(w(z-DW%=DV%vDe5 z3kysMw5yD!xe<>DIo5gqJU#bbNvY2yt(sb)!k!vD6@~+(pIV_W2OA)Ri4Oj4i z;tO%9Y+2B#A^cf%CALk@mD;r!@uHp^A?=SR^m!a56!se#-=bY#Hc2PG^70H(?#J67 z@%$caR?u2qFuO26?a@nzBpHv(jp@GwA&5(jmJeie$HZ^f=N|mnXAmg@OwLz(b!~FM zJ<=He$}>)=_8`c)fRforiD?{PVEojt#!JcbnFWMceHsna8|J-0#d&z>`tcn@as`6Z zz799%hGsq$gOJaQu;zhOX2l^DtR;SmueH{*$YW!07}EsO2f@e4-kO(_2b4%Pitt}j z9Kza@vcG^L!ia!-8XNf_`f4+}UW#*R&>uMhQ4({FRR-zwI)LaaoF8n_#3f9}IS;FkuXJ5reRYP3XO>-O z+&o&W=^zc$IYI#49B8xdw?Khe3zV{J!lw_!zwt}R97wz#K6%>*PaY?wyzbM1`CUq* z1P7(=3rydrPbnuA+#PPb?g58z3->*6G^md!tbiHS!Q6V8G}pVEPg>&!Eb$*n3UUzg z;)4P`E8NDqOFLP8As6KK`w~f4DP~pZ407UB;V6tBw^|cQTH}NZVz#z}#uQ<2*kq*m zWkUk_mj*gMGqKco&dQ^{<}7vD^OR{0fO5}AT9CD}W+n)#Ru?bQ!oAia;60wX4vEKt ztfB|YT=VTcL;OI#-o;Wms7N3I)L**u_~EsbVCjmek@_tudi>Eos_t}Ia5|j!=-Mp% zxxsCF*TFueE~j|Pq{V7!Xd_j0FLZ`mM5iZl+Hn*KP86fsf7zVu zktSitf`)-kj`QAb_^<%5xcY=n+oI3j1dO0Wx6wk^S(+`~TY;V1aWxW+A&I4SulTX< z)wwch4GQmlS-$!Jfef4?od*a9%n*+D0rN*hK&$uSY@Eln#yC$W2Qqm&yy^gch0<0K zEcgl64hs4`+4sJLXN7PsoVr}7BYI_UOWg;uI^febs#=WeOJo8iYpUBx$-_NDMAvEP z{%A}_;8!?}*fV*72zB3`%vORw%xOCVh& z9cFbI;c^OlRXdm)$Tl+!TZD8sobhMCH4zd#n|KQeI>S5=SYe)n%L2;b)pDY>Or(`) zAvO5TBaR==1$mocV>keLNVpiwl!}nd3RFmqcM_Z@#7-bm%^Y8yUgaK81(*Q%Lh{o0 zW@*V-1<>4VkUGimtb=&Pd+~lSb{i z(Ag;_(Cd;S9_kHm-?RmAf4YAEFg^UybYM^k)u4hfQ^J?m9aM2siE{VPp%*5n8mx@z(3Ubl3*=$cXdW?=;2BmYB zjTe%1x7U@sRw>(vk&fVrZ(#^J!iI&x>n1;5HrIf@3S7~vN<0bWmHK>T;kv!pb}4ka zVVQmmKbfi81=b{1SW($$y-x!`V=t;;`L;)_EZu)b9)x6Szd!~;06wbm77E?fe0zWj zYwvv2Jkzo;TZDf(kd^3ywQfyxF9CZpe@3|!V(*J}7p))jQd#7fg`evh zlgR+7Wc*PwE)@zL7fgj^9`^4~G)_-+bsbUTe~p=)TB%61CE$KF(ghi47Ko$k!MWcg zAY|&Lq0^QsZjKID`%bim=2*Dm-3Y z_FNAKEnAd_KqCICPNaiNgPNQ|ThD8%y21ibD*4CBW)PUeLimUGjsjhy z7q{9wM)GX3{-GqjsUG6{#Z#H_p2f%4kXsCx(XVH9l)|t#$%m!KWG&ZOH{239prIyL zzSl*bUDi1i`_gWE+FeAD<~Vf-MZ4$WHfD6zu%l`sr6tDRWrc)dxhT&+IkUb$%4&nlf*Tqten+=0NmF<|eV-bD z-R*>Z+llpwS$TnE8OCY1H_P(?lH_9*$Mvnl{#YM>@;*)|KfP#saLjm$9s2v3w#ThP zlIg!Px9^!b^(^ugC+2dRf@HSUmZSw(gyn6rVru=84u9fFX*x7zH<750`5+K|IXP4d zUVH0%O+E<%RZRovP2eM#o@om(d34Yvr(gVT`0A_cV7@m2oGtb7exxnZMv*oG!M}O^ zj%=vL_K1I?e3XljP8tB5_q$n>@M5VuYG`GpV={_zi9@uvkdoUsm3>mW-2i8*p$?X` zN1&XP8Sj`IEPP=x2w`KN#j~XD_VKB_Ox>V?$>&E0R>K)`i$KfHM7C zuDEA7xGc$}5J2-dSwPj81%kFV!4A@^=$TtfClny>7?Xdn&99MOFlrFE+l!J5Uz-uOG+K-a$XCpZuft8iF(5PB`WjX9ePJ2ISbr zMw_e|P~SQlVQ5^8u=11dH71()fXm&kNL~+MSx+xb^Pq>O?b!)| zaWzal-j6)yH|gnHn*Ar2t97c+MO_?K0W2uj)052`z=96TM1Vn{kCM{s)Ay4wETTWI)W(3%;KEl zae5MUDA6K)U)^ImKxpdBy5iIAHoqk+511uD6dxvyAgMe2MxE>?F ztg;$fN?`U53Qc#M&Q_d}d|%EE2ScdNK5Jz=g;fLB`xch^wMd;Gh^k+t6 z`>T)08t6&2oEwY>2HzhHl56G(OKxL@#us-Ame zsoFZ6XpUZdbS+GncCo4+)JgR z+@2vb$>a$)ZYUl^T6F!ot=r5q=Zw5NS+Qvb2mBwj8vg}FDTD#YCAuSV3Mh!7`q3gIGjE! z9CKLl;%GQ+oz=wv0C6e{q*w3!i15D5T0kT_Ty9VP9Dsv~y=7t{C&FU7*7%|bBT@<@ z6*u~%=F$@ZfN_j6n_}fYqK))9T$voV^?ax3+ujL)8;TKb8{)X<0On{Fs<7O3XVuvN z9+45Y@r|xpNAEMVxt0yTXQTk}9Qx90&{gEDMn_w!PFa5d@r*IJqH$p{H?iCRwI*s< zXd?~?cpAk0qnuMv^7;qcBP&&bMJ1!R0U{vUBfZUORTJ~$hrtTKmFvdeY69A|eB%tC zn=*Fx5`uGp2@BC+KPHNZ|VCT$$)dzO9;8%@uc4RB!C_6olCt0FbR)-EUxCb&h#yj>br~R z01L5;i_76LHULChmNUE5U<{B%(_&U_fSz|p!hevB-OuXOHH=bI-oD7xj#N4qL?p{H z4yk^>U)*I!{Edt@k;2Za^+{p3m~r(4f{ka3hx^6?5^mmu8ogL#>Uqo;xx{yIyGF$B zDTy9fxtZ_9ZCJ)p2|lI4vs)atgk8>F1X!=DDOV0j3XpkKaUNVkNrk=cmki5ZE@Ps= z;e2vvHVq4@MUv~YvE8?~jEvxd3#n~DoV)==go$hE$pN^Bv+RQw)WMc3ba+Og8ei7d zg#S({|G{0{L(C_x#r>%T10LVT{0LuFpP!U-dObT+ra#n?s#8fodB8v`$Rr)8T)X&% z-@Cl!q!6tEyHzc8RLmfZRZhTsNj!$nOy_GiiUZo>_6qnA>*L8`>i&j`95f?6A{%ZghsxR239AcCZb9`5i>IF55! zCw&YQ(fk{E-OkJhhi%M3o0RGeP0~+2N}fBOQgj zw_|Vc*%_PD%bT^G(S9k#wMdDF>o^|o8=c+NgVb*=*SNxV)A#p(PY(rlkDge0Ga8Jt z`kaWs0T~A1A;kzu=TFn1M2yP>UiRUjCY20AQv4cSP6M~~4cv7!ceG(7JVJ++i@La~pQ15Zsc&d6fa%u1O3980!FcO$E@YLs( zbdRJG1QoMiwC7@t&>YYu9oy;bQWtPlu}5h9gD`HYENN~z>@vcf9UAGTFiH`sLCY-b z>}1K`n}r*cbn0DZ0oah&?@*-5bIaquIf)gBo3k<-?)0r(m zRAYz200>kayWTkiuJIHabhP-VF3sl)O2MI1Y8vA!x^b^UfP=hq*7EL|RDId9rK7Bj zxWl%ty$CU^d~GC|OA4JoN{=3=m%n`6@Ux*9x z&O3Wf8R$O%e;fjIa&sY2LjxRq9&zsO8#))A49smb!zrYu(71Qg8Ld?H4^J<}Km9wskOa0dH8p5-!71#tjzv!eF=LN z^dw9q-)QCdd|B!drtp!A9?tB?+@SPYj2712RA(D0*(e;h9{@c;>Wg>GcHtw667frS znT01|FC#Fzb##TK>YW3Oq!rGHO>+}|yxfJq(Q}j!_dM3qz;%(1|FLunANOcxKzks* zBw~+1zDjP(A0f%DQ40HM6OObE!0E#2oBSGiPS9<-I-&II(c{?II}0iNiOK;S+c7-z zHulU7AlRrCydqc2=SN@SZB$xxhhULp`VqxAkhbo4Emys^z3X{w%swX#h?2p;!IqQF znIUs4{)E@fQFMP}d;Lwk>(L1W#fUWEc1cK;#;~QfqGzaNbxgY?4-tKz4k=@Ph2<)T z%3z%?#n@V+kS(YD=6>}|jPnL@$+nM8Xm>lg*%V&YLK|yb1SVSywblKy{MyeRSjH4Bcg!^4!=wX-|k2K zs7+k^4)I7JP7AVYxp^NZB(L8y>AjQ8WkCo69ci!E_kR}Jq%Gh9RA!p?JLd`?-*<+! zK=&gi?z^WMARa#;xBBJbS9?PFh8#lJp`r89>DIVss){v|2u|^H#K~$yL&$h?&KpdK z*j3yDKx_s|-{-#$R@8EhL)3X@<#ofD&UA}YYD?lSL{3xke}Tx2YS~g4Qu}{eZ(y4g zrGS<82C~V!+?6-&!VYC?9lEiI8PQRAh<0zJ3u9NQVw!Cx9iyq}Rl2M*9qQkp!#N$i z6^?|{Szx~SrN1b}57KQLY`q7d4wLx7FDu^{d7{#hOU{W|6X1f|jDr07zGkk`@H8{A z2SqfBHr9#mUg(Ecy3ECQfoqSu`vY_b5fja5JW9FX3|HssKo3RTm&lH52IuExh%}x9 zeBX&80Kcqf0L7CQ0)d7HG;e?1%A)9?GSQNf)Ux55FP0@ zGUn8~As$EU79>&qb3A2NS3ppD0D964faqvkGK?VRoBZ5WW466(nor>f3+(?@ zcJ1L%rfqz;zAkL}+M+LgGImp@#l{e#K~W|l5m8EMWKc*&PQ&gdu0%uSSV#^z&NwW> zPOVc@6g#Lf)fmkKBkZB z<{MeQ#lF4vTP6;mWr7;};=5NzNs%f6b4Vw2N~hmZr)qzjVh!^7Rfch86gMl^GREN< z-WxBeLxM{tR3dGq#{M+^Vzmt{RT!!I60NGpi{l!)lZ=;o&8~b8g^5Bpmv`mKX(#VPQg*M*p)w<1p1G38_nTw> z6|6cQs#R;+G_7zxbVjgIXLojd76W+`4XtGtC(}>AD_O&RvIf?`Tk;@hYLH~ z*~Z2gSDKX#^W&X2`G0t5!g&w4iJL%OA}NDd756Jr6FjU}iQZrb@^{T^x90Sl6#SLS zd3IYR__UZ(2FCo!ZG5UoB3Gn;FxuqMH$C0H5w`34OeT)gb;_T7tLzDIdv$mXD^$SVEP#~16Ls~oH5!B#M>Be;#vB67&om37u@#Hlb=U49sKL#xa$;I zs@7*BGdNt)B<|xebB7L16^}3QG3N0A7fck}hDh-Hd`gHplx^?LaONh)Smn)Ou!z?u z;5?>hv&{WwfLd4&Tgk}uI+&Vjt~1u~e4u?l-I$3q>P$%wxU{VyLq&>iaL3h2_s{zF zCQyiGltO)-QKQ4trL*LW?M?S2RFnj~NUFgRU{@#&J9cYEZp0NUI;+@QO?^x3M zP0CkEq;uh)%E9db8I%{_$v}J*JnN8kd{UtERgChJGcM~J7fd=CO^t}cb+|m;OiJx`An4K-)u#Q3JlK{dr!d!13gW@OP-(uOb~IU~LqYRj^^%VjXc| zAQo^CZmBX5Prk#c!{2#j_@ge;pQv(d$Wi>+q*nh~ArWe0$OxRf8QxDXK(!J(w_jf< z$Gm+(!Z0p8axj&Ff`8PBx%0{hQs3IbRy*0E*1x0JDTg*ABwQ+77rVeqS&u=+P*H6d zg)2tE3>h9hXj6mRsh-^~o5pq$GXId*B8H<>Ld@!;ZnpZT$=*@@X`#|x#Dm5W{E4;9xz=)DCnU+i%?P-p<-EuP4 zEmQ+#1zel@!m7Y0-!jA?8no^nVQlAct_zc7W1>oALpt3cvC4cbzxqLzTBdv?*T$DE z0~Q?_;Azh_UM;dn8H0wnXM5{1ScWAGZXCB${w%-I?@>@6E#3rlk%NuA%F-KJWr`CZ zrWX11$g7B}g(-i!v@laIHdI&~Cw4|8*nR#4l38O@d*Ywyum8duVrEemECs4w7+nl_ zjE*P?T1g(Va(x2|+wO?qa_r{MSMJ&~@8Hu8Wz{{nBATVZT4I2pE z@AMDSX|i@h^mn7whZL}4c24(6K$?tm~^MnARkGBoKo`80BSLZ zbsSuKDb`Nzn~sN><+MC+R}4-l_Aqd!%QiHJvMiLgcI?`u`+Qz+*-C}yeY+P^6{?09 zGUvwWqhV<@`B+jyx$iZ2@p)XokDclW1LF1e6mjO&#s+W!A+062De3xTa{dZ@r>scV z1SH+7ay+V!Gby`0uezn8vsC%)<>)MVzZ16o`gRm`K9;5Wpa7+Oj$~BT^kQ*NNf!JA z1@+HbzjxO+PsurhC?ngn-3c=Dv+TK?F|NsYb(=5L*inS46V86Me=V*D%QL9W*2yu0 z)7K8&$}?Jufu;FacN@u)Oo8d0C7i%M&$>ZFSezmw!3k+su6CksENNokfa6cyLecz2 z!OT#&0@TL{e!=t6;r_|fdz%=wJp@b@+kBxvIKK9u5ozHUPUmIln&5Z6uyP;Lsj|Ks z6Ftj5|JCz>dEsF%f;}qQ2*9?6MoZ6_B?;W@MTDhTS8c+(Z&qM6ujoyR@vqJ0%!+rv zVdz;Aq@sR0S(#|+Mp#`{UbN%&-uA}ldCBv`U$0Hzed#LUoM@acQo8yLbv~nQoa|HRdFtHgd^M9l@Zjt@s@`D04Nn8t+vqIAuxgd+&dti@+f^RXkPzCwy&2W1|OQ(rBmtR14s8xd)C!lQ_|1y9a`B%K zr`lrHf&9}rv=rKcM*_477sJW80zANH7{;6-rKs83MfgZggMH;MC~-bZkSf>bA5?sG zQTO@M_?L1(|DesYBaEP!E@YsHK~dj$3y({AtN^@C6ZD*r?{TE3^iu7q`;6Ek_zmhZ zZYuIa;nJYk9Sy=$CZC10j&y2j8%5D7Wn;_Ja}b6s8A-oOEoW-xZ-O_mrS*r zrQ<3k(c4k|Zg5UE&)^@!F!cPcq6*~N^$vVgyH=pP9(K&>wX)soIwK2U?zg=Msl$yH$j zf7fiZHF3oMaF*tFyx0y8(Z1~QXfq_3P$na#lk$xYXrsP`-9{1ah<`p)%=A2SiMwH|} zm-XW1?5OUma&ifwV@pGXi#sqx)LlNm2gck7RzJW|5!`n9plCthjiV|`>f^ { + case Idle -> { if(isTargetVisible()) { + owner.getEntity().getEvents().trigger(CHARGE_START); + towerState = STATE.Charge_start; + } + } + case Charge_start -> { + if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK); - towerState = STATE.ATTACK; + towerState = STATE.Attack; + } else { + owner.getEntity().getEvents().trigger(CHARGE_END); + towerState = STATE.Charge_end; } } - case ATTACK -> { - if (!isTargetVisible()) { - owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.IDLE; + + case Charge_end -> { + 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(ATTACK); - Entity newProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, - // double check if this is the correct projectile to call - new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), - 3); + Entity newProjectile = ProjectileFactory.createFireworks(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), (float) (owner.getEntity().getPosition().y + 0.25)); ServiceLocator.getEntityService().register(newProjectile); + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState=STATE.Idle; } } - case DEATH -> { + case Death -> { if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { owner.getEntity().setFlagForDelete(true); } diff --git a/source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java new file mode 100644 index 000000000..c0f27df6c --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java @@ -0,0 +1,66 @@ +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 FireworksTowerAnimationController 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("idleStart", this::animateDefault); + entity.getEvents().addListener("chargeStart", this::animateChargeStart); + entity.getEvents().addListener("chargeEnd", this::animateChargeEnd); + entity.getEvents().addListener("attackStart", this::animateAttack); + entity.getEvents().addListener("deathStart", this::animateDeath); + + } + + /** + * the towers starts charging to shoot a projectile + */ + void animateChargeStart() { + animator.startAnimation("Charge"); + } + + /** + * the towers starts charging to shoot a projectile and after that this + * animation is the wind down of that charging up of the tower + */ + void animateChargeEnd() { + animator.startAnimation("Charge_end"); + } + + /** + * THIS IS TO SHOW THE TOWER DEATH IN THIS SITUATION THE TOWER GETS BLASTED + */ + void animateAttack() { + animator.startAnimation("Attack"); + } + + + void animateDeath() { + animator.startAnimation("Death"); + } + + + /** + * Triggers the "default" or "Idle animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { + animator.startAnimation("Idle"); + } + + +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java new file mode 100644 index 000000000..9e6216986 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java @@ -0,0 +1,7 @@ +package com.csse3200.game.entities.configs; + +public class HealTowerConfig { + 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 9a1d62c47..c1b3f997a 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 @@ -14,4 +14,5 @@ public class baseTowerConfigs { public FireworksTowerConfig fireworksTower = new FireworksTowerConfig(); public PierceTowerConfig pierceTower = new PierceTowerConfig(); public RicochetTowerConfig ricochetTower = new RicochetTowerConfig(); + public HealTowerConfig HealTower = new HealTowerConfig(); } \ 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 4a85e83ab..07408af69 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 @@ -46,6 +46,7 @@ public class TowerFactory { private static final String TURRET_ATLAS = "images/towers/turret01.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 FIREWORKS_TOWER_ATLAS = "images/towers/fireworks_tower.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; @@ -83,6 +84,12 @@ public class TowerFactory { private static final float STUN_TOWER_ATTACK_SPEED = 0.12f; private static final String STUN_TOWER_DEATH_ANIM = "death"; private static final float STUN_TOWER_DEATH_SPEED = 0.12f; + private static final String FIREWORKS_TOWER_DEATH_ANIM ="DEATH"; + private static final float FIREWORKS_TOWER_ANIM_SPEED = 0.4f; + private static final String FIREWORKS_TOWER_CHARGE_START_ANIM ="Charge"; + private static final String FIREWORKS_TOWER_CHARGE_END_ANIM ="Charge_end"; + private static final String FIREWORKS_TOWER_IDLE_ANIM ="Idle"; + private static final String FIREWORKS_TOWER_ATTACK_ANIM ="Attack"; private static final int INCOME_INTERVAL = 300; private static final int INCOME_TASK_PRIORITY = 1; private static final String ECO_ATLAS = "images/economy/econ-tower.atlas"; @@ -318,22 +325,26 @@ public static Entity createFireworksTower() { AITaskComponent aiTaskComponent = new AITaskComponent() .addTask(new FireworksTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); - // NEED TO MAKE FIREWORKS_TOWER_ATLAS -// AnimationRenderComponent animator = -// new AnimationRenderComponent( -// ServiceLocator.getResourceService() -// .getAsset(FIREWORKS_TOWER_ATLAS, TextureAtlas.class)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(FIREWORKS_TOWER_ATLAS, TextureAtlas.class)); + animator.addAnimation(FIREWORKS_TOWER_ATTACK_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(FIREWORKS_TOWER_IDLE_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(FIREWORKS_TOWER_DEATH_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(FIREWORKS_TOWER_CHARGE_END_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(FIREWORKS_TOWER_CHARGE_START_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.LOOP); fireworksTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent((new CostComponent(config.cost))) - .addComponent(aiTaskComponent); - // NEED TO ADD ANIMATIONS -// .addComponent(animator) -// .addComponent(new FireworksTowerAnimationController()); + .addComponent(aiTaskComponent) + .addComponent(animator) + .addComponent(new FireworksTowerAnimationController()); fireworksTower.setScale(1.5f, 1.5f); - PhysicsUtils.setScaledCollider(fireworksTower, 0.5f, 0.5f); + PhysicsUtils.setScaledCollider(fireworksTower, 0.2f, 0.2f); return fireworksTower; } @@ -384,6 +395,25 @@ public static Entity createRicochetTower() { PhysicsUtils.setScaledCollider(ricochetTower, 0.5f, 0.5f); return ricochetTower; } + public static Entity createHealTower() { + Entity ricochetTower = createBaseTower(); + HealTowerConfig config = configs.HealTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new RicochetTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + // ADD AnimationRenderComponent + + ricochetTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + // ADD ANIMATION COMPONENTS + + ricochetTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(ricochetTower, 0.5f, 0.5f); + return ricochetTower; + } /** * Creates a generic tower entity to be used as a base entity by more specific tower creation methods. From b519f89448b23229c8754eda7e9401f6f9e98243 Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Mon, 2 Oct 2023 07:06:22 +1000 Subject: [PATCH 07/31] created a new tower code for a health tower(experimenting) --- .../main/com/csse3200/game/entities/configs/HealTowerConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java index 9e6216986..0eb826bcc 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java @@ -4,4 +4,5 @@ public class HealTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + //comment to commit message } From aa4bec5a8003418db81353af4cf5738279f4fb70 Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Mon, 2 Oct 2023 18:08:43 +1000 Subject: [PATCH 08/31] added attackRate and incomeRate variables for the ui team to use in the config files --- .../csse3200/game/entities/configs/FireworksTowerConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java index 6e24e2e6f..c1bde5dd3 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java @@ -4,4 +4,6 @@ public class FireworksTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + public int attackRate =0; + public int incomeRate =0; } From 12412cef57aab3b3e993ef715b34bf9443f7ad07 Mon Sep 17 00:00:00 2001 From: Thivan W Date: Mon, 2 Oct 2023 20:57:04 +1000 Subject: [PATCH 09/31] Added Fireworks Tower Combat Task Test --- .../tasks/FireworksTowerCombatTask.java | 40 ++--- .../tasks/FireworksTowerCombatTaskTest.java | 146 ++++++++++++++++++ 2 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java index 47ad61f20..a859667eb 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -26,11 +26,11 @@ public class FireworksTowerCombatTask extends DefaultTask implements PriorityTas // The type of targets this tower will detect private static final short TARGET = PhysicsLayer.NPC; //Following constants are names of events that will be triggered in the state machine - private static final String IDLE = "idleStart"; - public static final String ATTACK = "attackStart"; - public static final String DEATH = "deathStart"; - public static final String CHARGE_END = "chargeEnd"; - public static final String CHARGE_START = "chargeStart"; + public static final String IDLE = "startIdle"; + public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; + public static final String CHARGE_END = "endCharge"; + public static final String CHARGE_START = "startCharge"; // Class attributes @@ -44,9 +44,9 @@ public class FireworksTowerCombatTask extends DefaultTask implements PriorityTas private final RaycastHit hit = new RaycastHit(); public enum STATE { - Idle,Attack, Death, Charge_start, Charge_end + IDLE, ATTACK, DEATH, CHARGE_START, CHARGE_END } - public STATE towerState = STATE.Idle; + public STATE towerState = STATE.IDLE; /** * @param priority Task priority when targets are detected (0 when nothing is present) @@ -91,40 +91,40 @@ public void update() { */ public void updateTowerState() { - if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.Death) { + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { owner.getEntity().getEvents().trigger(DEATH); - towerState = STATE.Death; + towerState = STATE.DEATH; return; } switch (towerState) { - case Idle -> { + case IDLE -> { if(isTargetVisible()) { owner.getEntity().getEvents().trigger(CHARGE_START); - towerState = STATE.Charge_start; + towerState = STATE.CHARGE_START; } } - case Charge_start -> { + case CHARGE_START -> { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK); - towerState = STATE.Attack; + towerState = STATE.ATTACK; } else { owner.getEntity().getEvents().trigger(CHARGE_END); - towerState = STATE.Charge_end; + towerState = STATE.CHARGE_END; } } - case Charge_end -> { + case CHARGE_END -> { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK); - towerState = STATE.Attack; + towerState = STATE.ATTACK; } else { owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.Idle; + towerState = STATE.IDLE; } } - case Attack -> { + case ATTACK -> { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK); Entity newProjectile = ProjectileFactory.createFireworks(PhysicsLayer.NPC, @@ -134,10 +134,10 @@ public void updateTowerState() { ServiceLocator.getEntityService().register(newProjectile); } else { owner.getEntity().getEvents().trigger(IDLE); - towerState=STATE.Idle; + towerState=STATE.IDLE; } } - case Death -> { + case DEATH -> { if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { owner.getEntity().setFlagForDelete(true); } diff --git a/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java new file mode 100644 index 000000000..ebf4c677f --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java @@ -0,0 +1,146 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +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.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; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class FireworksTowerCombatTaskTest { + FireworksTowerCombatTask fireworksTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + fireworksTowerCombatTask = new FireworksTowerCombatTask(1, 4); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testStartTriggersIdleEvent() { + Entity entity = createFireworksTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(FireworksTowerCombatTask.IDLE, idleListener); + fireworksTowerCombatTask.start(); + verify(idleListener).handle(); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetInRange() { + Entity entity = createFireworksTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 chargeStart = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); +// EventListener0 chargeEnd = mock(EventListener0.class); + entity.getEvents().addListener(FireworksTowerCombatTask.CHARGE_START, chargeStart); + entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); +// entity.getEvents().addListener(FireworksTowerCombatTask.CHARGE_END, chargeEnd); + //Jump to IDLE state + fireworksTowerCombatTask.start(); + fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertTrue(fireworksTowerCombatTask.isTargetVisible()); + + fireworksTowerCombatTask.updateTowerState(); + verify(chargeStart).handle(); + assertEquals(FireworksTowerCombatTask.STATE.CHARGE_START, fireworksTowerCombatTask.getState()); + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertTrue(fireworksTowerCombatTask.isTargetVisible()); + + fireworksTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(FireworksTowerCombatTask.STATE.ATTACK, fireworksTowerCombatTask.getState()); + +// ServiceLocator.getPhysicsService().getPhysics().update(); +// entity.update(); +// assertTrue(fireworksTowerCombatTask.isTargetVisible()); + +// fireworksTowerCombatTask.updateTowerState(); +// verify(chargeEnd).handle(); +// assertEquals(FireworksTowerCombatTask.STATE.CHARGE_END, fireworksTowerCombatTask.getState()); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetNotInRange() { + Entity entity = createFireworksTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 chargeStart = mock(EventListener0.class); + entity.getEvents().addListener(FireworksTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(FireworksTowerCombatTask.CHARGE_START, chargeStart); + + fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(fireworksTowerCombatTask.isTargetVisible()); + + fireworksTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(chargeStart); + assertEquals(FireworksTowerCombatTask.STATE.IDLE, fireworksTowerCombatTask.getState()); + } + + Entity createFireworksTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(fireworksTowerCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new CombatStatsComponent(100,10)); + entity.create(); + return entity; + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + Target.create(); + return Target; + } +} From 0ca893ea3a146b7a1ec70a26afdd9554e433e280 Mon Sep 17 00:00:00 2001 From: Thivan W Date: Mon, 2 Oct 2023 21:07:03 +1000 Subject: [PATCH 10/31] Added Pierce Tower Combat Task Test --- .../tasks/PierceTowerCombatTask.java | 2 +- .../tasks/PierceTowerCombatTaskTest.java | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java index 379f60800..a7fcc3e08 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -25,7 +25,7 @@ public class PierceTowerCombatTask extends DefaultTask implements PriorityTask { private static final int INTERVAL = 1; // The type of targets this tower will detect private static final short TARGET = PhysicsLayer.NPC; - private static final String IDLE = "startIdle"; + public static final String IDLE = "startIdle"; public static final String ATTACK = "startAttack"; public static final String DEATH = "startDeath"; diff --git a/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java new file mode 100644 index 000000000..a1a6ff11c --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java @@ -0,0 +1,127 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +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.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; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class PierceTowerCombatTaskTest { + PierceTowerCombatTask pierceTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + pierceTowerCombatTask = new PierceTowerCombatTask(1, 4); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testStartTriggersIdleEvent() { + Entity entity = createPierceTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(PierceTowerCombatTask.IDLE, idleListener); + pierceTowerCombatTask.start(); + verify(idleListener).handle(); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetInRange() { + Entity entity = createPierceTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(PierceTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + pierceTowerCombatTask.start(); + pierceTowerCombatTask.towerState = PierceTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + + assertTrue(pierceTowerCombatTask.isTargetVisible()); + + pierceTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(PierceTowerCombatTask.STATE.ATTACK, pierceTowerCombatTask.getState()); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetNotInRange() { + Entity entity = createPierceTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(PierceTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(PierceTowerCombatTask.ATTACK, attack); + + pierceTowerCombatTask.towerState = PierceTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(pierceTowerCombatTask.isTargetVisible()); + + pierceTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(attack); + assertEquals(PierceTowerCombatTask.STATE.IDLE, pierceTowerCombatTask.getState()); + } + + Entity createPierceTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(pierceTowerCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new CombatStatsComponent(100,10)); + entity.create(); + return entity; + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + Target.create(); + return Target; + } +} From 3984d3b0b11e751f9e138041e801f236e5132fec Mon Sep 17 00:00:00 2001 From: Thivan W Date: Mon, 2 Oct 2023 21:15:17 +1000 Subject: [PATCH 11/31] Added Ricochet Tower Combat Task Test --- .../tasks/RicochetTowerCombatTaskTest.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java diff --git a/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java new file mode 100644 index 000000000..b1c2dac7d --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java @@ -0,0 +1,127 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +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.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; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class RicochetTowerCombatTaskTest { + RicochetTowerCombatTask ricochetTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + ricochetTowerCombatTask = new RicochetTowerCombatTask(1, 4); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testStartTriggersIdleEvent() { + Entity entity = createRicochetTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(RicochetTowerCombatTask.IDLE, idleListener); + ricochetTowerCombatTask.start(); + verify(idleListener).handle(); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetInRange() { + Entity entity = createRicochetTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(RicochetTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + ricochetTowerCombatTask.start(); + ricochetTowerCombatTask.towerState = RicochetTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + + assertTrue(ricochetTowerCombatTask.isTargetVisible()); + + ricochetTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(RicochetTowerCombatTask.STATE.ATTACK, ricochetTowerCombatTask.getState()); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetNotInRange() { + Entity entity = createRicochetTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(RicochetTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(RicochetTowerCombatTask.ATTACK, attack); + + ricochetTowerCombatTask.towerState = RicochetTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(ricochetTowerCombatTask.isTargetVisible()); + + ricochetTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(attack); + assertEquals(RicochetTowerCombatTask.STATE.IDLE, ricochetTowerCombatTask.getState()); + } + + Entity createRicochetTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(ricochetTowerCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new CombatStatsComponent(100,10)); + entity.create(); + return entity; + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + Target.create(); + return Target; + } +} From 9c4fd94aeaeca1a8c5052e41f46045fe0f88ee64 Mon Sep 17 00:00:00 2001 From: Thivan W Date: Mon, 2 Oct 2023 22:14:25 +1000 Subject: [PATCH 12/31] removed charge states for the fireworks tower combat task --- .../tasks/FireworksTowerCombatTask.java | 22 ---------------- .../tasks/FireworksTowerCombatTaskTest.java | 26 +++---------------- .../tasks/RicochetTowerCombatTaskTest.java | 1 - 3 files changed, 3 insertions(+), 46 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java index a859667eb..1979f0241 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -29,8 +29,6 @@ public class FireworksTowerCombatTask extends DefaultTask implements PriorityTas public static final String IDLE = "startIdle"; public static final String ATTACK = "startAttack"; public static final String DEATH = "startDeath"; - public static final String CHARGE_END = "endCharge"; - public static final String CHARGE_START = "startCharge"; // Class attributes @@ -100,30 +98,10 @@ public void updateTowerState() { switch (towerState) { case IDLE -> { if(isTargetVisible()) { - owner.getEntity().getEvents().trigger(CHARGE_START); - towerState = STATE.CHARGE_START; - } - } - case CHARGE_START -> { - if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK); towerState = STATE.ATTACK; - } else { - owner.getEntity().getEvents().trigger(CHARGE_END); - towerState = STATE.CHARGE_END; } } - - case CHARGE_END -> { - 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(ATTACK); diff --git a/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java index ebf4c677f..29f5496d5 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java @@ -60,12 +60,8 @@ public void testUpdateTowerStateWithTargetInRange() { Entity target = createNPC(); target.setPosition(12, 10); - EventListener0 chargeStart = mock(EventListener0.class); EventListener0 attack = mock(EventListener0.class); -// EventListener0 chargeEnd = mock(EventListener0.class); - entity.getEvents().addListener(FireworksTowerCombatTask.CHARGE_START, chargeStart); entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); -// entity.getEvents().addListener(FireworksTowerCombatTask.CHARGE_END, chargeEnd); //Jump to IDLE state fireworksTowerCombatTask.start(); fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; @@ -74,25 +70,9 @@ public void testUpdateTowerStateWithTargetInRange() { entity.update(); assertTrue(fireworksTowerCombatTask.isTargetVisible()); - fireworksTowerCombatTask.updateTowerState(); - verify(chargeStart).handle(); - assertEquals(FireworksTowerCombatTask.STATE.CHARGE_START, fireworksTowerCombatTask.getState()); - - ServiceLocator.getPhysicsService().getPhysics().update(); - entity.update(); - assertTrue(fireworksTowerCombatTask.isTargetVisible()); - fireworksTowerCombatTask.updateTowerState(); verify(attack).handle(); assertEquals(FireworksTowerCombatTask.STATE.ATTACK, fireworksTowerCombatTask.getState()); - -// ServiceLocator.getPhysicsService().getPhysics().update(); -// entity.update(); -// assertTrue(fireworksTowerCombatTask.isTargetVisible()); - -// fireworksTowerCombatTask.updateTowerState(); -// verify(chargeEnd).handle(); -// assertEquals(FireworksTowerCombatTask.STATE.CHARGE_END, fireworksTowerCombatTask.getState()); } /** @@ -108,9 +88,9 @@ public void testUpdateTowerStateWithTargetNotInRange() { target.setPosition(15, 10); EventListener0 idle = mock(EventListener0.class); - EventListener0 chargeStart = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); entity.getEvents().addListener(FireworksTowerCombatTask.IDLE, idle); - entity.getEvents().addListener(FireworksTowerCombatTask.CHARGE_START, chargeStart); + entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; @@ -121,7 +101,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { fireworksTowerCombatTask.updateTowerState(); verify(idle).handle(); - verifyNoInteractions(chargeStart); + verifyNoInteractions(attack); assertEquals(FireworksTowerCombatTask.STATE.IDLE, fireworksTowerCombatTask.getState()); } diff --git a/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java index b1c2dac7d..7f78761c8 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java @@ -68,7 +68,6 @@ public void testUpdateTowerStateWithTargetInRange() { ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); - assertTrue(ricochetTowerCombatTask.isTargetVisible()); ricochetTowerCombatTask.updateTowerState(); From fd11f324412fcb04605311700f42055a69f67031 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Mon, 2 Oct 2023 22:25:50 +1000 Subject: [PATCH 13/31] Added Bombship Movement Task --- .../tasks/bombship/BombshipMovementTask.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java new file mode 100644 index 000000000..5f85e0929 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java @@ -0,0 +1,84 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * Move the ship entity to a given position, finishing when you get close enough. Requires an entity with a + * PhysicsMovementComponent. + */ +public class BombshipMovementTask extends DefaultTask { + + private final GameTime gameTime; + private Vector2 target; + private float stopDistance = 0.01f; + private long lastTimeMoved; + private Vector2 lastPos; + private PhysicsMovementComponent movementComponent; + + public BombshipMovementTask(Vector2 target) { + this.target = target; + this.gameTime = ServiceLocator.getTimeSource(); + } + + public BombshipMovementTask(Vector2 target, float stopDistance) { + this(target); + this.stopDistance = stopDistance; + } + + @Override + public void start() { + super.start(); + this.movementComponent = owner.getEntity().getComponent(PhysicsMovementComponent.class); + movementComponent.setTarget(target); + movementComponent.setMoving(true); + //making the ship move + owner.getEntity().getEvents().trigger("start"); + + lastTimeMoved = gameTime.getTime(); + lastPos = owner.getEntity().getPosition(); + } + + @Override + public void update() { + if (isAtTarget()) { + movementComponent.setMoving(false); + owner.getEntity().getEvents().trigger("idle"); + status = Status.FINISHED; + } else { + checkIfStuck(); + } + } + + public void setTarget(Vector2 target) { + this.target = target; + movementComponent.setTarget(target); + } + + @Override + public void stop() { + super.stop(); + movementComponent.setMoving(false); + } + + private boolean isAtTarget() { + return owner.getEntity().getPosition().dst(target) <= stopDistance; + } + + private void checkIfStuck() { + if (didMove()) { + lastTimeMoved = gameTime.getTime(); + lastPos = owner.getEntity().getPosition(); + } else if (gameTime.getTimeSince(lastTimeMoved) > 500L) { + movementComponent.setMoving(false); + status = Status.FAILED; + } + } + + private boolean didMove() { + return owner.getEntity().getPosition().dst2(lastPos) > 0.001f; + } +} From 9675d4b597ad73b27a743d17942d210af9773326 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Mon, 2 Oct 2023 22:27:23 +1000 Subject: [PATCH 14/31] Added Wait Task for the bombship --- .../tasks/bombship/BombshipWaitTask.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java new file mode 100644 index 000000000..88002eb27 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java @@ -0,0 +1,39 @@ +package com.csse3200.game.components.tasks.tower; + +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * Task that does nothing other than waiting for a given time. Status is Finished + * after the time has passed. + */ +public class BombshipWaitTask extends DefaultTask { + private final GameTime totalTime; + private final float duration; + private long endTime; + + /** + * @param duration How long to wait for, in seconds. + */ + public BombshipWaitTask(float duration) { + totalTime = ServiceLocator.getTimeSource(); + this.duration = duration; + } + + /** + * Start waiting from now until duration has passed. + */ + @Override + public void start() { + super.start(); + endTime = totalTime.getTime() + (int)(duration * 1200); + } + + @Override + public void update() { + if (totalTime.getTime() >= endTime) { + status = Status.FINISHED; + } + } +} From 705ddcb42f433f7814fb42e481e5854d56bb0d3c Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Mon, 2 Oct 2023 22:30:11 +1000 Subject: [PATCH 15/31] Added the Animation Controller for the Bombship --- .../BombShipAnimationController.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java diff --git a/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java b/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java new file mode 100644 index 000000000..a74a047fc --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java @@ -0,0 +1,35 @@ +//BombshipController +package com.csse3200.game.components.npc; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to the SHip entity's state and plays the animation when one + * of the events is triggered. + */ +public class BombShipAnimationController extends Component { + AnimationRenderComponent animator; + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("start", this::animateStart); + entity.getEvents().addListener("destroy", this::animateDestroy); + entity.getEvents().addListener("idle", this::animateIdle); + } + + + void animateIdle() { + animator.startAnimation("ship_Idle"); + } + + void animateDestroy() { + animator.startAnimation("ship_Destroy"); + } + + void animateStart() { + animator.startAnimation("ship_Start"); + } +} From c0a28abcc55573dbedc184ba9f56a95af7cbaeba Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Tue, 3 Oct 2023 02:31:42 +1000 Subject: [PATCH 16/31] changed some code regarding atlas files and animations --- .../images/towers/fireworks_tower.atlas | 237 ++++-------------- .../assets/images/towers/fireworks_tower.png | Bin 44667 -> 34225 bytes .../tasks/FireworksTowerCombatTask.java | 8 +- .../FireworksTowerAnimationController.java | 16 -- .../game/entities/factories/TowerFactory.java | 11 +- .../tasks/FireworksTowerCombatTaskTest.java | 126 ---------- 6 files changed, 60 insertions(+), 338 deletions(-) delete mode 100644 source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java diff --git a/source/core/assets/images/towers/fireworks_tower.atlas b/source/core/assets/images/towers/fireworks_tower.atlas index c298ab6d7..1c42d0b25 100644 --- a/source/core/assets/images/towers/fireworks_tower.atlas +++ b/source/core/assets/images/towers/fireworks_tower.atlas @@ -1,487 +1,354 @@ fireworks_tower.png -size: 1700, 560 +size: 1700, 420 format: RGBA8888 filter: Linear,Linear repeat: none -Charge +Attack rotate: false xy: 0, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 100, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 200, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 300, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 400, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 500, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 600, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 700, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 800, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 900, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge +Attack rotate: false xy: 1000, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Default - rotate: false - xy: 1100, 0 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Idle +Attack rotate: false xy: 1100, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 1200, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 1300, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 1400, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 1500, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 1600, 0 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 0, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 100, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 200, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 300, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 400, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Idle +Attack rotate: false xy: 500, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Attack rotate: false xy: 600, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Attack rotate: false xy: 700, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Death rotate: false xy: 800, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Death rotate: false xy: 900, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Death rotate: false xy: 1000, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Death rotate: false xy: 1100, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Death rotate: false xy: 1200, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Charge_end +Death rotate: false xy: 1300, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Death rotate: false xy: 1400, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Death rotate: false xy: 1500, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Death rotate: false xy: 1600, 140 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Death rotate: false xy: 0, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Death rotate: false xy: 100, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Death rotate: false xy: 200, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 300, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 400, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 500, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 600, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 700, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 800, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 900, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 1000, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 1100, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 1200, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 1300, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack +Idle rotate: false xy: 1400, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 index: -1 -Attack - rotate: false - xy: 1500, 280 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Attack - rotate: false - xy: 1600, 280 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Attack - rotate: false - xy: 0, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Attack - rotate: false - xy: 100, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Attack - rotate: false - xy: 200, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Attack - rotate: false - xy: 300, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Attack - rotate: false - xy: 400, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 500, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 600, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 700, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 800, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 900, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 1000, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 1100, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 1200, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 1300, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 1400, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death - rotate: false - xy: 1500, 420 - size: 100, 140 - orig: 100, 140 - offset: 0, 0 - index: -1 -Death +Default rotate: false - xy: 1600, 420 + xy: 1400, 280 size: 100, 140 orig: 100, 140 offset: 0, 0 diff --git a/source/core/assets/images/towers/fireworks_tower.png b/source/core/assets/images/towers/fireworks_tower.png index d4d1a6d2964bd5fd604d0a1b22d97f4be3c63281..fe50292786cc5c73e06d1d38aa5ad96529b100e2 100644 GIT binary patch literal 34225 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

BkDS&M0zM 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 Date: Tue, 3 Oct 2023 03:29:12 +1000 Subject: [PATCH 17/31] created walltowerdestructiontask and the wall towers or barricades are in place on the map to obstruct the mobs --- .../core/assets/images/towers/barrier.atlas | 40 ++++++++ source/core/assets/images/towers/barrier.png | Bin 0 -> 763 bytes .../core/assets/images/towers/wall_tower.png | Bin 0 -> 1066 bytes .../csse3200/game/areas/ForestGameArea.java | 8 +- .../tasks/WallTowerDestructionTask.java | 91 ++++++++++++++++++ .../tower/WallTowerAnimationController.java | 35 +++++++ .../game/entities/factories/TowerFactory.java | 30 +++--- .../entities/factories/TowerFactoryTest.java | 5 +- 8 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 source/core/assets/images/towers/barrier.atlas create mode 100644 source/core/assets/images/towers/barrier.png create mode 100644 source/core/assets/images/towers/wall_tower.png create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java create mode 100644 source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java diff --git a/source/core/assets/images/towers/barrier.atlas b/source/core/assets/images/towers/barrier.atlas new file mode 100644 index 000000000..cd22b3771 --- /dev/null +++ b/source/core/assets/images/towers/barrier.atlas @@ -0,0 +1,40 @@ +barrier.png +size: 64, 35 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Death + rotate: false + xy: 0, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: 2 +Death + rotate: false + xy: 32, 0 + size: 32, 17 + orig: 32, 17 + offset: 0, 0 + index: 3 +Death + rotate: false + xy: 0, 0 + size: 32, 12 + orig: 32, 12 + offset: 0, 0 + index: 4 +Death + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/barrier.png b/source/core/assets/images/towers/barrier.png new file mode 100644 index 0000000000000000000000000000000000000000..715fa4accb260d855a40bc486b78c45dad1b6bb4 GIT binary patch literal 763 zcmVE00009a7bBm000XU z000XU0RWnu7ytkQtw}^dRA}DiSifuAKotHY?k@;D09eN!LPX3Z;;^o$8XoWbu$8^pChMQ8+!l(>qBg$)Io3`Q-P$ z@BPr-3y_3{9RaF^7Je`65B&q(?TxVi^L=dbRcjjnaC!EwN)hy%qx1WR;}55a4R`(& z5=GGkO1>uOZf}HN=i_^y2G1WRHmzfq6Gdmq7FA)|23g3{fJ{!VW1EJZK3?psC0@RG z`WNdzR&BxrVhQ|t{2*ayHoc)~7ublKX&Xfl06rh?xro4$rxDqG3#)C~B# z{|RjXz&Iv?L_8vIzVl>j*BS+hOjhz7Z1xpZe+Zzx5l^l5#u{eRQzzmPIKf;+@In6L zzb}a_;1E)xSvs_hyqWr(2x*@;Uyu5S)F>RStQ{FRv`6m2RvF zYTCRaAZwh41^$yV;{KQb!FX$&YtqkTx0Nw44 zG84C@xzvQs9;D_a{3dyt1+Mih5w7YkkSqFmn^;k(d5eCjN;8=tUQRsw;L>w+$vOnC z4#f^fgR28})7wX{+4aHI0bbrdi>*1_2(|5`na4f=(Ff%^keJwxoVR24h&llN^^yKWsAj z#5Bfb+?53`6Pdd0hbReEU1o~f56eGL`XVwFg!!nT$OejnB74~o!52rp>DtZ*>m|v(=kR^sIp>~}g=Av1 zwdGI?MNzHsF)>AUK;C_xX4li+GfB3CIF`j}G=+<@1*stwjXOp5c0fJkRw2zmM~?KJxHhu2%^1fL#UfJ- zFvyx_xo|k_YWV$Lg7DgN29_PKVRzRQL})9PW?~H)z*UrUXck9klIeB{x>=Vs?CLd< z3u7JGWI4v?mQ({u(*L2lUPs$F1$XlOr?8!wGa;LTHk!2*(zvN^H$)yMyw$hijh0^F^>-zC6{nF?2?JLcZP55x*glFi-k(Gfxua*|)+g`k6OWzB(es^6j zUIjOD`!^4C?antXHMRe|IR>7*@7U_NaL3uzN!?Aiw63o2{X`;C@mNB9Fg#iM11;-W Ay#N3J 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 e2f50fd6b..2fee95936 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -115,7 +115,9 @@ public class ForestGameArea extends GameArea { "images/mobboss/demon2.png", "images/mobs/fire_worm.png", "images/mobboss/patrick.png", - "images/towers/fireworks_tower.png" + "images/towers/fireworks_tower.png", + "images/towers/barrier.png", + "images/towers/wall_tower.png" }; private static final String[] forestTextureAtlases = { "images/economy/econ-tower.atlas", @@ -156,7 +158,8 @@ public class ForestGameArea extends GameArea { "images/mobs/water_queen.atlas", "images/mobs/water_slime.atlas", "images/mobboss/patrick.atlas", - "images/towers/fireworks_tower.atlas" + "images/towers/fireworks_tower.atlas", + "images/towers/barrier.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -629,6 +632,7 @@ private void spawnWeaponTower() { Entity stunTower = TowerFactory.createStunTower(); spawnEntityAt(fireTower, randomPos1, true, true); spawnEntityAt(stunTower, randomPos2, true, true); + spawnEntityAt(wallTower, randomPos2, true, true); } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java b/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java new file mode 100644 index 000000000..9636e7b6d --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java @@ -0,0 +1,91 @@ +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.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 FireworksTowerCombatTask runs the AI for the FireworksTower class. The tower scans for mobs and targets in a + * straight line from its centre coordinate and executes the trigger phrases for animations depeending on the current + * state of tower. + */ +public class WallTowerDestructionTask extends DefaultTask implements PriorityTask { + // constants + + // The type of targets this tower will detect + 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 DEATH = "startDeath"; + + + // 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(); + + public enum STATE { + IDLE, ATTACK, DEATH + } + + public 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 WallTowerDestructionTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @Override + public void start() { + super.start(); + // Get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + + endTime = timeSource.getTime() + (5000); + } + + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (1000); + } + } + + public void updateTowerState() { + + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + } + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java new file mode 100644 index 000000000..8e8b86215 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java @@ -0,0 +1,35 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * Listens for events relevant to a weapon tower state. + * Each event will have a trigger phrase and have a certain animation attached. + */ +public class WallTowerAnimationController extends Component{ + //Event name constants + private static final String DEATH = "startDeath"; + + //animation name constants + private static final String DEATH_ANIM = "Death"; + //here we can add the sounds for the implemented animations + + AnimationRenderComponent animator; + + /** + * Create method for FireTowerAnimationController. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(DEATH, this::animateDeath); + } + /** + * Starts the idle animation. + */ + void animateDeath() { + animator.startAnimation(DEATH_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 16e540822..a6a402781 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,22 +1,14 @@ package com.csse3200.game.entities.factories; -import com.csse3200.game.components.tasks.DroidCombatTask; -import com.csse3200.game.components.tasks.TNTTowerCombatTask; +import com.csse3200.game.components.tasks.*; import com.csse3200.game.components.tower.*; import com.csse3200.game.entities.configs.*; -import com.csse3200.game.components.tasks.FireTowerCombatTask; -import com.csse3200.game.components.tasks.StunTowerCombatTask; -import com.csse3200.game.components.tasks.FireworksTowerCombatTask; -import com.csse3200.game.components.tasks.PierceTowerCombatTask; -import com.csse3200.game.components.tasks.RicochetTowerCombatTask; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.CostComponent; -import com.csse3200.game.components.tasks.TowerCombatTask; -import com.csse3200.game.components.tasks.CurrencyTask; import com.csse3200.game.entities.Entity; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.PhysicsUtils; @@ -41,13 +33,14 @@ public class TowerFactory { private static final int TNT_TOWER_MAX_RANGE = 6; 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 WALL_IMAGE = "images/towers/wall_tower.png"; private static final String RESOURCE_TOWER = "images/towers/mine_tower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.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 FIREWORKS_TOWER_ATLAS = "images/towers/fireworks_tower.atlas"; private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; + private static final String WALL_ATLAS = "images/towers/barrier.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"; @@ -79,6 +72,7 @@ public class TowerFactory { private static final String FIRE_TOWER_DEATH_ANIM = "death"; private static final float FIRE_TOWER_DEATH_SPEED = 0.12f; private static final String STUN_TOWER_IDLE_ANIM = "idle"; + private static final String WALL_TOWER_DEATH_ANIM = "Death"; 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; @@ -135,11 +129,25 @@ public static Entity createIncomeTower() { public static Entity createWallTower() { Entity wall = createBaseTower(); WallTowerConfig config = configs.wall; + 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(WALL_TOWER_DEATH_ANIM,0.12f, Animation.PlayMode.NORMAL); wall + .addComponent(aiTaskComponent) .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(new CostComponent(config.cost)) + .addComponent(animator) + .addComponent(new WallTowerAnimationController()) .addComponent(new TextureRenderComponent(WALL_IMAGE)); + + wall.setScale(0.5f,0.5f); return wall; } @@ -154,7 +162,7 @@ public static Entity createTNTTower() { TNTTowerConfigs config = configs.TNTTower; AITaskComponent aiTaskComponent = new AITaskComponent() - .addTask(new TNTTowerCombatTask(COMBAT_TASK_PRIORITY, TNT_TOWER_MAX_RANGE)); + .addTask(new TNTTowerCombatTask(COMBAT_TASK_PRIORITY,TNT_TOWER_MAX_RANGE)); AnimationRenderComponent animator = new AnimationRenderComponent( diff --git a/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java index 4d6188c88..3b72c699a 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java @@ -38,7 +38,7 @@ public class TowerFactoryTest { private String[] texture = { "images/towers/turret_deployed.png", "images/towers/turret01.png", - "images/towers/wallTower.png", + "images/towers/wall_tower.png", "images/towers/fire_tower_atlas.png", "images/towers/stun_tower.png", "images/towers/DroidTower.png", @@ -49,7 +49,8 @@ public class TowerFactoryTest { "images/towers/stun_tower.atlas", "images/towers/fire_tower_atlas.atlas", "images/towers/DroidTower.atlas", - "images/towers/TNTTower.atlas" + "images/towers/TNTTower.atlas", + "images/towers/barrier.atlas" }; private static final String[] sounds = { "sounds/towers/gun_shot_trimmed.mp3", From c42b661dc91501e176d0f08d3c9eea37c6927430 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Tue, 3 Oct 2023 06:10:00 +1000 Subject: [PATCH 18/31] Added the combat task for the bombship --- .../tasks/bombship/BombshipCombatTask.java | 113 ++++++++++++++++++ .../tasks/human/HumanWanderTask.java | 2 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java new file mode 100644 index 000000000..7f02204be --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java @@ -0,0 +1,113 @@ +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.ai.tasks.Task; +import com.csse3200.game.components.tasks.MovementTask; +import com.csse3200.game.components.tasks.WaitTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.physics.PhysicsLayer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wander around by moving a random position within a range of the starting position. Wait a little + * bit between movements. Requires an entity with a PhysicsMovementComponent. + */ +public class BombshipCombatTask extends DefaultTask implements PriorityTask { + private static final Logger logger = LoggerFactory.getLogger(BombshipCombatTask.class); + + private final float waitTime; + private Vector2 currentPos; + private MovementTask movementTask; + private WaitTask waitTask; + private Task currentTask; + /** + * Animation event names + */ + private static final String IDLE = "idle"; + private static final String START = "start"; + private static final String DESTROY = "destroy"; + + private enum STATE { + IDLE, START, DESTROY + } + + private PhysicsEngine physics; + private static final short TARGET = PhysicsLayer.TOWER; + private final RaycastHit hit = new RaycastHit(); + + /** + * @param waitTime How long in seconds to wait between wandering. + */ + public BombshipCombatTask(float waitTime) { + this.waitTime = waitTime; + physics = ServiceLocator.getPhysicsService().getPhysics(); + } + + @Override + public int getPriority() { + return 1; // Low priority task + } + + @Override + public void start() { + super.start(); + currentPos = owner.getEntity().getPosition(); + + waitTask = new WaitTask(waitTime); + waitTask.create(owner); + movementTask = new MovementTask(currentPos.sub(2, 0)); + movementTask.create(owner); + + movementTask.start(); + owner.getEntity().getEvents().trigger("idle"); + currentTask = movementTask; + + this.owner.getEntity().getEvents().trigger("start"); + } + + @Override + public void update() { + if (currentTask.getStatus() != Status.ACTIVE) { + if (currentTask != movementTask) { + if (isEngineerDied()) { + owner.getEntity().getEvents().trigger("start"); + this.owner.getEntity().getEvents().trigger("destroy"); + } + owner.getEntity().getEvents().trigger(START); + startWaiting(); + } else { + startMoving(); + } + } + currentTask.update(); + } + + private void startWaiting() { + logger.debug("Starting waiting"); + owner.getEntity().getEvents().trigger("idle"); + swapTask(waitTask); + } + + private void startMoving() { + logger.debug("Starting moving"); + owner.getEntity().getEvents().trigger("start"); + owner.getEntity().getEvents().trigger("destroy"); + movementTask.setTarget(currentPos.sub(2, 0)); + swapTask(movementTask); + } + + private void swapTask(Task newTask) { + if (currentTask != null) { + currentTask.stop(); + } + currentTask = newTask; + currentTask.start(); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java index 528348b8a..32e185088 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java @@ -29,7 +29,7 @@ public class HumanWanderTask extends DefaultTask implements PriorityTask { private HumanWaitTask waitTask; private EngineerCombatTask combatTask; private Task currentTask; - private boolean isDead = false; + private boolean isDead = false; private boolean hasDied = false; From 5af5a14d4ac0a5713fcbaef6a19c8786d32f8fd9 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Tue, 3 Oct 2023 07:32:29 +1000 Subject: [PATCH 19/31] Added the Bombship Wander Task --- .../tasks/bombship/BombshipWaitTask.java | 2 +- .../tasks/bombship/BombshipWanderTask.java | 168 ++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java index 88002eb27..c126fef03 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks.tower; +package com.csse3200.game.components.tasks; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.services.GameTime; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java new file mode 100644 index 000000000..8817c331b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java @@ -0,0 +1,168 @@ +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.ai.tasks.Task; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.components.ColliderComponent; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +/** + * BombshipWanderTask is the entry point for the engineer entity's behaviour. Instantiates subtasks HumanWaitTask, + * BombshipMovementTask and BombshipCombatTask, and manages transitions between the tasks. Bombship damage and destruction is + * handled in this class. + */ +public class BombshipWanderTask extends DefaultTask implements PriorityTask { + private static final int TOLERANCE = 1; + private static final float STOP_DISTANCE = 0.5f; + private static final int DEFAULT_PRIORITY = 1; + private static final String START = "start"; + private static final String DESTROY = "destroy"; + private static final String IDLE = "idle"; + private AnimationRenderComponent animator; + private final float maxRange; + private final float waitTime; + private BombshipMovementTask movementTask; + private BombshipWaitTask waitTask; + private BombshipCombatTask combatTask; + private Task currentTask; + private boolean isDestroyed = false; + + /** + * Constructor of BombshipWanderTask + * + * @param waitTime How long in seconds to wait between wandering. + * @param maxRange Maximum of the entity to fight + */ + public BombshipWanderTask(float waitTime, float maxRange) { + this.waitTime = waitTime; + this.maxRange = maxRange; + } + + /** + * Fetches the priority of this task. + * @return current priority of this task. Priority for this task is a set value and does not change. + */ + @Override + public int getPriority() { + return DEFAULT_PRIORITY; // Low priority task + } + + /** + * Starts the BombshipWanderTask instance and instantiates subtasks (BombshipWaitTask, BombshipWanderTask, BombshipCombatTask). + */ + @Override + public void start() { + super.start(); + Vector2 startPos = owner.getEntity().getCenterPosition(); + waitTask = new BombshipWaitTask(waitTime); + waitTask.create(owner); + + movementTask = new BombshipMovementTask(startPos, STOP_DISTANCE); + movementTask.create(owner); + movementTask.start(); + + combatTask = new BombshipCombatTask(maxRange); + combatTask.create(owner); + combatTask.start(); + + currentTask = movementTask; + + animator = owner.getEntity().getComponent(AnimationRenderComponent.class); + } + + /** + * Operates the main logic of the entity in this task. All calls to switch to particular states are determined during + * the update phase. + * The logical flow is: + * - Check if the entity has died since last update + * - Check if the entity has finished dying + * - If not dead + */ + @Override + public void update() { + if (!isDestroyed) { + startDestroying(); + } + + boolean justDied = owner.getEntity().getComponent(CombatStatsComponent.class).isDestroyed(); + // Check if engineer has died since last update + if (!isDestroyed) { + startDestroying(); + } else if (isDestroyed && animator.isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + + // otherwise doing engineer things since engineer is alive + else if (!isDestroyed){ + doBombshipThings(); + + currentTask.update(); + } + } + + private void doBombshipThings() { + if (currentTask.getStatus() != Status.ACTIVE) { + + // if the engineer is in move state and update has been called, engineer has arrived at destination + if (currentTask == movementTask) { + startWaiting(); + owner.getEntity().getEvents().trigger(IDLE); + + } else if (combatTask.isEngineerDied()) { + owner.getEntity().getEvents().trigger(START); + } + } + } + /** + * Handle the dying phase of the entity. Triggers an event to play the appropriate media, + * sets HitBox and Collider components to ignore contact (stops the body being pushed around) + * and stops the current task. + */ + private void startDestroying() { + owner.getEntity().getEvents().trigger(DESTROY); + owner.getEntity().getComponent(ColliderComponent.class).setLayer(PhysicsLayer.NONE); + owner.getEntity().getComponent(HitboxComponent.class).setLayer(PhysicsLayer.NONE); + currentTask.stop(); + isDestroyed = true; + } + + /** + * Starts the wait task. + */ + private void startWaiting() { + swapTask(waitTask); + } + + /** + * Starts the movement task, to a particular destination + * @param destination the Vector2 position to which the entity needs to move + */ + private void startMoving(Vector2 destination) { + movementTask.setTarget(destination); + swapTask(movementTask); + } + + /** + * Starts the combat task. + */ + private void startCombat() { + swapTask(combatTask); + } + + /** + * Allows manual switching of tasks, from the current task to the supplied newTask. + * @param newTask the task being switched to. + */ + private void swapTask(Task newTask) { + if (currentTask != null) { + currentTask.stop(); + } + currentTask = newTask; + currentTask.start(); + } +} From 7ec9e429ea035d977f99eb72cb1c20d7343e5b3d Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Tue, 3 Oct 2023 08:22:52 +1000 Subject: [PATCH 20/31] implemented the animations and working for the PierceTower --- .../assets/images/towers/PierceTower.atlas | 299 ++++++++++++++++++ .../core/assets/images/towers/PierceTower.png | Bin 0 -> 8795 bytes .../core/assets/images/towers/barrier.atlas | 36 +++ .../csse3200/game/areas/ForestGameArea.java | 20 +- .../tasks/PierceTowerCombatTask.java | 3 + .../tasks/WallTowerDestructionTask.java | 62 +++- .../tower/PierceTowerAnimationController.java | 66 ++++ .../tower/WallTowerAnimationController.java | 6 + .../game/entities/factories/TowerFactory.java | 33 +- 9 files changed, 508 insertions(+), 17 deletions(-) create mode 100644 source/core/assets/images/towers/PierceTower.atlas create mode 100644 source/core/assets/images/towers/PierceTower.png create mode 100644 source/core/src/main/com/csse3200/game/components/tower/PierceTowerAnimationController.java diff --git a/source/core/assets/images/towers/PierceTower.atlas b/source/core/assets/images/towers/PierceTower.atlas new file mode 100644 index 000000000..85d6d35d1 --- /dev/null +++ b/source/core/assets/images/towers/PierceTower.atlas @@ -0,0 +1,299 @@ +PierceTower.png +size: 896, 192 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Attack + rotate: false + xy: 0, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 64, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 128, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 192, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 256, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 320, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 384, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 448, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 512, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 576, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 640, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 704, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 768, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 832, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 0, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 64, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 128, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 192, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 256, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 320, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 384, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 448, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 512, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 576, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 640, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 704, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 768, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 832, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 0, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 64, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 128, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 192, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 256, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 320, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 384, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 256, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 448, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 512, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 576, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 640, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 704, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 768, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/PierceTower.png b/source/core/assets/images/towers/PierceTower.png new file mode 100644 index 0000000000000000000000000000000000000000..3d0d0e5b44a8b1b53e744469caf1e6e80841449f GIT binary patch literal 8795 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 literal 0 HcmV?d00001 diff --git a/source/core/assets/images/towers/barrier.atlas b/source/core/assets/images/towers/barrier.atlas index cd22b3771..b8e31f3c2 100644 --- a/source/core/assets/images/towers/barrier.atlas +++ b/source/core/assets/images/towers/barrier.atlas @@ -31,6 +31,41 @@ Death orig: 32, 18 offset: 0, 0 index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 Default rotate: false xy: 32, 17 @@ -38,3 +73,4 @@ Default orig: 32, 18 offset: 0, 0 index: -1 + 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 2fee95936..4d202cb10 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -117,7 +117,8 @@ public class ForestGameArea extends GameArea { "images/mobboss/patrick.png", "images/towers/fireworks_tower.png", "images/towers/barrier.png", - "images/towers/wall_tower.png" + "images/towers/wall_tower.png", + "images/towers/PierceTower.png" }; private static final String[] forestTextureAtlases = { "images/economy/econ-tower.atlas", @@ -159,7 +160,8 @@ public class ForestGameArea extends GameArea { "images/mobs/water_slime.atlas", "images/mobboss/patrick.atlas", "images/towers/fireworks_tower.atlas", - "images/towers/barrier.atlas" + "images/towers/barrier.atlas", + "images/towers/PierceTower.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -287,6 +289,7 @@ public void create() { spawnGapScanners(); spawnDroidTower(); spawnFireWorksTower(); + spawnPierceTower(); } @@ -663,7 +666,7 @@ private void spawnTNTTower() { } private void spawnFireWorksTower() { GridPoint2 minPos = new GridPoint2(0, 2); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(1, 1); for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); @@ -671,6 +674,17 @@ private void spawnFireWorksTower() { spawnEntityAt(FireWorksTower, randomPos, true, true); } + } + private void spawnPierceTower() { + GridPoint2 minPos = new GridPoint2(0, 2); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(3, 3); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity PierceTower = TowerFactory.createPierceTower(); + spawnEntityAt(PierceTower, randomPos, true, true); + } + } private void spawnDroidTower() { diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java index a7fcc3e08..8d8723f65 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -28,6 +28,7 @@ public class PierceTowerCombatTask extends DefaultTask implements PriorityTask { public static final String IDLE = "startIdle"; public static final String ATTACK = "startAttack"; public static final String DEATH = "startDeath"; + public static final String ALERT = "startAlert"; // Class attributes private final int priority; @@ -96,6 +97,7 @@ public void updateTowerState() { switch (towerState) { case IDLE -> { if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ALERT); owner.getEntity().getEvents().trigger(ATTACK); towerState = STATE.ATTACK; } @@ -105,6 +107,7 @@ public void updateTowerState() { owner.getEntity().getEvents().trigger(IDLE); towerState = STATE.IDLE; } else { + owner.getEntity().getEvents().trigger(ALERT); owner.getEntity().getEvents().trigger(ATTACK); Entity newProjectile = ProjectileFactory.createPierceFireBall(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java b/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java index 9636e7b6d..988ff0071 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java @@ -4,6 +4,8 @@ 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; @@ -19,10 +21,12 @@ */ public class WallTowerDestructionTask extends DefaultTask implements PriorityTask { // constants - + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; // The type of targets this tower will detect 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 DEATH = "startDeath"; @@ -39,7 +43,6 @@ public class WallTowerDestructionTask extends DefaultTask implements PriorityTas public enum STATE { IDLE, ATTACK, DEATH } - public STATE towerState = STATE.IDLE; /** @@ -62,29 +65,76 @@ public void 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() + (5000); + 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(); - endTime = timeSource.getTime() + (1000); + endTime = timeSource.getTime() + (INTERVAL * 1000); } } + /** + * 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() { if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { owner.getEntity().getEvents().trigger(DEATH); - if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { - owner.getEntity().setFlagForDelete(true); + towerState = STATE.DEATH; + return; + } + + switch (towerState) { + case IDLE -> { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.ATTACK; + } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } } } } + + /** + * Returns the state that the tower is currently in + * @return this.towerState + */ + public STATE getState() { + return this.towerState; + } + + /** + * 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; } + + /** + * 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/PierceTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/PierceTowerAnimationController.java new file mode 100644 index 000000000..9daa39acb --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/PierceTowerAnimationController.java @@ -0,0 +1,66 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * Listens to triggers phrases and executes the required animations. + */ +public class PierceTowerAnimationController extends Component { + //Event name constants + private static final String IDLE = "startIdle"; + private static final String ATTACK = "startAttack"; + private static final String DEATH = "startDeath"; + private static final String ALERT = "startAlert"; + //animation name constants + private static final String IDLE_ANIM = "Idle"; + private static final String ATTACK_ANIM = "Attack"; + private static final String DEATH_ANIM = "Death"; + private static final String ALERT_ANIM = "Warning"; + + //further sounds can be added for the tower attacks/movement + + 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(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(IDLE, this::animateIdle); + entity.getEvents().addListener(ATTACK, this::animateAttack); + entity.getEvents().addListener(DEATH, this::animateDeath); + entity .getEvents().addListener(ALERT,this::animateAlert); + } + + /** + * Starts the idle animation + */ + void animateIdle() { + animator.startAnimation(IDLE_ANIM); + } + + /** + * starts the attack animation + */ + void animateAttack() { + animator.startAnimation(ATTACK_ANIM); + } + + /** + * starts the death animation + */ + void animateDeath() { + animator.startAnimation(DEATH_ANIM); + } + + /** + * starts the alert animation when enemy in range + */ + void animateAlert() { + animator.startAnimation(ALERT_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java index 8e8b86215..4f22bfaf5 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java @@ -10,9 +10,11 @@ public class WallTowerAnimationController extends Component{ //Event name constants private static final String DEATH = "startDeath"; + private static final String IDLE = "startIdle"; //animation name constants private static final String DEATH_ANIM = "Death"; + private static final String Idle_ANIM = "Idle"; //here we can add the sounds for the implemented animations AnimationRenderComponent animator; @@ -25,6 +27,7 @@ public void create() { super.create(); animator = this.entity.getComponent(AnimationRenderComponent.class); entity.getEvents().addListener(DEATH, this::animateDeath); + entity.getEvents().addListener(IDLE, this::animateIdle); } /** * Starts the idle animation. @@ -32,4 +35,7 @@ public void create() { void animateDeath() { animator.startAnimation(DEATH_ANIM); } + void animateIdle(){ + animator.startAnimation(Idle_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 a6a402781..e35ab96f0 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 @@ -39,6 +39,7 @@ public class TowerFactory { 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 FIREWORKS_TOWER_ATLAS = "images/towers/fireworks_tower.atlas"; + private static final String PIERCE_TOWER_ATLAS = "images/towers/PierceTower.atlas"; private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; private static final String WALL_ATLAS = "images/towers/barrier.atlas"; private static final String DROID_ATLAS = "images/towers/DroidTower.atlas"; @@ -73,16 +74,22 @@ public class TowerFactory { private static final float FIRE_TOWER_DEATH_SPEED = 0.12f; private static final String STUN_TOWER_IDLE_ANIM = "idle"; private static final String WALL_TOWER_DEATH_ANIM = "Death"; + private static final String WALL_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 String STUN_TOWER_DEATH_ANIM = "death"; private static final float STUN_TOWER_DEATH_SPEED = 0.12f; - private static final String FIREWORKS_TOWER_DEATH_ANIM ="DEATH"; + private static final String FIREWORKS_TOWER_DEATH_ANIM ="Death"; private static final float FIREWORKS_TOWER_ANIM_ATTACK_SPEED = 0.12f; private static final float FIREWORKS_TOWER_ANIM_SPEED = 0.06f; private static final String FIREWORKS_TOWER_IDLE_ANIM ="Idle"; private static final String FIREWORKS_TOWER_ATTACK_ANIM ="Attack"; + private static final String PIERCE_TOWER_IDLE_ANIM ="Idle"; + private static final String PIERCE_TOWER_ATTACK_ANIM ="Attack"; + private static final String PIERCE_TOWER_DEATH_ANIM ="Death"; + private static final String PIERCE_TOWER_ALERT_ANIM ="Warning"; + private static final float PIERCE_TOWER_ANIM_ATTACK_SPEED = 0.12f; private static final int INCOME_INTERVAL = 300; private static final int INCOME_TASK_PRIORITY = 1; private static final String ECO_ATLAS = "images/economy/econ-tower.atlas"; @@ -130,24 +137,25 @@ public static Entity createWallTower() { Entity wall = createBaseTower(); WallTowerConfig config = configs.wall; AITaskComponent aiTaskComponent = new AITaskComponent() - .addTask(new TNTTowerCombatTask(COMBAT_TASK_PRIORITY,TNT_TOWER_MAX_RANGE)); + .addTask(new WallTowerDestructionTask(COMBAT_TASK_PRIORITY,TNT_TOWER_MAX_RANGE)); AnimationRenderComponent animator = new AnimationRenderComponent( ServiceLocator.getResourceService() - .getAsset(TNT_ATLAS, TextureAtlas.class)); + .getAsset(WALL_ATLAS, TextureAtlas.class)); - animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.12f, Animation.PlayMode.NORMAL); + animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.10f, Animation.PlayMode.NORMAL); + animator.addAnimation(WALL_TOWER_IDLE_ANIM,0.12f, Animation.PlayMode.LOOP); wall .addComponent(aiTaskComponent) .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(new CostComponent(config.cost)) .addComponent(animator) - .addComponent(new WallTowerAnimationController()) - .addComponent(new TextureRenderComponent(WALL_IMAGE)); + .addComponent(new WallTowerAnimationController()); wall.setScale(0.5f,0.5f); + PhysicsUtils.setScaledCollider(wall, 0.5f, 0.5f); return wall; } @@ -364,13 +372,22 @@ public static Entity createPierceTower() { AITaskComponent aiTaskComponent = new AITaskComponent() .addTask(new PierceTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); - // ADD AnimationRenderComponent + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(PIERCE_TOWER_ATLAS, TextureAtlas.class)); + animator.addAnimation(PIERCE_TOWER_ATTACK_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(PIERCE_TOWER_IDLE_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(PIERCE_TOWER_DEATH_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(PIERCE_TOWER_ALERT_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.NORMAL); + pierceTower + .addComponent(animator) + .addComponent(new PierceTowerAnimationController()) .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent((new CostComponent(config.cost))) .addComponent(aiTaskComponent); - // ADD ANIMATION COMPONENTS pierceTower.setScale(1.5f, 1.5f); PhysicsUtils.setScaledCollider(pierceTower, 0.5f, 0.5f); From dd5ccbbbec6ea827e52d13434868be4bfffb541d Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Tue, 3 Oct 2023 08:45:40 +1000 Subject: [PATCH 21/31] Added the ricochetTower and fine tuned the animations for the wall tower as it is doing the death animation sometimes while its not doing it sometimes --- .../assets/images/towers/RicochetTower.atlas | 187 ++++++++++++++++++ .../assets/images/towers/RicochetTower.png | Bin 0 -> 4227 bytes .../csse3200/game/areas/ForestGameArea.java | 18 +- .../RicochetTowerAnimationController.java | 50 +++++ .../game/entities/factories/TowerFactory.java | 16 +- 5 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 source/core/assets/images/towers/RicochetTower.atlas create mode 100644 source/core/assets/images/towers/RicochetTower.png create mode 100644 source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java diff --git a/source/core/assets/images/towers/RicochetTower.atlas b/source/core/assets/images/towers/RicochetTower.atlas new file mode 100644 index 000000000..1bafec97b --- /dev/null +++ b/source/core/assets/images/towers/RicochetTower.atlas @@ -0,0 +1,187 @@ +RicochetTower.png +size: 832, 128 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Attack + rotate: false + xy: 0, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 64, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 128, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 192, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 256, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 320, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 384, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 448, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 512, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 576, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 640, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 704, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 768, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 0, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 64, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 128, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 192, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 256, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 320, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 384, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 448, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 512, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 576, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 640, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 704, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 448, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/RicochetTower.png b/source/core/assets/images/towers/RicochetTower.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7b6631847f6fb02a0beb202e96fa4caabc370c GIT binary patch literal 4227 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 O;da#Hry7^QYySl^J|jN> 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 4d202cb10..95c51316e 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -118,7 +118,8 @@ public class ForestGameArea extends GameArea { "images/towers/fireworks_tower.png", "images/towers/barrier.png", "images/towers/wall_tower.png", - "images/towers/PierceTower.png" + "images/towers/PierceTower.png", + "images/towers/RicochetTower.png" }; private static final String[] forestTextureAtlases = { "images/economy/econ-tower.atlas", @@ -161,7 +162,8 @@ public class ForestGameArea extends GameArea { "images/mobboss/patrick.atlas", "images/towers/fireworks_tower.atlas", "images/towers/barrier.atlas", - "images/towers/PierceTower.atlas" + "images/towers/PierceTower.atlas", + "images/towers/RicochetTower.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -290,6 +292,7 @@ public void create() { spawnDroidTower(); spawnFireWorksTower(); spawnPierceTower(); + spawnRicochetTower(); } @@ -685,6 +688,17 @@ private void spawnPierceTower() { spawnEntityAt(PierceTower, randomPos, true, true); } + } + private void spawnRicochetTower() { + GridPoint2 minPos = new GridPoint2(0, 2); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 3); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity RicochetTower = TowerFactory.createRicochetTower(); + spawnEntityAt(RicochetTower, randomPos, true, true); + } + } private void spawnDroidTower() { diff --git a/source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java new file mode 100644 index 000000000..1a197868b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java @@ -0,0 +1,50 @@ +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 RicochetTowerAnimationController 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("startIdle", this::animateDefault); + entity.getEvents().addListener("startAttack", this::animateAttack); + entity.getEvents().addListener("startDeath", this::animateDeath); + + } + + + /** + * THIS IS TO SHOW THE TOWER DEATH IN THIS SITUATION THE TOWER GETS BLASTED + */ + void animateAttack() { + animator.startAnimation("Attack"); + } + + + void animateDeath() { + animator.startAnimation("Death"); + } + + + /** + * Triggers the "default" or "Idle animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { + animator.startAnimation("Idle"); + } + + +} \ 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 e35ab96f0..35f691af7 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 @@ -40,6 +40,7 @@ public class TowerFactory { private static final String STUN_TOWER_ATLAS = "images/towers/stun_tower.atlas"; private static final String FIREWORKS_TOWER_ATLAS = "images/towers/fireworks_tower.atlas"; private static final String PIERCE_TOWER_ATLAS = "images/towers/PierceTower.atlas"; + private static final String RICOCHET_TOWER_ATLAS = "images/towers/RicochetTower.atlas"; private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; private static final String WALL_ATLAS = "images/towers/barrier.atlas"; private static final String DROID_ATLAS = "images/towers/DroidTower.atlas"; @@ -88,6 +89,10 @@ public class TowerFactory { private static final String PIERCE_TOWER_IDLE_ANIM ="Idle"; private static final String PIERCE_TOWER_ATTACK_ANIM ="Attack"; private static final String PIERCE_TOWER_DEATH_ANIM ="Death"; + private static final String RICOCHET_TOWER_IDLE_ANIM ="Idle"; + private static final String RICOCHET_TOWER_ATTACK_ANIM ="Attack"; + private static final String RICOCHET_TOWER_DEATH_ANIM ="Death"; + private static final float RICOCHET_TOWER_ANIM_ATTACK_SPEED = 0.12f; private static final String PIERCE_TOWER_ALERT_ANIM ="Warning"; private static final float PIERCE_TOWER_ANIM_ATTACK_SPEED = 0.12f; private static final int INCOME_INTERVAL = 300; @@ -144,7 +149,7 @@ public static Entity createWallTower() { ServiceLocator.getResourceService() .getAsset(WALL_ATLAS, TextureAtlas.class)); - animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.10f, Animation.PlayMode.NORMAL); + animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.5f, Animation.PlayMode.NORMAL); animator.addAnimation(WALL_TOWER_IDLE_ANIM,0.12f, Animation.PlayMode.LOOP); wall @@ -405,9 +410,14 @@ public static Entity createRicochetTower() { AITaskComponent aiTaskComponent = new AITaskComponent() .addTask(new RicochetTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); - // ADD AnimationRenderComponent - + AnimationRenderComponent animator = new AnimationRenderComponent( + ServiceLocator.getResourceService().getAsset(RICOCHET_TOWER_ATLAS,TextureAtlas.class)); + animator.addAnimation(RICOCHET_TOWER_ATTACK_ANIM,RICOCHET_TOWER_ANIM_ATTACK_SPEED,Animation.PlayMode.LOOP); + animator.addAnimation(RICOCHET_TOWER_DEATH_ANIM,RICOCHET_TOWER_ANIM_ATTACK_SPEED,Animation.PlayMode.NORMAL); + animator.addAnimation(RICOCHET_TOWER_IDLE_ANIM,RICOCHET_TOWER_ANIM_ATTACK_SPEED,Animation.PlayMode.LOOP); ricochetTower + .addComponent(animator) + .addComponent(new RicochetTowerAnimationController()) .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent((new CostComponent(config.cost))) .addComponent(aiTaskComponent); From b3c67a1d8121d2e49f7cf088c0264451eed42d82 Mon Sep 17 00:00:00 2001 From: karthikeya-v Date: Tue, 3 Oct 2023 09:00:57 +1000 Subject: [PATCH 22/31] changed the barrier or wallTower atlas file contents --- source/core/assets/images/towers/barrier.atlas | 2 +- .../main/com/csse3200/game/entities/factories/TowerFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/assets/images/towers/barrier.atlas b/source/core/assets/images/towers/barrier.atlas index b8e31f3c2..c3327a790 100644 --- a/source/core/assets/images/towers/barrier.atlas +++ b/source/core/assets/images/towers/barrier.atlas @@ -24,7 +24,7 @@ Death orig: 32, 12 offset: 0, 0 index: 4 -Death +Idle rotate: false xy: 32, 17 size: 32, 18 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 35f691af7..e89498877 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 @@ -149,7 +149,7 @@ public static Entity createWallTower() { ServiceLocator.getResourceService() .getAsset(WALL_ATLAS, TextureAtlas.class)); - animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.5f, Animation.PlayMode.NORMAL); + animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.5f, Animation.PlayMode.REVERSED); animator.addAnimation(WALL_TOWER_IDLE_ANIM,0.12f, Animation.PlayMode.LOOP); wall From 2a40dd9e38ac8ee290757af0f65c466935728e56 Mon Sep 17 00:00:00 2001 From: Thivan W Date: Tue, 3 Oct 2023 09:14:41 +1000 Subject: [PATCH 23/31] fixed wall tower animation being played on reverse --- .../main/com/csse3200/game/entities/factories/TowerFactory.java | 2 +- 1 file changed, 1 insertion(+), 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 e89498877..35f691af7 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 @@ -149,7 +149,7 @@ public static Entity createWallTower() { ServiceLocator.getResourceService() .getAsset(WALL_ATLAS, TextureAtlas.class)); - animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.5f, Animation.PlayMode.REVERSED); + animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.5f, Animation.PlayMode.NORMAL); animator.addAnimation(WALL_TOWER_IDLE_ANIM,0.12f, Animation.PlayMode.LOOP); wall From 2e52c800e7db2a2cb5f8a1ee7cdf85dca0f70c6b Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Tue, 3 Oct 2023 09:37:11 +1000 Subject: [PATCH 24/31] Added targets to combat task , the targets are boss and mob --- .../BombShipAnimationController.java | 1 - .../tasks/bombship/BombshipCombatTask.java | 197 ++++++++++++------ .../tasks/bombship/BombshipWanderTask.java | 3 +- 3 files changed, 131 insertions(+), 70 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java b/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java index a74a047fc..5fdabc7a7 100644 --- a/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java @@ -1,4 +1,3 @@ -//BombshipController package com.csse3200.game.components.npc; import com.csse3200.game.components.Component; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java index 7f02204be..349e5434b 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java @@ -3,111 +3,174 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; -import com.csse3200.game.ai.tasks.Task; -import com.csse3200.game.components.tasks.MovementTask; -import com.csse3200.game.components.tasks.WaitTask; 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; -import com.csse3200.game.physics.PhysicsLayer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import java.util.ArrayList; /** - * Wander around by moving a random position within a range of the starting position. Wait a little - * bit between movements. Requires an entity with a PhysicsMovementComponent. + * The AI Task for the Engineer entity. The Engineer will scan for targets within its detection range + * and trigger events to change its state accordingly. This task must be called once the Engineer has + * appropiately moved into position. */ public class BombshipCombatTask extends DefaultTask implements PriorityTask { - private static final Logger logger = LoggerFactory.getLogger(BombshipCombatTask.class); - private final float waitTime; - private Vector2 currentPos; - private MovementTask movementTask; - private WaitTask waitTask; - private Task currentTask; - /** - * Animation event names - */ - private static final String IDLE = "idle"; + private static final int INTERVAL = 1; // The time interval for each target scan from the Engineer. + private static final int PRIORITY = 3; // Default priority of the combat task when mobs are in range. + private static final short TARGET1 = PhysicsLayer.BOSS; // The type of targets that the Engineer will detect. + private static final short TARGET2 = PhysicsLayer.XENO; + + // Animation event names for the Engineer's state machine. private static final String START = "start"; + private static final String IDLE = "idle"; private static final String DESTROY = "destroy"; - private enum STATE { - IDLE, START, DESTROY - } + // The Engineer's attributes. + private final float maxRange; // The maximum range of the bombship. + private Vector2 bombShipPosition = new Vector2(0, 0); // Placeholder value for the Engineer's position. + private final Vector2 maxRangePosition = new Vector2(); private PhysicsEngine physics; - private static final short TARGET = PhysicsLayer.TOWER; + private GameTime timeSource; + private long endTime; + private long reloadTime; + + private ArrayList hits = new ArrayList<>(); private final RaycastHit hit = new RaycastHit(); + private ArrayList targets = new ArrayList<>(); - /** - * @param waitTime How long in seconds to wait between wandering. - */ - public BombshipCombatTask(float waitTime) { - this.waitTime = waitTime; - physics = ServiceLocator.getPhysicsService().getPhysics(); + /** The Engineer's states. */ + private enum STATE { + IDLE, START , DESTROY } + private STATE bombshipState = STATE.IDLE; - @Override - public int getPriority() { - return 1; // Low priority task + public BombshipCombatTask(float maxRange) { + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); } + /** + * Runs the task and triggers Bombship's idle animation. + */ @Override public void start() { super.start(); - currentPos = owner.getEntity().getPosition(); - - waitTask = new WaitTask(waitTime); - waitTask.create(owner); - movementTask = new MovementTask(currentPos.sub(2, 0)); - movementTask.create(owner); - - movementTask.start(); - owner.getEntity().getEvents().trigger("idle"); - currentTask = movementTask; - - this.owner.getEntity().getEvents().trigger("start"); + this.bombShipPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(bombShipPosition.x + maxRange, bombShipPosition.y); + // Default to idle mode + owner.getEntity().getEvents().trigger(IDLE); + endTime = timeSource.getTime() + (INTERVAL * 600); } + /** + * 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 (currentTask.getStatus() != Status.ACTIVE) { - if (currentTask != movementTask) { + if (timeSource.getTime() >= endTime) { + updateBombshipState(); + endTime = timeSource.getTime() + (INTERVAL * 1200); + } + } + + /** + * Bombship state machine + */ + public void updateBombshipState() { + // configure engineer state depending on target visibility + switch (bombshipState) { + case IDLE -> { + // targets detected in idle mode - start deployment if (isEngineerDied()) { - owner.getEntity().getEvents().trigger("start"); - this.owner.getEntity().getEvents().trigger("destroy"); + combatState(); } - owner.getEntity().getEvents().trigger(START); - startWaiting(); - } else { - startMoving(); + } + case START -> { + // targets gone - stop firing + if (!isEngineerDied()) { + owner.getEntity().getEvents().trigger(IDLE); + bombshipState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(START); + } + } + case DESTROY -> { + owner.getEntity().getEvents().trigger(DESTROY); } } - currentTask.update(); } - private void startWaiting() { - logger.debug("Starting waiting"); - owner.getEntity().getEvents().trigger("idle"); - swapTask(waitTask); + /** + * Puts the BombshipCombatTask state into combat mode + */ + private void combatState() { + owner.getEntity().getEvents().trigger(START); + bombshipState = STATE.START; + } + /** + * For stopping the running task + */ + @Override + public void stop() { + super.stop(); } - private void startMoving() { - logger.debug("Starting moving"); - owner.getEntity().getEvents().trigger("start"); - owner.getEntity().getEvents().trigger("destroy"); - movementTask.setTarget(currentPos.sub(2, 0)); - swapTask(movementTask); + /** + * Simplified getPriority function, returns the priority of the task + * @return priority as an integer value. If mobs are visible, return the current priority, otherwise return 0. + */ + @Override + public int getPriority() { + return isEngineerDied() ? PRIORITY : 0; } - private void swapTask(Task newTask) { - if (currentTask != null) { - currentTask.stop(); + /** + * Uses a raycast to determine whether there are any targets in detection range. Performs multiple raycasts + * to a range of points at x = engineer.x + maxRange, and a range of y values above and below current y position. + * Allows the bombship entity to detect mobs in adjacent lanes. + * @return true if a target is detected, false otherwise + */ + public boolean isEngineerDied() { + // If there is an obstacle in the path to the max range point, mobs visible. + Vector2 position = owner.getEntity().getCenterPosition(); + hits.clear(); + for (int i = 8; i > -8; i--) { + if (physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET1, hit) + || physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET2, hit)) { + hits.add(hit); + targets.add(new Vector2(position.x + maxRange, position.y + i)); + } + } + return !hits.isEmpty(); + } + + /** + * Fetches the nearest target from the array of detected target positions created during the last call of + * isTargetVisible + * @return a Vector2 position of the nearest mob detected. + */ + public Vector2 fetchTarget() { + // Initial nearest position for comparison + int lowest = 10; + + Vector2 nearest = new Vector2(owner.getEntity().getCenterPosition().x, + owner.getEntity().getCenterPosition().y); + + // Find the nearest target from the array of targets + for (Vector2 tgt : targets){ + if (Math.abs(tgt.y - nearest.y) < lowest) { + lowest = (int)Math.abs(tgt.y - nearest.y); + nearest = tgt; + } } - currentTask = newTask; - currentTask.start(); + return nearest; } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java index 8817c331b..8e6ba4a45 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java @@ -89,8 +89,7 @@ public void update() { startDestroying(); } - boolean justDied = owner.getEntity().getComponent(CombatStatsComponent.class).isDestroyed(); - // Check if engineer has died since last update + // Check if bombship has destroyed since last update if (!isDestroyed) { startDestroying(); } else if (isDestroyed && animator.isFinished()) { From a93747b197f2dd181d5489efccabc3ac9315a355 Mon Sep 17 00:00:00 2001 From: Thivan W Date: Tue, 3 Oct 2023 09:37:59 +1000 Subject: [PATCH 25/31] changed the projectile used by the fireworks tower --- .../game/components/tasks/FireworksTowerCombatTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java index 912afb430..ef7c74f89 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -105,8 +105,8 @@ public void updateTowerState() { case ATTACK -> { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK); - Entity newProjectile = ProjectileFactory.createFireworks(PhysicsLayer.NPC, - new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + Entity newProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), 3); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), (float) (owner.getEntity().getPosition().y + 0.25)); ServiceLocator.getEntityService().register(newProjectile); From 915648fa39bffada2117513506cbd6321fbe2833 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Tue, 3 Oct 2023 10:37:43 +1000 Subject: [PATCH 26/31] Added the Config class --- .../BombShipAnimationController.java | 0 .../entities/configs/BombshipConfigs.java | 10 ++ .../entities/factories/BombshipFactory.java | 94 +++++++++++++++++++ .../csse3200/game/physics/PhysicsLayer.java | 1 + 4 files changed, 105 insertions(+) rename source/core/src/main/com/csse3200/game/components/{ => player}/BombShipAnimationController.java (100%) create mode 100644 source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java create mode 100644 source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java diff --git a/source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java b/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java similarity index 100% rename from source/core/src/main/com/csse3200/game/components/BombShipAnimationController.java rename to source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java diff --git a/source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java new file mode 100644 index 000000000..4869dbb9b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java @@ -0,0 +1,10 @@ +package com.csse3200.game.entities.configs; + +/** + * Defines the properties stored in Bombship config files to be loaded by the Bombship Factory. + */ +public class BombshipConfigs extends BaseEntityConfig { + public BaseEntityConfig bombship = new BaseEntityConfig(); + public int health = 100; + public int baseAttack = 20; +} diff --git a/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java new file mode 100644 index 000000000..b86ad2f2b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java @@ -0,0 +1,94 @@ +package com.csse3200.game.entities.factories; + +import com.badlogic.gdx.graphics.g2d.Animation; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.player.BombShipAnimationController; +import com.csse3200.game.components.tasks.bombship.BombshipWanderTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.configs.*; +import com.csse3200.game.files.FileLoader; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsUtils; +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.physics.components.PhysicsMovementComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * Factory to create non-playable human character (NPC) entities with predefined components. + * + * These may be modified to become controllable characters in future sprints. + * + *

Each NPC entity type should have a creation method that returns a corresponding entity. + * Predefined entity properties can be loaded from configs stored as json files which are defined in + * "NPCConfigs". + * + *

If needed, this factory can be separated into more specific factories for entities with + * similar characteristics. + */ +public class BombshipFactory { + + private static final int COMBAT_TASK_PRIORITY = 2; + private static final int BOMBSHIP_RANGE = 30; + private static final EngineerConfigs configs = + FileLoader.readClass(EngineerConfigs.class, "configs/Bombship.json"); + + private static final float HUMAN_SCALE_X = 1f; + private static final float HUMAN_SCALE_Y = 0.8f; + + /** + * Creates an Engineer entity, based on a base Human entity, with the appropriate components and animations + * + * + * @return entity + */ + public static Entity createEngineer() { + Entity bombship = createBaseshipNPC(); + BaseEntityConfig config = configs.bombship; + + AnimationRenderComponent animator = new AnimationRenderComponent( + new TextureAtlas("images/engineers/bombship.atlas")); + animator.addAnimation("start", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("destroy", 0.1f, Animation.PlayMode.NORMAL); + animator.addAnimation("idle", 0.2f, Animation.PlayMode.LOOP); + AITaskComponent aiComponent = new AITaskComponent(); + + bombship + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(animator) + .addComponent(new BombShipAnimationController()) + .addComponent(aiComponent); + + bombship.getComponent(AITaskComponent.class).addTask(new BombshipWanderTask(COMBAT_TASK_PRIORITY, BOMBSHIP_RANGE)); + bombship.getComponent(AnimationRenderComponent.class).scaleEntity(); + bombship.setScale(HUMAN_SCALE_X, HUMAN_SCALE_Y); + return bombship; + } + + /** + * Creates a generic human npc to be used as a base entity by more specific NPC creation methods. + * + * @return entity + */ + public static Entity createBaseshipNPC() { + + + Entity ship = + new Entity() + .addComponent(new PhysicsComponent()) + .addComponent(new PhysicsMovementComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.BOMBSHIP)) + .addComponent(new TouchAttackComponent(PhysicsLayer.NPC, 1.5f)); + + return ship; + } + + private BombshipFactory() { + throw new IllegalStateException("Instantiating static util class"); + } +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java b/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java index 37d4b64c5..32487b9a8 100644 --- a/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java +++ b/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java @@ -4,6 +4,7 @@ public class PhysicsLayer { public static final short NONE = 0; public static final short DEFAULT = (1 << 0); public static final short ENGINEER = (1 << 1); + public static final short BOMBSHIP = (1 << 1); // Terrain obstacle, e.g. trees public static final short OBSTACLE = (1 << 2); // NPC (Non-Playable Character) colliders From 8e3585df635cdf1998f4b16b20168b7ed696aab9 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Tue, 3 Oct 2023 10:57:28 +1000 Subject: [PATCH 27/31] Added the config classes and also cleared errors --- source/core/assets/configs/Bombship.json | 6 ++++++ .../game/components/player/BombShipAnimationController.java | 2 +- .../game/components/tasks/bombship/BombshipCombatTask.java | 2 +- .../components/tasks/bombship/BombshipMovementTask.java | 2 +- .../game/components/tasks/bombship/BombshipWaitTask.java | 2 +- .../game/components/tasks/bombship/BombshipWanderTask.java | 2 +- .../csse3200/game/entities/factories/BombshipFactory.java | 6 +++--- 7 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 source/core/assets/configs/Bombship.json diff --git a/source/core/assets/configs/Bombship.json b/source/core/assets/configs/Bombship.json new file mode 100644 index 000000000..f776b4224 --- /dev/null +++ b/source/core/assets/configs/Bombship.json @@ -0,0 +1,6 @@ +{ + "Bombship" : { + "health": 100, + "baseAttack": 20 +} +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java b/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java index 5fdabc7a7..a8bdf5b40 100644 --- a/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.npc; +package com.csse3200.game.components.player; import com.csse3200.game.components.Component; import com.csse3200.game.rendering.AnimationRenderComponent; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java index 349e5434b..1286786d1 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks; +package com.csse3200.game.components.tasks.bombship; import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java index 5f85e0929..4ba3d89af 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks; +package com.csse3200.game.components.tasks.bombship; import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java index c126fef03..7c62d98bb 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks; +package com.csse3200.game.components.tasks.bombship; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.services.GameTime; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java index 8e6ba4a45..63c611885 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks; +package com.csse3200.game.components.tasks.bombship; import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; diff --git a/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java index b86ad2f2b..a8eff43bf 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java @@ -34,8 +34,8 @@ public class BombshipFactory { private static final int COMBAT_TASK_PRIORITY = 2; private static final int BOMBSHIP_RANGE = 30; - private static final EngineerConfigs configs = - FileLoader.readClass(EngineerConfigs.class, "configs/Bombship.json"); + private static final BombshipConfigs configs = + FileLoader.readClass(BombshipConfigs.class, "configs/Bombship.json"); private static final float HUMAN_SCALE_X = 1f; private static final float HUMAN_SCALE_Y = 0.8f; @@ -46,7 +46,7 @@ public class BombshipFactory { * * @return entity */ - public static Entity createEngineer() { + public static Entity createBombship() { Entity bombship = createBaseshipNPC(); BaseEntityConfig config = configs.bombship; From 102a537f1b36afc87f58ab72a2ad617fe5b53972 Mon Sep 17 00:00:00 2001 From: Vishal-jodd Date: Tue, 3 Oct 2023 11:39:24 +1000 Subject: [PATCH 28/31] Added spawn , but its working --- .../assets/images/bombship/bombship.atlas | 89 ++++++++++++++++++ .../core/assets/images/bombship/bombship.png | Bin 0 -> 2093 bytes .../csse3200/game/areas/ForestGameArea.java | 15 ++- .../entities/factories/BombshipFactory.java | 2 +- 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 source/core/assets/images/bombship/bombship.atlas create mode 100644 source/core/assets/images/bombship/bombship.png diff --git a/source/core/assets/images/bombship/bombship.atlas b/source/core/assets/images/bombship/bombship.atlas new file mode 100644 index 000000000..ec9415a7c --- /dev/null +++ b/source/core/assets/images/bombship/bombship.atlas @@ -0,0 +1,89 @@ +bombship.png +size: 32, 286 +format: RGBA8888 +filter: Linear,Linear +repeat: none +destroy + rotate: false + xy: 0, 0 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 32 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 64 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 96 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 128 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 0, 160 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +idle + rotate: false + xy: 0, 160 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +idle + rotate: false + xy: 0, 181 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +idle + rotate: false + xy: 0, 202 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +start + rotate: false + xy: 0, 223 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +start + rotate: false + xy: 0, 244 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +start + rotate: false + xy: 0, 265 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/bombship/bombship.png b/source/core/assets/images/bombship/bombship.png new file mode 100644 index 0000000000000000000000000000000000000000..ba743448fdfad6eaaf98381fc31b754296f4be79 GIT binary patch literal 2093 zcmV+|2-5e7P)wOoU00009a7bBm000XU z000XU0RWnu7ytkV-$_J4RCwC$T}@~kM-=`>5xuBC80?Y-3B-^w5zI`+Az35D2FXmi9o-Iggx(F*Y^=apPG?u)K_? zCBgDEo`yu}v=<=}I9rQwBEm55L_{DE2m}Iwz)S>3kz#|HN-ja-$OWHuZ5%_1@?6SPo2nn5jET%7Tw=nAtrYmV^SvJ>FFPL6^ zm;_dF1n}@V<5N&p>H7!?TjL56#!(Qgbk$c5j&b)hBp?+H)c>nL8w}cOJ;y z3!1Cf63B%_g7HKGnMfEIHzJUVgrRX`0wY>5Oytexh-9`6+Djve;vO?HtTS#p zCK3hxWi~LLF!v@WRgRZLMJX?rr#-GzwDF|l&qjNm_3Cx!nH&$yd0>4H!G?2Dh$o`6 zu?9@DTH<+jMyq0kK+)4+v{9)dk=6^M&>gwp+H!2F32Z{rM3^eqK;E9P)`UXQ>eP|8 z9PfblJz(}ex0mHA*V)SQ!n+y*;gtZhiH0IJp_pwb2Az{(u}NKyRe50czB)b3x`Pau zbk8`P&Y3L%)8@t0BZVp=lRM~20{vx{>FP~M==TSJ=#Kc%I9ma;kzKm!U|Qqx!7Jt~ zTt-B>KajU^8xm%n(Mr1dZgO0yDC4HbmvrvWYCJ$_A*b3O$Rb38gm#_VO+onTvvkLz zfLY7(Tq~ZYik^CNT2?I~o6PF-&8!wz`S?uV{5&zpd;aUBW%a*LSr!s1x2Fnm>p3h66GMbR7=N9}d4tk;gTkhB%Jk{_q^O0WXfivWk&dfbhzJjjaC50K0&(-7F~ey@41FunX|R@r4=#fKIK#>+?PUoP0Q8;0YWbA7f@_ zCjQC*V8Pvp5Pj_3O#h8cVG;tfF2Gg*$8k`tR?+MAP_0(6xw#3xGaz~Ciw#UQ8UTP7 z-L5s6_#ob?RYnFa?fY;X2kmwnj^kjPwJ|rGHhu_P`SD>e)@>}JSqCmH-!>wl8W0d! z+V^GYI6`3iUkC>l}o}NavTE*SFck#))Z=gQ!+7D=Q$h=;RaT?K8yu-L%Y~a2pH=E)ouNF zUN8yCQ3A3oxT`lzAP@*7N=cNGC?!!!qLf4_iBbZAKp;^{qLf4_iBb}!BuWVc0)a#+ ziBb}!BuYt?k|-rnN+6tVMI^06v0PAO_!$u>l8E&Ag1)Dy(L4D+m9M)ZE-ONY$6Baj zk$QJWT`YUr5&-rN0;gZ1UCB%$P{nb@6W0O4 z{YUPJ2kP@aKE#Gq_0y_i@xDIqkJNib`3)3tSd@Ny^R_XOh$d{cT3B0K!_3T#P)QLv z9+u@{<6#vfRPmMVM|jcgCXxuku+?gz*XyCxYDJ8%t*wo`#;-X1-s?WX_TE5{PBj`z zkrHT8@n&;#69A-oP={vy%E$dWwF>qQDkG)2?;%&DEyt`-0=D0>V`)wL3LV0?F#nn*w#Dav3KyIQmNr0*_ zoEDbb8n|=uDrB2fs*THGLE?J{mB=O)0CZR1XVwI&)oSEbkdSRu#;4`6cM}WlMo<+S zTMzkz=AzM;>l$y<37r^6d~A5P?6>=0!sh?7;WC4Qlu9p#NrnF|h)gva+9}aR0oQrM z1STX(@o?(YDqIlCV%%B}+_`uaOZ$HCs+#k{Qn4xuigbU3Kv)aLU(*;CNXiM?D2R8% zl0<~Pg9?`R{YbB9(i0j Date: Tue, 3 Oct 2023 11:56:24 +1000 Subject: [PATCH 29/31] Changes the combat logic and is not working --- .../tasks/bombship/BombshipCombatTask.java | 32 ++++++++----------- .../tasks/bombship/BombshipWaitTask.java | 2 +- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java index 1286786d1..0c044d89f 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java @@ -1,5 +1,6 @@ package com.csse3200.game.components.tasks.bombship; + import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; @@ -11,6 +12,7 @@ import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; + import java.util.ArrayList; /** @@ -33,17 +35,17 @@ public class BombshipCombatTask extends DefaultTask implements PriorityTask { // The Engineer's attributes. private final float maxRange; // The maximum range of the bombship. - private Vector2 bombShipPosition = new Vector2(0, 0); // Placeholder value for the Engineer's position. + private Vector2 bombShipPosition = new Vector2(0, 0); // Placeholder value for the Bombship's position. private final Vector2 maxRangePosition = new Vector2(); private PhysicsEngine physics; private GameTime timeSource; private long endTime; private long reloadTime; - +/** private ArrayList hits = new ArrayList<>(); private final RaycastHit hit = new RaycastHit(); private ArrayList targets = new ArrayList<>(); - +*/ /** The Engineer's states. */ private enum STATE { IDLE, START , DESTROY @@ -66,7 +68,7 @@ public void start() { this.maxRangePosition.set(bombShipPosition.x + maxRange, bombShipPosition.y); // Default to idle mode owner.getEntity().getEvents().trigger(IDLE); - endTime = timeSource.getTime() + (INTERVAL * 600); + endTime = timeSource.getTime() + (INTERVAL * 500); } /** @@ -77,7 +79,7 @@ public void start() { public void update() { if (timeSource.getTime() >= endTime) { updateBombshipState(); - endTime = timeSource.getTime() + (INTERVAL * 1200); + endTime = timeSource.getTime() + (INTERVAL * 1000); } } @@ -139,25 +141,17 @@ public int getPriority() { * @return true if a target is detected, false otherwise */ public boolean isEngineerDied() { - // If there is an obstacle in the path to the max range point, mobs visible. - Vector2 position = owner.getEntity().getCenterPosition(); - hits.clear(); - for (int i = 8; i > -8; i--) { - if (physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET1, hit) - || physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET2, hit)) { - hits.add(hit); - targets.add(new Vector2(position.x + maxRange, position.y + i)); - } - } - return !hits.isEmpty(); + //if (engineerCount < maxEngineers) { + return true; + //} } /** * Fetches the nearest target from the array of detected target positions created during the last call of - * isTargetVisible + * this could be done in the next sprint , the scan doesnt work as of now ! * @return a Vector2 position of the nearest mob detected. */ - public Vector2 fetchTarget() { + /** public Vector2 fetchTarget() { // Initial nearest position for comparison int lowest = 10; @@ -173,4 +167,6 @@ public Vector2 fetchTarget() { } return nearest; } + */ } + diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java index 7c62d98bb..895225131 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java @@ -27,7 +27,7 @@ public BombshipWaitTask(float duration) { @Override public void start() { super.start(); - endTime = totalTime.getTime() + (int)(duration * 1200); + endTime = totalTime.getTime() + (int)(duration * 1000); } @Override From ee863ced592e729bccc6c71afd89766fa9bd984b Mon Sep 17 00:00:00 2001 From: Deven Bhasin Date: Tue, 3 Oct 2023 12:10:58 +1000 Subject: [PATCH 30/31] No other tower in the same lane --- .../game/entities/factories/TowerFactory.java | 30 ++++++++++++++++++- 1 file changed, 29 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 c54209516..59fb98b9c 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.tasks.*; import com.csse3200.game.components.tower.*; import com.csse3200.game.entities.configs.*; @@ -19,7 +20,9 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.rendering.TextureRenderComponent; import com.csse3200.game.services.ServiceLocator; -import com.csse3200.game.input.UpgradeUIComponent; +import com.csse3200.game.input.UpgradeUIComponent;import java.util.HashSet; +import java.util.Set; + /** * Factory to create a tower entity. * @@ -27,6 +30,8 @@ * the properties stores in 'baseTowerConfigs'. */ public class TowerFactory { + // Define a set to keep track of occupied lanes + private static final Set occupiedLanes = new HashSet<>(); private static final int COMBAT_TASK_PRIORITY = 2; private static final int WEAPON_TOWER_MAX_RANGE = 40; @@ -468,4 +473,27 @@ public static Entity createBaseTower() { tower.setLayer(1); // Set priority to 1, which is 1 below scrap (which is 0) return tower; } + public static Entity createAndPlaceTower(int lane) { + if (isLaneOccupied(lane)) { + System.out.println("Lane " + lane + " is already occupied by a tower"); + return null; + } + + Entity tower = createBaseTower(); + // Customize the tower creation here based on the chosen tower type + + // Add the lane to the set of occupied lanes + occupiedLanes.add(lane); + + return tower; + } + + /** + * Checks if a lane is already occupied by a tower. + * @param lane The lane to check. + * @return True if the lane is occupied, false otherwise. + */ + public static boolean isLaneOccupied(int lane) { + return occupiedLanes.contains(lane); + } } \ No newline at end of file From 6aaa551c5d961e6cfa35554f818b2ae0c08c4f62 Mon Sep 17 00:00:00 2001 From: Kevin <104761532+Hasakev@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:16:48 +1000 Subject: [PATCH 31/31] Added temporary fix to prevent updating of new towers --- .../com/csse3200/game/areas/ForestGameArea.java | 10 +++++----- .../csse3200/game/input/UpgradeUIComponent.java | 14 ++++++++++---- 2 files changed, 15 insertions(+), 9 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 a9e61f90f..df615de1b 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -322,14 +322,14 @@ public void create() { playMusic(); spawnScrap(); - spawnTNTTower(); - spawnWeaponTower(); - spawnGapScanners(); - spawnDroidTower(); +// spawnTNTTower(); +// spawnWeaponTower(); +// spawnGapScanners(); +// spawnDroidTower(); spawnFireWorksTower(); spawnPierceTower(); spawnRicochetTower(); - //spawnBombship(); +// spawnBombship(); } private void displayUI() { diff --git a/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java b/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java index cffd36cc6..f9d4ca67e 100644 --- a/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java +++ b/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java @@ -22,10 +22,7 @@ import com.csse3200.game.areas.ForestGameArea; import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.tasks.TowerCombatTask; -import com.csse3200.game.components.tower.IncomeUpgradeComponent; -import com.csse3200.game.components.tower.TNTDamageComponent; -import com.csse3200.game.components.tower.TowerUpgraderComponent; -import com.csse3200.game.components.tower.UpgradableStatsComponent; +import com.csse3200.game.components.tower.*; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.EntityService; import com.csse3200.game.services.ServiceLocator; @@ -79,6 +76,15 @@ public boolean touchDown(int screenX, int screenY, int pointer, int button) { Vector2 cursorPosition = new Vector2(worldCoordinates.x, worldCoordinates.y); Entity clickedEntity = entityService.getEntityAtPosition(cursorPosition.x, cursorPosition.y); + //temp fix to prevent upgrading of new towers + if (clickedEntity!= null && (clickedEntity.getComponent(RicochetTowerAnimationController.class) != null || + clickedEntity.getComponent(PierceTowerAnimationController.class) != null || + clickedEntity.getComponent(FireworksTowerAnimationController.class) != null)) { + return false; + } + // + + if (clickedEntity != null && clickedEntity.getComponent(TowerUpgraderComponent.class) != null && clickedEntity.getComponent(TNTDamageComponent.class) == null) { // logger.info("clicked a turret that is upgradable!"); clearUpgradeTables();