diff --git a/source/core/assets/images/xeno-Grunt.png b/source/core/assets/images/mobs/xeno-Grunt.png similarity index 100% rename from source/core/assets/images/xeno-Grunt.png rename to source/core/assets/images/mobs/xeno-Grunt.png diff --git a/source/core/assets/images/xenoGrunt.atlas b/source/core/assets/images/mobs/xenoGrunt.atlas similarity index 100% rename from source/core/assets/images/xenoGrunt.atlas rename to source/core/assets/images/mobs/xenoGrunt.atlas 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 f1f0bb5bb..d45c99dda 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -49,9 +49,7 @@ public class ForestGameArea extends GameArea { private int bossSpawnInterval = 10000; // 1 minute in milliseconds private static final int NUM_WEAPON_TOWERS = 3; - - private static final GridPoint2 PLAYER_SPAWN = new GridPoint2(1, 4); - + private static final GridPoint2 PLAYER_SPAWN = new GridPoint2(0, 0); // Temporary spawn point for testing private static final float WALL_WIDTH = 0.1f; @@ -124,18 +122,16 @@ public class ForestGameArea extends GameArea { "images/ghostKing.atlas", "images/towers/turret.atlas", "images/towers/turret01.atlas", + "images/mobs/xenoGrunt.atlas", "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", "images/projectiles/engineer_projectile.atlas", "images/projectiles/mobKing_projectile.atlas", @@ -379,14 +375,14 @@ private void spawnProjectile(Vector2 position, short targetLayer, int space, in private void spawnXenoGrunts() { - GridPoint2 minPos = terrain.getMapBounds(0).sub(1, 5); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(1, 25); + int[] pickedLanes = new Random().ints(1, 7) + .distinct().limit(5).toArray(); for (int i = 0; i < NUM_GRUNTS; i++) { - GridPoint2 randomPos = RandomUtils.random(maxPos, minPos); + GridPoint2 randomPos = new GridPoint2(19, pickedLanes[i]); System.out.println(randomPos); Entity xenoGrunt = NPCFactory.createXenoGrunt(player); xenoGrunt.setScale(1.5f, 1.5f); - spawnEntityAt(xenoGrunt, randomPos, true, true); + spawnEntityAt(xenoGrunt, randomPos, true, false); } } @@ -620,6 +616,12 @@ private void spawnGapScanners() { Entity scanner = GapScannerFactory.createScanner(); spawnEntityAt(scanner, new GridPoint2(0, i), true, true); } +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = new GridPoint2(5, terrain.getMapBounds(0).sub(2, 2).y); +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// +// Entity engineer = EngineerFactory.createEngineer(); +// spawnEntityAt(engineer, randomPos, true, true); } // private void gameTrackerStart() { diff --git a/source/core/src/main/com/csse3200/game/components/TouchAttackComponent.java b/source/core/src/main/com/csse3200/game/components/TouchAttackComponent.java index 9570f7d25..3a203d3d0 100644 --- a/source/core/src/main/com/csse3200/game/components/TouchAttackComponent.java +++ b/source/core/src/main/com/csse3200/game/components/TouchAttackComponent.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.physics.box2d.Body; import com.badlogic.gdx.physics.box2d.Fixture; import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.Weapon; import com.csse3200.game.physics.BodyUserData; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.components.HitboxComponent; @@ -64,7 +65,7 @@ public void create() { hitboxComponent = entity.getComponent(HitboxComponent.class); } - private void onCollisionStart(Fixture me, Fixture other) { + public void onCollisionStart(Fixture me, Fixture other) { if (hitboxComponent.getFixture() != me) { // Not triggered by hitbox, ignore return; @@ -105,9 +106,22 @@ public void setDisposeOnHit(boolean disposeOnHit) { this.disposeOnHit = disposeOnHit; } + public void setKnockBack(float knockback) { this.knockbackForce = knockback; } + public Weapon chooseWeapon(Fixture other) { + Entity target = ((BodyUserData) other.getBody().getUserData()).entity; + Weapon weapon = null; + if (target.getComponent(CombatStatsComponent.class) != null) { + weapon = combatStats.getWeapon(target); + } + return weapon; + } + + private void onCollisionEnd(Fixture me, Fixture other) { + // Nothing to do on collision end + } // private void onCollisionEnd(Fixture me, Fixture other) { // // Nothing to do on collision end // } diff --git a/source/core/src/main/com/csse3200/game/components/npc/XenoAnimationController.java b/source/core/src/main/com/csse3200/game/components/npc/XenoAnimationController.java index c125babe9..d8eaa7be5 100644 --- a/source/core/src/main/com/csse3200/game/components/npc/XenoAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/npc/XenoAnimationController.java @@ -1,8 +1,11 @@ package com.csse3200.game.components.npc; +import com.badlogic.gdx.graphics.g2d.Animation; import com.csse3200.game.components.Component; import com.csse3200.game.rendering.AnimationRenderComponent; +import java.util.Objects; + /** * This class listens to events relevant to a ghost entity's state and plays the animation when one * of the events is triggered. @@ -14,34 +17,48 @@ public class XenoAnimationController extends Component { public void create() { super.create(); animator = this.entity.getComponent(AnimationRenderComponent.class); - entity.getEvents().addListener("wanderStart", this::animateWander); - entity.getEvents().addListener("chaseStart", this::animateChase); + entity.getEvents().addListener("wanderStart", this::animateRun); + entity.getEvents().addListener("runHurt", this::animateHurt); + entity.getEvents().addListener("meleeStart", this::animateMelee1); entity.getEvents().addListener("meleeStart", this::animateMelee2); entity.getEvents().addListener("shootStart", this::animateShoot); entity.getEvents().addListener("dieStart", this::animateDie); + entity.getEvents().addListener("stop", this::stopAnimation); } - void animateWander() { - animator.startAnimation("xeno_run"); + void animateRun() { + if (!Objects.equals(animator.getCurrentAnimation(), "xeno_shoot")) { + animator.stopAnimation(); + animator.startAnimation("xeno_run"); + } } - void animateChase() { - animator.startAnimation("xeno_run"); + void animateHurt() { + animator.stopAnimation(); + animator.startAnimation("xeno_hurt"); } void animateShoot() { + animator.stopAnimation(); animator.startAnimation("xeno_shoot"); } void animateMelee1() { + animator.stopAnimation(); animator.startAnimation("xeno_melee_1"); } void animateMelee2() { + animator.stopAnimation(); animator.startAnimation("xeno_melee_2"); } void animateDie() { + animator.stopAnimation(); animator.startAnimation("xeno_die"); } + + void stopAnimation() { + animator.stopAnimation(); + } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java index f027205be..1292d116b 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java @@ -1,17 +1,22 @@ package com.csse3200.game.components.tasks; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.physics.box2d.Fixture; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; -import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.TouchAttackComponent; import com.csse3200.game.entities.Entity; -import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.entities.Melee; +import com.csse3200.game.entities.Weapon; +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.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsMovementComponent; import com.csse3200.game.physics.raycast.RaycastHit; -import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.GameTime; -import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.services.ServiceLocator; /** @@ -22,11 +27,13 @@ public class MobAttackTask extends DefaultTask implements PriorityTask { private static final short TARGET = PhysicsLayer.HUMANS; // mobs detecting for towers // ^ fix this - private static final String STOW = "stowStart"; + private static final String STOW = "wanderStart"; private static final String DEPLOY = "deployStart"; - private static final String FIRING = "firingStart"; + private static final String FIRING = "shootStart"; private static final String IDLE = "idleStart"; + private Fixture target; + private final int priority; private final float maxRange; private Vector2 mobPosition = new Vector2(10f,10f); @@ -67,7 +74,7 @@ public void start() { startTime = timeSource.getTime(); this.mobPosition = owner.getEntity().getCenterPosition(); this.maxRangePosition.set(0, mobPosition.y); - owner.getEntity().getEvents().trigger(IDLE); + //owner.getEntity().getEvents().trigger(IDLE); endTime = timeSource.getTime() + (INTERVAL * 500); owner.getEntity().getEvents().trigger("shootStart"); } @@ -90,14 +97,6 @@ public void update() { * triggers the appropriate events corresponding to the STATE enum. */ public void updateMobState() { -// TouchAttackComponent attackComp = owner.getEntity().getComponent(TouchAttackComponent.class); - CombatStatsComponent statsComp = owner.getEntity().getComponent(CombatStatsComponent.class); -// if (statsComp != null) { -// System.out.println("is the target visible " + isTargetVisible()); -// } - if (!isTargetVisible()) { - System.out.println("target is not visible for " + owner.getEntity().getId()); - } switch (mobState) { case IDLE -> { @@ -110,7 +109,8 @@ public void updateMobState() { case DEPLOY -> { // currently deploying, - if (isTargetVisible()) { + if (isTargetVisible() || this.meleeOrProjectile() != null) { + owner.getEntity().getComponent(PhysicsMovementComponent.class).setEnabled(false); owner.getEntity().getEvents().trigger(FIRING); mobState = STATE.FIRING; } else { @@ -120,19 +120,29 @@ public void updateMobState() { } case FIRING -> { - // targets gone - stop firing - if (!isTargetVisible()) { + // targets gone or cannot be attacked - stop firing + if (!isTargetVisible() || this.meleeOrProjectile() == null) { owner.getEntity().getEvents().trigger(STOW); mobState = STATE.STOW; } else { - owner.getEntity().getEvents().trigger(FIRING); - Entity newProjectile = ProjectileFactory.createMobBall(PhysicsLayer.HUMANS, new Vector2(0, owner.getEntity().getPosition().y), new Vector2(2f,2f)); - newProjectile.setPosition((float) (owner.getEntity().getPosition().x), (float) (owner.getEntity().getPosition().y + 0.1)); - newProjectile.setScale(-0.7f, 0.7f); - ServiceLocator.getEntityService().register(newProjectile); - mobState = STATE.STOW; - owner.getEntity().getEvents().trigger("shootStart"); + if (this.meleeOrProjectile() instanceof Melee) { + System.out.println("Melee attack"); + TouchAttackComponent attackComp = owner.getEntity().getComponent(TouchAttackComponent.class); + HitboxComponent hitboxComp = owner.getEntity().getComponent(HitboxComponent.class); + attackComp.onCollisionStart(hitboxComp.getFixture(), target); + } else { + Entity newProjectile = ProjectileFactory.createMobBall(PhysicsLayer.HUMANS, new Vector2(0, owner.getEntity().getPosition().y), new Vector2(2f,2f)); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x), (float) (owner.getEntity().getPosition().y)); +// newProjectile.setScale(-1f, 0.5f); + ServiceLocator.getEntityService().register(newProjectile); + +// System.out.printf("ANIMATION: " + owner.getEntity().getComponent(AnimationRenderComponent.class).getCurrentAnimation() + "\n"); + owner.getEntity().getEvents().trigger(FIRING); + mobState = STATE.STOW; + } } + owner.getEntity().getComponent(PhysicsMovementComponent.class).setEnabled(true); + } case STOW -> { @@ -153,8 +163,12 @@ public void updateMobState() { */ @Override public void stop() { - super.stop(); - owner.getEntity().getEvents().trigger(STOW); + if (mobState == STATE.FIRING || mobState == STATE.DEPLOY) { + this.updateMobState(); + } else { + super.stop(); + owner.getEntity().getEvents().trigger(STOW); + } } /** @@ -163,12 +177,10 @@ public void stop() { */ @Override public int getPriority() { -// return -1; if (status == Status.ACTIVE) { return getActivePriority(); } return getInactivePriority(); -// return isTargetVisible() ? getActivePriority() : getInactivePriority(); } /** @@ -176,13 +188,9 @@ public int getPriority() { * @return (int) active priority if a target is visible, -1 otherwise */ private int getActivePriority() { - if ((startTime + delay) < timeSource.getTime()) { -// if (isTargetVisible() && (startTime + delay) > timeSource.getTime()) { -// System.out.println("ready to fire while active"); + if ((startTime + delay) < timeSource.getTime() && isTargetVisible() && this.meleeOrProjectile() != null) { return priority; } -// System.out.println("not ready to fire while active"); -// return !isTargetVisible() ? -1 : priority; return -1; } @@ -191,15 +199,10 @@ private int getActivePriority() { * @return (int) -1 if a target is not visible, active priority otherwise */ private int getInactivePriority() { -// return isTargetVisible() ? priority : 0; - if ((startTime + delay) < timeSource.getTime()) { -// if (isTargetVisible() && (startTime + delay) > timeSource.getTime()) { -// System.out.println("ready to fire while inactive"); + if ((startTime + delay) < timeSource.getTime() && isTargetVisible() && this.meleeOrProjectile() != null) { return priority; } return -1; -// System.out.println("not ready to fire while inactive"); -// return isTargetVisible() ? priority : -1; } /** @@ -207,6 +210,35 @@ private int getInactivePriority() { * @return true if a target is visible, false otherwise */ private boolean isTargetVisible() { - return physics.raycast(mobPosition, maxRangePosition, TARGET, hit); + Vector2 newVector = new Vector2(owner.getEntity().getPosition().x - 10f, owner.getEntity().getPosition().y - 2f); + return physics.raycast(owner.getEntity().getPosition(), newVector, TARGET, hit); + } + + /** + * Uses a custom raycast method to find the closest target to the mob. Based on the distance to the + * target, the mob will choose a weapon to attack with. + * + * If the object does not have a CombatStatsComponent (which handles dealing damage etc), then + * the function will return null. If it returns null when the mob is in state FIRING or DEPLOY, it will not fire + * and will STOW. + * + * returns the Weapon (Melee or Projectile) the mob will use to attack the target. null if immune target or no target + * */ + private Weapon meleeOrProjectile() { +// Vector2 newVector = new Vector2(owner.getEntity().getPosition().x - 10f, owner.getEntity().getPosition().y - 2f); +// Fixture hitraycast = physics.raycastGetHit(owner.getEntity().getPosition(), newVector, TARGET); + setTarget(); + TouchAttackComponent comp = owner.getEntity().getComponent(TouchAttackComponent.class); + Weapon chosenWeapon = null; + if (comp != null) { + chosenWeapon = comp.chooseWeapon(target); + } + + return chosenWeapon; + } + + private void setTarget() { + Vector2 newVector = new Vector2(owner.getEntity().getPosition().x - 10f, owner.getEntity().getPosition().y - 2f); + target = physics.raycastGetHit(owner.getEntity().getPosition(), newVector, TARGET); } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java index f04ad39a0..434a6ab76 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java @@ -1,22 +1,27 @@ 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.badlogic.gdx.math.Vector2; -import com.csse3200.game.currency.Currency; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.DropFactory; -import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.raycast.RaycastHit; -import com.csse3200.game.services.ServiceLocator; import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; //import com.csse3200.game.rendering.DebugRenderer; /** - * Task that prints a message to the terminal whenever it is called. + * THIS TASK IS NO LONGER USED. It may be deleted at a later date. + * Do not read this aweful task. + * + * DOES NOT DO ANYTHING. + * + * This task didn't work with the Wander & ShootTasks, + * and then it was + * decided to have mob death in wanderTask. */ public class MobDeathTask extends DefaultTask implements PriorityTask { private static final int INTERVAL = 1; // time interval to scan for towers in @@ -50,6 +55,7 @@ public void start() { this.mobPosition = owner.getEntity().getCenterPosition(); //sets endTime endTime = timeSource.getTime() + (INTERVAL * 500); + this.owner.getEntity().getEvents().trigger("dieStart"); } @Override diff --git a/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java b/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java index cb326b8b4..d6c4a3d85 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java @@ -1,9 +1,7 @@ package com.csse3200.game.components.tasks; -import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; -import com.csse3200.game.ai.tasks.Task; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; import org.slf4j.Logger; @@ -12,17 +10,15 @@ public class SpawnWaveTask extends DefaultTask implements PriorityTask { private static final Logger logger = LoggerFactory.getLogger(SpawnWaveTask.class); private final GameTime globalTime; - private long endTime; - + private long endTime = 0; private final int SPAWNING_INTERVAL = 10; - public SpawnWaveTask() { this.globalTime = ServiceLocator.getTimeSource(); } @Override public int getPriority() { - return 10; // Low priority task + return 10; // High priority task } @Override diff --git a/source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java index 6fd754880..f02852bdb 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java @@ -1,13 +1,25 @@ package com.csse3200.game.components.tasks; +import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; import com.csse3200.game.ai.tasks.Task; +import com.csse3200.game.areas.ForestGameArea; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.DropFactory; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.components.ColliderComponent; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; import com.csse3200.game.utils.math.RandomUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.TimeUnit; + /** * Wander around by moving a random position within a range of the starting position. Wait a little * bit between movements. Requires an entity with a PhysicsMovementComponent. @@ -21,6 +33,8 @@ public class WanderTask extends DefaultTask implements PriorityTask { private MovementTask movementTask; private WaitTask waitTask; private Task currentTask; + private boolean isDead = false; + private Vector2 mobPosition; /** * @param wanderRange Distance in X and Y the entity can move from its position when start() is @@ -58,14 +72,45 @@ public void start() { @Override public void update() { - if (currentTask.getStatus() != Status.ACTIVE) { - if (currentTask == movementTask) { - startWaiting(); - } else { - startMoving(); + + //Update the position of the mob + mobPosition = owner.getEntity().getPosition(); + + // If the mob is at zero health, kill the mob, + // play the death animation and stop the task + // This method is the idea of Ahmad who very kindly helped + // with section, massive props to him for his help! + if (!isDead && owner.getEntity().getComponent(CombatStatsComponent.class).isDead()) { + owner.getEntity().getEvents().trigger("dieStart"); + currentTask.stop(); + isDead = true; + } + + // Check if the mob has finished death animation + else if (isDead && owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + + // Drop scrap at the mobs location for player + // to collect. + Entity scrap = DropFactory.createScrapDrop(); + scrap.setPosition(mobPosition.x,mobPosition.y); + ServiceLocator.getEntityService().register(scrap); + + // Delete the mob. + owner.getEntity().setFlagForDelete(true); + + } + // If not dead, do normal things... + else if (!isDead) { + + if (currentTask.getStatus() != Status.ACTIVE) { + if (currentTask == movementTask) { + startWaiting(); + } else { + startMoving(); + } } + currentTask.update(); } - currentTask.update(); } private void startWaiting() { diff --git a/source/core/src/main/com/csse3200/game/entities/PredefinedWeapons.java b/source/core/src/main/com/csse3200/game/entities/PredefinedWeapons.java index a9be6e6c4..5955f34f6 100644 --- a/source/core/src/main/com/csse3200/game/entities/PredefinedWeapons.java +++ b/source/core/src/main/com/csse3200/game/entities/PredefinedWeapons.java @@ -1,5 +1,8 @@ package com.csse3200.game.entities; +import com.csse3200.game.entities.configs.NPCConfigs; +import com.csse3200.game.entities.configs.ProjectileConfig; + public class PredefinedWeapons { // Melee attacks public static Melee sword = new Melee(10, 4, "fire", 1, 1); @@ -7,8 +10,11 @@ public class PredefinedWeapons { public static Melee axe = new Melee(9, 3, "fire", 1, 1); public static Melee kick = new Melee(2, 1, "earth", 1, 1); + public static ProjectileConfig fireBall = new ProjectileConfig(); + public static ProjectileConfig frostBall = new ProjectileConfig(); + // Projectile attacks TODO: change Weapon and Melee to Projectile class - public static Weapon fireBall = new Melee(9, 20, "fire", 1, 1); - public static Weapon frostBall = new Melee(6, 20, "ice", 1, 1); - public static Weapon hurricane = new Melee(7, 20, "air", 1, 1); +// public static Weapon fireBall = new Melee(9, 20, "fire", 1, 1); +// public static Weapon frostBall = new Melee(6, 20, "ice", 1, 1); +// public static Weapon hurricane = new Melee(7, 20, "air", 1, 1); } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/NPCConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/NPCConfigs.java index 146e364a4..3bc7d9eb2 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/NPCConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/NPCConfigs.java @@ -16,9 +16,12 @@ public class NPCConfigs { public BaseEntityConfig projectile = new ProjectileConfig(); public GhostKingConfig ghostKing = new GhostKingConfig(); public BaseEnemyConfig xenoGrunt = new BaseEnemyConfig( + 10, + 100, new ArrayList(), new ArrayList(), - new ArrayList()); + new ArrayList(), + 10); public BossKingConfigs BossKing = new BossKingConfigs(); diff --git a/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java index c3735c269..11dccd986 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java @@ -5,17 +5,15 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.TouchAttackComponent; import com.csse3200.game.components.npc.GhostAnimationController; import com.csse3200.game.components.npc.XenoAnimationController; -import com.csse3200.game.components.TouchAttackComponent; import com.csse3200.game.components.tasks.MobAttackTask; -import com.csse3200.game.components.tasks.SpawnWaveTask; import com.csse3200.game.components.tasks.MobDeathTask; import com.csse3200.game.components.tasks.WanderTask; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.Melee; import com.csse3200.game.entities.PredefinedWeapons; -import com.csse3200.game.entities.Weapon; import com.csse3200.game.entities.configs.*; import com.csse3200.game.files.FileLoader; import com.csse3200.game.physics.PhysicsLayer; @@ -109,24 +107,25 @@ public static Entity createXenoGrunt(Entity target) { Entity xenoGrunt = createBaseNPC(target); BaseEnemyConfig config = configs.xenoGrunt; ArrayList melee = new ArrayList<>(Arrays.asList(PredefinedWeapons.sword, PredefinedWeapons.kick)); - ArrayList projectiles = new ArrayList<>(); -// ArrayList projectiles = new ArrayList<>(Arrays.asList(PredefinedWeapons.fireBall, PredefinedWeapons.hurricane)); -// ArrayList drops = new ArrayList<>(Arrays.asList(1, 2)); + // tester projectiles + ArrayList projectiles = new ArrayList<>(Arrays.asList(PredefinedWeapons.fireBall, PredefinedWeapons.frostBall)); ArrayList drops = new ArrayList<>(); AnimationRenderComponent animator = new AnimationRenderComponent( - ServiceLocator.getResourceService().getAsset("images/xenoGrunt.atlas", TextureAtlas.class)); + ServiceLocator.getResourceService().getAsset("images/mobs/xenoGrunt.atlas", TextureAtlas.class)); animator.addAnimation("xeno_run", 0.1f, Animation.PlayMode.LOOP); - animator.addAnimation("xeno_shoot", 0.1f, Animation.PlayMode.NORMAL); - animator.addAnimation("xeno_melee_1", 0.1f, Animation.PlayMode.NORMAL); - animator.addAnimation("xeno_melee_2", 0.1f, Animation.PlayMode.NORMAL); - animator.addAnimation("xeno_die", 0.1f, Animation.PlayMode.NORMAL); + animator.addAnimation("xeno_hurt", 0.1f, Animation.PlayMode.LOOP); + animator.addAnimation("xeno_shoot", 0.1f); + animator.addAnimation("xeno_melee_1", 0.1f); + animator.addAnimation("xeno_melee_2", 0.1f); + animator.addAnimation("xeno_die", 0.1f); xenoGrunt .addComponent(new CombatStatsComponent(config.fullHeath, config.baseAttack, drops, melee, projectiles)) .addComponent(animator) .addComponent(new XenoAnimationController()); + xenoGrunt.getComponent(HitboxComponent.class).setAsBoxAligned(new Vector2(.3f, .5f), PhysicsComponent.AlignX.RIGHT, PhysicsComponent.AlignY.BOTTOM); xenoGrunt.getComponent(AnimationRenderComponent.class).scaleEntity(); return xenoGrunt; @@ -141,18 +140,17 @@ public static Entity createBaseNPC(Entity target) { AITaskComponent aiComponent = new AITaskComponent() .addTask(new WanderTask(new Vector2(2f, 2f), 2f)) - .addTask(new MobAttackTask(2, 40)) - .addTask(new MobDeathTask(2)); + .addTask(new MobAttackTask(2, 40)); Entity npc = new Entity() .addComponent(new PhysicsComponent()) .addComponent(new PhysicsMovementComponent()) .addComponent(new ColliderComponent()) - .addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) - .addComponent(new TouchAttackComponent(PhysicsLayer.HUMANS, 1.5f)) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.XENO)) + .addComponent(new TouchAttackComponent(PhysicsLayer.HUMANS)) .addComponent(aiComponent); - PhysicsUtils.setScaledCollider(npc, 0.9f, 0.4f); + PhysicsUtils.setScaledCollider(npc, 0.3f, 0.5f); return npc; } diff --git a/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java b/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java index bb7258f4c..017b18278 100644 --- a/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java +++ b/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java @@ -141,6 +141,24 @@ public boolean raycast(Vector2 from, Vector2 to, short layerMask, RaycastHit hit return singleHitCallback.didHit; } + + /** + * Cast a ray in a straight line from one point to another, checking for a collision + * against colliders in the specified layers. + * + * @param from The starting point of the ray. + * @param to The end point of the ray. + * @param layerMask The physics layer mask which specifies layers that can be hit. Other layers + * will be ignored. + * @return The fixture of the closest collider hit by the ray, or null if no collider was hit. + * */ + public Fixture raycastGetHit(Vector2 from, Vector2 to, short layerMask) { + singleHitCallback.didHit = false; + singleHitCallback.layerMask = layerMask; + world.rayCast(singleHitCallback, from, to); + return singleHitCallback.hit.fixture; + } + /** * Cast a ray in a straight line from one point to another, checking for all collision against * colliders in the specified layers. diff --git a/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java b/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java index c72e1e137..437e46b79 100644 --- a/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java +++ b/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java @@ -13,6 +13,7 @@ public class PhysicsLayer { // * TEMPORARY WALL BOUNDARIES? public static final short WALL = (1 << 6); + public static final short XENO = (1 << 3); public static final short HUMANS = (1 << 1) | (1 << 5); public static final short ALL = ~0; diff --git a/source/core/src/main/com/csse3200/game/physics/PhysicsUtils.java b/source/core/src/main/com/csse3200/game/physics/PhysicsUtils.java index 44936b077..168065d1b 100644 --- a/source/core/src/main/com/csse3200/game/physics/PhysicsUtils.java +++ b/source/core/src/main/com/csse3200/game/physics/PhysicsUtils.java @@ -13,7 +13,7 @@ public static void setScaledCollider(Entity entity, float scaleX, float scaleY) entity .getComponent(ColliderComponent.class) .setAsBoxAligned( - boundingBox, PhysicsComponent.AlignX.CENTER, PhysicsComponent.AlignY.BOTTOM); + boundingBox, PhysicsComponent.AlignX.RIGHT, PhysicsComponent.AlignY.BOTTOM); } private PhysicsUtils() { diff --git a/source/core/src/test/com/csse3200/game/components/tasks/SpawnWaveTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/SpawnWaveTaskTest.java new file mode 100644 index 000000000..55a00537d --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/SpawnWaveTaskTest.java @@ -0,0 +1,42 @@ +package com.csse3200.game.components.tasks; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.utils.math.Vector2Utils; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.*; + +@ExtendWith(GameExtension.class) +@ExtendWith(MockitoExtension.class) +class SpawnWaveTaskTest { + + @Test + void shouldTriggerSpawning() { + GameTime time = mock(GameTime.class); + when(time.getTime()).thenReturn(11000L); + ServiceLocator.registerTimeSource(time); + SpawnWaveTask waveTask = new SpawnWaveTask(); + + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(waveTask); + Entity entity = new Entity().addComponent(aiTaskComponent).addComponent(new PhysicsMovementComponent()); + entity.create(); + + // Register callbacks + EventListener0 callback = mock(EventListener0.class); + entity.getEvents().addListener("spawnWave", callback); + + waveTask.update(); + + verify(callback).handle(); + } +} \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tasks/WaitTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/WaitTaskTest.java index 6ba90f421..59e7ea7c8 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/WaitTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/WaitTaskTest.java @@ -14,7 +14,6 @@ @ExtendWith(GameExtension.class) class WaitTaskTest { - @Disabled("Testing without use of WaitTask") @Test void shouldWaitUntilTime() { GameTime time = mock(GameTime.class); diff --git a/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java new file mode 100644 index 000000000..9c02ba0c4 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java @@ -0,0 +1,106 @@ +package com.csse3200.game.entities.factories; + +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.physics.components.ColliderComponent; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +import com.csse3200.game.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; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(GameExtension.class) +public class NPCFactoryTest { + + private Entity xenoGrunt; + private Entity towerTarget; + private Entity engineerTarget; + private Entity playerTarget; + + private String[] texture = { + "images/towers/turret_deployed.png", + "images/towers/turret01.png", + "images/towers/wallTower.png" + }; + private String[] atlas = {"images/towers/turret01.atlas", + "images/mobs/xenoGrunt.atlas"}; + private static final String[] sounds = { + "sounds/towers/gun_shot_trimmed.mp3", + "sounds/towers/deploy.mp3", + "sounds/towers/stow.mp3" + }; + + + @BeforeEach + public void setUp() { + GameTime gameTime = mock(GameTime.class); + when(gameTime.getDeltaTime()).thenReturn(0.02f); + ServiceLocator.registerTimeSource(gameTime); + 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.loadSounds(sounds); + resourceService.loadAll(); + ServiceLocator.getResourceService() + .getAsset("images/mobs/xenoGrunt.atlas", TextureAtlas.class); + //playerTarget = PlayerFactory.createPlayer(); + towerTarget = TowerFactory.createBaseTower(); + //engineerTarget = EngineerFactory.createEngineer(); + xenoGrunt = NPCFactory.createXenoGrunt(playerTarget); + } + + @Test + public void testCreateXenoGruntNotNull() { + assertNotNull(xenoGrunt, "Xeno Grunt should not be null"); + } + + @Test + public void testCreateXenoGruntHasColliderComponent() { + assertNotNull(xenoGrunt.getComponent(ColliderComponent.class), + "Xeno Grunt should have ColliderComponent"); + } + + @Test + public void testCreateXenoGruntHasHitboxComponent() { + assertNotNull(xenoGrunt.getComponent(HitboxComponent.class), + "Xeno Grunt should have HitboxComponent"); + } + + @Test + public void testCreateXenoGruntHasPhysicsComponent() { + assertNotNull(xenoGrunt.getComponent(PhysicsComponent.class), + "Xeno Grunt should have PhysicsComponent"); + } + + @Test + public void testXenoGruntCombatStatsComponent() { + assertEquals(100, xenoGrunt.getComponent(CombatStatsComponent.class).getHealth(), + "Health should be 100"); + assertEquals(10, xenoGrunt.getComponent(CombatStatsComponent.class).getBaseAttack(), + "BaseAttack should be 10"); + } + + @Test + public void xenoGruntHasAnimationComponent() { + assertNotNull(xenoGrunt.getComponent(AnimationRenderComponent.class)); + } + +}