diff --git a/source/core/assets/images/mobboss/demon.atlas b/source/core/assets/images/mobboss/demon.atlas new file mode 100644 index 000000000..7c058a971 --- /dev/null +++ b/source/core/assets/images/mobboss/demon.atlas @@ -0,0 +1,523 @@ +demon.png +size:2048,2048 +repeat:none +demon_cast_spell +index:5 +bounds:288,1760,288,160 +demon_cast_spell +index:2 +bounds:1440,1600,288,160 +demon_cast_spell +index:4 +bounds:1152,640,288,160 +demon_cast_spell +index:1 +bounds:1728,0,288,160 +demon_cleave +index:6 +bounds:0,1440,288,160 +demon_cleave +index:11 +bounds:576,1600,288,160 +demon_cleave +index:3 +bounds:1728,1760,288,160 +demon_cleave +index:8 +bounds:1152,1120,288,160 +demon_cleave +index:13 +bounds:576,640,288,160 +demon_cleave +index:5 +bounds:1440,800,288,160 +demon_cleave +index:10 +bounds:864,320,288,160 +demon_cleave +index:15 +bounds:1152,0,288,160 +demon_death +index:11 +bounds:0,1280,288,160 +demon_death +index:4 +bounds:0,1120,288,160 +demon_death +index:16 +bounds:576,1280,288,160 +demon_death +index:9 +bounds:0,800,288,160 +demon_death +index:1 +bounds:576,960,288,160 +demon_death +index:21 +bounds:864,1120,288,160 +demon_death +index:22 +bounds:864,1120,288,160 +demon_death +index:13 +bounds:1440,1280,288,160 +demon_death +index:6 +bounds:1440,1120,288,160 +demon_death +index:18 +bounds:1152,800,288,160 +demon_death +index:10 +bounds:288,0,288,160 +demon_death +index:3 +bounds:1152,320,288,160 +demon_death +index:15 +bounds:864,0,288,160 +demon_death +index:8 +bounds:1152,160,288,160 +demon_fire_breath +index:20 +bounds:288,1600,288,160 +demon_fire_breath +index:12 +bounds:0,960,288,160 +demon_fire_breath +index:3 +bounds:864,1280,288,160 +demon_fire_breath +index:17 +bounds:288,800,288,160 +demon_fire_breath +index:8 +bounds:288,640,288,160 +demon_fire_breath +index:14 +bounds:576,480,288,160 +demon_fire_breath +index:5 +bounds:576,320,288,160 +demon_fire_breath +index:19 +bounds:1440,640,288,160 +demon_fire_breath +index:11 +bounds:1728,640,288,160 +demon_fire_breath +index:2 +bounds:1440,0,288,160 +demon_idle +index:3 +bounds:864,1760,288,160 +demon_idle +index:5 +bounds:288,480,288,160 +demon_idle +index:2 +bounds:1152,480,288,160 +demon_smash +index:17 +bounds:0,1760,288,160 +demon_smash +index:6 +bounds:0,1600,288,160 +demon_smash +index:14 +bounds:576,1120,288,160 +demon_smash +index:3 +bounds:1152,1440,288,160 +demon_smash +index:8 +bounds:576,800,288,160 +demon_smash +index:11 +bounds:1152,960,288,160 +demon_smash +index:16 +bounds:288,160,288,160 +demon_smash +index:5 +bounds:864,480,288,160 +demon_smash +index:13 +bounds:1728,320,288,160 +demon_smash +index:2 +bounds:1728,160,288,160 +demon_take_hit +index:2 +bounds:576,1760,288,160 +demon_take_hit +index:4 +bounds:864,960,288,160 +demon_take_hit +index:1 +bounds:1728,960,288,160 +demon_walk +index:8 +bounds:288,1440,288,160 +demon_walk +index:5 +bounds:0,640,288,160 +demon_walk +index:11 +bounds:0,160,288,160 +demon_walk +index:2 +bounds:288,320,288,160 +demon_walk +index:7 +bounds:576,160,288,160 +idle +index:1 +bounds:864,1440,288,160 +idle +index:6 +bounds:1440,1440,288,160 +idle +index:3 +bounds:1440,960,288,160 +move +index:4 +bounds:1152,1760,288,160 +move +index:1 +bounds:1728,1600,288,160 +move +index:6 +bounds:1728,1280,288,160 +move +index:3 +bounds:1440,480,288,160 +projectile_explosion +index:7 +bounds:2016,1888,32,32 +projectile_explosion +index:4 +bounds:2016,1824,32,32 +projectile_explosion +index:11 +bounds:2016,1792,32,32 +projectile_explosion +index:9 +bounds:2016,1760,32,32 +projectile_explosion +index:1 +bounds:2016,1728,32,32 +projectile_explosion +index:6 +bounds:2016,1696,32,32 +projectile_explosion +index:3 +bounds:2016,1632,32,32 +projectile_explosion +index:10 +bounds:2016,1600,32,32 +projectile_explosion +index:8 +bounds:2016,1568,32,32 +projectile_explosion +index:5 +bounds:2016,1504,32,32 +projectile_explosion +index:2 +bounds:2016,1472,32,32 +projectile_idle +index:2 +bounds:2016,1856,32,32 +projectile_idle +index:1 +bounds:2016,1664,32,32 +projectile_idle +index:3 +bounds:2016,1536,32,32 +take_hit +index:5 +bounds:1440,1760,288,160 +take_hit +index:2 +bounds:0,320,288,160 +take_hit +index:4 +bounds:1728,480,288,160 +transform +index:2 +bounds:288,1280,288,160 +transform +index:31 +bounds:576,1440,288,160 +transform +index:18 +bounds:864,1600,288,160 +transform +index:10 +bounds:288,1120,288,160 +transform +index:23 +bounds:1152,1600,288,160 +transform +index:7 +bounds:288,960,288,160 +transform +index:15 +bounds:1152,1280,288,160 +transform +index:28 +bounds:0,480,288,160 +transform +index:20 +bounds:1728,1440,288,160 +transform +index:4 +bounds:864,800,288,160 +transform +index:12 +bounds:864,640,288,160 +transform +index:25 +bounds:1728,1120,288,160 +transform +index:9 +bounds:0,0,288,160 +transform +index:1 +bounds:1728,800,288,160 +transform +index:30 +bounds:576,0,288,160 +transform +index:17 +bounds:864,160,288,160 +transform +index:22 +bounds:1440,320,288,160 +transform +index:6 +bounds:1440,160,288,160 + +demon2.png +size:2048,2048 +repeat:none +demon_cast_spell +index:6 +bounds:288,1280,288,160 +demon_cast_spell +index:3 +bounds:1152,1120,288,160 +demon_cleave +index:2 +bounds:0,1760,288,160 +demon_cleave +index:7 +bounds:576,1440,288,160 +demon_cleave +index:12 +bounds:288,960,288,160 +demon_cleave +index:4 +bounds:864,960,288,160 +demon_cleave +index:9 +bounds:288,320,288,160 +demon_cleave +index:14 +bounds:576,320,288,160 +demon_death +index:20 +bounds:288,1600,288,160 +demon_death +index:12 +bounds:0,960,288,160 +demon_death +index:5 +bounds:576,1280,288,160 +demon_death +index:17 +bounds:576,960,288,160 +demon_death +index:2 +bounds:1728,1440,288,160 +demon_death +index:14 +bounds:864,640,288,160 +demon_death +index:7 +bounds:1152,800,288,160 +demon_death +index:19 +bounds:1440,640,288,160 +demon_fire_breath +index:16 +bounds:0,1440,288,160 +demon_fire_breath +index:7 +bounds:576,1600,288,160 +demon_fire_breath +index:21 +bounds:864,1600,288,160 +demon_fire_breath +index:13 +bounds:0,640,288,160 +demon_fire_breath +index:4 +bounds:0,480,288,160 +demon_fire_breath +index:18 +bounds:0,320,288,160 +demon_fire_breath +index:9 +bounds:1440,1120,288,160 +demon_fire_breath +index:10 +bounds:0,160,288,160 +demon_fire_breath +index:1 +bounds:1440,800,288,160 +demon_cleave +index:1 +bounds:1440,800,288,160 +demon_fire_breath +index:15 +bounds:864,320,288,160 +demon_fire_breath +index:6 +bounds:864,160,288,160 +demon_idle +index:4 +bounds:1152,1600,288,160 +demon_idle +index:1 +bounds:288,480,288,160 +demon_idle +index:6 +bounds:0,0,288,160 +demon_smash +index:18 +bounds:864,1760,288,160 +demon_smash +index:7 +bounds:0,1120,288,160 +demon_smash +index:10 +bounds:864,1280,288,160 +demon_smash +index:15 +bounds:288,640,288,160 +demon_smash +index:4 +bounds:576,800,288,160 +demon_smash +index:9 +bounds:1728,1280,288,160 +demon_smash +index:12 +bounds:864,480,288,160 +demon_smash +index:1 +bounds:1152,640,288,160 +demon_take_hit +index:3 +bounds:1152,1760,288,160 +demon_take_hit +index:5 +bounds:576,480,288,160 +demon_walk +index:4 +bounds:288,1760,288,160 +demon_walk +index:9 +bounds:288,1120,288,160 +demon_walk +index:10 +bounds:1440,1600,288,160 +demon_walk +index:1 +bounds:1728,1760,288,160 +demon_walk +index:6 +bounds:1440,1280,288,160 +demon_walk +index:12 +bounds:288,0,288,160 +demon_walk +index:3 +bounds:576,160,288,160 +idle +index:5 +bounds:0,1280,288,160 +idle +index:2 +bounds:864,1120,288,160 +idle +index:4 +bounds:1728,800,288,160 +move +index:8 +bounds:0,1600,288,160 +move +index:5 +bounds:1152,1440,288,160 +move +index:2 +bounds:864,800,288,160 +move +index:7 +bounds:1728,960,288,160 +take_hit +index:1 +bounds:864,1440,288,160 +take_hit +index:6 +bounds:1440,1440,288,160 +take_hit +index:3 +bounds:1728,1120,288,160 +transform +index:14 +bounds:576,1760,288,160 +transform +index:27 +bounds:288,1440,288,160 +transform +index:3 +bounds:1440,1760,288,160 +transform +index:32 +bounds:0,800,288,160 +transform +index:19 +bounds:576,1120,288,160 +transform +index:11 +bounds:288,800,288,160 +transform +index:24 +bounds:1152,1280,288,160 +transform +index:8 +bounds:1728,1600,288,160 +transform +index:16 +bounds:576,640,288,160 +transform +index:29 +bounds:1152,960,288,160 +transform +index:21 +bounds:1440,960,288,160 +transform +index:5 +bounds:288,160,288,160 +transform +index:13 +bounds:1152,480,288,160 +transform +index:26 +bounds:576,0,288,160 +default +index:3 +bounds:864,1760,288,160 diff --git a/source/core/assets/images/mobboss/demon.png b/source/core/assets/images/mobboss/demon.png new file mode 100644 index 000000000..7d7141a31 Binary files /dev/null and b/source/core/assets/images/mobboss/demon.png differ diff --git a/source/core/assets/images/mobboss/demon2.png b/source/core/assets/images/mobboss/demon2.png new file mode 100644 index 000000000..1e570a1b4 Binary files /dev/null and b/source/core/assets/images/mobboss/demon2.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 f292cb629..2e01f8254 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -105,6 +105,8 @@ public class ForestGameArea extends GameArea { "images/projectiles/firework_anim.png", "images/projectiles/pierce_anim.png", "images/projectiles/snow_ball.png", + "images/mobboss/demon.png", + "images/mobboss/demon2.png", "images/mobs/fire_worm.png" }; private static final String[] forestTextureAtlases = { @@ -138,6 +140,7 @@ public class ForestGameArea extends GameArea { "images/projectiles/firework_anim.atlas", "images/projectiles/mobProjectile.atlas", "images/projectiles/stun_effect.atlas", + "images/mobboss/demon.atlas", "images/mobs/fire_worm.atlas", "images/mobs/dragon_knight.atlas" }; @@ -231,30 +234,32 @@ public void create() { player = spawnPlayer(); player.getEvents().addListener("spawnWave", this::spawnFireWorm); - playMusic(); + //playMusic(); // Types of projectile // spawnAoeProjectile(new Vector2(0, 10), player, towardsMobs, new Vector2(2f, 2f), 1); - spawnProjectile(new Vector2(0, 10), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); - spawnMultiProjectile(new Vector2(0, 10), PhysicsLayer.NPC, towardsMobs, 20, new Vector2(2f, 2f), 7); - spawnEffectProjectile(new Vector2(0, 10), PhysicsLayer.HUMANS, towardsMobs, new Vector2(2f, 2f), ProjectileEffects.BURN, true); - spawnPierceFireBall(new Vector2(2, 3), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); - spawnRicochetFireball(new Vector2(2, 4), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); - spawnSplitFireWorksFireBall(new Vector2(2, 5), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f), 12); - spawnEffectProjectile(new Vector2(2, 6), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f), ProjectileEffects.SLOW, false); - // spawnXenoGrunts(); - // spawnFireWorm(); - spawnWeaponTower(); - //mobBoss1 = spawnMobBoss1(); - startWaveTimer(); -// spawnIncome(); - spawnScrap(); - spawnTNTTower(); - - spawnGapScanners(); - spawnDroidTower(); +// spawnProjectile(new Vector2(0, 10), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); +// spawnMultiProjectile(new Vector2(0, 10), PhysicsLayer.NPC, towardsMobs, 20, new Vector2(2f, 2f), 7); +// spawnEffectProjectile(new Vector2(0, 10), PhysicsLayer.HUMANS, towardsMobs, new Vector2(2f, 2f), ProjectileEffects.BURN, true); +// spawnPierceFireBall(new Vector2(2, 3), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); +// spawnRicochetFireball(new Vector2(2, 4), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); +// spawnSplitFireWorksFireBall(new Vector2(2, 5), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f), 12); +// spawnEffectProjectile(new Vector2(2, 6), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f), ProjectileEffects.SLOW, false); +// spawnXenoGrunts(); +// spawnWeaponTower(); + + spawnDemonBoss(); - mobBoss2 = spawnMobBoss2(); + //mobBoss1 = spawnMobBoss1(); +// startWaveTimer(); +//// spawnIncome(); +// spawnScrap(); +// spawnTNTTower(); +// +// spawnGapScanners(); +// spawnDroidTower(); +// +// mobBoss2 = spawnMobBoss2(); } @@ -389,6 +394,10 @@ private Entity spawnPlayer(GridPoint2 position) { // return ghostKing; // } + private void spawnDemonBoss() { + Entity demon = MobBossFactory.createDemonBoss(); + spawnEntityAt(demon, new GridPoint2(19, 4), true, false); + } private Entity spawnMobBoss1() { int[] pickedLanes = new Random().ints(0, 8) diff --git a/source/core/src/main/com/csse3200/game/components/bosses/DemonAnimationController.java b/source/core/src/main/com/csse3200/game/components/bosses/DemonAnimationController.java new file mode 100644 index 000000000..90246415e --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/bosses/DemonAnimationController.java @@ -0,0 +1,39 @@ +package com.csse3200.game.components.bosses; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +public class DemonAnimationController extends Component { + AnimationRenderComponent animator; + + /** + * Creation call for a TowerAnimationController, 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 = entity.getComponent(AnimationRenderComponent.class); + registerAnimationListener("demon_walk"); + registerAnimationListener("demon_cleave"); + registerAnimationListener("demon_take_hit"); + registerAnimationListener("demon_idle"); + registerAnimationListener("demon_death"); + registerAnimationListener("demon_cast_spell"); + registerAnimationListener("demon_fire_breath"); + registerAnimationListener("demon_smash"); + registerAnimationListener("demon_take_hit"); + registerAnimationListener("idle"); + registerAnimationListener("move"); + registerAnimationListener("projectile_explosion"); + registerAnimationListener("projectile_idle"); + registerAnimationListener("take_hit"); + registerAnimationListener("transform"); + } + + private void registerAnimationListener(String animationName) { + entity.getEvents().addListener(animationName, () -> animator.startAnimation(animationName)); + } + + +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/DemonBossMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/DemonBossMovementTask.java deleted file mode 100644 index 8c31befaa..000000000 --- a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/DemonBossMovementTask.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.csse3200.game.components.tasks.bosstask; - -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.tasks.MovementTask; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DemonBossMovementTask extends DefaultTask implements PriorityTask { - - private static final Logger logger = LoggerFactory.getLogger(DemonBossMovementTask.class); - private static final int PRIORITY = 3; - private Vector2 currentPos; - private MovementTask movementTask; - - private enum STATE { - WALK, JUMP, IDLE - } - private STATE demonState = STATE.IDLE; - - public DemonBossMovementTask() { - - } - @Override - public int getPriority() { - return PRIORITY; - } -} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/DemonBossTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/DemonBossTask.java new file mode 100644 index 000000000..3152d4a33 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/DemonBossTask.java @@ -0,0 +1,249 @@ +package com.csse3200.game.components.tasks.bosstask; + +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.TimeUtils; +import com.badlogic.gdx.utils.Timer; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.ProjectileEffects; +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.components.PhysicsMovementComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.badlogic.gdx.math.MathUtils; + +public class DemonBossTask extends DefaultTask implements PriorityTask { + + // Constants + private static final int PRIORITY = 3; + private static final Vector2 DEMON_JUMP_SPEED = new Vector2(2f, 2f); + private static final float STOP_DISTANCE = 0.1f; + private static final int BURN_BALLS = 5; + private static final int X_LENGTH = 20; // for projectile destination calculations + private static final float JUMP_DISTANCE = 3.0f; + private static final int xRightBoundary = 17; + private static final int xLeftBoundary = 1; + private static final int yTopBoundary = 6; + private static final int yBotBoundary = 1; + + // Private variables + private static final Logger logger = LoggerFactory.getLogger(DemonBossTask.class); + private Vector2 currentPos; + private final PhysicsEngine physics; + private final GameTime gameTime; + private Vector2 jumpPos; + private MovementTask jumpTask; + private boolean isJumping; + private DemonState state = DemonState.IDLE; + private DemonState prevState; + private AnimationRenderComponent animation; + private Entity demon; + private boolean waitFlag = false; + private boolean timerFlag; + private int numBalls = 5; + private ProjectileEffects effect = ProjectileEffects.FIREBALL; + private boolean aoe = true; + + // Enums + private enum AnimState { + TRANSFORM(6.4f), + CLEAVE(3f), + DEATH(4.4f), + BREATH(4.2f), + SMASH(3.6f), + TAKE_HIT(1f); + + private final float duration; + + private AnimState(float duration) { + this.duration = duration; + } + + public float getDuration() { + return duration; + } + } + + private enum DemonState { + TRANSFORM, IDLE, CAST, CLEAVE, DEATH, BREATH, SMASH, TAKE_HIT, WALK + } + + public DemonBossTask() { + physics = ServiceLocator.getPhysicsService().getPhysics(); + gameTime = ServiceLocator.getTimeSource(); + } + + @Override + public void start() { + super.start(); + demon = owner.getEntity(); + animation = owner.getEntity().getComponent(AnimationRenderComponent.class); // get animation + currentPos = owner.getEntity().getPosition(); // get current position + + // Handle initial transform animation + changeState(DemonState.TRANSFORM); + animate(); + if (animation.getCurrentAnimation().equals("transform")) { + Timer.schedule(new Timer.Task() { + @Override + public void run() { + changeState(DemonState.IDLE); + } + }, 6.4f); // Delay in seconds + } + } + + @Override + public void update() { + animate(); + currentPos = demon.getPosition(); + + switch (state) { + case IDLE -> { jump(getJumpPos()); } + case SMASH -> { + if (jumpComplete()) { + fireBreath(); + } + } + case BREATH -> { + if (!timerFlag) { + waitTask(AnimState.BREATH.getDuration()); + timerFlag = true; + } + if (!waitFlag) { + changeState(DemonState.IDLE); + timerFlag = false; + } + } + } + } + + /** + * Starts a timer when called and returns true. When timer is complete, + * false will be returned + * @param duration time to set the timer for + * @return true or false depending if the timer is on + */ + private void waitTask(float duration) { + waitFlag = true; + Timer.schedule(new Timer.Task() { + @Override + public void run() { + waitFlag = false; + } + }, duration); + } + private void changeState(DemonState state) { + prevState = this.state; + this.state = state; + } + + private void animate() { + // Check if same animation is being called + if (prevState.equals(state)) { + return; // skip rest of function + } + + switch (state) { + case CAST -> {demon.getEvents().trigger("demon_cast_spell");} + case IDLE -> demon.getEvents().trigger("demon_idle"); + case WALK -> demon.getEvents().trigger("demon_walk"); + case DEATH -> demon.getEvents().trigger("demon_death"); + case BREATH -> demon.getEvents().trigger("demon_fire_breath"); + case SMASH -> demon.getEvents().trigger("demon_smash"); + case CLEAVE -> demon.getEvents().trigger("demon_cleave"); + case TAKE_HIT -> demon.getEvents().trigger("demon_take_hit"); + case TRANSFORM -> demon.getEvents().trigger("transform"); + default -> logger.debug("Demon animation {state} not found"); + } + + prevState = state; + } + + @Override + public int getPriority() { + return PRIORITY; + } + + private void jump(Vector2 finalPos) { + changeState(DemonState.SMASH); + isJumping = true; + + jumpTask = new MovementTask(finalPos); + jumpTask.create(owner); + demon.getComponent(PhysicsMovementComponent.class).setSpeed(DEMON_JUMP_SPEED); + jumpTask.start(); + + logger.debug("Demon jump starting"); + } + + private Vector2 getJumpPos() { + float randomAngle = MathUtils.random(0, 2 * MathUtils.PI); + float x = JUMP_DISTANCE * MathUtils.cos(randomAngle); + float y = JUMP_DISTANCE * MathUtils.sin(randomAngle); + + // check boundaries + if (x + currentPos.x > xRightBoundary || x + currentPos.x < xLeftBoundary) { x *= -1; } + if (y + currentPos.y > yTopBoundary || y + currentPos.y < yBotBoundary) { y *= -1; } + + // get final jump position + float finalX = x + currentPos.x; + float finalY = y + currentPos.y; + jumpPos = new Vector2(finalX, finalY); + return jumpPos; + } + + private boolean isAtTarget() { + return currentPos.dst(jumpPos) <= STOP_DISTANCE; + } + + private boolean jumpComplete() { + if (isAtTarget() && isJumping) { + isJumping = false; + jumpTask.stop(); + return true; + } + return false; + } + + public void changeBreathAttack(int numBalls, ProjectileEffects effect, boolean aoe) { + this.numBalls = numBalls; + this.effect = effect; + this.aoe = aoe; + } + + private void fireBreath() { + changeState(DemonState.BREATH); + + long delay = (long) AnimState.BREATH.getDuration() / numBalls; + + float startAngle = (float) Math.toRadians(135); + float endAngle = (float) Math.toRadians(225); + float angleIncrement = (endAngle - startAngle) / (numBalls - 1); + + // spawn projectiles + for (int i = 0; i < numBalls; i++) { + // calculate unit vectors for projectiles + float currentAngle = startAngle + i * angleIncrement; + float x = MathUtils.cos(currentAngle) * 20; + float y = MathUtils.sin(currentAngle) * 20; + Vector2 destination = new Vector2(x, y); + + // Create burn projectiles + Entity projectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.HUMANS, destination, + new Vector2(2, 2), effect, aoe); + projectile.setPosition(demon.getPosition().x, demon.getPosition().y); + ServiceLocator.getEntityService().register(projectile); + } + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/factories/MobBossFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/MobBossFactory.java index 2b1258645..855b28196 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/MobBossFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/MobBossFactory.java @@ -4,9 +4,10 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.*; +import com.csse3200.game.components.bosses.DemonAnimationController; import com.csse3200.game.components.npc.Boss1AnimationController; import com.csse3200.game.components.npc.Boss2AnimationController; -import com.csse3200.game.components.tasks.bosstask.DemonBossMovementTask; +import com.csse3200.game.components.tasks.bosstask.DemonBossTask; import com.csse3200.game.components.tasks.bosstask.FinalBossMovementTask; import com.csse3200.game.components.tasks.bosstask.RangeBossTask; import com.csse3200.game.components.tasks.bosstask.bossDeathTask; @@ -25,20 +26,47 @@ public class MobBossFactory { private static final NPCConfigs configs = FileLoader.readClass(NPCConfigs.class, "configs/Boss.json"); private static final int PRIORITY = 1; private static final int BOSS_MOB_AGRO_RANGE = 10; + private static final int DEMON_HEALTH = 10000; + private static final int DEMON_ATTACK = 50; // Create Demon Boss - private static Entity createDemonBoss() { + public static Entity createDemonBoss() { MobBossConfigs config = configs.MobBoss; Entity demon = createBaseBoss(); // Animation addition + AnimationRenderComponent animator = new AnimationRenderComponent( + ServiceLocator.getResourceService().getAsset("images/mobboss/demon.atlas", TextureAtlas.class)); + animator.addAnimation("demon_cast_spell", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_cleave", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_death", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_fire_breath", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_idle", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_smash", 0.10f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_take_hit", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("demon_walk", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("idle", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("move", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("projectile_explosion", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("projectile_idle", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("take_hit", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("transform", 0.2f, Animation.PlayMode.LOOP); // AI task addition AITaskComponent aiTaskComponent = new AITaskComponent() - .addTask(new DemonBossMovementTask()); + .addTask(new DemonBossTask()); // Component addition - + demon + .addComponent(animator) + .addComponent(new DemonAnimationController()) + .addComponent(aiTaskComponent) + .addComponent(new CombatStatsComponent(DEMON_HEALTH, DEMON_ATTACK)); + + // Scale demon + demon.getComponent(AnimationRenderComponent.class).scaleEntity(); + demon.scaleHeight(5f); + demon.scaleWidth(5f); return demon; }