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 aabb2a480..19428756f 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.areas.terrain.TerrainFactory; import com.csse3200.game.areas.terrain.TerrainFactory.TerrainType; +import com.csse3200.game.components.ProjectileEffects; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.*; import com.csse3200.game.utils.math.RandomUtils; @@ -257,7 +258,40 @@ public void create() { // Set up infrastructure for end game tracking player = spawnPlayer(); - logger.info("Lol"); + //player.getEvents().addListener("spawnWave", this::spawnWave); + //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(); + // spawnWeaponTower(); + + // spawnDragonKnight(); + // spawnFireWorm(19, 5); // * TEMPORARY for testing + spawnFireTowerTowerAt(3, 1); + spawnFireTowerTowerAt(3, 2); + spawnFireTowerTowerAt(3, 3); + spawnFireTowerTowerAt(3, 4); + spawnFireTowerTowerAt(3, 5); +// spawnDroidTowerAt(3, 1); +// spawnDroidTowerAt(3, 2); +// spawnDroidTowerAt(3, 3); +// spawnDroidTowerAt(3, 4); +// spawnDroidTowerAt(3, 5); + // spawnSplittingXenoGrunt(15, 5); +// spawnDeflectXenoGrunt(15, 5); + // spawnSplittingXenoGrunt(15, 4); + // spawnSplittingXenoGrunt(15, 5); + // spawnDodgingDragonKnight(15, 3); + spawnDemonBoss(); + spawnPatrick(); spawnIceBaby(); player.getEvents().addListener("spawnWave", this::spawnWave); playMusic(); @@ -269,10 +303,19 @@ public void create() { spawnScrap(); spawnTNTTower(); spawnWeaponTower(); - logger.info("Lol"); spawnGapScanners(); spawnDroidTower(); - logger.info("Lol"); +// spawnPatrickDeath(); + // spawnFireWorm(); + + // startWaveTimer(); +//// spawnIncome(); +// spawnScrap(); + spawnTNTTower(); +// +// spawnGapScanners(); +// spawnDroidTower(); +// } private void displayUI() { @@ -414,30 +457,20 @@ private void spawnDemonBoss() { } private void spawnPatrick() { - Entity patrick = MobBossFactory.createPatrickBoss(2500); + Entity patrick = MobBossFactory.createPatrickBoss(); spawnEntityAt(patrick, new GridPoint2(18, 5), true, false); } + private void spawnPatrickDeath() { + Entity patrickDeath = MobBossFactory.patrickDead(); + spawnEntityAt(patrickDeath, new GridPoint2(18, 5), true, false); + } + private void spawnIceBaby() { Entity iceBaby = MobBossFactory.createIceBoss(); spawnEntityAt(iceBaby, new GridPoint2(19, 5), true, false); } - private Entity spawnMobBoss1() { - int[] pickedLanes = rand.ints(0, 8) - .distinct().limit(5).toArray(); - for (int i = 0; i < NUM_MOBBOSS1; i++) { - GridPoint2 randomPos = new GridPoint2(19, pickedLanes[i]); - mobBoss1 = MobBossFactory.createMobBoss1(pickedLanes[i]); - spawnEntityAt(mobBoss1, - randomPos, - true, - false); - } - return mobBoss1; - } - - /** * Spawns a projectile that only heads towards the enemies in its lane. * @@ -593,6 +626,91 @@ private void spawnWaterSlime() { // return ghostKing; // // } + + /** + * Creates multiple projectiles that travel simultaneous. They all have same + * the starting point but different destinations. + * + * @param position The position of the Entity that's shooting the projectile. + * @param targetLayer The enemy layer of the "shooter". + * @param direction The direction the projectile should head towards. + * @param space The space between the projectiles' destination. + * @param speed The speed of the projectiles. + * @param quantity The amount of projectiles to spawn. + */ + 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; + } + } + + /** + * Returns projectile that can do an area of effect damage + * + * @param position The position of the Entity that's shooting the projectile. + * @param targetLayer The enemy layer of the "shooter". + * @param direction The direction the projectile should head towards. + * @param speed The speed of the projectiles. + * @param effect Type of effect. + * @param aoe Whether it is an aoe projectile. + */ + private void spawnEffectProjectile(Vector2 position, short targetLayer, int direction, Vector2 speed, + ProjectileEffects effect, boolean aoe) { + Entity Projectile = ProjectileFactory.createEffectProjectile(targetLayer, new Vector2(direction, position.y), speed, effect, aoe); + Projectile.setPosition(position); + spawnEntity(Projectile); + } + + /** + * Spawns a pierce fireball. + * Pierce fireball can go through targetlayers without disappearing but damage + * will still be applied. + * + * @param position The position of the Entity that's shooting the projectile. + * @param targetLayer The enemy layer of the "shooter". + * @param direction The direction the projectile should head towards. + * @param speed The speed of the projectiles. + */ + private void spawnPierceFireBall(Vector2 position, short targetLayer, int direction, Vector2 speed) { + Entity projectile = ProjectileFactory.createPierceFireBall(targetLayer, new Vector2(direction, position.y), speed); + projectile.setPosition(position); + spawnEntity(projectile); + } + + /** + * Spawns a ricochet fireball + * Ricochet fireballs bounce off targets with a specified maximum count of 3 + * Possible extensions: Make the bounce count flexible with a param. + * + * @param position The position of the Entity that's shooting the projectile. + * @param targetLayer The enemy layer of the "shooter". + * @param direction The direction the projectile should head towards. + * @param speed The speed of the projectiles. + */ + private void spawnRicochetFireball(Vector2 position, short targetLayer, int direction, Vector2 speed) { + // Bounce count set to 0. + Entity projectile = ProjectileFactory.createRicochetFireball(targetLayer, new Vector2(direction, position.y), speed, 0); + projectile.setPosition(position); + spawnEntity(projectile); + } + + /** + * Spawns a split firework fireball. + * Splits into mini projectiles that spreads out after collision. + * + * @param position The position of the Entity that's shooting the projectile. + * @param targetLayer The enemy layer of the "shooter". + * @param direction The direction the projectile should towards. + * @param speed The speed of the projectiles. + * @param amount The amount of projectiles appearing after collision. + */ + private void spawnSplitFireWorksFireBall(Vector2 position, short targetLayer, int direction, Vector2 speed, int amount) { + Entity projectile = ProjectileFactory.createSplitFireWorksFireball(targetLayer, new Vector2(direction, position.y), speed, amount); + projectile.setPosition(position); + spawnEntity(projectile); + } private void spawnWeaponTower() { GridPoint2 minPos = new GridPoint2(0, 2); diff --git a/source/core/src/main/com/csse3200/game/components/npc/DodgingComponent.java b/source/core/src/main/com/csse3200/game/components/npc/DodgingComponent.java index bada67e3c..40c79a03c 100644 --- a/source/core/src/main/com/csse3200/game/components/npc/DodgingComponent.java +++ b/source/core/src/main/com/csse3200/game/components/npc/DodgingComponent.java @@ -37,6 +37,7 @@ public class DodgingComponent extends Component { // top and bottom detection is also taken care of, ensuring the entity will // dodge. private static final float Y_OFFSET_MOB_DETECTION = 0.35f; + public static final String DODGE_EVENT = "dodgeIncomingEntity"; /** * Initialises a component that dodges an incoming entity based on its target @@ -78,7 +79,7 @@ public DodgingComponent(short targetLayer, float rangeDetection, float dodgeSpee @Override public void create() { physics = ServiceLocator.getPhysicsService().getPhysics(); - entity.getEvents().addListener("dodgeIncomingEntity", this::changeTraverseDirection); + entity.getEvents().addListener(DODGE_EVENT, this::changeTraverseDirection); originalSpeed = entity.getComponent(PhysicsMovementComponent.class).getSpeed().y; } 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 index 4bb34ffc4..bef595f7f 100644 --- 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 @@ -38,7 +38,7 @@ public class DemonBossTask extends DefaultTask implements PriorityTask { private static final int Y_BOT_BOUNDARY = 1; private static final int BREATH_ANIM_TIME = 2; private static final int SMASH_RADIUS = 3; - private static final float MOVE_FORWARD_DELAY = 30; + private static final int MOVE_FORWARD_DELAY = 15; private static final float BREATH_DURATION = 4.2f; private static final int SMASH_DAMAGE = 30; private static final int CLEAVE_DAMAGE = 50; @@ -56,7 +56,7 @@ public class DemonBossTask extends DefaultTask implements PriorityTask { private DemonState prevState; private AnimationRenderComponent animation; private Entity demon; - private int numBalls = 6; + private int numBalls = 3; private static int xRightBoundary = 17; private static int xLeftBoundary = 12; private ProjectileEffects effect = ProjectileEffects.BURN; @@ -72,9 +72,7 @@ public class DemonBossTask extends DefaultTask implements PriorityTask { * The different demon states. */ private enum DemonState { - TRANSFORM, IDLE, CAST, CLEAVE, DEATH, BREATH, SMASH, TAKE_HIT, - WALK, TRANSFORM_REVERSE, SLIME_IDLE, SLIME_MOVE, PROJECTILE_EXPLOSION, - PROJECTILE_IDLE, SLIME_TAKE_HIT + TRANSFORM, IDLE, CAST, CLEAVE, DEATH, BREATH, SMASH, TAKE_HIT, WALK } /** @@ -217,12 +215,6 @@ private void animate() { case CLEAVE -> demon.getEvents().trigger("demon_cleave"); case TAKE_HIT -> demon.getEvents().trigger("demon_take_hit"); case TRANSFORM -> demon.getEvents().trigger("transform"); - case TRANSFORM_REVERSE -> demon.getEvents().trigger("transform_reverse"); - case SLIME_IDLE -> demon.getEvents().trigger("idle"); - case SLIME_MOVE -> demon.getEvents().trigger("move"); - case SLIME_TAKE_HIT -> demon.getEvents().trigger("take_hit"); - case PROJECTILE_IDLE -> demon.getEvents().trigger("projectile_explosion"); - case PROJECTILE_EXPLOSION -> demon.getEvents().trigger("projectile_idle"); default -> logger.debug("Demon animation {state} not found"); } prevState = state; @@ -285,7 +277,7 @@ private void jump(Vector2 finalPos) { /** * Returns a random position 3 units away for the demon to jump to. - * + * * @return a position 3 units away from the demon to jump to */ private Vector2 getJumpPos() { @@ -295,6 +287,12 @@ private Vector2 getJumpPos() { return jumpPos; } + // jump backwards if right next to tower + if (currentPos.dst(ServiceLocator.getEntityService().getClosestEntityOfLayer( + demon, PhysicsLayer.HUMANS).getPosition()) < 2f) { + jumpPos = new Vector2(currentPos.x + JUMP_DISTANCE, currentPos.y); + } + float randomAngle = MathUtils.random(0, 2 * MathUtils.PI); float x = JUMP_DISTANCE * MathUtils.cos(randomAngle); float y = JUMP_DISTANCE * MathUtils.sin(randomAngle); @@ -316,7 +314,7 @@ private Vector2 getJumpPos() { /** * Returns a boolean to confirm whether the demon has completed a jump or not. - * + * * @return if demon has completed jump or not */ private boolean jumpComplete() { @@ -394,30 +392,7 @@ private void applyAoeDamage(Array targets, int damage) { } /** - * Returns the closest human entity from a given array. - * - * @param targets array of human entities - * @return closest human entity - */ - private Entity getClosestHuman(Array targets) { - Entity closestEntity = null; - float closestDistance = SMASH_RADIUS; - - for (int i = 0; i < targets.size; i++) { - Entity targetEntity = targets.get(i); - Vector2 targetPosition = targetEntity.getPosition(); - float distance = currentPos.dst(targetPosition); - - if (distance < closestDistance) { - closestEntity = targetEntity; - closestDistance = distance; - } - } - return closestEntity; - } - - /** - * Change state to cleave and deals damage to target. + * Change state to cleave and deals damage to target */ private void cleave() { changeState(DemonState.CLEAVE); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickDeathTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickDeathTask.java new file mode 100644 index 000000000..b68f60400 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickDeathTask.java @@ -0,0 +1,45 @@ +package com.csse3200.game.components.tasks.bosstask; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Timer; +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.MobBossFactory; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +public class PatrickDeathTask extends DefaultTask implements PriorityTask { + + private boolean startFlag = false; + private static final int PRIORITY = 3; + + /** + * What is run when patrick's death task is assigned + */ + @Override + public void start() { + super.start(); + startFlag = true; + owner.getEntity().getEvents().trigger("patrick_death"); + } + + /** + * What is run every frame + */ + @Override + public void update() { + if (startFlag && owner.getEntity().getComponent(AnimationRenderComponent.class). + isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + + /** + * @return priority of task + */ + @Override + public int getPriority() { + return PRIORITY; + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickTask.java index bbfb4d4e0..9121e0c60 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/PatrickTask.java @@ -8,6 +8,7 @@ import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.ProjectileEffects; import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.MobBossFactory; import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.PhysicsLayer; @@ -15,9 +16,14 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import net.dermetfan.gdx.physics.box2d.PositionController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Patrick boss task that controls the boss' sequence and actions based on a predetermined sequence + * and the boss' current hp + */ public class PatrickTask extends DefaultTask implements PriorityTask { // Constants @@ -56,11 +62,17 @@ private enum PatrickState { IDLE, WALK, ATTACK, HURT, DEATH, SPELL, APPEAR } + /** + * Constructor for PatrickTask + */ public PatrickTask() { physics = ServiceLocator.getPhysicsService().getPhysics(); gameTime = ServiceLocator.getTimeSource(); } + /** + * What is called when the patrick task is assigned + */ @Override public void start() { super.start(); @@ -80,6 +92,9 @@ public void run() { }, 0.1f); } + /** + * Updates the sequence every frame + */ @Override public void update() { // give game time to load @@ -98,6 +113,16 @@ public void update() { } } + // check if patrick is dead + if (patrick.getComponent(CombatStatsComponent.class).getHealth() <= 0) { + // play patrick death animation + Entity deadPatrick = MobBossFactory.patrickDead(); + deadPatrick.setPosition(patrick.getPosition().x, patrick.getPosition().y); + deadPatrick.setScale(4f, 4f); + ServiceLocator.getEntityService().register(deadPatrick); + patrick.setFlagForDelete(true); + } + animate(); int health = patrick.getComponent(CombatStatsComponent.class).getHealth(); @@ -173,6 +198,9 @@ private void animate() { prevState = state; } + /** + * @return priority of this task + */ @Override public int getPriority() { return PRIORITY; @@ -196,6 +224,10 @@ private ProjectileEffects getEffect() { } } + /** + * Teleports patrick to the position given + * @param pos position for patrick to teleport to + */ private void teleport(Vector2 pos) { teleportTask = new PatrickTeleportTask(patrick, pos); teleportTask.create(owner); @@ -203,6 +235,9 @@ private void teleport(Vector2 pos) { teleportFlag = true; } + /** + * Patrick teleports to the closest human entity and attacks it + */ private void meleeAttack() { initialPos = patrick.getPosition(); meleeTarget = ServiceLocator.getEntityService().getClosestEntityOfLayer( @@ -211,17 +246,24 @@ private void meleeAttack() { meleeFlag = true; } - private void spawnRandProjectile(Vector2 destination) { + /** + * spawns a random effect projectile and increments the shots fired counter + * @param destination destination for projectile to travel to + */ + private void spawnRandProjectile(Vector2 destination, boolean aoe) { // spawn random projectile Entity projectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.HUMANS, destination, new Vector2(2, 2), - getEffect(), false); + getEffect(), aoe); projectile.setPosition(patrick.getPosition().x, patrick.getPosition().y); projectile.setScale(-1f, 1f); ServiceLocator.getEntityService().register(projectile); shotsFired++; } + /** + * teleports to a random location within given range + */ private void randomTeleport() { // teleport to random position float randomX = MathUtils.random(RANGE_MIN_X, RANGE_MAX_X); @@ -229,25 +271,30 @@ private void randomTeleport() { teleport(new Vector2(randomX, randomY)); } + /** + * performs a random teleport and a range attack + */ private void rangeAttack() { randomTeleport(); - spawnRandProjectile(new Vector2(0f, patrick.getPosition().y)); + spawnRandProjectile(new Vector2(0f, patrick.getPosition().y), false); } + /** + * when patrick is at half health, he spawns a bunch of random aoe effect projectiles + */ private void halfHealth() { float startAngle = (float) Math.toRadians(135); float endAngle = (float) Math.toRadians(225); float angleIncrement = (endAngle - startAngle) / (HALF_HEALTH_ATTACKS - 1); for (int i = 0; i < HALF_HEALTH_ATTACKS; i++) { - randomTeleport(); // 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); - spawnRandProjectile(destination); + spawnRandProjectile(destination, true); } if (shotsFired == HALF_HEALTH_ATTACKS) { meleeFlag = true; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/SlimeyBoyTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/SlimeyBoyTask.java index 5cef0c8ec..7174f7a05 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/bosstask/SlimeyBoyTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/bosstask/SlimeyBoyTask.java @@ -31,10 +31,16 @@ public class SlimeyBoyTask extends DefaultTask implements PriorityTask { private SlimeState prevState; private Entity targetEntity; + /** + * States that the slime cycles through + */ private enum SlimeState { IDLE, MOVE, PROJECTILE_EXPLOSION, PROJECTILE_IDLE, TAKE_HIT, TRANSFORM } + /** + * What is called when task is assigned + */ @Override public void start() { super.start(); @@ -45,6 +51,9 @@ public void start() { changeState(SlimeState.TRANSFORM); } + /** + * What is run every frame update + */ @Override public void update() { animate(); @@ -146,6 +155,9 @@ private void applyAoeDamage(Array targets, int damage) { } } + /** + * @return priority of class + */ @Override public int getPriority() { return PRIORITY; 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 37d9d41b9..05b85614d 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 @@ -20,6 +20,9 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.ServiceLocator; +/** + * Where all entities of mob bosses are created + */ public class MobBossFactory { private static final NPCConfigs configs = FileLoader.readClass(NPCConfigs.class, "configs/Boss.json"); @@ -28,10 +31,14 @@ public class MobBossFactory { private static final int DEMON_HEALTH = 5000; private static final int DEMON_ATTACK = 0; private static final int PATRICK_ATTACK = 0; + private static final int PATRICK_HEALTH = 2500; private static final int ICEBABY_ATTACK = 0; private static final int ICEBABY_HEALTH = 3000; - // Create Demon Boss + /** + * Creates the demon boss + * @return demon boss entity + */ public static Entity createDemonBoss() { Entity demon = createBaseBoss(); @@ -67,6 +74,10 @@ public static Entity createDemonBoss() { return demon; } + /** + * Creates slime that is spawned after demon boss dies + * @return slime entity + */ public static Entity createSlimeyBoy() { Entity slimeyBoy = createBaseBoss(); @@ -99,8 +110,12 @@ public static Entity createSlimeyBoy() { return slimeyBoy; } - public static Entity createPatrickBoss(int health) { - Entity demon = createBaseBoss(); + /** + * Creates the patrick boss + * @return patrick boss entity + */ + public static Entity createPatrickBoss() { + Entity patrick = createBaseBoss(); // Animation addition AnimationRenderComponent animator = new AnimationRenderComponent( @@ -118,17 +133,47 @@ public static Entity createPatrickBoss(int health) { .addTask(new PatrickTask()); // Component addition - demon + patrick .addComponent(animator) .addComponent(new PatrickAnimationController()) .addComponent(aiTaskComponent) - .addComponent(new CombatStatsComponent(health, PATRICK_ATTACK)); + .addComponent(new CombatStatsComponent(PATRICK_HEALTH, PATRICK_ATTACK)); // Scale demon - demon.getComponent(AnimationRenderComponent.class).scaleEntity(); - demon.scaleHeight(4f); - demon.scaleWidth(4f); - return demon; + patrick.getComponent(AnimationRenderComponent.class).scaleEntity(); + patrick.scaleHeight(4f); + patrick.scaleWidth(4f); + return patrick; + } + + /** + * Creates a patrick entity whose sole purpose is to display death animation + * @return patrick death entity + */ + public static Entity patrickDead() { + Entity patrick = createBaseBoss(); + + // Animation addition + AnimationRenderComponent animator = new AnimationRenderComponent( + ServiceLocator.getResourceService().getAsset("images/mobboss/patrick.atlas", TextureAtlas.class)); + animator.addAnimation("patrick_death", 0.2f, Animation.PlayMode.NORMAL); + + // AI task addition + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new PatrickDeathTask()); + + // Component addition + patrick + .addComponent(animator) + .addComponent(new PatrickAnimationController()) + .addComponent(aiTaskComponent) + .addComponent(new CombatStatsComponent(1, 0)); + + // Scale patrick + patrick.getComponent(AnimationRenderComponent.class).scaleEntity(); + patrick.scaleHeight(4f); + patrick.scaleWidth(4f); + return patrick; } public static Entity createIceBoss() { @@ -163,64 +208,10 @@ public static Entity createIceBoss() { return iceBaby; } - // Create Boss King 1 - public static Entity createMobBoss1(int numLane) { - MobBossConfigs config = configs.mobBoss; - Entity mobBoss1 = createBaseBoss(); - - AITaskComponent aiTaskComponent1 = new AITaskComponent() - .addTask(new FinalBossMovementTask(1f, numLane)) - .addTask(new MobBossDeathTask(1));; - - // Animation section - AnimationRenderComponent animator1 = new AnimationRenderComponent( - ServiceLocator.getResourceService().getAsset("images/mobs/robot.atlas", TextureAtlas.class)); - animator1.addAnimation("Walk", 0.3f, Animation.PlayMode.LOOP_REVERSED); - - mobBoss1.addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent(animator1) - .addComponent(aiTaskComponent1) - .addComponent(new Boss1AnimationController()); - - mobBoss1.getComponent(AnimationRenderComponent.class).scaleEntity(); - mobBoss1.setScale(1f, 1f); - - return mobBoss1; - } - - // Create Boss King 2 - public static Entity createMobBoss2() { - MobBossConfigs config = configs.mobBoss; - Entity mobBoss2 = createBaseBoss(); - - AITaskComponent aiTaskComponent2 = new AITaskComponent() - .addTask(new RangeBossTask(2f)); - - // Animation section - AnimationRenderComponent animator2 = new AnimationRenderComponent( - ServiceLocator.getResourceService().getAsset("images/mobs/boss2.atlas", TextureAtlas.class)); - animator2.addAnimation("boss_death", 0.3f, Animation.PlayMode.LOOP); - animator2.addAnimation("Idle", 0.3f, Animation.PlayMode.LOOP); - animator2.addAnimation("Walk", 0.3f, Animation.PlayMode.LOOP); - animator2.addAnimation("Charging", 0.3f, Animation.PlayMode.LOOP_REVERSED); - animator2.addAnimation("A1", 0.3f, Animation.PlayMode.LOOP); - animator2.addAnimation("A2", 0.3f, Animation.PlayMode.LOOP); - animator2.addAnimation("Hurt", 0.3f, Animation.PlayMode.LOOP); - - mobBoss2.addComponent(new CombatStatsComponent(config.health, config.baseAttack)) - .addComponent(animator2) - .addComponent(aiTaskComponent2) - .addComponent(new Boss2AnimationController()) - .addComponent(new PhysicsComponent()); - - mobBoss2.getComponent(AnimationRenderComponent.class).scaleEntity(); - mobBoss2.scaleHeight(3f); - mobBoss2.scaleWidth(3f); - - return mobBoss2; - } - - // Create the base boss entity + /** + * Create base boss entity that all boss mobs will inherit + * @return base mob boss entity + */ public static Entity createBaseBoss() { Entity boss = new Entity() .addComponent(new PhysicsComponent()) @@ -234,6 +225,9 @@ public static Entity createBaseBoss() { return boss; } + /** + * Throw IllegalStateException + */ private MobBossFactory() { throw new IllegalStateException("Instantiating static util class"); } diff --git a/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java index d77ff1d1d..adef6ba79 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java @@ -47,7 +47,7 @@ public static Entity createPlayer() { .addComponent(new TouchAttackComponent(PhysicsLayer.NPC)) .addComponent(new HitboxComponent().setLayer(PhysicsLayer.HUMANS)) .addComponent(new PlayerActions()) - .addComponent(new CombatStatsComponent(1000, 5000)) + .addComponent(new CombatStatsComponent(1000, 0)) .addComponent(new InventoryComponent(stats.gold)) .addComponent(inputComponent) .addComponent(aiComponent) diff --git a/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java b/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java new file mode 100644 index 000000000..63d813191 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java @@ -0,0 +1,101 @@ +package com.csse3200.game.components; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.npc.DodgingComponent; +import com.csse3200.game.components.tasks.MobDodgeTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.entities.factories.NPCFactory; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.events.listeners.EventListener1; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.physics.components.ColliderComponent; +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; + +@ExtendWith(GameExtension.class) +public class DodgingComponentTest { + Entity baseMob, baseProjectile; + private static final float VALID_POSITION_Y = 4; + private static final float VALID_POSITION_X = 7; + + @BeforeEach + public void setUp() { + GameTime gameTime = mock(GameTime.class); + when(gameTime.getDeltaTime()).thenReturn(0.02f); + ServiceLocator.registerTimeSource(gameTime); + + ServiceLocator.registerPhysicsService(new PhysicsService()); + + ServiceLocator.registerEntityService(new EntityService()); + + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + + ResourceService resourceService = new ResourceService(); + ServiceLocator.registerResourceService(resourceService); + + baseMob = createDodgeMob(VALID_POSITION_X, VALID_POSITION_Y); + baseProjectile = createProjectile(VALID_POSITION_X - 0.2f, VALID_POSITION_Y); + } + + @Test + public void shouldNotBeNullComponent() { + assertNotNull("Dodging combat component should not be null", baseMob.getComponent(DodgingComponent.class)); + } + + @Test + public void shouldNotBeNullTask() { + assertNotNull("Mob dodging tasks should not be null", baseMob.getComponent(AITaskComponent.class)); + } + + Entity createDodgeMob(float posX, float posY) { + Entity mob = NPCFactory.createRangedBaseNPC(); + mob.getComponent(AITaskComponent.class).addTask(new MobDodgeTask(new Vector2(2f, 2f), 2f, 5)); + mob.addComponent(new CombatStatsComponent(10, 10)); + mob.addComponent(new DodgingComponent(PhysicsLayer.PROJECTILE)); + mob.setPosition(posX, posY); + + return mob; + } + + Entity createDodgeMob(float posX, float posY, float rangeDetection, float dodgeSpeed) { + Entity mob = NPCFactory.createRangedBaseNPC(); + mob.getComponent(AITaskComponent.class).addTask(new MobDodgeTask(new Vector2(2f, 2f), 2f, 5)); + mob.addComponent(new CombatStatsComponent(10, 10)); + mob.addComponent(new DodgingComponent(PhysicsLayer.PROJECTILE, rangeDetection, dodgeSpeed)); + + ServiceLocator.getEntityService().register(mob); + mob.setPosition(posX, posY); + + return mob; + } + + Entity createProjectile(float posX, float posY) { + Entity projectile = ProjectileFactory.createBaseProjectile(baseMob.getComponent(ColliderComponent.class).getLayer(), + new Vector2(100, VALID_POSITION_Y), new Vector2(2f, 2f)); + projectile.addComponent(new CombatStatsComponent(10, 10)); + ServiceLocator.getEntityService().register(projectile); + projectile.setPosition(posX, posY); + + return projectile; + } +} diff --git a/source/core/src/test/com/csse3200/game/entities/factories/MobBossFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/MobBossFactoryTest.java index 231823921..685774993 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/MobBossFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/MobBossFactoryTest.java @@ -150,19 +150,19 @@ public void testDemonAnimationController() { } @Test public void testPatrickCreation() { - Entity Patrick = MobBossFactory.createPatrickBoss(3000); + Entity Patrick = MobBossFactory.createPatrickBoss(); assertNotNull(Patrick); } @Test public void testPatrickAnimationRenderComponent() { - Entity Patrick = MobBossFactory.createPatrickBoss(3000); + Entity Patrick = MobBossFactory.createPatrickBoss(); assertNotNull(Patrick.getComponent(AnimationRenderComponent.class), "Patrick does not have an AnimationRenderComponent"); } @Test public void testPatrickAnimationController() { - Entity Patrick = MobBossFactory.createPatrickBoss(3000); + Entity Patrick = MobBossFactory.createPatrickBoss(); assertNotNull(Patrick.getComponent(PatrickAnimationController.class), "Patrick does not have an Animation Controller"); }