diff --git a/source/core/assets/images/towers/stun_tower.atlas b/source/core/assets/images/towers/stun_tower.atlas new file mode 100644 index 000000000..feeea736b --- /dev/null +++ b/source/core/assets/images/towers/stun_tower.atlas @@ -0,0 +1,118 @@ + +stun_tower.png +size: 1024, 64 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +attack + rotate: false + xy: 2, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 4 +attack + rotate: false + xy: 136, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 9 +attack + rotate: false + xy: 203, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 1 +attack + rotate: false + xy: 270, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 6 +attack + rotate: false + xy: 404, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 3 +attack + rotate: false + xy: 538, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 8 +attack + rotate: false + xy: 672, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 0 +attack + rotate: false + xy: 739, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 5 +attack + rotate: false + xy: 873, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 2 +attack + rotate: false + xy: 940, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 7 +idle + rotate: false + xy: 69, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 1 +idle + rotate: false + xy: 337, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 3 +idle + rotate: false + xy: 471, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 0 +idle + rotate: false + xy: 605, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 5 +idle + rotate: false + xy: 605, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 4 +idle + rotate: false + xy: 806, 2 + size: 65, 45 + orig: 65, 45 + offset: 0, 0 + index: 2 diff --git a/source/core/assets/images/towers/stun_tower.png b/source/core/assets/images/towers/stun_tower.png new file mode 100644 index 000000000..0d5c0d4f6 Binary files /dev/null and b/source/core/assets/images/towers/stun_tower.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 d1aedf83c..9f5edad80 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -69,6 +69,7 @@ public class ForestGameArea extends GameArea { "images/towers/turret01.png", "images/towers/turret_deployed.png", "images/towers/fire_tower_atlas.png", + "images/towers/stun_tower.png", "images/background/building2.png", "images/mobs/robot.png", "images/mobs/Attack_1.png", @@ -94,6 +95,7 @@ public class ForestGameArea extends GameArea { "images/towers/turret.atlas", "images/towers/turret01.atlas", "images/towers/fire_tower_atlas.atlas", + "images/towers/stun_tower.atlas", "images/mobs/xenoGruntRunning.atlas", "images/mobs/robot.atlas", "images/mobs/rangeBossRight.atlas" @@ -136,9 +138,9 @@ public void create() { displayUI(); spawnTerrain(); - spawnBuilding1(); - spawnBuilding2(); - spawnMountains(); +// spawnBuilding1(); +// spawnBuilding2(); +// spawnMountains(); player = spawnPlayer(); playMusic(); @@ -146,16 +148,16 @@ public void create() { // Types of projectile spawnAoeProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f), 1); spawnProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f)); - spawnMultiProjectile(new Vector2(0, 10), player, towardsMobs, 20, new Vector2(2f, 2f), 7); +// spawnMultiProjectile(new Vector2(0, 10), player, towardsMobs, 20, new Vector2(2f, 2f), 7); spawnXenoGrunts(); spawnGhosts(); spawnWeaponTower(); spawnIncome(); - spawnScrap(); +// spawnScrap(); bossKing1 = spawnBossKing1(); - bossKing2 = spawnBossKing2(); +// bossKing2 = spawnBossKing2(); playMusic(); } @@ -196,40 +198,40 @@ 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); - } - } - - private void spawnMountains() { - ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList - - fixedPositions.add(new GridPoint2(5, 8)); - fixedPositions.add(new GridPoint2(12, 4)); - fixedPositions.add(new GridPoint2(20, 10)); - fixedPositions.add(new GridPoint2(33, 17)); - - for (GridPoint2 fixedPos : fixedPositions) { - Entity tree = ObstacleFactory.createMountain(); - spawnEntityAt(tree, fixedPos, true, false); - } - } +// private void spawnBuilding1() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building1 = ObstacleFactory.createBuilding1(); +// spawnEntityAt(building1, randomPos, true, false); +// } +// } +// private void spawnBuilding2() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building2 = ObstacleFactory.createBuilding2(); +// spawnEntityAt(building2, randomPos, true, false); +// } +// } + +// private void spawnMountains() { +// ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList +// +// fixedPositions.add(new GridPoint2(5, 8)); +// fixedPositions.add(new GridPoint2(12, 4)); +// fixedPositions.add(new GridPoint2(20, 10)); +// fixedPositions.add(new GridPoint2(33, 17)); +// +// for (GridPoint2 fixedPos : fixedPositions) { +// Entity tree = ObstacleFactory.createMountain(); +// spawnEntityAt(tree, fixedPos, true, false); +// } +// } private Entity spawnPlayer() { Entity newPlayer = PlayerFactory.createPlayer(); @@ -324,34 +326,34 @@ private void spawnXenoGrunts() { } } - private Entity spawnGhostKing() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 0); - GridPoint2 randomPos - = RandomUtils.random(minPos, maxPos); - // = new GridPoint2(26, 26); - Entity ghostKing = NPCFactory.createGhostKing(player); - spawnEntityAt(ghostKing, randomPos, true, true); - return ghostKing; - - } - - private Entity spawnBossKing2() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - - for (int i = 0; i < NUM_BOSS; i++) { - int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate - int randomY = MathUtils.random(0, maxPos.y); - GridPoint2 randomPos = new GridPoint2(fixedX, randomY); - bossKing2 = BossKingFactory.createBossKing2(player); - spawnEntityAt(bossKing2, - randomPos, - true, - false); - } - return bossKing2; - } +// private Entity spawnGhostKing() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 0); +// GridPoint2 randomPos +// = RandomUtils.random(minPos, maxPos); +// // = new GridPoint2(26, 26); +// Entity ghostKing = NPCFactory.createGhostKing(player); +// spawnEntityAt(ghostKing, randomPos, true, true); +// return ghostKing; +// +// } + +// private Entity spawnBossKing2() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BOSS; i++) { +// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate +// int randomY = MathUtils.random(0, maxPos.y); +// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); +// bossKing2 = BossKingFactory.createBossKing2(player); +// spawnEntityAt(bossKing2, +// randomPos, +// true, +// false); +// } +// return bossKing2; +// } /** * Creates multiple projectiles that travel simultaneous. They all have same @@ -364,13 +366,13 @@ private Entity spawnBossKing2() { * @param speed The speed of the projectiles. * @param quantity The amount of projectiles to spawn. */ - private void spawnMultiProjectile(Vector2 position, Entity target, int direction, int space, Vector2 speed, int quantity) { - int half = quantity / 2; - for (int i = 0; i < quantity; i++) { - spawnProjectile(position, target, space * half, direction, speed); - --half; - } - } +// private void spawnMultiProjectile(Vector2 position, Entity target, int direction, int space, Vector2 speed, int quantity) { +// int half = quantity / 2; +// for (int i = 0; i < quantity; i++) { +// spawnProjectile(position, target, space * half, direction, speed); +// --half; +// } +// } /** * Returns projectile that can do an area of effect damage @@ -393,11 +395,13 @@ private void spawnWeaponTower() { for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity weaponTower = TowerFactory.createWeaponTower(); + //Entity weaponTower = TowerFactory.createWeaponTower(); Entity wallTower = TowerFactory.createWallTower(); Entity fireTower = TowerFactory.createFireTower(); - spawnEntityAt(weaponTower, randomPos, true, true); + Entity stunTower = TowerFactory.createStunTower(); + //spawnEntityAt(weaponTower, randomPos, true, true); spawnEntityAt(fireTower, randomPos, true, true); + spawnEntityAt(stunTower, randomPos, true, true); spawnEntityAt(wallTower, new GridPoint2(randomPos.x + 3, randomPos.y), true, true); } } @@ -440,16 +444,16 @@ public void dispose() { this.unloadAssets(); } - private void spawnScrap() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - - for (int i = 0; i < 50; i++) { - GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); - Entity scrap = DropFactory.createScrapDrop(); - spawnEntityAt(scrap, randomPos, true, false); - } - } +// private void spawnScrap() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < 50; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity scrap = DropFactory.createScrapDrop(); +// spawnEntityAt(scrap, randomPos, true, false); +// } +// } private void spawnIncome() { GridPoint2 minPos = new GridPoint2(0, 0); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java new file mode 100644 index 000000000..1c2cc5863 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -0,0 +1,109 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + + +public class StunTowerCombatTask extends DefaultTask implements PriorityTask { + //constants + private static final int INTERVAL = 1; + private static final short TARGET = PhysicsLayer.NPC; + //Following constants are names of events that will be triggered in the state machine + public static final String IDLE = "startIdle"; + public static final String ATTACK = "startAttack"; + + //Following are the class constants + private final int priority; + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + //enums for the state triggers + private enum STATE { + IDLE, ATTACK + } + private STATE towerState = STATE.IDLE; + + public StunTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + @Override + public void start() { + super.start(); + //get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + //set the default state to IDLE state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 5000); + } + + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + public void updateTowerState() { + switch (towerState) { + case IDLE -> { + if(isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } + } + case ATTACK -> { + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createFireBall(owner.getEntity(), + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.75), + (float) (owner.getEntity().getPosition().y + 0.75)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + } + } + + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + public int getActivePriority() { + return !isTargetVisible() ? 0 : priority; + } + + public int getInactivePriority() { + return isTargetVisible() ? priority : 0; + } + + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java new file mode 100644 index 000000000..8ff908b35 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java @@ -0,0 +1,35 @@ +package com.csse3200.game.components.tower; + +import com.badlogic.gdx.audio.Sound; +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +public class StunTowerAnimationController extends Component { + //Event name constants + private static final String IDLE = "startIdle"; + private static final String ATTACK = "startAttack"; + //animation name constants + private static final String IDLE_ANIM = "idle"; + private static final String ATTACK_ANIM = "attack"; + + //further sounds can be added for the tower attacks/movement + + AnimationRenderComponent animator; + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(IDLE, this::animateIdle); + entity.getEvents().addListener(ATTACK, this::animateAttack); + } + + void animateIdle() { + animator.startAnimation(IDLE_ANIM); + } + + void animateAttack() { + animator.startAnimation(ATTACK_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index c290e4212..90edc11af 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,7 +1,9 @@ package com.csse3200.game.entities.factories; import com.csse3200.game.components.tasks.FireTowerCombatTask; +import com.csse3200.game.components.tasks.StunTowerCombatTask; import com.csse3200.game.components.tower.FireTowerAnimationController; +import com.csse3200.game.components.tower.StunTowerAnimationController; import com.csse3200.game.entities.configs.*; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; @@ -22,6 +24,8 @@ import com.csse3200.game.rendering.TextureRenderComponent; import com.csse3200.game.services.ServiceLocator; +import java.util.ServiceConfigurationError; + /** * Factory to create a tower entity. * @@ -34,7 +38,8 @@ public class TowerFactory { private static final int WEAPON_TOWER_MAX_RANGE = 40; private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; - private static final String FIRE_TURRET_ATLAS = "images/towers/fire_tower_atlas.atlas"; + private static final String FIRE_TOWER_ATLAS = "images/towers/fire_tower_atlas.atlas"; + private static final String STUN_TOWER_ATLAS = "images/towers/stun_tower.atlas"; private static final String IDLE_ANIM = "idle"; private static final float IDLE_SPEED = 0.3f; private static final String DEPLOY_ANIM = "deploy"; @@ -49,7 +54,11 @@ public class TowerFactory { private static final float FIRE_TOWER_PREP_ATTACK_SPEED = 0.2f; private static final String FIRE_TOWER_ATTACK_ANIM = "attack"; private static final float FIRE_TOWER_ATTACK_SPEED = 0.25f; - private static final int INCOME_INTERVAL = 300; + private static final String STUN_TOWER_IDLE_ANIM = "idle"; + private static final float STUN_TOWER_IDLE_SPEED = 0.33f; + private static final String STUN_TOWER_ATTACK_ANIM = "attack"; + private static final float STUN_TOWER_ATTACK_SPEED = 0.12f; + private static final int INCOME_INTERVAL = 300; private static final int INCOME_TASK_PRIORITY = 1; private static final baseTowerConfigs configs = @@ -135,7 +144,7 @@ public static Entity createFireTower() { AnimationRenderComponent animator = new AnimationRenderComponent( ServiceLocator.getResourceService() - .getAsset(FIRE_TURRET_ATLAS, TextureAtlas.class)); + .getAsset(FIRE_TOWER_ATLAS, TextureAtlas.class)); animator.addAnimation(FIRE_TOWER_IDLE_ANIM, FIRE_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); animator.addAnimation(FIRE_TOWER_PREP_ATTACK_ANIM, FIRE_TOWER_PREP_ATTACK_SPEED, Animation.PlayMode.NORMAL); animator.addAnimation(FIRE_TOWER_ATTACK_ANIM, FIRE_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); @@ -147,6 +156,7 @@ public static Entity createFireTower() { .addComponent(animator) .addComponent(new FireTowerAnimationController()); + fireTower.setScale(3, 3); return fireTower; } @@ -154,10 +164,24 @@ public static Entity createStunTower() { Entity stunTower = createBaseTower(); StunTowerConfig config = configs.stunTower; + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new StunTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(STUN_TOWER_ATLAS, TextureAtlas.class)); + animator.addAnimation(STUN_TOWER_IDLE_ANIM, STUN_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(STUN_TOWER_ATTACK_ANIM, STUN_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); + stunTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent((new CostComponent(config.cost))); + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent) + .addComponent(animator) + .addComponent(new StunTowerAnimationController()); + stunTower.setScale(1.5f, 1.5f); return stunTower; }