diff --git a/source/core/assets/configs/tower.json b/source/core/assets/configs/tower.json index 3566b90d9..61716cb3e 100644 --- a/source/core/assets/configs/tower.json +++ b/source/core/assets/configs/tower.json @@ -14,7 +14,16 @@ "baseAttack": 0, "cost": 1 }, -<<<<<<< HEAD + "TNTTower": { + "health": 10, + "baseAttack": 5, + "cost": 1 + }, + "DroidTower": { + "health": 10, + "baseAttack": 5, + "cost": 1 + }, "fireTower": { "health": 10, "baseAttack": 10, @@ -24,11 +33,5 @@ "health": 10, "baseAttack": 10, "cost": 10 -======= - "TNTTower": { - "health": 10, - "baseAttack": 5, - "cost": 1 ->>>>>>> main } } \ No newline at end of file diff --git a/source/core/assets/images/towers/DroidTower.atlas b/source/core/assets/images/towers/DroidTower.atlas new file mode 100644 index 000000000..47c4d1626 --- /dev/null +++ b/source/core/assets/images/towers/DroidTower.atlas @@ -0,0 +1,251 @@ + +DroidTower.png +size: 1024, 64 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +attackDown + rotate: false + xy: 72, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +attackDown + rotate: false + xy: 212, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +attackDown + rotate: false + xy: 457, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +attackDown + rotate: false + xy: 597, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +attackDown + rotate: false + xy: 737, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +attackUp + rotate: false + xy: 247, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +attackUp + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +attackUp + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +death + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +idle + rotate: false + xy: 422, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +attackUp + rotate: false + xy: 632, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +attackUp + rotate: false + xy: 842, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 2, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 177, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 6 +death + rotate: false + xy: 387, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 8 +death + rotate: false + xy: 527, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 5 +death + rotate: false + xy: 702, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 7 +goDown + rotate: false + xy: 37, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 37, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +goUp + rotate: false + xy: 37, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +goDown + rotate: false + xy: 282, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +goUp + rotate: false + xy: 282, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +goDown + rotate: false + xy: 562, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 5 +goDown + rotate: false + xy: 772, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +goUp + rotate: false + xy: 142, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 142, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 +goDown + rotate: false + xy: 142, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +goUp + rotate: false + xy: 352, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +death + rotate: false + xy: 352, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +goDown + rotate: false + xy: 352, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +walk + rotate: false + xy: 107, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 1 +walk + rotate: false + xy: 317, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 3 +walk + rotate: false + xy: 492, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 0 +walk + rotate: false + xy: 667, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 2 +walk + rotate: false + xy: 807, 2 + size: 33, 38 + orig: 33, 38 + offset: 0, 0 + index: 4 diff --git a/source/core/assets/images/towers/DroidTower.png b/source/core/assets/images/towers/DroidTower.png new file mode 100644 index 000000000..6fab7ed2d Binary files /dev/null and b/source/core/assets/images/towers/DroidTower.png differ 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 f79cb0819..d55332ee1 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -101,7 +101,8 @@ public class ForestGameArea extends GameArea { "images/towers/mine_tower.png", - "images/towers/TNTTower.png" + "images/towers/TNTTower.png", + "images/towers/DroidTower.png" }; private static final String[] forestTextureAtlases = { "images/economy/econ-tower.atlas", @@ -110,18 +111,23 @@ public class ForestGameArea extends GameArea { "images/ghostKing.atlas", "images/towers/turret.atlas", "images/towers/turret01.atlas", -<<<<<<< HEAD "images/towers/fire_tower_atlas.atlas", "images/towers/stun_tower.atlas", "images/mobs/xenoGruntRunning.atlas", -======= "images/xenoGrunt.atlas", ->>>>>>> main + "images/towers/fire_tower_atlas.atlas", + "images/towers/stun_tower.atlas", + "images/mobs/xenoGruntRunning.atlas", + "images/mobs/robot.atlas", + "images/mobs/rangeBossRight.atlas", + "images/towers/DroidTower.atlas", + "images/xenoGrunt.atlas", "images/mobs/robot.atlas", "images/mobs/rangeBossRight.atlas", "images/towers/TNTTower.atlas", "images/projectiles/basic_projectile.atlas", "images/projectiles/mobProjectile.atlas" + }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -163,44 +169,40 @@ public void create() { loadAssets(); displayUI(); spawnTerrain(); -<<<<<<< HEAD // spawnBuilding1(); // spawnBuilding2(); // spawnMountains(); -======= - ->>>>>>> main player = spawnPlayer(); player.getEvents().addListener("spawnWave", this::spawnXenoGrunts); playMusic(); // Types of projectile -<<<<<<< HEAD - spawnAoeProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f), 1); - spawnProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f)); +// spawnAoeProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f), 1); +// spawnProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f)); // spawnMultiProjectile(new Vector2(0, 10), player, towardsMobs, 20, new Vector2(2f, 2f), 7); -======= spawnEffectProjectile(new Vector2(0, 10), PhysicsLayer.HUMANS, towardsMobs, new Vector2(2f, 2f), ProjectileEffects.BURN, true); ->>>>>>> main + + spawnEffectProjectile(new Vector2(0, 10), PhysicsLayer.HUMANS, towardsMobs, new Vector2(2f, 2f), ProjectileEffects.BURN, true); spawnXenoGrunts(); - spawnGhosts(); +// spawnGhosts(); spawnWeaponTower(); -<<<<<<< HEAD spawnIncome(); // spawnScrap(); - bossKing1 = spawnBossKing1(); +// bossKing1 = spawnBossKing1(); // bossKing2 = spawnBossKing2(); playMusic(); -======= + spawnTNTTower(); + spawnDroidTower(); spawnEngineer(); - bossKing1 = spawnBossKing1(); + spawnIncome(); +// bossKing1 = spawnBossKing1(); bossKing2 = spawnBossKing2(); spawnTNTTower(); ->>>>>>> main + } private void displayUI() { @@ -239,28 +241,7 @@ private void spawnTerrain() { spawnEntityAt( ObstacleFactory.createWall(worldBounds.x, WALL_WIDTH), GridPoint2Utils.ZERO, false, false); } -// private void spawnBuilding1() { -// GridPoint2 minPos = new GridPoint2(0, 0); -// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); -// -// for (int i = 0; i < NUM_BUILDINGS; i++) { -// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); -// Entity building1 = ObstacleFactory.createBuilding1(); -// spawnEntityAt(building1, randomPos, true, false); -// } -// } -// private void spawnBuilding2() { -// GridPoint2 minPos = new GridPoint2(0, 0); -// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); -// -// for (int i = 0; i < NUM_BUILDINGS; i++) { -// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); -// Entity building2 = ObstacleFactory.createBuilding2(); -// spawnEntityAt(building2, randomPos, true, false); -// } -// } -<<<<<<< HEAD // private void spawnMountains() { // ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList // @@ -273,14 +254,13 @@ private void spawnTerrain() { // Entity tree = ObstacleFactory.createMountain(); // spawnEntityAt(tree, fixedPos, true, false); // } +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building1 = ObstacleFactory.createBuilding1(); +// spawnEntityAt(building1, randomPos, true, false); +// } // } -======= - for (int i = 0; i < NUM_BUILDINGS; i++) { - GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity building1 = ObstacleFactory.createBuilding1(); - spawnEntityAt(building1, randomPos, true, false); - } - } + private void spawnBuilding2() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); @@ -292,16 +272,15 @@ private void spawnBuilding2() { } } - private void spawnMountains() { - ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList - - - for (GridPoint2 fixedPos : fixedPositions) { - Entity tree = ObstacleFactory.createMountain(); - spawnEntityAt(tree, fixedPos, true, false); - } - } ->>>>>>> main +// private void spawnMountains() { +// ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList +// +// +// for (GridPoint2 fixedPos : fixedPositions) { +// Entity tree = ObstacleFactory.createMountain(); +// spawnEntityAt(tree, fixedPos, true, false); +// } +// } private Entity spawnPlayer() { Entity newPlayer = PlayerFactory.createPlayer(); @@ -316,28 +295,28 @@ private Entity spawnPlayer(GridPoint2 position) { return newPlayer; } - private void spawnGhosts() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 2); - - for (int i = 0; i < NUM_GHOSTS; i++) { - int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate - int randomY = MathUtils.random(0, maxPos.y); - GridPoint2 randomPos = new GridPoint2(fixedX, randomY); - Entity ghost = createGhost(player); - spawnEntityAt(ghost, randomPos, true, true); - } - } +// private void spawnGhosts() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 2); +// +// for (int i = 0; i < NUM_GHOSTS; i++) { +// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate +// int randomY = MathUtils.random(0, maxPos.y); +// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); +// Entity ghost = createGhost(player); +// spawnEntityAt(ghost, randomPos, true, true); +// } +// } - private Entity spawnBossKing1() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - GridPoint2 randomPos - = new GridPoint2(0, 0); - Entity ghostKing = NPCFactory.createGhostKing(player); - spawnEntityAt(ghostKing, randomPos, true, true); - return ghostKing; - } +// private Entity spawnBossKing1() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// GridPoint2 randomPos +// = new GridPoint2(0, 0); +// Entity ghostKing = NPCFactory.createGhostKing(player); +// spawnEntityAt(ghostKing, randomPos, true, true); +// return ghostKing; +// } /** * Spawns a projectile that only heads towards the enemies in its lane. @@ -424,22 +403,22 @@ private void spawnXenoGrunts() { // // } -// private Entity spawnBossKing2() { -// GridPoint2 minPos = new GridPoint2(0, 0); -// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); -// -// for (int i = 0; i < NUM_BOSS; i++) { -// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate -// int randomY = MathUtils.random(0, maxPos.y); -// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); -// bossKing2 = BossKingFactory.createBossKing2(player); -// spawnEntityAt(bossKing2, -// randomPos, -// true, -// false); -// } -// return bossKing2; -// } + private Entity spawnBossKing2() { + GridPoint2 minPos = new GridPoint2(0, 0); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); + + for (int i = 0; i < NUM_BOSS; i++) { + int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate + int randomY = MathUtils.random(0, maxPos.y); + GridPoint2 randomPos = new GridPoint2(fixedX, randomY); + bossKing2 = BossKingFactory.createBossKing2(player); + spawnEntityAt(bossKing2, + randomPos, + true, + false); + } + return bossKing2; + } /** * Creates multiple projectiles that travel simultaneous. They all have same @@ -452,23 +431,13 @@ private void spawnXenoGrunts() { * @param speed The speed of the projectiles. * @param quantity The amount of projectiles to spawn. */ -<<<<<<< HEAD -// private void spawnMultiProjectile(Vector2 position, Entity target, int direction, int space, Vector2 speed, int quantity) { +// private void spawnMultiProjectile(Vector2 position, short targetLayer, int direction, int space, Vector2 speed, int quantity) { // int half = quantity / 2; // for (int i = 0; i < quantity; i++) { -// spawnProjectile(position, target, space * half, direction, speed); +// spawnProjectile(position, targetLayer, space * half, direction, speed); // --half; // } // } -======= - private void spawnMultiProjectile(Vector2 position, short targetLayer, int direction, int space, Vector2 speed, int quantity) { - int half = quantity / 2; - for (int i = 0; i < quantity; i++) { - spawnProjectile(position, targetLayer, space * half, direction, speed); - --half; - } - } ->>>>>>> main /** * Returns projectile that can do an area of effect damage @@ -495,7 +464,7 @@ private void spawnWeaponTower() { GridPoint2 randomPos1 = RandomUtils.random(minPos, maxPos); GridPoint2 randomPos2 = RandomUtils.random(minPos, maxPos); //Entity weaponTower = TowerFactory.createWeaponTower(); - Entity wallTower = TowerFactory.createWallTower(); +// Entity wallTower = TowerFactory.createWallTower(); Entity fireTower = TowerFactory.createFireTower(); Entity stunTower = TowerFactory.createStunTower(); //spawnEntityAt(weaponTower, randomPos, true, true); @@ -517,6 +486,18 @@ private void spawnTNTTower() { } + private void spawnDroidTower() { + GridPoint2 minPos = new GridPoint2(0, 0); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity weaponTower = TowerFactory.createDroidTower(); + spawnEntityAt(weaponTower, randomPos, true, true); + } + + } + private void playMusic() { Music music = ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class); @@ -555,41 +536,29 @@ public void dispose() { this.unloadAssets(); } -<<<<<<< HEAD + // private void spawnScrap() { // GridPoint2 minPos = new GridPoint2(0, 0); // GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); // -// for (int i = 0; i < 50; i++) { +// for (int i = 0; i < 5; i++) { // GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); // Entity scrap = DropFactory.createScrapDrop(); // spawnEntityAt(scrap, randomPos, true, false); // } +// +// for (int i = 0; i < 5; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity crystal = DropFactory.createCrystalDrop(); +// spawnEntityAt(crystal, randomPos, true, false); +// } // } -======= - private void spawnScrap() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - - for (int i = 0; i < 5; i++) { - GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity scrap = DropFactory.createScrapDrop(); - spawnEntityAt(scrap, randomPos, true, false); - } - - for (int i = 0; i < 5; i++) { - GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity crystal = DropFactory.createCrystalDrop(); - spawnEntityAt(crystal, randomPos, true, false); - } - } ->>>>>>> main private void spawnIncome() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - for (int i = 0; i < 50; i++) { + for (int i = 0; i < 2; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity towerfactory = TowerFactory.createIncomeTower(); spawnEntityAt(towerfactory, randomPos, true, true); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java new file mode 100644 index 000000000..8512c2221 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java @@ -0,0 +1,183 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * The DroidCombatTask runs the AI for the DroidTower class. The tower will scan for targets in a straight line + * from its center point until a point at (x + maxRange, y), where x,y are the cooridinates of the tower's center + * position. This component should be added to an AiTaskComponent attached to the tower instance. + */ +public class DroidCombatTask extends DefaultTask implements PriorityTask { + // Constants + private static final int INTERVAL = 1; // time interval to scan for enemies in seconds + private static final short TARGET = PhysicsLayer.NPC; // The type of targets that the tower will detect + // the following four constants are the event names that will be triggered in the state machine + private static final String GO_UP = "goUpStart"; + private static final String GO_DOWN = "goDownStart"; + private static final String ATTACK_UP = "attackUpStart"; + private static final String ATTACK_DOWN = "attackDownStart"; + private static final String WALK = "walkStart"; + private static final String DEATH = "deathStart"; + private static final String IDLE = "idleStart"; + + + // class attributes + private final int priority; // The active priority this task will have + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); // initial placeholder value - will be overwritten + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + private enum STATE { + IDLE, UP, DOWN, SHOOT_UP, SHOOT_DOWN, WALK, DIE + } + private STATE towerState = STATE.WALK; + + /** + * @param priority Task priority when targets are detected (0 when nothing detected). Must be a positive integer. + * @param maxRange Maximum effective range of the weapon tower. This determines the detection distance of targets + */ + public DroidCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the Task running, triggers the initial "idleStart" event. + */ + @Override + public void start() { + super.start(); + // Set the tower's coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Default to idle mode + owner.getEntity().getEvents().trigger(WALK); + + endTime = timeSource.getTime() + (INTERVAL * 500); + } + + /** + * The update method is what is run every time the TaskRunner in the AiTaskComponent calls update(). + * triggers events depending on the presence or otherwise of targets in the detection range + */ + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * Droid tower state machine. Updates tower state by scanning for mobs, and + * triggers the appropriate events corresponding to the STATE enum. + */ + public void updateTowerState() { + // configure tower state depending on target visibility + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DIE) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DIE; + return; + } + switch (towerState) { + case WALK -> { + owner.getEntity().getEvents().trigger(WALK); + towerState = STATE.IDLE; + } + case IDLE -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_UP); + towerState = STATE.DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + } + } + case SHOOT_DOWN -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_DOWN); + towerState = STATE.UP; + } else { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.UP; + } + } + case SHOOT_UP -> { + if (isTargetVisible()) { + + owner.getEntity().getEvents().trigger(ATTACK_UP); + towerState = STATE.DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + case DOWN -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(GO_DOWN); + towerState = STATE.SHOOT_DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + case UP -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.SHOOT_UP; + } else { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.IDLE; + + + } + } + case DIE -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) + owner.getEntity().setFlagForDelete(true); + } + } + } + /** + * For stopping the running task + */ + @Override + public void stop() { + super.stop(); +// owner.getEntity().getEvents().trigger(STOW); + } + + /** + * Returns the current priority of the task. + * @return active priority value if targets detected, inactive priority otherwise + */ + @Override + public int getPriority() { + return isTargetVisible() ? priority : 0; + } + + /** + * Uses a raycast to determine whether there are any targets in detection range + * @return true if a target is visible, false otherwise + */ + private boolean isTargetVisible() { + // If there is an obstacle in the path to the max range point, mobs visible. + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java index eff97273f..3ac568f1b 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -102,7 +102,7 @@ public void updateTowerState() { towerState = STATE.IDLE; } else { owner.getEntity().getEvents().trigger(ATTACK); - Entity newProjectile = ProjectileFactory.createFireBall(owner.getEntity(), + Entity newProjectile = ProjectileFactory.createFireBall(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)); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java index b4fb3d802..2919d49bc 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -95,7 +95,7 @@ public void updateTowerState() { towerState = STATE.IDLE; } else { owner.getEntity().getEvents().trigger(ATTACK); - Entity newProjectile = ProjectileFactory.createFireBall(owner.getEntity(), + Entity newProjectile = ProjectileFactory.createFireBall(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)); diff --git a/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java new file mode 100644 index 000000000..e1fb8f098 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java @@ -0,0 +1,86 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to DroidTower entity's state and plays the animation when one + * of the events is triggered. + */ +public class DroidAnimationController extends Component { + private AnimationRenderComponent animator; + + /** + * Creation call for a DroidAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations and sounds. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("walkStart", this::animateWalk); + entity.getEvents().addListener("idleStart", this::animateDefault); + entity.getEvents().addListener("goUpStart",this::animateGoUp); + entity.getEvents().addListener("goDownStart",this::animateGoDown); + entity.getEvents().addListener("attackUpStart",this::animateAttackUp); + entity.getEvents().addListener("attackDownStart",this::animateAttackDown); + entity.getEvents().addListener("deathStart",this::animateDeath); + + } + + /** + * Initiates the walking animation for the robot. + * This method should be invoked when the robot is moving but not in combat. + */ + void animateWalk() { + animator.startAnimation("walk"); + } + + /** + * Starts the animation sequence for switching aim from above. + * Use this method when the robot is preparing to target mobs after aiming from below. + */ + void animateGoUp() { + animator.startAnimation("goUp"); + } + + /** + * Activates the animation sequence for switching aim from below. + * Use this method when the robot is preparing to target mobs after aiming from above. + */ + void animateGoDown() { + animator.startAnimation("goDown"); + } + + /** + * Triggers the animation for firing projectiles from an elevated aim. + * Invoke this method when the robot engages with mobs and aiming above. + */ + void animateAttackUp() { + animator.startAnimation("attackUp"); + } + + /** + * Starts the animation sequence for firing projectiles from a lowered aim. + * Use this method when the robot engages with mobs and aiming below. + */ + void animateAttackDown() { + animator.startAnimation("attackDown"); + } + + /** + * Triggers the robot's death animation. + * This method should be invoked when the robot's health reaches zero. + */ + void animateDeath() { + animator.startAnimation("death"); + } + + + /** + * Triggers the "default" animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { animator.startAnimation("default");} + +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java index c019b92fd..c6acb3c28 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java +++ b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java @@ -78,7 +78,7 @@ private void applyTNTDamage() { Vector2 positionSource = entity.getPosition(); Vector2 positionOther = otherEntity.getPosition(); - if (positionSource.dst(positionOther) <= radius) { + if (positionSource.dst(positionOther) <= radius && positionSource.y -1 <= positionOther.y && positionSource.y +1 >= positionOther.y) { HitboxComponent sourceHitbox = entity.getComponent(HitboxComponent.class); HitboxComponent otherHitbox = otherEntity.getComponent(HitboxComponent.class); diff --git a/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java new file mode 100644 index 000000000..18ce675d5 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java @@ -0,0 +1,10 @@ +package com.csse3200.game.entities.configs; + +/** + * Defines a basic set of properties stored in entities config files to be loaded by Entity Factories. + */ +public class DroidTowerConfig { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java index 0e802e322..968f4ec93 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java @@ -7,10 +7,8 @@ public class baseTowerConfigs { public WeaponTowerConfig weapon = new WeaponTowerConfig(); public WallTowerConfig wall = new WallTowerConfig(); public IncomeTowerConfig income = new IncomeTowerConfig(); -<<<<<<< HEAD public FireTowerConfig fireTower = new FireTowerConfig(); public StunTowerConfig stunTower = new StunTowerConfig(); -======= public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); ->>>>>>> main + public DroidTowerConfig DroidTower = new DroidTowerConfig(); } \ 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 a9e7e8148..bf1aa2807 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,19 +1,16 @@ package com.csse3200.game.entities.factories; -<<<<<<< HEAD +import com.csse3200.game.components.tasks.DroidCombatTask; +import com.csse3200.game.components.tasks.TNTTowerCombatTask; +import com.csse3200.game.components.tower.DroidAnimationController; +import com.csse3200.game.components.tower.TNTAnimationController; +import com.csse3200.game.components.tower.TNTDamageComponent; +import com.csse3200.game.entities.configs.*; import com.csse3200.game.components.tasks.FireTowerCombatTask; import com.csse3200.game.components.tasks.StunTowerCombatTask; import com.csse3200.game.components.tower.FireTowerAnimationController; import com.csse3200.game.components.tower.StunTowerAnimationController; -import com.csse3200.game.entities.configs.*; import com.csse3200.game.components.tower.TowerUpgraderComponent; -======= - -import com.csse3200.game.components.tasks.TNTTowerCombatTask; -import com.csse3200.game.components.tower.TNTAnimationController; -import com.csse3200.game.components.tower.TNTDamageComponent; - ->>>>>>> main import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; @@ -45,7 +42,7 @@ public class TowerFactory { private static final int COMBAT_TASK_PRIORITY = 2; private static final int WEAPON_TOWER_MAX_RANGE = 40; private static final int TNT_TOWER_MAX_RANGE = 6; - private static final int TNT_TOWER_RANGE = 5; + private static final int TNT_TOWER_RANGE = 6; private static final int TNT_KNOCK_BACK_FORCE = 10; private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String RESOURCE_TOWER = "images/towers/mine_tower.png"; @@ -53,7 +50,15 @@ 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 TNT_ATLAS = "images/towers/TNTTower.atlas"; + private static final String DROID_ATLAS = "images/towers/DroidTower.atlas"; + private static final float DROID_SPEED = 0.25f; private static final String DEFAULT_ANIM = "default"; + private static final String WALK_ANIM = "walk"; + private static final String DEATH_ANIM = "death"; + private static final String GO_UP = "goUp"; + private static final String GO_DOWN = "goDown"; + private static final String SHOOT_UP = "attackUp"; + private static final String SHOOT_DOWN = "attackDown"; private static final float DEFAULT_SPEED= 0.2f; private static final String DIG_ANIM = "dig"; private static final float DIG_SPEED = 0.2f; @@ -79,8 +84,6 @@ public class TowerFactory { private static final float STUN_TOWER_ATTACK_SPEED = 0.12f; private static final int INCOME_INTERVAL = 300; private static final int INCOME_TASK_PRIORITY = 1; - - private static final String ECO_ATLAS = "images/economy/econ-tower.atlas"; private static final String ECO_MOVE = "move1"; private static final String ECO_IDLE = "idle"; @@ -122,16 +125,16 @@ public static Entity createIncomeTower() { return income; } - public static Entity createWallTower() { - Entity wall = createBaseTower(); - WallTowerConfig config = configs.wall; - - wall - .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent(new CostComponent(config.cost)) - .addComponent(new TextureRenderComponent(WALL_IMAGE)); - return wall; - } +// public static Entity createWallTower() { +// Entity wall = createBaseTower(); +// WallTowerConfig config = configs.wall; +// +// wall +// .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) +// .addComponent(new CostComponent(config.cost)) +// .addComponent(new TextureRenderComponent(WALL_IMAGE)); +// return wall; +// } /** @@ -168,6 +171,44 @@ public static Entity createTNTTower() { return TNTTower; } + /** + * This robotic unit is programmed to detect mobs within its vicinity and fire projectiles at them. + * The droid has the capability to switch its aim from high to low positions, thereby providing a versatile attack strategy. + * When it detects a mob, the droid releases a projectile that inflicts both physical damage and a slow-down effect on the target. + * @return entity + */ + public static Entity createDroidTower() { + Entity DroidTower = createBaseTower(); + DroidTowerConfig config = configs.DroidTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new DroidCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(DROID_ATLAS, TextureAtlas.class)); + + animator.addAnimation(IDLE_ANIM, DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(SHOOT_UP,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(SHOOT_DOWN,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(WALK_ANIM,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(DEATH_ANIM,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(GO_UP,DROID_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(GO_DOWN,DROID_SPEED, Animation.PlayMode.NORMAL); + + + + DroidTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new CostComponent(config.cost)) + .addComponent(new DroidAnimationController()) + .addComponent(animator) + .addComponent(aiTaskComponent); + + return DroidTower; + } + /** * Creates a weaponry tower that shoots at mobs - This will most likely need to be extended @@ -271,11 +312,10 @@ public static Entity createBaseTower() { // we're going to add more components later on Entity tower = new Entity() .addComponent(new ColliderComponent()) - .addComponent(new HitboxComponent().setLayer(PhysicsLayer.OBSTACLE)) // TODO: we might have to change the names of the layers + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.TOWER)) // TODO: we might have to change the names of the layers .addComponent(new PhysicsComponent().setBodyType(BodyType.StaticBody)) .addComponent(new TowerUpgraderComponent()) - .addComponent(new HitboxComponent().setLayer(PhysicsLayer.TOWER)) // TODO: we might have to change the names of the layers - .addComponent(new PhysicsComponent().setBodyType(BodyType.StaticBody)); + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.TOWER)); // TODO: we might have to change the names of the layers return tower; } diff --git a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java index 8f52f18bb..0143567f3 100644 --- a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java +++ b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java @@ -179,7 +179,7 @@ protected void draw(SpriteBatch batch) { @Override public void dispose() { - // atlas.dispose(); + // atlas.dispose(); // this has to be disabled to keep the atlas file for other entities that rely on it super.dispose(); } } diff --git a/source/core/src/test/com/csse3200/game/components/tower/TNTAnimationControllerTest.java b/source/core/src/test/com/csse3200/game/components/tower/TNTAnimationControllerTest.java new file mode 100644 index 000000000..bdaa14041 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tower/TNTAnimationControllerTest.java @@ -0,0 +1,76 @@ +package com.csse3200.game.components.tower; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +import com.badlogic.gdx.graphics.g2d.Animation; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.TowerFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.rendering.DebugRenderer; +import com.csse3200.game.rendering.RenderService; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ResourceService; +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; + + + +@ExtendWith(GameExtension.class) +public class TNTAnimationControllerTest { + + private Entity mockEntity; + private final String[] texture = {"images/towers/TNTTower.png"}; + private final String[] atlas = {"images/towers/TNTTower.atlas"}; + + + @BeforeEach + public void setUp() { + // Initialize the TNTAnimationController object + ServiceLocator.registerPhysicsService(new PhysicsService()); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = new ResourceService(); + ServiceLocator.registerResourceService(resourceService); + resourceService.loadTextures(texture); + resourceService.loadTextureAtlases(atlas); + resourceService.loadAll(); + + mockEntity = TowerFactory.createTNTTower(); + mockEntity.create(); + } + + @Test + public void testAnimateDig() { + + // Trigger the animateDig method + mockEntity.getEvents().trigger("digStart"); + + // Verify if the "dig" animation was started + assertEquals("dig",mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateDefault() { + // Trigger the animateDefault method + mockEntity.getEvents().trigger("defaultStart"); + + // Verify if the "default" animation was started + assertEquals("default",mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateExplode() { + // Trigger the animateExplode method + mockEntity.getEvents().trigger("explodeStart"); + + // Verify if the "explode" animation was started + assertEquals("explode",mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } +}