diff --git a/source/core/assets/badlogic.jpg b/source/core/assets/badlogic.jpg deleted file mode 100644 index 4390da6e0..000000000 Binary files a/source/core/assets/badlogic.jpg and /dev/null differ diff --git a/source/core/assets/configs/tower.json b/source/core/assets/configs/tower.json index c7a95392e..df72c561c 100644 --- a/source/core/assets/configs/tower.json +++ b/source/core/assets/configs/tower.json @@ -33,6 +33,5 @@ "health": 10, "baseAttack": 10, "cost": 10 - } } \ No newline at end of file diff --git a/source/core/assets/images/engineers/engineerBanner.png b/source/core/assets/images/engineers/engineerBanner.png new file mode 100644 index 000000000..f689a8fef Binary files /dev/null and b/source/core/assets/images/engineers/engineerBanner.png differ diff --git a/source/core/assets/images/ingamebg.png b/source/core/assets/images/ingamebg.png index 0fbc1c543..2301477d4 100644 Binary files a/source/core/assets/images/ingamebg.png and b/source/core/assets/images/ingamebg.png differ 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/assets/images/projectiles/burn_effect.atlas b/source/core/assets/images/projectiles/burn_effect.atlas new file mode 100644 index 000000000..140875f0f --- /dev/null +++ b/source/core/assets/images/projectiles/burn_effect.atlas @@ -0,0 +1,55 @@ + +burn_effect.png +size: 256, 64 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +projectile + rotate: false + xy: 78, 2 + size: 36, 31 + orig: 36, 31 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 2, 2 + size: 36, 31 + orig: 36, 31 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 154, 2 + size: 35, 31 + orig: 35, 31 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 40, 2 + size: 36, 31 + orig: 36, 31 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 116, 2 + size: 36, 31 + orig: 36, 31 + offset: 0, 0 + index: -1 +projectileFinal + rotate: false + xy: 191, 2 + size: 31, 31 + orig: 31, 31 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 78, 2 + size: 36, 31 + orig: 36, 31 + offset: 0, 0 + index: -1 \ No newline at end of file diff --git a/source/core/assets/images/projectiles/burn_effect.png b/source/core/assets/images/projectiles/burn_effect.png new file mode 100644 index 000000000..18df3b6d5 Binary files /dev/null and b/source/core/assets/images/projectiles/burn_effect.png differ diff --git a/source/core/assets/images/projectiles/firework_anim.atlas b/source/core/assets/images/projectiles/firework_anim.atlas new file mode 100644 index 000000000..45f2d54c9 --- /dev/null +++ b/source/core/assets/images/projectiles/firework_anim.atlas @@ -0,0 +1,41 @@ + +firework_anim.png +size: 128, 32 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +projectile + rotate: false + xy: 23, 2 + size: 19, 16 + orig: 19, 16 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 65, 2 + size: 19, 16 + orig: 19, 16 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 44, 2 + size: 19, 16 + orig: 19, 16 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 2, 2 + size: 19, 16 + orig: 19, 16 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 23, 2 + size: 19, 16 + orig: 19, 16 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/projectiles/firework_anim.png b/source/core/assets/images/projectiles/firework_anim.png new file mode 100644 index 000000000..56a46b556 Binary files /dev/null and b/source/core/assets/images/projectiles/firework_anim.png differ diff --git a/source/core/assets/images/projectiles/oldstun_effect.atlas b/source/core/assets/images/projectiles/oldstun_effect.atlas new file mode 100644 index 000000000..a50132d0b --- /dev/null +++ b/source/core/assets/images/projectiles/oldstun_effect.atlas @@ -0,0 +1,41 @@ + +stun_effect.png +size: 256, 32 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +projectile + rotate: false + xy: 86, 3 + size: 41, 27 + orig: 41, 27 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 2, 2 + size: 40, 28 + orig: 40, 28 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 129, 2 + size: 40, 28 + orig: 40, 28 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 44, 2 + size: 40, 28 + orig: 40, 28 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 86, 3 + size: 41, 27 + orig: 41, 27 + offset: 0, 0 + index: -1 \ No newline at end of file diff --git a/source/core/assets/images/projectiles/oldstun_effect.png b/source/core/assets/images/projectiles/oldstun_effect.png new file mode 100644 index 000000000..238eac60d Binary files /dev/null and b/source/core/assets/images/projectiles/oldstun_effect.png differ diff --git a/source/core/assets/images/projectiles/pierce_anim.atlas b/source/core/assets/images/projectiles/pierce_anim.atlas new file mode 100644 index 000000000..0dea23b97 --- /dev/null +++ b/source/core/assets/images/projectiles/pierce_anim.atlas @@ -0,0 +1,42 @@ + +pierce_anim.png +size: 256, 32 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +projectile + rotate: false + xy: 2, 2 + size: 35, 26 + orig: 35, 26 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 76, 2 + size: 35, 26 + orig: 35, 26 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 39, 2 + size: 35, 26 + orig: 35, 26 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 113, 2 + size: 35, 26 + orig: 35, 26 + offset: 0, 0 + index: -1 +default +projectile + rotate: false + xy: 2, 2 + size: 35, 26 + orig: 35, 26 + offset: 0, 0 + index: -1 \ No newline at end of file diff --git a/source/core/assets/images/projectiles/pierce_anim.png b/source/core/assets/images/projectiles/pierce_anim.png new file mode 100644 index 000000000..e9c349e6f Binary files /dev/null and b/source/core/assets/images/projectiles/pierce_anim.png differ diff --git a/source/core/assets/images/projectiles/stun_effect.atlas b/source/core/assets/images/projectiles/stun_effect.atlas new file mode 100644 index 000000000..6aa98a315 --- /dev/null +++ b/source/core/assets/images/projectiles/stun_effect.atlas @@ -0,0 +1,41 @@ + +stun_effect.png +size: 128, 32 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +projectile + rotate: false + xy: 2, 2 + size: 21, 19 + orig: 21, 19 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 48, 2 + size: 20, 19 + orig: 20, 19 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 25, 2 + size: 21, 19 + orig: 21, 19 + offset: 0, 0 + index: -1 +projectile + rotate: false + xy: 70, 2 + size: 17, 19 + orig: 17, 19 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 2, 2 + size: 21, 19 + orig: 21, 19 + offset: 0, 0 + index: -1 \ No newline at end of file diff --git a/source/core/assets/images/projectiles/stun_effect.png b/source/core/assets/images/projectiles/stun_effect.png new file mode 100644 index 000000000..cd862a426 Binary files /dev/null and b/source/core/assets/images/projectiles/stun_effect.png differ diff --git a/source/core/assets/images/terrain_use.png b/source/core/assets/images/terrain_use.png index 59ddab66d..698343323 100644 Binary files a/source/core/assets/images/terrain_use.png and b/source/core/assets/images/terrain_use.png differ diff --git a/source/core/assets/images/towers/fire_tower_atlas.atlas b/source/core/assets/images/towers/fire_tower_atlas.atlas index 7c9ce2206..4c14d5e15 100644 --- a/source/core/assets/images/towers/fire_tower_atlas.atlas +++ b/source/core/assets/images/towers/fire_tower_atlas.atlas @@ -1,83 +1,167 @@ fire_tower_atlas.png -size: 1024, 64 +size: 2048, 64 format: RGBA8888 filter: Nearest, Nearest repeat: none attack rotate: false - xy: 122, 2 + xy: 242, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 1 attack rotate: false - xy: 302, 2 + xy: 542, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 3 attack rotate: false - xy: 422, 2 + xy: 782, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 0 attack rotate: false - xy: 602, 2 + xy: 1082, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 2 -idle +death rotate: false xy: 62, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 - index: 1 -idle + index: 6 +death rotate: false xy: 182, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 3 -idle +death + rotate: false + xy: 302, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 8 +death rotate: false xy: 362, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 0 -idle +prepAttack rotate: false - xy: 542, 2 + xy: 362, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 1 +death + rotate: false + xy: 482, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 5 +death + rotate: false + xy: 662, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 11 +death + rotate: false + xy: 722, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 2 -prep_attack +death rotate: false - xy: 2, 2 + xy: 842, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 7 +death + rotate: false + xy: 1022, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 4 +death + rotate: false + xy: 1142, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 9 +death + rotate: false + xy: 1202, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 10 +death + rotate: false + xy: 1262, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 1 +idle + rotate: false + xy: 122, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 1 -prep_attack +idle rotate: false - xy: 242, 2 + xy: 422, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 3 +idle + rotate: false + xy: 602, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 0 -prep_attack +idle rotate: false - xy: 482, 2 + xy: 902, 2 size: 58, 58 orig: 58, 58 offset: 0, 0 index: 2 +prepAttack + rotate: false + xy: 2, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 2 +prepAttack + rotate: false + xy: 962, 2 + size: 58, 58 + orig: 58, 58 + offset: 0, 0 + index: 0 diff --git a/source/core/assets/images/towers/fire_tower_atlas.png b/source/core/assets/images/towers/fire_tower_atlas.png index a8c5cc3ee..cae7db217 100644 Binary files a/source/core/assets/images/towers/fire_tower_atlas.png and b/source/core/assets/images/towers/fire_tower_atlas.png differ diff --git a/source/core/assets/images/towers/stun_tower.atlas b/source/core/assets/images/towers/stun_tower.atlas index f0033a197..4cae61f81 100644 --- a/source/core/assets/images/towers/stun_tower.atlas +++ b/source/core/assets/images/towers/stun_tower.atlas @@ -1,118 +1,174 @@ stun_tower.png -size: 1024, 64 +size: 256, 256 format: RGBA8888 filter: Nearest, Nearest repeat: none attack rotate: false - xy: 2, 2 + xy: 59, 207 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 4 attack rotate: false - xy: 116, 2 + xy: 173, 207 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 9 attack rotate: false - xy: 173, 2 + xy: 2, 160 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 1 attack rotate: false - xy: 230, 2 + xy: 2, 113 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 6 attack rotate: false - xy: 344, 2 + xy: 2, 19 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 3 attack rotate: false - xy: 458, 2 + xy: 116, 160 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 8 attack rotate: false - xy: 572, 2 + xy: 59, 113 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 0 attack rotate: false - xy: 629, 2 + xy: 59, 66 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 5 attack rotate: false - xy: 743, 2 + xy: 116, 113 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 2 attack rotate: false - xy: 800, 2 + xy: 173, 113 size: 55, 45 orig: 55, 45 offset: 0, 0 index: 7 +death + rotate: false + xy: 116, 76 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 6 +death + rotate: false + xy: 116, 39 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 3 +death + rotate: false + xy: 116, 2 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 0 +death + rotate: false + xy: 158, 76 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 5 +death + rotate: false + xy: 200, 76 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 2 +death + rotate: false + xy: 158, 39 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 7 +death + rotate: false + xy: 158, 2 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 4 +death + rotate: false + xy: 200, 39 + size: 40, 35 + orig: 40, 35 + offset: 0, 0 + index: 1 idle rotate: false - xy: 59, 2 + xy: 2, 207 size: 55, 45 orig: 55, 45 offset: 0, 0 - index: 1 + index: 4 idle rotate: false - xy: 287, 2 + xy: 116, 207 size: 55, 45 orig: 55, 45 offset: 0, 0 - index: 3 + index: 1 idle rotate: false - xy: 401, 2 + xy: 2, 66 size: 55, 45 orig: 55, 45 offset: 0, 0 - index: 0 + index: 3 idle rotate: false - xy: 515, 2 + xy: 59, 160 size: 55, 45 orig: 55, 45 offset: 0, 0 - index: 5 + index: 0 idle rotate: false - xy: 686, 2 + xy: 173, 160 size: 55, 45 orig: 55, 45 offset: 0, 0 - index: 2 + index: 5 idle rotate: false - xy: 857, 2 + xy: 59, 19 size: 55, 45 orig: 55, 45 offset: 0, 0 - index: 4 + index: 2 diff --git a/source/core/assets/images/towers/stun_tower.png b/source/core/assets/images/towers/stun_tower.png index 025b35a4c..f88d66a1d 100644 Binary files a/source/core/assets/images/towers/stun_tower.png and b/source/core/assets/images/towers/stun_tower.png differ diff --git a/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java b/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java index 7b4dd37da..a0a91095b 100644 --- a/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java +++ b/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java @@ -18,8 +18,8 @@ public class AITaskComponent extends Component implements TaskRunner { private static final Logger logger = LoggerFactory.getLogger(AITaskComponent.class); private final List priorityTasks = new ArrayList<>(2); + private final List priorityTasksToBeRestored = new ArrayList<>(2); private PriorityTask currentTask; - /** * Add a priority task to the list of tasks. This task will be run only when it has the highest * priority, and can be stopped to run a higher priority task. @@ -59,6 +59,33 @@ public void dispose() { } } + /** + * Empties the priorityTasks List. Disposes all of the entity's tasks. + */ + public void disposeAll() { + currentTask = null; + for (int i = 0; i < priorityTasks.size(); i++) { + priorityTasksToBeRestored.add(priorityTasks.get(i)); + } + for (int i = 0; i < priorityTasks.size(); i++) { + priorityTasks.remove(i); + } + } + + /** + * Restores the priorityTasks List. Adds all of the entity's disposed tasks + * back into priorityTasks. + */ + public void restore() { + for (int i = 0; i < priorityTasksToBeRestored.size(); i++) { + priorityTasks.add(priorityTasksToBeRestored.get(i)); + } + for (int i = 0; i < priorityTasksToBeRestored.size(); i++) { + priorityTasksToBeRestored.remove(i); + } + this.update(); + } + private PriorityTask getHighestPriorityTask() { try { return Collections.max(priorityTasks, Comparator.comparingInt(PriorityTask::getPriority)); 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 5ba12caf0..f82f618d5 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -7,10 +7,11 @@ import com.csse3200.game.components.ProjectileEffects; import com.csse3200.game.areas.terrain.TerrainFactory; import com.csse3200.game.areas.terrain.TerrainFactory.TerrainType; +import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.player.PlayerStatsDisplay; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.*; import com.csse3200.game.physics.PhysicsLayer; -import com.csse3200.game.utils.math.GridPoint2Utils; import com.csse3200.game.utils.math.RandomUtils; import com.csse3200.game.services.ResourceService; import com.csse3200.game.services.ServiceLocator; @@ -29,31 +30,22 @@ /** Forest area for the demo game with trees, a player, and some enemies. */ public class ForestGameArea extends GameArea { private static final Logger logger = LoggerFactory.getLogger(ForestGameArea.class); - private static final int NUM_BUILDINGS = 4; - - private static final int NUM_WALLS = 7; - - private static final int NUM_TREES = 0; private static final int NUM_GHOSTS = 0; private static final int NUM_GRUNTS = 5; - - private static final int NUM_BOSS=4; - + private static final int NUM_BOSS = 4; + + private Timer bossSpawnTimer; private int bossSpawnInterval = 10000; // 1 minute in milliseconds - - private static final int NUM_WEAPON_TOWERS = 3; private static final GridPoint2 PLAYER_SPAWN = new GridPoint2(0, 0); // Temporary spawn point for testing private static final float WALL_WIDTH = 0.1f; - - private static final GridPoint2 BOSS_SPAWN = new GridPoint2(5, 5); - + // Required to load assets before using them private static final String[] forestTextures = { - "images/ingamebg.png", + "images/ingamebg.png", "images/box_boy_leaf.png", "images/background/building1.png", "images/ghost_1.png", @@ -90,26 +82,24 @@ public class ForestGameArea extends GameArea { "images/towers/wallTower.png", "images/background/building2.png", "images/iso_grass_3.png", - "images/terrain_use.png", "images/Dusty_MoonBG.png", - "images/economy/scrap.png", "images/economy/crystal.png", "images/economy/econ-tower.png", - - "images/towers/mine_tower.png", "images/towers/TNTTower.png", - "images/towers/DroidTower.png", "images/projectiles/basic_projectile.png", "images/projectiles/mobProjectile.png", "images/projectiles/engineer_projectile.png", "images/projectiles/mobKing_projectile.png", + "images/projectiles/snow_ball.png", + "images/projectiles/burn_effect.png", + "images/projectiles/stun_effect.png", + "images/projectiles/firework_anim.png", + "images/projectiles/pierce_anim.png", "images/projectiles/snow_ball.png" - - }; private static final String[] forestTextureAtlases = { "images/economy/econ-tower.atlas", @@ -118,23 +108,27 @@ 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/xenoGrunt.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", - "images/projectiles/snow_ball.atlas" - + "images/projectiles/snow_ball.atlas", + "images/projectiles/pierce_anim.atlas", + "images/projectiles/burn_effect.atlas", + "images/projectiles/firework_anim.atlas", + "images/projectiles/mobProjectile.atlas", + "images/projectiles/stun_effect.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -150,21 +144,20 @@ public class ForestGameArea extends GameArea { }; private static final String backgroundMusic = "sounds/background/Sci-Fi1.ogg"; private static final String[] forestMusic = {backgroundMusic}; - + private final TerrainFactory terrainFactory; - + private Entity player; - // Variables to be used with spawn projectile methods. This is the variable + // Variables to be used with spawn projectile methods. This is the variable // that should occupy the direction param. private static final int towardsMobs = 100; - private static final int towardsTowers = 0; - private Entity bossKing1; private Entity bossKing2; - - + + /** * Initialise this ForestGameArea to use the provided TerrainFactory. + * * @param terrainFactory TerrainFactory used to create the terrain for the GameArea. * @requires terrainFactory != null */ @@ -172,72 +165,76 @@ public ForestGameArea(TerrainFactory terrainFactory) { super(); this.terrainFactory = terrainFactory; } - - /** Create the game area, including terrain, static entities (trees), dynamic entities (player) */ + + /** + * Create the game area, including terrain, static entities (trees), dynamic entities (player) + */ @Override public void create() { // Load game assets loadAssets(); displayUI(); spawnTerrain(); - +// spawnBuilding1(); +// spawnBuilding2(); +// spawnMountains(); + + // Set up infrastructure for end game tracking player = spawnPlayer(); player.getEvents().addListener("spawnWave", this::spawnXenoGrunts); - + 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); // spawnProjectileTest(new Vector2(0, 8), PhysicsLayer.NPC, towardsMobs, new Vector2(2f, 2f)); - - spawnXenoGrunts(); - spawnScrap(); - spawnIncome(); - spawnGhosts(); +// spawnGhosts(); spawnWeaponTower(); +// spawnIncome(); + spawnScrap(); spawnTNTTower(); spawnDroidTower(); - spawnEngineer(); - spawnIncome(); - bossKing1 = spawnBossKing1(); +// spawnGapScanners(); +// bossKing1 = spawnBossKing1(); +// bossKing2 = spawnBossKing2(); + bossKing2 = spawnBossKing2(); - - - } - + private void displayUI() { Entity ui = new Entity(); ui.addComponent(new GameAreaDisplay("Box Forest")); + ui.addComponent(ServiceLocator.getGameEndService().getDisplay()); ui.addComponent(ServiceLocator.getCurrencyService().getDisplay()); spawnEntity(ui); } - + private void spawnTerrain() { // Background terrain terrain = terrainFactory.createTerrain(TerrainType.FOREST_DEMO); spawnEntity(new Entity().addComponent(terrain)); - + // Terrain walls float tileSize = terrain.getTileSize(); GridPoint2 tileBounds = terrain.getMapBounds(0); Vector2 worldBounds = new Vector2(tileBounds.x * tileSize, tileBounds.y * tileSize); - + // Left // ! THIS ONE DOESNT WORK. GRIDPOINTS2UTIL.ZERO is (0, 4), not (0, 0) - // spawnEntityAt( - // ObstacleFactory.createWall(WALL_WIDTH, worldBounds.y), GridPoint2Utils.ZERO, false, false); spawnEntityAt( ObstacleFactory.createWall(WALL_WIDTH, worldBounds.y), new GridPoint2(1, 0), false, false); // Right spawnEntityAt( ObstacleFactory.createWall(WALL_WIDTH, worldBounds.y), - new GridPoint2(tileBounds.x , 0), + new GridPoint2(tileBounds.x, 0), false, false); // Top @@ -253,35 +250,66 @@ private void spawnTerrain() { spawnEntityAt( ObstacleFactory.createWall(worldBounds.x, WALL_WIDTH * 7), new GridPoint2(0, 0), false, false); } +// private void spawnBuilding1() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building1 = ObstacleFactory.createBuilding1(); +// spawnEntityAt(building1, randomPos, true, false); +// } +// } +// private void spawnBuilding2() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building2 = ObstacleFactory.createBuilding2(); +// spawnEntityAt(building2, randomPos, true, false); +// } +// } + +// private void spawnMountains() { +// ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList +// +// fixedPositions.add(new GridPoint2(5, 8)); +// fixedPositions.add(new GridPoint2(12, 4)); +// fixedPositions.add(new GridPoint2(20, 10)); +// fixedPositions.add(new GridPoint2(33, 17)); +// +// for (GridPoint2 fixedPos : fixedPositions) { +// Entity tree = ObstacleFactory.createMountain(); +// spawnEntityAt(tree, fixedPos, true, false); +// } +// for (int i = 0; i < NUM_BUILDINGS; i++) { +// GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); +// Entity building1 = ObstacleFactory.createBuilding1(); +// spawnEntityAt(building1, randomPos, true, false); +// } +// } + private void spawnBuilding2() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < NUM_BUILDINGS; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity building2 = ObstacleFactory.createBuilding2(); spawnEntityAt(building2, randomPos, true, false); } } - - private void spawnMountains() { - ArrayList fixedPositions = new ArrayList<>(); //Generating ArrayList - - - for (GridPoint2 fixedPos : fixedPositions) { - Entity tree = ObstacleFactory.createMountain(); - spawnEntityAt(tree, fixedPos, true, false); - } - } - - + + private Entity spawnPlayer() { Entity newPlayer = PlayerFactory.createPlayer(); spawnEntityAt(newPlayer, PLAYER_SPAWN, true, true); + newPlayer.addComponent(new TouchAttackComponent(PhysicsLayer.NPC)); return newPlayer; } - + // Spawn player at a specific position private Entity spawnPlayer(GridPoint2 position) { Entity newPlayer = PlayerFactory.createPlayer(); @@ -289,99 +317,98 @@ private Entity spawnPlayer(GridPoint2 position) { return newPlayer; } - private void spawnGhosts() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 2); - - for (int i = 0; i < NUM_GHOSTS; i++) { - int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate - int randomY = MathUtils.random(0, maxPos.y); - GridPoint2 randomPos = new GridPoint2(fixedX, randomY); - Entity ghost = createGhost(player); - spawnEntityAt(ghost, randomPos, true, true); - } - } - - private Entity spawnBossKing1() { - GridPoint2 minPos = new GridPoint2(0, 0); - GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - GridPoint2 randomPos - = new GridPoint2(0, 0); - Entity ghostKing = NPCFactory.createGhostKing(player); - spawnEntityAt(ghostKing, randomPos, true, true); - return ghostKing; - } +// private void spawnGhosts() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 2); +// +// for (int i = 0; i < NUM_GHOSTS; i++) { +// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate +// int randomY = MathUtils.random(0, maxPos.y); +// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); +// Entity ghost = createGhost(player); +// spawnEntityAt(ghost, randomPos, true, true); +// } +// } - /** - * Spawns a projectile that only heads towards the enemies in its lane. - * - * @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 Entity spawnBossKing1() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// GridPoint2 randomPos +// = new GridPoint2(0, 0); +// Entity ghostKing = NPCFactory.createGhostKing(player); +// spawnEntityAt(ghostKing, randomPos, true, true); +// return ghostKing; +// } + + /** + * Spawns a projectile that only heads towards the enemies in its lane. + * + * @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 spawnProjectile(Vector2 position, short targetLayer, int direction, Vector2 speed) { Entity Projectile = ProjectileFactory.createFireBall(targetLayer, new Vector2(direction, position.y), speed); Projectile.setPosition(position); spawnEntity(Projectile); } - /** + + /** * Spawns a projectile specifically for general mobs/xenohunters - * - * @param position The position of the Entity that's shooting the projectile. + * + * @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 direction The direction the projectile should head towards. + * @param speed The speed of the projectiles. */ private void spawnProjectileTest(Vector2 position, short targetLayer, int direction, Vector2 speed) { Entity Projectile = ProjectileFactory.createEngineerBullet(targetLayer, new Vector2(direction, position.y), speed); Projectile.setPosition(position); spawnEntity(Projectile); } - + /** - * Spawns a projectile to be used for multiple projectile function. - * - * @param position The position of the Entity that's shooting the projectile. - * @param targetLayer The enemy layer of the "shooter". - * @param space The space between the projectiles' destination. - * @param direction The direction the projectile should head towards. - * @param speed The speed of the projectiles. - * + * Spawns a projectile to be used for multiple projectile function. + * + * @param position The position of the Entity that's shooting the projectile. + * @param targetLayer The enemy layer of the "shooter". + * @param space The space between the projectiles' destination. + * @param direction The direction the projectile should head towards. + * @param speed The speed of the projectiles. */ - private void spawnProjectile(Vector2 position, short targetLayer, int space, int direction, Vector2 speed) { + private void spawnProjectile(Vector2 position, short targetLayer, int space, int direction, Vector2 speed) { Entity Projectile = ProjectileFactory.createFireBall(targetLayer, new Vector2(direction, position.y + space), speed); Projectile.setPosition(position); spawnEntity(Projectile); } - // private Entity spawnBossKing() { - // for (int i = 0; i < NUM_BOSS; i++) { - // int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate - // int randomY = MathUtils.random(0, maxPos.y); - // GridPoint2 randomPos = new GridPoint2(fixedX, randomY); - // bossKing1 = BossKingFactory.createBossKing1(player); - // spawnEntityAt(bossKing1, - // randomPos, - // true, - // false); - // } - // return bossKing1; - - // } - +// private Entity spawnBossKing() { +// for (int i = 0; i < NUM_BOSS; i++) { +// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate +// int randomY = MathUtils.random(0, maxPos.y); +// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); +// bossKing1 = BossKingFactory.createBossKing1(player); +// spawnEntityAt(bossKing1, +// randomPos, +// true, +// false); +// } +// return bossKing1; +// +// } + + 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); } } @@ -397,10 +424,27 @@ private void spawnXenoGrunts() { // // } +// private Entity spawnBossKing2() { +// GridPoint2 minPos = new GridPoint2(0, 0); +// GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); +// +// for (int i = 0; i < NUM_BOSS; i++) { +// int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate +// int randomY = MathUtils.random(0, maxPos.y); +// GridPoint2 randomPos = new GridPoint2(fixedX, randomY); +// bossKing2 = BossKingFactory.createBossKing2(player); +// spawnEntityAt(bossKing2, +// randomPos, +// true, +// false); +// } +// return bossKing2; +// } + private Entity spawnBossKing2() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < NUM_BOSS; i++) { int fixedX = terrain.getMapBounds(0).x - 1; // Rightmost x-coordinate int randomY = MathUtils.random(0, maxPos.y); @@ -413,37 +457,35 @@ private Entity spawnBossKing2() { } return bossKing2; } - + /** - * Creates multiple projectiles that travel simultaneous. They all have same + * 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 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. + * @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; + 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 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. + * @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) { @@ -451,32 +493,32 @@ private void spawnEffectProjectile(Vector2 position, short targetLayer, int dire 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 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 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 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 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. @@ -484,51 +526,48 @@ private void spawnRicochetFireball(Vector2 position, short targetLayer, int dire 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 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 amount The amount of projectiles appearing after collision. + * @param direction The direction the projectile should head 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, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { GridPoint2 randomPos1 = RandomUtils.random(minPos, maxPos); GridPoint2 randomPos2 = RandomUtils.random(minPos, maxPos); - //Entity weaponTower = TowerFactory.createWeaponTower(); Entity wallTower = TowerFactory.createWallTower(); Entity fireTower = TowerFactory.createFireTower(); Entity stunTower = TowerFactory.createStunTower(); - //spawnEntityAt(weaponTower, randomPos, true, true); spawnEntityAt(fireTower, randomPos1, true, true); spawnEntityAt(stunTower, randomPos2, true, true); - //spawnEntityAt(wallTower, new GridPoint2(randomPos1.x + 3, randomPos1.y), true, true); } } - + private void spawnTNTTower() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity weaponTower = TowerFactory.createTNTTower(); spawnEntityAt(weaponTower, randomPos, true, true); } - + } private void spawnDroidTower() { @@ -540,17 +579,16 @@ private void spawnDroidTower() { Entity weaponTower = TowerFactory.createDroidTower(); spawnEntityAt(weaponTower, randomPos, true, true); } - } - - + + private void playMusic() { Music music = ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class); music.setLooping(true); music.setVolume(0.3f); music.play(); } - + private void loadAssets() { logger.debug("Loading assets"); ResourceService resourceService = ServiceLocator.getResourceService(); @@ -558,13 +596,13 @@ private void loadAssets() { resourceService.loadTextureAtlases(forestTextureAtlases); resourceService.loadSounds(forestSounds); resourceService.loadMusic(forestMusic); - + while (!resourceService.loadForMillis(10)) { // This could be upgraded to a loading screen logger.info("Loading... {}%", resourceService.getProgress()); } } - + private void unloadAssets() { logger.debug("Unloading assets"); ResourceService resourceService = ServiceLocator.getResourceService(); @@ -573,37 +611,36 @@ private void unloadAssets() { resourceService.unloadAssets(forestSounds); resourceService.unloadAssets(forestMusic); } - + @Override public void dispose() { super.dispose(); ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class).stop(); this.unloadAssets(); } - + private void spawnScrap() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < 5; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity scrap = DropFactory.createScrapDrop(); spawnEntityAt(scrap, randomPos, true, false); } - + for (int i = 0; i < 5; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity crystal = DropFactory.createCrystalDrop(); spawnEntityAt(crystal, randomPos, true, false); } } - - + private void spawnIncome() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - - for (int i = 0; i < 2; i++) { + + for (int i = 0; i < 50; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity towerfactory = TowerFactory.createIncomeTower(); spawnEntityAt(towerfactory, randomPos, true, true); @@ -611,10 +648,47 @@ private void spawnIncome() { } private void spawnEngineer() { - for (int i = 0; i < terrain.getMapBounds(0).x; i += 3) { Entity engineer = EngineerFactory.createEngineer(); spawnEntityAt(engineer, new GridPoint2(1, i), true, true); } } + + /** + * Creates the scanners (one per lane) that detect absence of towers and presence of mobs, + * and trigger engineer spawning + */ +// private void spawnGapScanners() { +// for (int i = 0; i < terrain.getMapBounds(0).y; i++) { +// 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() { +// Entity endGameTracker = new Entity(); +// +// endGameTracker +// .addComponent(new CombatStatsComponent(2, 0)) +// .addComponent(new PlayerStatsDisplay()); +//// .getEvents().addListener("engineerKilled" , this::decrementCounter); +// endGameTracker.create(); +// } + +// private void decrementCounter() { +// this.endStateCounter -= 1; +// logger.info("Engineer killed"); +// if (endStateCounter <= 0) { +// // we've reached the end, game over +// this.dispose(); +// } +// } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java b/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java index 991a3589d..794878666 100644 --- a/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java +++ b/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java @@ -152,6 +152,111 @@ private void fillInvisibleTiles(TiledMapTileLayer layer, GridPoint2 mapSize) { } } +//tile class + public static class Tile { + private int row; + private int col; + private Object object; + + public Tile(int row, int col) { + this.row = row; + this.col = col; + this.object = null; // Initially, no object is placed on the tile + } + + public void setObject(Object object) { + this.object = object; + } + + public Object getObject() { + return object; + } + + public String getLogCoordinates() { + return "(" + row + ", " + col + ")"; + } + } + +// grid class +public static class Grid { + private Tile[][] tiles; + + public Grid(int numRows, int numCols) { + tiles = new Tile[numRows][numCols]; + + for (int row = 0; row < numRows; row++) { + for (int col = 0; col < numCols; col++) { + tiles[row][col] = new Tile(row,col); + } + } + } + + public void placeObject(int row, int col, Object object) { + if (isValidCoordinate(row, col)) { + tiles[row][col].setObject(object); + } else { + System.out.println("Invalid coordinates."); + } + } + + public Object getObject(int row, int col) { + if (isValidCoordinate(row, col)) { + return tiles[row][col].getObject(); + } else { + System.out.println("Invalid coordinates."); + return null; + } + } + + public String getLogCoordinates(int row, int col) { + if (isValidCoordinate(row, col)) { + return tiles[row][col].getLogCoordinates(); + } else { + return "Invalid coordinates."; + } + } + + private boolean isValidCoordinate(int row, int col) { + return row >= 0 && row < tiles.length && col >= 0 && col < tiles[0].length; + } + + public void placeEntity(int row, int col, Object existingEntity) { + } + + public Object getEntity(int row, int col) { + return null; + } +} + +// Array class 1+2 +public class Array { + public static void main(String[] args) { + int numRows = 8; + int numCols = 20; + + Grid grid = new Grid(numRows, numCols); + + // Place an existing entity in a specific tile + int row = 3; + int col = 5; + // Replace 'Object' with the type of existing entity you want to place + Object existingEntity = new YourExistingEntity(); + + grid.placeEntity(row, col, existingEntity); + + // Get the entity from a tile + Object entity = grid.getEntity(row, col); + System.out.println("Entity at " + grid.getLogCoordinates(row, col) + ": " + entity); + } + + private static class YourExistingEntity { + } +} + + + + + private static void fillTiles(TiledMapTileLayer layer, GridPoint2 mapSize, TerrainTile tile) { BitmapFont font = new BitmapFont(); TextButton.TextButtonStyle textButtonStyle = new TextButton.TextButtonStyle(); diff --git a/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java b/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java index 77e5a224c..cdf8bacbe 100644 --- a/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java +++ b/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java @@ -29,6 +29,11 @@ public class CombatStatsComponent extends Component { private static final Logger logger = LoggerFactory.getLogger(CombatStatsComponent.class); + private static final String HEALTH_FULL = "fullHealth"; + private static final String HEALTH_MID = "midHealth"; + private static final String HEALTH_LOW = "lowHealth"; + private static final String HIT_EVENT = "hitStart"; + private static final String UPDATE_HEALTH_EVENT = "updateHealth"; private int health; private int baseAttack; private int fullHealth; @@ -41,7 +46,7 @@ public CombatStatsComponent(int health, int baseAttack) { setHealth(health); setBaseAttack(baseAttack); this.fullHealth = health; - this.state = "fullHealth"; + this.state = HEALTH_FULL; } public CombatStatsComponent(int health, int baseAttack, @@ -54,7 +59,7 @@ public CombatStatsComponent(int health, int baseAttack, this.drops = drops; this.closeRangeAbilities = closeRangeAbilities; this.longRangeAbilities = longRangeAbilities; - this.state = "fullHealth"; + this.state = HEALTH_FULL; } /** @@ -88,7 +93,7 @@ public void setHealth(int health) { } if (entity != null) { - entity.getEvents().trigger("updateHealth", this.health); + entity.getEvents().trigger(UPDATE_HEALTH_EVENT, this.health); } } @@ -150,7 +155,7 @@ public void hit(Integer damage) { int newHealth = getHealth() - damage; setHealth(newHealth); if (entity != null && !this.isDead()) { - entity.getEvents().trigger("hitStart"); + entity.getEvents().trigger(HIT_EVENT); } changeState(); } @@ -159,7 +164,7 @@ public void hit(Integer damage) { public void hit(CombatStatsComponent attacker) { int newHealth = getHealth() - attacker.getBaseAttack(); if (entity != null && !this.isDead()) { - entity.getEvents().trigger("hitStart"); + entity.getEvents().trigger(HIT_EVENT); } setHealth(newHealth); changeState(); @@ -226,11 +231,11 @@ public Weapon getWeapon(Entity target) { * */ public void changeState() { if (this.health <= (this.fullHealth * 0.33)) { - this.state = "lowHealth"; + this.state = HEALTH_LOW; } else if (this.health <= (this.fullHealth * 0.66)) { - this.state = "midHealth"; + this.state = HEALTH_MID; } else { - this.state = "fullHealth"; + this.state = HEALTH_FULL; } } diff --git a/source/core/src/main/com/csse3200/game/components/EffectsComponent.java b/source/core/src/main/com/csse3200/game/components/EffectsComponent.java index 843d65288..76314fd87 100644 --- a/source/core/src/main/com/csse3200/game/components/EffectsComponent.java +++ b/source/core/src/main/com/csse3200/game/components/EffectsComponent.java @@ -1,14 +1,15 @@ package com.csse3200.game.components; +import java.util.ArrayList; + import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.Fixture; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.tower.TowerUpgraderComponent; import com.csse3200.game.entities.Entity; -import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.BodyUserData; import com.csse3200.game.physics.PhysicsLayer; -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.physics.components.PhysicsMovementComponent; import com.csse3200.game.services.ServiceLocator; @@ -29,6 +30,7 @@ public class EffectsComponent extends Component { private HitboxComponent hitboxComponent; private final short targetLayer; private Array burnEntities = new Array<>(); + private ArrayList stunnedEntities = new ArrayList<>(); /** * Constructor for the AoEComponent. @@ -50,7 +52,7 @@ public void create() { } private void onCollisionStart(Fixture me, Fixture other) { - // Nothing to do on collision start + // Nothing to do in collision start } private void onCollisionEnd(Fixture me, Fixture other) { @@ -72,28 +74,19 @@ private void onCollisionEnd(Fixture me, Fixture other) { return; } + System.out.println("target layer: " + otherEntity.getLayer()); + // Apply effect - switch (effect) { - case FIREBALL -> { - if (aoe) { - applyAoeEffect(ProjectileEffects.FIREBALL); - } - } - case BURN -> { - if (aoe) { - applyAoeEffect(ProjectileEffects.BURN); - } else { - applySingleEffect(ProjectileEffects.BURN, otherCombatStats, otherEntity); - } + if (effect == ProjectileEffects.FIREBALL) { + if (aoe) { + applyAoeEffect(ProjectileEffects.FIREBALL); } - case SLOW -> { - if (aoe) { - applyAoeEffect(ProjectileEffects.SLOW); - } else { - applySingleEffect(ProjectileEffects.SLOW, otherCombatStats, otherEntity); - } + } else { + if (aoe) { + applyAoeEffect(effect); + } else { + applySingleEffect(effect, otherCombatStats, otherEntity); } - case STUN -> {} } } @@ -117,7 +110,7 @@ public void applySingleEffect(ProjectileEffects effect, CombatStatsComponent tar burnEffect(targetCombatStats, hostCombatStats); } case SLOW -> {slowEffect(targetEntity);} - case STUN -> {} + case STUN -> {stunEffect(targetEntity);} } } /** @@ -152,7 +145,9 @@ public void applyAoeEffect(ProjectileEffects effect) { case FIREBALL -> {fireballEffect(targetCombatStats, hostCombatStats);} case BURN -> {burnEffect(targetCombatStats, hostCombatStats);} case SLOW -> {slowEffect(targetEntity);} - case STUN -> {} + case STUN -> { + stunEffect(targetEntity); + } } } else { return; @@ -180,7 +175,6 @@ private void burnEffect(CombatStatsComponent target, CombatStatsComponent host) return; } burnEntities.add(target); - // Create a timer task to apply the effect repeatedly int numberOfTicks = 5; long delay = 1; @@ -218,7 +212,7 @@ private void slowEffect(Entity targetEntity) { if (PhysicsLayer.contains(PhysicsLayer.HUMANS, targetEntity.getComponent(HitboxComponent.class).getLayer())) { // towers towerFlag = true; - //targetEntity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, -30); + targetEntity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, -30); } else if (PhysicsLayer.contains(PhysicsLayer.NPC, targetEntity.getComponent(HitboxComponent.class).getLayer())) { // mobs mobFlag = true; @@ -245,11 +239,47 @@ private void slowEffect(Entity targetEntity) { @Override public void run() { if (finalTowerFlag) { - //targetEntity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, 30); + targetEntity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, 30); } else if (finalMobFlag) { finalTargetPhysics.setSpeed(new Vector2(finalXSpeed, finalYSpeed)); } } }, 5); // 5 seconds delay } + + /** + * Applies stun effect to a taget entity. + * @param targetEntity Entity for stun effect to be applied to. + */ + private void stunEffect(Entity targetEntity) { + CombatStatsComponent hostCombatStats = targetEntity.getComponent(CombatStatsComponent.class); + AITaskComponent taskComponent = targetEntity.getComponent(AITaskComponent.class); + + if (hostCombatStats == null || taskComponent == null) { + return; + } + + hostCombatStats.setBaseAttack(0); + + if (stunnedEntities.contains(targetEntity)) { + return; + } + + taskComponent.disposeAll(); + stunnedEntities.add(targetEntity); + + new java.util.Timer().schedule( + new java.util.TimerTask() { + @Override + public void run() { + taskComponent.restore(); + for (int i = 0; i < stunnedEntities.size(); i++) { + if (stunnedEntities.get(i).equals(targetEntity)) { + stunnedEntities.remove(stunnedEntities.get(i)); + } + } + this.cancel(); + } + }, 5000); + } } diff --git a/source/core/src/main/com/csse3200/game/components/SplitFireworksComponent.java b/source/core/src/main/com/csse3200/game/components/SplitFireworksComponent.java index a48f6595a..6527271e8 100644 --- a/source/core/src/main/com/csse3200/game/components/SplitFireworksComponent.java +++ b/source/core/src/main/com/csse3200/game/components/SplitFireworksComponent.java @@ -18,16 +18,17 @@ public class SplitFireworksComponent extends Component { private HitboxComponent hitboxComponent; private int amount; private static int TOTAL_RANGE = 450; + private static double SPAWN_OFFSET_X = 1.75; /** - * Initialises a component that splits the projectile into multiple fireballs + * Initialises a component that splits the projectile into multiple fireballs * upon collision on a specified target layer. - * The spawned projectiles will be spawned just before original projectile + * The spawned projectiles will be spawned just before original projectile * and spread out in multiple direction set by a certain range. * Assumes amount of split projectiles is greater or equal than 2. * * @param targetLayer Target layer upon collision. - * @param amount Amount of projectiles that is split after collision event. + * @param amount Amount of projectiles that is split after collision event. */ public SplitFireworksComponent(short targetLayer, int amount) { this.targetLayer = targetLayer; @@ -52,13 +53,13 @@ private void onCollisionEnd(Fixture me, Fixture other) { int newDirection = (i * TOTAL_RANGE) / (amount - 1); // Boundaries - float newXPosition = (float) (projectile.getPosition().x + 1.75); + float newXPosition = (float) (projectile.getPosition().x + SPAWN_OFFSET_X); if (newXPosition >= 18 || newXPosition <= 1) return; // * RIGHT NOW TARGET IS NPC, SUBJECT TO CHANGE // Speed is a bit faster than normal but can change. - Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, + Entity newProjectile = ProjectileFactory.createFireworks(PhysicsLayer.NPC, new Vector2(100, projectile.getPosition().y + (newDirection - (TOTAL_RANGE/2))), new Vector2(3f, 3f)); newProjectile.setPosition(newXPosition, (float) projectile.getPosition().y); 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..80c93f434 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; @@ -59,12 +60,12 @@ public TouchAttackComponent(short targetLayer, float knockback, boolean disposeO @Override public void create() { entity.getEvents().addListener("collisionStart", this::onCollisionStart); - // entity.getEvents().addListener("collisionEnd", this::onCollisionEnd); + entity.getEvents().addListener("collisionEnd", this::onCollisionEnd); combatStats = entity.getComponent(CombatStatsComponent.class); 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,11 +106,37 @@ public void setDisposeOnHit(boolean disposeOnHit) { this.disposeOnHit = disposeOnHit; } + public void setKnockBack(float knockback) { this.knockbackForce = knockback; } - // 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 + if (hitboxComponent.getFixture() != me) { + // Not triggered by hitbox, ignore + return; + } + + if (!PhysicsLayer.contains(targetLayer, other.getFilterData().categoryBits)) { + // Doesn't match our target layer, ignore + return; + } + + if (disposeOnHit) { + Entity projectile = ((BodyUserData) me.getBody().getUserData()).entity; + projectile.setFlagForDelete(true); + } + } + + 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; + } + } diff --git a/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java b/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java index b64a2511d..0b9703a39 100644 --- a/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java +++ b/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java @@ -51,7 +51,7 @@ private void addActors() { table = new Table(); table.top().left(); table.setFillParent(true); - table.padTop(100f).padLeft(20f); + table.padTop(140f).padLeft(20f); scrapsTb = createButton("images/economy/scrapBanner.png", ServiceLocator.getCurrencyService().getScrap().getAmount()); diff --git a/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java b/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java new file mode 100644 index 000000000..39e849574 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java @@ -0,0 +1,70 @@ +package com.csse3200.game.components.gamearea; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.utils.Drawable; +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; +import com.badlogic.gdx.utils.Align; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.ui.UIComponent; + +public class EngineerCountDisplay extends UIComponent { + private Table table; + private TextButton engineerTb; + + @Override + public void create() { + super.create(); + addActors(); + } + + /** + * Initialises the engineer count display + * Positions it on the stage using a table + */ + private void addActors() { + table = new Table(); + table.top().left(); + table.setFillParent(true); + table.padTop(80f).padLeft(20f); + + Drawable drawable = new TextureRegionDrawable(new TextureRegion( + new Texture("images/engineers/engineerBanner.png"))); + TextButton.TextButtonStyle style = new TextButton.TextButtonStyle( + drawable, drawable, drawable, new BitmapFont()); + + String text = String.format("%d", ServiceLocator.getGameEndService().getEngineerCount()); + engineerTb = new TextButton(text, style); + engineerTb.setDisabled(true); + engineerTb.getLabel().setAlignment(Align.right); + + engineerTb.pad(0, 0, 0, 50); + engineerTb.setTransform(true); + + table.add(engineerTb).width(engineerTb.getWidth() * 0.5f).height(engineerTb.getHeight() * 0.5f); + stage.addActor(table); + } + + /** + * Updates the engineer count on the UI component + */ + public void updateCount() { + String text = String.format("%d", ServiceLocator.getGameEndService().getEngineerCount()); + engineerTb.getLabel().setText(text); + } + + @Override + protected void draw(SpriteBatch batch) { + // handled by stage + } + + @Override + public void dispose() { + super.dispose(); + engineerTb.remove(); + } +} 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..42c04ce19 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,47 +1,62 @@ package com.csse3200.game.components.npc; +import com.badlogic.gdx.audio.Sound; import com.csse3200.game.components.Component; import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +import java.util.Random; /** * This class listens to events relevant to a ghost entity's state and plays the animation when one * of the events is triggered. */ public class XenoAnimationController extends Component { + // // For on collision sounds later + // private static final String COLLISION_SFX = "sounds/projectiles/on_collision.mp3"; + // Sound onCollisionSound = ServiceLocator.getResourceService().getAsset( + // COLLISION_SFX, Sound.class); AnimationRenderComponent animator; + Random rand = new Random(); @Override 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("meleeStart", this::animateMelee2); + entity.getEvents().addListener("wanderStart", this::animateRun); + entity.getEvents().addListener("runHurt", this::animateHurt); + entity.getEvents().addListener("meleeStart", this::animateMelee); entity.getEvents().addListener("shootStart", this::animateShoot); entity.getEvents().addListener("dieStart", this::animateDie); + entity.getEvents().addListener("stop", this::stopAnimation); } - void animateWander() { + void animateRun() { animator.startAnimation("xeno_run"); } - void animateChase() { - animator.startAnimation("xeno_run"); + void animateHurt() { + animator.startAnimation("xeno_hurt"); } void animateShoot() { animator.startAnimation("xeno_shoot"); } - void animateMelee1() { - animator.startAnimation("xeno_melee_1"); - } - - void animateMelee2() { - animator.startAnimation("xeno_melee_2"); + void animateMelee() { + int randAnim = rand.nextInt(2); + if (randAnim == 1) { + animator.startAnimation("xeno_melee_1"); + } else { + animator.startAnimation("xeno_melee_2"); + } } void animateDie() { animator.startAnimation("xeno_die"); } + + void stopAnimation() { + animator.startAnimation("default"); + } } diff --git a/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java b/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java index e65fc8763..c6231f29e 100644 --- a/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java @@ -35,10 +35,10 @@ public class HumanAnimationController extends Component { private static final String FIRE_AUTO_SFX = "sounds/engineers/firing_auto.mp3"; private static final String FIRE_SINGLE_SFX = "sounds/engineers/firing_single.mp3"; - AnimationRenderComponent animator; - Sound fireAutoSound = ServiceLocator.getResourceService().getAsset( + private AnimationRenderComponent animator; + private final Sound fireAutoSound = ServiceLocator.getResourceService().getAsset( FIRE_AUTO_SFX, Sound.class); - Sound fireSingleSound = ServiceLocator.getResourceService().getAsset( + private final Sound fireSingleSound = ServiceLocator.getResourceService().getAsset( FIRE_SINGLE_SFX, Sound.class); /** @@ -56,7 +56,7 @@ public void create() { entity.getEvents().addListener(PREP, this::animatePrep); entity.getEvents().addListener(WALK_PREP, this::animatePrepWalk); entity.getEvents().addListener(FIRING_SINGLE, this::animateSingleFiring); - entity.getEvents().addListener(FIRING_AUTO, this::animateFiring); + entity.getEvents().addListener(FIRING_AUTO, this::animateFiringAuto); entity.getEvents().addListener(HIT, this::animateHit); entity.getEvents().addListener(DEATH, this::animateDeath); } @@ -110,7 +110,7 @@ void animateSingleFiring() { * Callback that starts the shoot animation in auto mode and plays the auto fire sound. * Currently unused, but intended to be incorporated as engineer functionality expands. */ - void animateFiring() { + void animateFiringAuto() { animator.startAnimation(FIRE_AUTO_ANIM); fireAutoSound.play(); } diff --git a/source/core/src/main/com/csse3200/game/components/projectile/BurnEffectProjectileAnimationController.java b/source/core/src/main/com/csse3200/game/components/projectile/BurnEffectProjectileAnimationController.java new file mode 100644 index 000000000..847f77027 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/projectile/BurnEffectProjectileAnimationController.java @@ -0,0 +1,33 @@ +package com.csse3200.game.components.projectile; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +public class BurnEffectProjectileAnimationController extends Component { + /** Event name constants */ + private static final String START = "startProjectile"; + private static final String FINAL = "startProjectileFinal"; + + /** Animation name constants */ + private static final String START_ANIM = "projectile"; + private static final String FINAL_ANIM = "projectileFinal"; + AnimationRenderComponent animator; + + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(START, this::animateStart); + entity.getEvents().addListener(FINAL, this::animateFinal); + + } + + void animateStart() { + animator.startAnimation(START_ANIM); + } + + void animateFinal() { + animator.startAnimation(FINAL_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/projectile/FireworkAnimationController.java b/source/core/src/main/com/csse3200/game/components/projectile/FireworkAnimationController.java new file mode 100644 index 000000000..cc2239b18 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/projectile/FireworkAnimationController.java @@ -0,0 +1,26 @@ +package com.csse3200.game.components.projectile; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +public class FireworkAnimationController extends Component { + /** Event name constants */ + private static final String START = "startProjectile"; + + /** Animation name constants */ + private static final String START_ANIM = "projectile"; + AnimationRenderComponent animator; + + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(START, this::animateStart); + + } + + void animateStart() { + animator.startAnimation(START_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/projectile/PierceProjectileAnimationController.java b/source/core/src/main/com/csse3200/game/components/projectile/PierceProjectileAnimationController.java new file mode 100644 index 000000000..a5801aa80 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/projectile/PierceProjectileAnimationController.java @@ -0,0 +1,25 @@ +package com.csse3200.game.components.projectile; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +public class PierceProjectileAnimationController extends Component { + /** Event name constants */ + private static final String START = "startProjectile"; + /** Animation name constants */ + private static final String START_ANIM = "projectile"; + AnimationRenderComponent animator; + + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(START, this::animateStart); + + } + + void animateStart() { + animator.startAnimation(START_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/projectile/ProjectileAnimationController.java b/source/core/src/main/com/csse3200/game/components/projectile/ProjectileAnimationController.java index d39e91f46..9610554c9 100644 --- a/source/core/src/main/com/csse3200/game/components/projectile/ProjectileAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/projectile/ProjectileAnimationController.java @@ -13,6 +13,7 @@ public class ProjectileAnimationController extends Component{ private static final String FINAL_ANIM = "projectileFinal"; AnimationRenderComponent animator; + @Override public void create() { super.create(); diff --git a/source/core/src/main/com/csse3200/game/components/projectile/StunEffectProjectileAnimationController.java b/source/core/src/main/com/csse3200/game/components/projectile/StunEffectProjectileAnimationController.java new file mode 100644 index 000000000..94899ff17 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/projectile/StunEffectProjectileAnimationController.java @@ -0,0 +1,27 @@ +package com.csse3200.game.components.projectile; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +public class StunEffectProjectileAnimationController extends Component { + /** Event name constants */ + private static final String START = "startProjectile"; + + /** Animation name constants */ + private static final String START_ANIM = "projectile"; + AnimationRenderComponent animator; + + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(START, this::animateStart); + + } + + void animateStart() { + animator.startAnimation(START_ANIM); + } + +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java index 57b91f06e..9f1159de4 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java @@ -4,9 +4,6 @@ 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.ProjectileEffects; -import com.csse3200.game.entities.Entity; -import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.raycast.RaycastHit; @@ -24,13 +21,15 @@ public class DroidCombatTask extends DefaultTask implements PriorityTask { private static final int INTERVAL = 1; // time interval to scan for enemies in seconds private static final short TARGET = PhysicsLayer.NPC; // The type of targets that the tower will detect // the following four constants are the event names that will be triggered in the state machine - private static final String GO_UP = "goUpStart"; - private static final String GO_DOWN = "goDownStart"; - private static final String ATTACK_UP = "attackUpStart"; - private static final String ATTACK_DOWN = "attackDownStart"; - private static final String WALK = "walkStart"; - private static final String DEATH = "deathStart"; - private static final String IDLE = "idleStart"; + public static final String GO_UP = "goUpStart"; + public static final String GO_DOWN = "goDownStart"; + public static final String ATTACK_UP = "attackUpStart"; + public static final String ATTACK_DOWN = "attackDownStart"; + public static final String WALK = "walkStart"; + public static final String DEATH = "deathStart"; + public static final String IDLE = "idleStart"; + public static final String SHOOT_UP = "ShootUp"; + public static final String SHOOT_DOWN = "ShootDown"; // class attributes @@ -43,10 +42,10 @@ public class DroidCombatTask extends DefaultTask implements PriorityTask { private long endTime; private final RaycastHit hit = new RaycastHit(); - private enum STATE { + public enum STATE { IDLE, UP, DOWN, SHOOT_UP, SHOOT_DOWN, WALK, DIE } - private STATE towerState = STATE.WALK; + public STATE towerState = STATE.WALK; /** * @param priority Task priority when targets are detected (0 when nothing detected). Must be a positive integer. @@ -105,6 +104,7 @@ public void updateTowerState() { case IDLE -> { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK_UP); + owner.getEntity().getEvents().trigger(SHOOT_UP); towerState = STATE.DOWN; } else { owner.getEntity().getEvents().trigger(IDLE); @@ -113,12 +113,7 @@ public void updateTowerState() { case SHOOT_DOWN -> { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK_DOWN); - Entity Projectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(100, - owner.getEntity().getPosition().y), new Vector2(2,2), ProjectileEffects.SLOW, false); - Projectile.setScale(new Vector2(0.5f,0.5f)); - Projectile.setPosition((float) (owner.getEntity().getPosition().x + 0.2), - (float) (owner.getEntity().getPosition().y - 0.2)); - ServiceLocator.getEntityService().register(Projectile); + owner.getEntity().getEvents().trigger(SHOOT_DOWN); towerState = STATE.UP; } else { owner.getEntity().getEvents().trigger(GO_UP); @@ -129,13 +124,8 @@ public void updateTowerState() { if (isTargetVisible()) { owner.getEntity().getEvents().trigger(ATTACK_UP); + owner.getEntity().getEvents().trigger(SHOOT_UP); towerState = STATE.DOWN; - Entity Projectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(100, - owner.getEntity().getPosition().y), new Vector2(2,2), ProjectileEffects.SLOW, false); - Projectile.setScale(new Vector2(0.5f,0.5f)); - Projectile.setPosition((float) (owner.getEntity().getPosition().x + 0.2), - (float) (owner.getEntity().getPosition().y + 0.5)); - ServiceLocator.getEntityService().register(Projectile); } else { owner.getEntity().getEvents().trigger(IDLE); towerState = STATE.IDLE; @@ -162,8 +152,9 @@ public void updateTowerState() { } } case DIE -> { - if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) - owner.getEntity().setFlagForDelete(true); + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } } } } @@ -173,7 +164,15 @@ public void updateTowerState() { @Override public void stop() { super.stop(); -// owner.getEntity().getEvents().trigger(STOW); + } + + /** + * Returns the current state of the tower. + * + * @return the current state of the tower. + */ + public STATE getState() { + return this.towerState; } /** @@ -189,8 +188,10 @@ public int getPriority() { * Uses a raycast to determine whether there are any targets in detection range * @return true if a target is visible, false otherwise */ - private boolean isTargetVisible() { + public boolean isTargetVisible() { // If there is an obstacle in the path to the max range point, mobs visible. return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + + } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java index 3ac568f1b..e2f1327ef 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -3,11 +3,14 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.ProjectileEffects; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; @@ -20,9 +23,10 @@ public class FireTowerCombatTask extends DefaultTask implements PriorityTask { private static final int INTERVAL = 1; //time interval to scan for enemies in seconds private static final short TARGET = PhysicsLayer.NPC; //the type of targets this tower will detect //The constants are names of events that will be triggered in the state machine - private static final String IDLE = "startIdle"; - private static final String PREP_ATTACK = "startAttackPrep"; - private static final String ATTACK = "startAttack"; + public static final String IDLE = "startIdle"; + public static final String PREP_ATTACK = "startAttackPrep"; + public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; //Class attributes private final int priority; @@ -35,10 +39,10 @@ public class FireTowerCombatTask extends DefaultTask implements PriorityTask { private long endTime; private final RaycastHit hit = new RaycastHit(); - private enum STATE { - IDLE, PREP_ATTACK, ATTACK + public enum STATE { + IDLE, PREP_ATTACK, ATTACK, DEATH } - private STATE towerState = STATE.IDLE; + public STATE towerState = STATE.IDLE; /** * Starts the task running, triggers the initial 'IDLE' event @@ -80,6 +84,11 @@ public void update() { * finite state machine for the FireTower. Detects mobs in a straight line and changes the state of the tower. */ public void updateTowerState() { + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } switch (towerState) { case IDLE -> { if (isTargetVisible()) { @@ -102,13 +111,18 @@ public void updateTowerState() { towerState = STATE.IDLE; } else { owner.getEntity().getEvents().trigger(ATTACK); - Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, - new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + Entity newProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), ProjectileEffects.BURN, false); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), (float) (owner.getEntity().getPosition().y + 0.25)); ServiceLocator.getEntityService().register(newProjectile); } } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } } } @@ -120,6 +134,13 @@ public void stop() { owner.getEntity().getEvents().trigger(IDLE); } + /** + * @return returns the current state of the tower + */ + public STATE getState() { + return this.towerState; + } + /** * gets the priority for the current task. * @return (int) active priority if target is visible and inactive priority otherwise 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..fa6e41433 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,10 +27,12 @@ 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 IDLE = "idleStart"; + private static final String FIRING = "shootStart"; + private static final String IDLE = "stop"; + + private Fixture target; private final int priority; private final float maxRange; @@ -67,9 +74,9 @@ 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"); +// owner.getEntity().getEvents().trigger("shootStart"); } /** @@ -90,55 +97,59 @@ 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 -> { if (isTargetVisible()) { // targets detected in idle mode - start deployment - owner.getEntity().getEvents().trigger(DEPLOY); +// owner.getEntity().getEvents().trigger(DEPLOY); mobState = STATE.DEPLOY; } } case DEPLOY -> { // currently deploying, - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(FIRING); + if (isTargetVisible() || this.meleeOrProjectile() != null) { + owner.getEntity().getComponent(PhysicsMovementComponent.class).setEnabled(false); + this.owner.getEntity().getEvents().trigger(FIRING); mobState = STATE.FIRING; } else { - owner.getEntity().getEvents().trigger(STOW); + this.owner.getEntity().getEvents().trigger(STOW); mobState = STATE.STOW; } } case FIRING -> { - // targets gone - stop firing - if (!isTargetVisible()) { - owner.getEntity().getEvents().trigger(STOW); + // targets gone or cannot be attacked - stop firing + if (!isTargetVisible() || this.meleeOrProjectile() == null) { + this.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); + this.owner.getEntity().getEvents().trigger("meleeStart"); + } 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"); + this.owner.getEntity().getEvents().trigger(FIRING); + mobState = STATE.STOW; + } } + owner.getEntity().getComponent(PhysicsMovementComponent.class).setEnabled(true); + } case STOW -> { // currently stowing if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(DEPLOY); +// owner.getEntity().getEvents().trigger(DEPLOY); mobState = STATE.DEPLOY; } else { owner.getEntity().getEvents().trigger(IDLE); @@ -153,8 +164,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 +178,10 @@ public void stop() { */ @Override public int getPriority() { -// return -1; if (status == Status.ACTIVE) { return getActivePriority(); } return getInactivePriority(); -// return isTargetVisible() ? getActivePriority() : getInactivePriority(); } /** @@ -176,13 +189,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 +200,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 +211,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 deleted file mode 100644 index f04ad39a0..000000000 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.csse3200.game.components.tasks; - -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.rendering.DebugRenderer; - - -/** - * Task that prints a message to the terminal whenever it is called. - */ -public class MobDeathTask extends DefaultTask implements PriorityTask { - private static final int INTERVAL = 1; // time interval to scan for towers in - - private final int priority; - private Vector2 mobPosition = new Vector2(10f,10f); - private final PhysicsEngine physics; - private GameTime timeSource; - private long endTime; - private final RaycastHit hit = new RaycastHit(); - - private int mobHealth; - - /** - * @param priority Task priority when shooting (0 when not chasing). - */ - public MobDeathTask(int priority) { - this.priority = priority; - - physics = ServiceLocator.getPhysicsService().getPhysics(); - - timeSource = ServiceLocator.getTimeSource(); - } - - @Override - public void start() { - super.start(); - // gets starting health - this.mobHealth = owner.getEntity().getComponent(CombatStatsComponent.class).getHealth(); - //sets mob position - this.mobPosition = owner.getEntity().getCenterPosition(); - //sets endTime - endTime = timeSource.getTime() + (INTERVAL * 500); - } - - @Override - public void update() { - if (timeSource.getTime() >= endTime) { - updateMobState(); - endTime = timeSource.getTime() + (INTERVAL * 1000); - } - } - - public void updateMobState() { - - mobHealth = owner.getEntity().getComponent(CombatStatsComponent.class).getHealth(); - // TODO: inset a bit that picks from a list of drop options and drops this - - if (mobIsDead(mobHealth)) { - killMob(); - dropCurrency(); - } - - } - - @Override - public void stop() { - super.stop(); - } - - @Override - public int getPriority() { - if (status == Status.ACTIVE) { - return getActivePriority(); - } - - return getInactivePriority(); - } - - private int getActivePriority() { - if (mobHealth > 0) { - return -1; - } - return priority; - } - - private int getInactivePriority() { - if (mobHealth <= 0) { - return priority; - } - return -1; - } - private boolean mobIsDead(int mobhealth) { - - if (mobhealth <= 0) { - return true; - } - return false; - } - - private void killMob() { - owner.getEntity().dispose(); - } - - private void dropCurrency() { - - Entity scrap = DropFactory.createScrapDrop(); - scrap.setPosition(mobPosition.x,mobPosition.y); - ServiceLocator.getEntityService().register(scrap); - - } - -} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobWanderTask.java similarity index 50% rename from source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java rename to source/core/src/main/com/csse3200/game/components/tasks/MobWanderTask.java index 6fd754880..ccd7acf8c 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/WanderTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobWanderTask.java @@ -4,7 +4,11 @@ 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.utils.math.RandomUtils; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.DropFactory; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,8 +16,8 @@ * 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. */ -public class WanderTask extends DefaultTask implements PriorityTask { - private static final Logger logger = LoggerFactory.getLogger(WanderTask.class); +public class MobWanderTask extends DefaultTask implements PriorityTask { + private static final Logger logger = LoggerFactory.getLogger(MobWanderTask.class); private final Vector2 wanderRange; private final float waitTime; @@ -21,13 +25,15 @@ 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 * called. * @param waitTime How long in seconds to wait between wandering. */ - public WanderTask(Vector2 wanderRange, float waitTime) { + public MobWanderTask(Vector2 wanderRange, float waitTime) { this.wanderRange = wanderRange; this.waitTime = waitTime; } @@ -53,29 +59,62 @@ public void start() { currentTask = movementTask; - this.owner.getEntity().getEvents().trigger("wanderStart"); +// this.owner.getEntity().getEvents().trigger("wanderStart"); } @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()) { + this.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() { logger.debug("Starting waiting"); + this.owner.getEntity().getEvents().trigger("stop"); swapTask(waitTask); } private void startMoving() { logger.debug("Starting moving"); movementTask.setTarget(getDirection()); + this.owner.getEntity().getEvents().trigger("wanderStart"); swapTask(movementTask); } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/RangeBossMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/RangeBossMovementTask.java index be0e655c6..81e0399d1 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/RangeBossMovementTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/RangeBossMovementTask.java @@ -79,7 +79,7 @@ public void update() { switchMobKingBallState(); // newProjectile.scaleHeight(-1f); newProjectile.setScale(-1.3f, 0.82f); - newProjectile.setPosition((float) (currentPos.x), (float) (currentPos.y+0.75f)); + newProjectile.setPosition((float) (currentPos.x), (float) (currentPos.y + 0.55f)); ServiceLocator.getEntityService().register(newProjectile); startWaiting(); } else { 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/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java index 2919d49bc..be56625f7 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -3,11 +3,14 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.ProjectileEffects; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; @@ -23,6 +26,7 @@ public class StunTowerCombatTask extends DefaultTask implements PriorityTask { //Following constants are names of events that will be triggered in the state machine public static final String IDLE = "startIdle"; public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; //Following are the class constants private final int priority; @@ -35,10 +39,10 @@ public class StunTowerCombatTask extends DefaultTask implements PriorityTask { private final RaycastHit hit = new RaycastHit(); //enums for the state triggers - private enum STATE { - IDLE, ATTACK + public enum STATE { + IDLE, ATTACK, DIE } - private STATE towerState = STATE.IDLE; + public STATE towerState = STATE.IDLE; /** * @param priority Task priority when targets are detected (0 when nothing is present) @@ -82,6 +86,14 @@ public void update() { * of the game. If enemies are detected, state of the tower is changed to attack state. */ public void updateTowerState() { + + if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && + towerState != STATE.DIE) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DIE; + return; + } + switch (towerState) { case IDLE -> { if(isTargetVisible()) { @@ -95,16 +107,28 @@ public void updateTowerState() { towerState = STATE.IDLE; } else { owner.getEntity().getEvents().trigger(ATTACK); - Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, - new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); +// Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, +// new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + Entity newProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), + ProjectileEffects.STUN, false); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), (float) (owner.getEntity().getPosition().y + 0.25)); ServiceLocator.getEntityService().register(newProjectile); } } + case DIE -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } } } + public STATE getState() { + return this.towerState; + } + /** * stops the current animation and switches back the state of the tower to IDLE. */ diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java index c55a92450..bcb210846 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java @@ -4,8 +4,6 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; -import com.csse3200.game.entities.Entity; -import com.csse3200.game.entities.EntityService; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.raycast.RaycastHit; @@ -22,10 +20,10 @@ public class TNTTowerCombatTask extends DefaultTask implements PriorityTask { private static final int INTERVAL = 1; // time interval to scan for enemies in seconds private static final short TARGET = PhysicsLayer.NPC; // The type of targets that the tower will detect // the following four constants are the event names that will be triggered in the state machine - private static final String DIG = "digStart"; - private static final String EXPLOSION = "explodeStart"; - private static final String DEFAULT = "defaultStart"; - private static final String DAMAGE = "TNTDamageStart"; + public static final String DIG = "digStart"; + public static final String EXPLOSION = "explodeStart"; + public static final String DEFAULT = "defaultStart"; + public static final String DAMAGE = "TNTDamageStart"; // class attributes @@ -37,9 +35,9 @@ public class TNTTowerCombatTask extends DefaultTask implements PriorityTask { private final GameTime timeSource; private long endTime; private final RaycastHit hit = new RaycastHit(); - private boolean readToDelete = false; + public boolean readToDelete = false; - private enum STATE { + public enum STATE { IDLE, EXPLODE, REMOVE } private STATE towerState = STATE.IDLE; @@ -136,36 +134,29 @@ public int getPriority() { } /** - * Fetches the active priority of the Task if a target is visible. - * @return (int) active priority if a target is visible, -1 otherwise + * Returns the current state of the tower. + * + * @return the current state of the tower. */ - private int getActivePriority() { - - return !isTargetVisible() ? 0 : priority; - } - - /** - * Fetches the inactive priority of the Task if a target is not visible. - * @return (int) -1 if a target is not visible, active priority otherwise - */ - private int getInactivePriority() { - - return isTargetVisible() ? priority : 0; + public STATE getState() { + return this.towerState; } /** * Uses a raycast to determine whether there are any targets in detection range * @return true if a target is visible, false otherwise */ - private boolean isTargetVisible() { + public boolean isTargetVisible() { // If there is an obstacle in the path to the max range point, mobs visible. return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } - private boolean isReadyToDelete() { + public boolean isReadyToDelete() { return readToDelete; } + + } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java index a84528c42..f986f9667 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java @@ -138,7 +138,7 @@ public void updateTowerState() { Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f,2f)); newProjectile.setScale(1.1f, 0.8f); - newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.75), (float) (owner.getEntity().getPosition().y + 0.5)); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.5), (float) (owner.getEntity().getPosition().y + 0.5)); ServiceLocator.getEntityService().register(newProjectile); // * TEMPRORARYYYYYYYY PLS DON'T DELETE THIS diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/EngineerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/EngineerCombatTask.java index 23dbea9c0..337218153 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/human/EngineerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/EngineerCombatTask.java @@ -26,12 +26,9 @@ public class EngineerCombatTask extends DefaultTask implements PriorityTask { private static final short TARGET = PhysicsLayer.NPC; // The type of targets that the Engineer will detect. // Animation event names for the Engineer's state machine. - private static final String STOW = ""; - private static final String DEPLOY = ""; private static final String FIRING = "firingSingleStart"; - private static final String IDLE_LEFT = "idleLeft"; private static final String IDLE_RIGHT = "idleRight"; - private static final String DYING = "deathStart"; + private static final String ENGINEER_PROJECTILE_FIRED = "engineerProjectileFired"; // The Engineer's attributes. private final float maxRange; // The maximum range of the Engineer's weapon. @@ -52,7 +49,7 @@ public class EngineerCombatTask extends DefaultTask implements PriorityTask { /** The Engineer's states. */ private enum STATE { - IDLE_LEFT, IDLE_RIGHT, DEPLOY, FIRING, STOW + IDLE_RIGHT, FIRING } private STATE engineerState = STATE.IDLE_RIGHT; @@ -96,48 +93,32 @@ public void update() { * Engineer state machine */ public void updateEngineerState() { - // configure tower state depending on target visibility + // configure engineer state depending on target visibility switch (engineerState) { - case IDLE_LEFT -> { - // targets detected in idle mode - start deployment - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(FIRING); - engineerState = STATE.FIRING; - } else { - - } - } case IDLE_RIGHT -> { // targets detected in idle mode - start deployment if (isTargetVisible()) { combatState(); } } - case DEPLOY -> { - // currently deploying, - if (isTargetVisible()) { - combatState(); - } else { - owner.getEntity().getEvents().trigger(STOW); - engineerState = STATE.STOW; - } - } case FIRING -> { // targets gone - stop firing if (!isTargetVisible()) { owner.getEntity().getEvents().trigger(IDLE_RIGHT); engineerState = STATE.IDLE_RIGHT; } else { - if (shotsFired <= 10) { + if (shotsFired <= weaponCapacity) { owner.getEntity().getEvents().trigger(FIRING); + owner.getEntity().getEvents().trigger(ENGINEER_PROJECTILE_FIRED); // this might be changed to an event which gets triggered everytime the tower enters the firing state Entity newProjectile = ProjectileFactory.createEngineerBullet(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(4f, 4f)); newProjectile.setScale(0.8f, 0.8f); - newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.3), (float) (owner.getEntity().getPosition().y + 0.15)); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.3), + (float) (owner.getEntity().getPosition().y + 0.15)); ServiceLocator.getEntityService().register(newProjectile); - shotsFired += 1; + shotsFired ++; reloadTime = timeSource.getTime(); } else { // engineer needs to reload @@ -185,7 +166,7 @@ public int getPriority() { public boolean isTargetVisible() { // If there is an obstacle in the path to the max range point, mobs visible. Vector2 position = owner.getEntity().getCenterPosition(); - + hits.clear(); for (int i = 5; i > -5; i--) { if (physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET, hit)) { hits.add(hit); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java index 6e957e311..fa589460f 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java @@ -2,20 +2,15 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; -import com.csse3200.game.entities.Entity; import com.csse3200.game.physics.components.PhysicsMovementComponent; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Move to a given position, finishing when you get close enough. Requires an entity with a + * Move a human entity to a given position, finishing when you get close enough. Requires an entity with a * PhysicsMovementComponent. */ public class HumanMovementTask extends DefaultTask { - private static final Logger logger = LoggerFactory.getLogger(HumanMovementTask.class); - private final GameTime gameTime; private Vector2 target; private float stopDistance = 0.01f; @@ -47,7 +42,6 @@ public void start() { owner.getEntity().getEvents().trigger("walkRightStart"); } - logger.debug("Starting movement towards {}", target); lastTimeMoved = gameTime.getTime(); lastPos = owner.getEntity().getPosition(); } @@ -58,7 +52,6 @@ public void update() { movementComponent.setMoving(false); owner.getEntity().getEvents().trigger("idleStart"); status = Status.FINISHED; - logger.debug("Finished moving to {}", target); } else { checkIfStuck(); } @@ -73,7 +66,6 @@ public void setTarget(Vector2 target) { public void stop() { super.stop(); movementComponent.setMoving(false); - logger.debug("Stopping movement"); } private boolean isAtTarget() { @@ -87,7 +79,6 @@ private void checkIfStuck() { } else if (gameTime.getTimeSince(lastTimeMoved) > 500L) { movementComponent.setMoving(false); status = Status.FAILED; - logger.debug("Got stuck! Failing movement task"); } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java index a9b497130..ceed79ea6 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java @@ -9,50 +9,60 @@ import com.csse3200.game.physics.components.ColliderComponent; import com.csse3200.game.physics.components.HitboxComponent; import com.csse3200.game.rendering.AnimationRenderComponent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.csse3200.game.services.ServiceLocator; /** - * 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. + * HumanWanderTask is the entry point for the engineer entity's behaviour. Instantiates subtasks HumanWaitTask, + * HumanMovementTask and EngineerCombatTask, and manages transitions between the tasks. Engineer damage and death + * handled in this class. */ public class HumanWanderTask extends DefaultTask implements PriorityTask { - private static final Logger logger = LoggerFactory.getLogger(HumanWanderTask.class); - - private float maxRange; - private Vector2 wanderRange; + private static final int TOLERANCE = 1; + private static final float STOP_DISTANCE = 0.5f; + private static final int DEFAULT_PRIORITY = 1; + private static final String DEATH_EVENT = "deathStart"; + private static final String IDLE_EVENT = "idleRight"; + private AnimationRenderComponent animator; + private final float maxRange; private final float waitTime; - private Vector2 startPos; private HumanMovementTask movementTask; private HumanWaitTask waitTask; - private EngineerCombatTask combatTask; private Task currentTask; - private boolean isDead = false; /** + * Constructor of HumanWanderTask + * * @param waitTime How long in seconds to wait between wandering. + * @param maxRange Maximum detection and fighting range of the entity */ public HumanWanderTask(float waitTime, float maxRange) { this.waitTime = waitTime; this.maxRange = maxRange; } + /** + * Fetches the priority of this task. + * @return current priority of this task. Priority for this task is a set value and does not change. + */ @Override public int getPriority() { - return 1; // Low priority task + return DEFAULT_PRIORITY; // Low priority task } + /** + * Starts the HumanWanderTask instance and instantiates subtasks (HumanWaitTask, HumanWanderTask, EngineerCombatTask). + * + */ @Override public void start() { super.start(); - startPos = owner.getEntity().getPosition(); - this.wanderRange = owner.getEntity().getCenterPosition(); + Vector2 startPos = owner.getEntity().getCenterPosition(); waitTask = new HumanWaitTask(waitTime); waitTask.create(owner); - movementTask = new HumanMovementTask(this.wanderRange, 1f); + movementTask = new HumanMovementTask(startPos, STOP_DISTANCE); movementTask.create(owner); movementTask.start(); @@ -61,60 +71,103 @@ public void start() { combatTask.start(); currentTask = movementTask; + + animator = owner.getEntity().getComponent(AnimationRenderComponent.class); } + /** + * Operates the main logic of the entity in this task. All calls to switch to particular states are determined during + * the update phase. + * The logical flow is: + * - Check if the entity has died since last update + * - Check if the entity has finished dying + * - If not dead + */ @Override public void update() { + + boolean justDied = owner.getEntity().getComponent(CombatStatsComponent.class).isDead(); // Check if engineer has died since last update - if (!isDead && owner.getEntity().getComponent(CombatStatsComponent.class).isDead()) { - owner.getEntity().getEvents().trigger("deathStart"); - owner.getEntity().getComponent(ColliderComponent.class).setLayer(PhysicsLayer.NONE); - owner.getEntity().getComponent(HitboxComponent.class).setLayer(PhysicsLayer.NONE); - currentTask.stop(); - // Add a time delay here to allow animation to play? - isDead = true; - } - // Check if engineer has finished dying - else if (isDead && owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + if (!isDead && justDied) { + startDying(); + } else if (isDead && animator.isFinished()) { owner.getEntity().setFlagForDelete(true); - // TODO: make the appropriate calls to decrement the human count. + // Decrement the engineer count + ServiceLocator.getGameEndService().updateEngineerCount(); + } + + // otherwise doing engineer things since engineer is alive + else if (!isDead){ + doEngineerThings(); + + currentTask.update(); } - // otherwise doing engineer things - else if (!isDead) { - if (currentTask.getStatus() != Status.ACTIVE) { - - if (currentTask == movementTask) { - startWaiting(); - owner.getEntity().getEvents().trigger("idleRight"); - } else if (combatTask.isTargetVisible()) { - if (combatTask.fetchTarget().y < owner.getEntity().getCenterPosition().y + 2 && - combatTask.fetchTarget().y > owner.getEntity().getCenterPosition().y - 2) { - startCombat(); - } else { - startMoving(new Vector2(owner.getEntity().getCenterPosition().x, combatTask.fetchTarget().y)); - } + } + + private void doEngineerThings() { + if (currentTask.getStatus() != Status.ACTIVE) { + + // if the engineer is in move state and update has been called, engineer has arrived at destination + if (currentTask == movementTask) { + startWaiting(); + owner.getEntity().getEvents().trigger(IDLE_EVENT); + + } else if (combatTask.isTargetVisible()) { + float engY = owner.getEntity().getCenterPosition().y; + float targetY = combatTask.fetchTarget().y; + // if the engineer is positioned within the tolerance range of the mob's y position, enter combat state + if (engY < targetY + TOLERANCE && + engY > targetY - TOLERANCE) { + startCombat(); + + // move into position for targeting mob + } else { + Vector2 newPos = new Vector2(owner.getEntity().getPosition().x, combatTask.fetchTarget().y); + startMoving(newPos); } } - currentTask.update(); } } + /** + * Handle the dying phase of the entity. Triggers an event to play the appropriate media, + * sets HitBox and Collider components to ignore contact (stops the body being pushed around) + * and stops the current task. + */ + private void startDying() { + owner.getEntity().getEvents().trigger(DEATH_EVENT); + owner.getEntity().getComponent(ColliderComponent.class).setLayer(PhysicsLayer.NONE); + owner.getEntity().getComponent(HitboxComponent.class).setLayer(PhysicsLayer.NONE); + currentTask.stop(); + isDead = true; + } + /** + * Starts the wait task. + */ private void startWaiting() { - logger.debug("Starting waiting"); swapTask(waitTask); } + /** + * Starts the movement task, to a particular destination + * @param destination the Vector2 position to which the entity needs to move + */ private void startMoving(Vector2 destination) { - logger.debug("Starting moving"); movementTask.setTarget(destination); swapTask(movementTask); } + /** + * Starts the combat task. + */ private void startCombat() { - logger.debug("Starting Combat"); swapTask(combatTask); } + /** + * Allows manual switching of tasks, from the current task to the supplied newTask. + * @param newTask the task being switched to. + */ private void swapTask(Task newTask) { if (currentTask != null) { currentTask.stop(); @@ -122,10 +175,4 @@ private void swapTask(Task newTask) { currentTask = newTask; currentTask.start(); } - - private Vector2 getDirection() { -// float y = startPos.y; -// return new Vector2(0, y); - return this.wanderRange; - } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/scanner/ScannerTask.java b/source/core/src/main/com/csse3200/game/components/tasks/scanner/ScannerTask.java new file mode 100644 index 000000000..3405cecd0 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/scanner/ScannerTask.java @@ -0,0 +1,115 @@ +package com.csse3200.game.components.tasks.scanner; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.EngineerFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * ScannerTask implements the behaviour of GapScannerEntities that detect the + * conditions to trigger engineer spawning, i.e., No towers, no engineers, mobs within + * a certain distance. + */ +public class ScannerTask extends DefaultTask implements PriorityTask { + + private static final int SCAN_INTERVAL = 1000; // how often to scan, in milliseconds + private final PhysicsEngine physics; + private final GameTime timeSource; + private final RaycastHit hit = new RaycastHit(); + private Vector2 selfPosition; + private long endTime; + + // booleans to track presence of towers, engineers and mobs + private boolean towers = false; + private boolean engineers = false; + private boolean mobs = false; + + // track the number of engineers spawned. + private static final int maxEngineers = 3; + private int engineerCount = 0; + + /** + * ScannerTask Constructor + */ + public ScannerTask() { + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Start method for the ScannerTask + */ + @Override + public void start() { + super.start(); + endTime = timeSource.getTime() + (SCAN_INTERVAL); + selfPosition = owner.getEntity().getCenterPosition(); + } + + /** + * Update method for the scanner task. Implements the scanning and spawning logic + * for populating the game area with engineers. + */ + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + // clear all presence booleans + towers = false; + engineers = false; + mobs = false; + + // carry out scan and behave accordingly + scan(); + if (!towers && !engineers && mobs) { + // spawn engineers now + if (engineerCount < maxEngineers) { + Entity engineer = EngineerFactory.createEngineer(); + + engineer.setPosition(new Vector2((int)(selfPosition.x + 1),(int) selfPosition.y)); + ServiceLocator.getEntityService().register(engineer); + engineerCount += 1; + } + } + endTime = timeSource.getTime() + SCAN_INTERVAL; + } + } + + /** + * Scanning method that detects the presence of towers/engineers/mobs. + * Sets the tracking booleans for each of the entity types + */ + private void scan() { + + if (physics.raycast(selfPosition, + new Vector2(selfPosition.x + 10, selfPosition.y), + PhysicsLayer.TOWER, + hit)) { + towers = true; + } else if (physics.raycast(selfPosition, + new Vector2(selfPosition.x + 10, selfPosition.y), + PhysicsLayer.ENGINEER, + hit)) { + engineers = true; + } else if (physics.raycast(selfPosition, + new Vector2(selfPosition.x + 10, selfPosition.y), + PhysicsLayer.NPC, + hit)) { + mobs = true; + } + } + + /** + * Return the priority of the task. + * @return the default priority of this task (a fixed value - no other tasks to run) + */ + @Override + public int getPriority() { + return 1; + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java index be123d483..d8307e0e4 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/DroidAnimationController.java @@ -1,7 +1,13 @@ package com.csse3200.game.components.tower; +import com.badlogic.gdx.math.Vector2; import com.csse3200.game.components.Component; +import com.csse3200.game.components.ProjectileEffects; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; /** * This class listens to events relevant to DroidTower entity's state and plays the animation when one @@ -25,6 +31,8 @@ public void create() { entity.getEvents().addListener("attackUpStart",this::animateAttackUp); entity.getEvents().addListener("attackDownStart",this::animateAttackDown); entity.getEvents().addListener("deathStart",this::animateDeath); + entity.getEvents().addListener("ShootUp",this::shootUp); + entity.getEvents().addListener("ShootDown",this::shootDown); } @@ -83,4 +91,32 @@ void animateDeath() { */ void animateDefault() { animator.startAnimation("idle");} + + //TODO: For the time being, these items will be positioned here. Next, we should create a component that enables an entity to fire projectiles. + + /** + * Fires a projectile upwards from the entity's current position. + */ + void shootUp() { + Entity Projectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(100, + entity.getPosition().y), new Vector2(2,2), ProjectileEffects.SLOW, false); + Projectile.setScale(new Vector2(0.5f,0.5f)); + Projectile.setPosition((float) (entity.getPosition().x + 0.2), + (float) (entity.getPosition().y + 0.5)); + ServiceLocator.getEntityService().register(Projectile); + } + + /** + * Fires a projectile downwards from the entity's current position. + */ + void shootDown() { + Entity Projectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(100, + entity.getPosition().y), new Vector2(2,2), ProjectileEffects.SLOW, false); + Projectile.setScale(new Vector2(0.5f,0.5f)); + Projectile.setPosition((float) (entity.getPosition().x + 0.2), + (float) (entity.getPosition().y - 0.2)); + ServiceLocator.getEntityService().register(Projectile); + + } + } diff --git a/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java index 358d7a3a6..6753b6ba1 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/FireTowerAnimationController.java @@ -14,11 +14,13 @@ public class FireTowerAnimationController extends Component{ private static final String IDLE = "startIdle"; private static final String PREP_ATTACK = "startAttackPrep"; private static final String ATTACK = "startAttack"; + private static final String DEATH = "startDeath"; //animation name constants private static final String IDLE_ANIM = "idle"; private static final String PREP_ATTACK_ANIM = "prepAttack"; private static final String ATTACK_ANIM = "attack"; + private static final String DEATH_ANIM = "death"; //here we can add the sounds for the implemented animations AnimationRenderComponent animator; @@ -33,6 +35,7 @@ public void create() { entity.getEvents().addListener(IDLE, this::animateIdle); entity.getEvents().addListener(PREP_ATTACK, this::animatePrepAttack); entity.getEvents().addListener(ATTACK, this::animateAttack); + entity.getEvents().addListener(DEATH, this::animateDeath); } /** @@ -55,4 +58,8 @@ void animatePrepAttack() { void animateAttack() { animator.startAnimation(ATTACK_ANIM); } + + void animateDeath() { + animator.startAnimation(DEATH_ANIM); + } } diff --git a/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java index fa4868c4c..846d344dd 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/tower/StunTowerAnimationController.java @@ -12,9 +12,11 @@ public class StunTowerAnimationController extends Component { //Event name constants private static final String IDLE = "startIdle"; private static final String ATTACK = "startAttack"; + private static final String DEATH = "startDeath"; //animation name constants private static final String IDLE_ANIM = "idle"; private static final String ATTACK_ANIM = "attack"; + private static final String DEATH_ANIM = "death"; //further sounds can be added for the tower attacks/movement @@ -30,6 +32,7 @@ public void create() { animator = this.entity.getComponent(AnimationRenderComponent.class); entity.getEvents().addListener(IDLE, this::animateIdle); entity.getEvents().addListener(ATTACK, this::animateAttack); + entity.getEvents().addListener(DEATH, this::animateDeath); } /** @@ -45,4 +48,11 @@ void animateIdle() { void animateAttack() { animator.startAnimation(ATTACK_ANIM); } + + /** + * starts the death animation + */ + void animateDeath() { + animator.startAnimation(DEATH_ANIM); + } } diff --git a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java index c6acb3c28..ece920971 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java +++ b/source/core/src/main/com/csse3200/game/components/tower/TNTDamageComponent.java @@ -24,7 +24,6 @@ * Utilizes HitboxComponent and CombatStatsComponent for functionality. */ public class TNTDamageComponent extends Component { - private static final Logger logger = LoggerFactory.getLogger(TNTDamageComponent.class); private short targetLayer; private float knockbackForce = 0f; private float radius; @@ -84,12 +83,7 @@ private void applyTNTDamage() { // Check for null components and log specifics if (sourceHitbox == null || otherHitbox == null) { - if (sourceHitbox == null) { - logger.debug("Warning: Source Entity without HitboxComponent. Source Entity: " + entity); - } - if (otherHitbox == null) { - logger.debug("Warning: Other Entity without HitboxComponent. Other Entity: " + otherEntity); - } + continue; } 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/configs/baseTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java index 1b3eab1ee..968f4ec93 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/baseTowerConfigs.java @@ -7,9 +7,8 @@ public class baseTowerConfigs { public WeaponTowerConfig weapon = new WeaponTowerConfig(); public WallTowerConfig wall = new WallTowerConfig(); public IncomeTowerConfig income = new IncomeTowerConfig(); - public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); - public DroidTowerConfig DroidTower = new DroidTowerConfig(); public FireTowerConfig fireTower = new FireTowerConfig(); public StunTowerConfig stunTower = new StunTowerConfig(); - + public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); + public DroidTowerConfig DroidTower = new DroidTowerConfig(); } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java index 3f0edc0a5..7bdd6396a 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java @@ -35,11 +35,11 @@ public class EngineerFactory { private static final int COMBAT_TASK_PRIORITY = 2; private static final int ENGINEER_RANGE = 10; private static final EngineerConfigs configs = - FileLoader.readClass(EngineerConfigs.class, "configs/Engineers.json"); - + FileLoader.readClass(EngineerConfigs.class, "configs/Engineers.json"); + private static final float HUMAN_SCALE_X = 1f; private static final float HUMAN_SCALE_Y = 0.8f; - + /** * Creates an Engineer entity, based on a base Human entity, with the appropriate components and animations * @@ -49,7 +49,7 @@ public class EngineerFactory { public static Entity createEngineer() { Entity engineer = createBaseHumanNPC(); BaseEntityConfig config = configs.engineer; - + AnimationRenderComponent animator = new AnimationRenderComponent( new TextureAtlas("images/engineers/engineer.atlas")); animator.addAnimation("walk_left", 0.2f, Animation.PlayMode.LOOP); @@ -61,43 +61,43 @@ public static Entity createEngineer() { animator.addAnimation("prep", 0.05f, Animation.PlayMode.NORMAL); animator.addAnimation("hit", 0.01f, Animation.PlayMode.NORMAL); animator.addAnimation("death", 0.1f, Animation.PlayMode.NORMAL); - + AITaskComponent aiComponent = new AITaskComponent(); - + engineer .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(animator) .addComponent(new HumanAnimationController()) .addComponent(aiComponent); - + engineer.getComponent(AITaskComponent.class).addTask(new HumanWanderTask(COMBAT_TASK_PRIORITY, ENGINEER_RANGE)); engineer.getComponent(AnimationRenderComponent.class).scaleEntity(); engineer.setScale(HUMAN_SCALE_X, HUMAN_SCALE_Y); return engineer; } - + /** * Creates a generic human npc to be used as a base entity by more specific NPC creation methods. * * @return entity */ public static Entity createBaseHumanNPC() { - - + + Entity human = - new Entity() - .addComponent(new PhysicsComponent()) - .addComponent(new PhysicsMovementComponent()) - .addComponent(new ColliderComponent()) - .addComponent(new HitboxComponent().setLayer(PhysicsLayer.ENGINEER)) - .addComponent(new TouchAttackComponent(PhysicsLayer.NPC, 1.5f)); - - + new Entity() + .addComponent(new PhysicsComponent()) + .addComponent(new PhysicsMovementComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.ENGINEER)) + .addComponent(new TouchAttackComponent(PhysicsLayer.NPC, 1.5f)); + + PhysicsUtils.setScaledCollider(human, 0.9f, 0.4f); return human; } - + private EngineerFactory() { throw new IllegalStateException("Instantiating static util class"); } -} +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/factories/GapScannerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/GapScannerFactory.java new file mode 100644 index 000000000..553747246 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/factories/GapScannerFactory.java @@ -0,0 +1,36 @@ +package com.csse3200.game.entities.factories; + + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.tasks.scanner.ScannerTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.physics.components.PhysicsComponent; + +/** + * Factory to create scanner entities that determine whether to spawn engineer entities. + * These do not interact with any of the entities in the game area except to detect other entities + * + */ +public class GapScannerFactory { + + /** + * Creates a scanner entity + * @return scanner entity + */ + public static Entity createScanner() { + Entity scanner = new Entity(); + + AITaskComponent aiComponent = new AITaskComponent(); + + scanner + .addComponent(new PhysicsComponent()) + .addComponent(aiComponent); + + scanner.getComponent(AITaskComponent.class).addTask(new ScannerTask()); + return scanner; + } + + private GapScannerFactory() { + throw new IllegalStateException("Instantiating static util class"); + } +} 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..331ffd8f8 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,14 @@ 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.components.tasks.MobWanderTask; 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 +106,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; @@ -140,19 +138,18 @@ public static Entity createXenoGrunt(Entity target) { 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 MobWanderTask(new Vector2(2f, 2f), 2f)) + .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/entities/factories/PlayerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java index 1f4f2f3f1..80bbc26b8 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 @@ -1,14 +1,11 @@ package com.csse3200.game.entities.factories; -import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.player.InventoryComponent; import com.csse3200.game.components.player.PlayerActions; import com.csse3200.game.components.player.PlayerStatsDisplay; -import com.csse3200.game.components.tasks.MobAttackTask; import com.csse3200.game.components.tasks.SpawnWaveTask; -import com.csse3200.game.components.tasks.WanderTask; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.configs.PlayerConfig; import com.csse3200.game.files.FileLoader; diff --git a/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java index 152685fdb..578b1abf4 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/ProjectileFactory.java @@ -7,7 +7,7 @@ import com.csse3200.game.components.TouchAttackComponent; import com.csse3200.game.components.RicochetComponent; import com.csse3200.game.components.SplitFireworksComponent; -import com.csse3200.game.components.projectile.MobKingProjectAnimController; +import com.csse3200.game.components.projectile.*; import com.csse3200.game.components.tasks.TrajectTask; import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; @@ -25,76 +25,74 @@ import com.csse3200.game.physics.components.PhysicsComponent; import com.csse3200.game.physics.components.PhysicsMovementComponent; import com.badlogic.gdx.math.Vector2; -import com.csse3200.game.components.projectile.EngineerBulletsAnimationController; -import com.csse3200.game.components.projectile.MobProjectileAnimationController; -import com.csse3200.game.components.projectile.ProjectileAnimationController; -import com.csse3200.game.components.projectile.SnowBallProjectileAnimationController; /** * Responsible for creating projectiles within the game. */ public class ProjectileFactory { - /** Animation constants */ + /** + * Animation constants + */ private static final String BASE_PROJECTILE_ATLAS = "images/projectiles/basic_projectile.atlas"; private static final String START_ANIM = "projectile"; private static final String FINAL_ANIM = "projectileFinal"; private static final float START_SPEED = 0.1f; private static final float FINAL_SPEED = 0.1f; - private static final NPCConfigs configs = - FileLoader.readClass(NPCConfigs.class, "configs/NPCs.json"); + private static final NPCConfigs configs = + FileLoader.readClass(NPCConfigs.class, "configs/NPCs.json"); /** * Creates a single-targeting projectile with specified effect * * @param targetLayer The enemy layer that the projectile collides with. * @param destination The destination the projectile heads towards. - * @param speed The speed of the projectile. - * @param effect Specified effect from the ProjectileEffects enums + * @param speed The speed of the projectile. + * @param effect Specified effect from the ProjectileEffects enums * @return Returns a new single-target projectile entity */ public static Entity createEffectProjectile(short targetLayer, Vector2 destination, Vector2 speed, ProjectileEffects effect, boolean aoe) { Entity projectile = createBaseProjectile(targetLayer, destination, speed); - switch(effect) { + switch (effect) { case FIREBALL -> { projectile.addComponent(new EffectsComponent(targetLayer, 3, ProjectileEffects.FIREBALL, aoe)); AnimationRenderComponent animator = - new AnimationRenderComponent( - ServiceLocator.getResourceService() - .getAsset(BASE_PROJECTILE_ATLAS, TextureAtlas.class)); + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(BASE_PROJECTILE_ATLAS, TextureAtlas.class)); animator.addAnimation(START_ANIM, START_SPEED, Animation.PlayMode.NORMAL); animator.addAnimation(FINAL_ANIM, FINAL_SPEED, Animation.PlayMode.NORMAL); - projectile - .addComponent(animator) - .addComponent(new ProjectileAnimationController()); + projectile + .addComponent(animator) + .addComponent(new ProjectileAnimationController()); } case BURN -> { projectile.addComponent(new EffectsComponent(targetLayer, 3, ProjectileEffects.BURN, aoe)); AnimationRenderComponent animator = - new AnimationRenderComponent( - ServiceLocator.getResourceService() - .getAsset(BASE_PROJECTILE_ATLAS, TextureAtlas.class)); + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset("images/projectiles/burn_effect.atlas", TextureAtlas.class)); animator.addAnimation(START_ANIM, START_SPEED, Animation.PlayMode.NORMAL); animator.addAnimation(FINAL_ANIM, FINAL_SPEED, Animation.PlayMode.NORMAL); - projectile - .addComponent(animator) - .addComponent(new ProjectileAnimationController()); + projectile + .addComponent(animator) + .addComponent(new BurnEffectProjectileAnimationController()); } case SLOW -> { projectile.addComponent(new EffectsComponent(targetLayer, 3, ProjectileEffects.SLOW, aoe)); AnimationRenderComponent animator = - new AnimationRenderComponent( - ServiceLocator.getResourceService() - .getAsset("images/projectiles/snow_ball.atlas", TextureAtlas.class)); + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset("images/projectiles/snow_ball.atlas", TextureAtlas.class)); animator.addAnimation(START_ANIM, START_SPEED, Animation.PlayMode.NORMAL); animator.addAnimation(FINAL_ANIM, FINAL_SPEED, Animation.PlayMode.NORMAL); - projectile - .addComponent(animator) - .addComponent(new SnowBallProjectileAnimationController()); + projectile + .addComponent(animator) + .addComponent(new SnowBallProjectileAnimationController()); // * TEMPORARY // .addComponent(new DeleteOnMapEdgeComponent()); // .addComponent(new SelfDestructOnHitComponent(PhysicsLayer.OBSTACLE)); @@ -103,9 +101,18 @@ public static Entity createEffectProjectile(short targetLayer, Vector2 destinati } case STUN -> { projectile.addComponent(new EffectsComponent(targetLayer, 3, ProjectileEffects.STUN, aoe)); + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset("images/projectiles/stun_effect.atlas", TextureAtlas.class)); + animator.addAnimation(START_ANIM, 0.1f, Animation.PlayMode.LOOP); + + projectile + .addComponent(animator) + .addComponent(new StunEffectProjectileAnimationController()); } } - return projectile; + return projectile; } /** @@ -113,7 +120,7 @@ public static Entity createEffectProjectile(short targetLayer, Vector2 destinati * Pierce fireball is basically a fireball that does damage but won't self destruct on hit. */ public static Entity createPierceFireBall(short targetLayer, Vector2 destination, Vector2 speed) { - Entity fireBall = createFireBall(targetLayer, destination, speed); + Entity fireBall = createPierceBallAnim(targetLayer, destination, speed); fireBall.getComponent(TouchAttackComponent.class).setDisposeOnHit(false); fireBall.getComponent(TouchAttackComponent.class).setKnockBack(0f); @@ -127,8 +134,8 @@ public static Entity createPierceFireBall(short targetLayer, Vector2 destination public static Entity createRicochetFireball(short targetLayer, Vector2 destination, Vector2 speed, int bounceCount) { Entity fireBall = createFireBall(targetLayer, destination, speed); fireBall - .addComponent(new RicochetComponent(targetLayer, bounceCount)); - + .addComponent(new RicochetComponent(targetLayer, bounceCount)); + setColliderSize(fireBall, (float) 0.1, (float) 0.1); return fireBall; @@ -137,17 +144,17 @@ public static Entity createRicochetFireball(short targetLayer, Vector2 destinati public static Entity createSplitFireWorksFireball(short targetLayer, Vector2 destination, Vector2 speed, int amount) { Entity fireBall = createFireBall(targetLayer, destination, speed); fireBall - .addComponent(new SplitFireworksComponent(targetLayer, amount)); - + .addComponent(new SplitFireworksComponent(targetLayer, amount)); + return fireBall; } /** * Creates a fireball Entity. - * + * * @param targetLayer The enemy layer that the projectile collides with. * @param destination The destination the projectile heads towards. - * @param speed The speed of the projectile. + * @param speed The speed of the projectile. * @return Returns a new fireball projectile entity. */ public static Entity createFireBall(short targetLayer, Vector2 destination, Vector2 speed) { @@ -161,14 +168,54 @@ public static Entity createFireBall(short targetLayer, Vector2 destination, Vect animator.addAnimation(FINAL_ANIM, FINAL_SPEED, Animation.PlayMode.NORMAL); projectile - .addComponent(animator) - .addComponent(new ProjectileAnimationController()); - // * TEMPORARY - // .addComponent(new DeleteOnMapEdgeComponent()); - // .addComponent(new SelfDestructOnHitComponent(PhysicsLayer.OBSTACLE)); + .addComponent(animator) + .addComponent(new ProjectileAnimationController()); + // * TEMPORARY + // .addComponent(new DeleteOnMapEdgeComponent()); + // .addComponent(new SelfDestructOnHitComponent(PhysicsLayer.OBSTACLE)); + + return projectile; + } + + /** + * Creates new animation and fireballs for SplitFireworkComponent. + * + * @param targetLayer The enemy layer that the projectile collides with. + * @param destination The destination the projectile heads towards. + * @param speed The speed of the projectile. + * @return Returns a new fireball projectile entity. + */ + public static Entity createFireworks(short targetLayer, Vector2 destination, Vector2 speed) { + Entity projectile = createBaseProjectile(targetLayer, destination, speed); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset("images/projectiles/firework_anim.atlas", TextureAtlas.class)); + animator.addAnimation(START_ANIM, 0.2f, Animation.PlayMode.LOOP); + projectile + .addComponent(animator) + .addComponent(new FireworkAnimationController()); + + return projectile; + } + + public static Entity createPierceBallAnim(short targetLayer, Vector2 destination, Vector2 speed) { + Entity projectile = createBaseProjectile(targetLayer, destination, speed); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset("images/projectiles/pierce_anim.atlas", TextureAtlas.class)); + animator.addAnimation(START_ANIM, 0.05f, Animation.PlayMode.LOOP); + projectile + .addComponent(animator) + .addComponent(new PierceProjectileAnimationController()); return projectile; } + + /** * Creates a engineer bullet * diff --git a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 9fe31184b..1341746fd 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java @@ -1,19 +1,11 @@ package com.csse3200.game.entities.factories; - - import com.csse3200.game.components.tasks.DroidCombatTask; import com.csse3200.game.components.tasks.TNTTowerCombatTask; -import com.csse3200.game.components.tower.DroidAnimationController; -import com.csse3200.game.components.tower.TNTAnimationController; -import com.csse3200.game.components.tower.TNTDamageComponent; +import com.csse3200.game.components.tower.*; import com.csse3200.game.entities.configs.*; import com.csse3200.game.components.tasks.FireTowerCombatTask; import com.csse3200.game.components.tasks.StunTowerCombatTask; -import com.csse3200.game.components.tower.FireTowerAnimationController; -import com.csse3200.game.components.tower.StunTowerAnimationController; -import com.csse3200.game.components.tower.TowerUpgraderComponent; - import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; @@ -21,8 +13,6 @@ import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.CostComponent; import com.csse3200.game.components.tasks.TowerCombatTask; -import com.csse3200.game.components.tower.EconTowerAnimationController; -import com.csse3200.game.components.tower.TowerAnimationController; import com.csse3200.game.components.tasks.CurrencyTask; import com.csse3200.game.entities.Entity; import com.csse3200.game.physics.PhysicsLayer; @@ -35,8 +25,6 @@ import com.csse3200.game.rendering.TextureRenderComponent; import com.csse3200.game.services.ServiceLocator; -import java.util.ServiceConfigurationError; - /** * Factory to create a tower entity. * @@ -53,6 +41,8 @@ public class TowerFactory { private static final String WALL_IMAGE = "images/towers/wallTower.png"; private static final String RESOURCE_TOWER = "images/towers/mine_tower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; + private static final String FIRE_TOWER_ATLAS = "images/towers/fire_tower_atlas.atlas"; + private static final String STUN_TOWER_ATLAS = "images/towers/stun_tower.atlas"; private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; private static final String DROID_ATLAS = "images/towers/DroidTower.atlas"; private static final float DROID_SPEED = 0.25f; @@ -68,9 +58,6 @@ public class TowerFactory { private static final float DIG_SPEED = 0.2f; private static final String EXPLODE_ANIM = "explode"; private static final float EXPLODE_SPEED = 0.2f; - private static final String FIRE_TOWER_ATLAS = "images/towers/fire_tower_atlas.atlas"; - private static final String STUN_TOWER_ATLAS = "images/towers/stun_tower.atlas"; - private static final String IDLE_ANIM = "idle"; private static final float IDLE_SPEED = 0.3f; private static final String DEPLOY_ANIM = "deploy"; @@ -79,22 +66,22 @@ public class TowerFactory { private static final float STOW_SPEED = 0.2f; private static final String FIRE_ANIM = "firing"; private static final float FIRE_SPEED = 0.25f; - private static final String FIRE_TOWER_IDLE_ANIM = "idle"; private static final float FIRE_TOWER_IDLE_SPEED = 0.3f; private static final String FIRE_TOWER_PREP_ATTACK_ANIM = "prepAttack"; private static final float FIRE_TOWER_PREP_ATTACK_SPEED = 0.2f; private static final String FIRE_TOWER_ATTACK_ANIM = "attack"; private static final float FIRE_TOWER_ATTACK_SPEED = 0.25f; + private static final String FIRE_TOWER_DEATH_ANIM = "death"; + private static final float FIRE_TOWER_DEATH_SPEED = 0.12f; private static final String STUN_TOWER_IDLE_ANIM = "idle"; private static final float STUN_TOWER_IDLE_SPEED = 0.33f; private static final String STUN_TOWER_ATTACK_ANIM = "attack"; private static final float STUN_TOWER_ATTACK_SPEED = 0.12f; + private static final String STUN_TOWER_DEATH_ANIM = "death"; + private static final float STUN_TOWER_DEATH_SPEED = 0.12f; private static final int INCOME_INTERVAL = 300; - private static final int INCOME_TASK_PRIORITY = 1; - - private static final String ECO_ATLAS = "images/economy/econ-tower.atlas"; private static final String ECO_MOVE = "move1"; private static final String ECO_IDLE = "idle"; @@ -274,6 +261,7 @@ public static Entity createFireTower() { animator.addAnimation(FIRE_TOWER_IDLE_ANIM, FIRE_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); animator.addAnimation(FIRE_TOWER_PREP_ATTACK_ANIM, FIRE_TOWER_PREP_ATTACK_SPEED, Animation.PlayMode.NORMAL); animator.addAnimation(FIRE_TOWER_ATTACK_ANIM, FIRE_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(FIRE_TOWER_DEATH_ANIM, FIRE_TOWER_DEATH_SPEED, Animation.PlayMode.NORMAL); fireTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) @@ -302,6 +290,7 @@ public static Entity createStunTower() { .getAsset(STUN_TOWER_ATLAS, TextureAtlas.class)); animator.addAnimation(STUN_TOWER_IDLE_ANIM, STUN_TOWER_IDLE_SPEED, Animation.PlayMode.LOOP); animator.addAnimation(STUN_TOWER_ATTACK_ANIM, STUN_TOWER_ATTACK_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(STUN_TOWER_DEATH_ANIM, STUN_TOWER_DEATH_SPEED, Animation.PlayMode.NORMAL); stunTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) @@ -323,11 +312,9 @@ public static Entity createBaseTower() { // we're going to add more components later on Entity tower = new Entity() .addComponent(new ColliderComponent()) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.TOWER)) // TODO: we might have to change the names of the layers .addComponent(new PhysicsComponent().setBodyType(BodyType.StaticBody)) - .addComponent(new TowerUpgraderComponent()) - .addComponent(new HitboxComponent().setLayer(PhysicsLayer.TOWER)); // TODO: we might have to change the names of the layers - - + .addComponent(new TowerUpgraderComponent()); return tower; } 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/main/com/csse3200/game/screens/MainGameScreen.java b/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java index 704ed65d7..e3cb512b0 100644 --- a/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java +++ b/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java @@ -33,10 +33,8 @@ import com.csse3200.game.physics.PhysicsService; import com.csse3200.game.rendering.RenderService; import com.csse3200.game.rendering.Renderer; -import com.csse3200.game.services.CurrencyService; -import com.csse3200.game.services.GameTime; -import com.csse3200.game.services.ResourceService; -import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.services.*; +import com.csse3200.game.ui.UIComponent; import com.csse3200.game.ui.terminal.Terminal; import com.csse3200.game.ui.terminal.TerminalDisplay; import com.csse3200.game.components.maingame.MainGameExitDisplay; @@ -62,6 +60,7 @@ public class MainGameScreen extends ScreenAdapter { static int screenWidth = Gdx.graphics.getWidth(); static int screenHeight = Gdx.graphics.getHeight(); + private Entity ui; public static int viewportWidth = screenWidth; @@ -100,6 +99,7 @@ public MainGameScreen(GdxGame game) { ServiceLocator.registerEntityService(new EntityService()); ServiceLocator.registerRenderService(new RenderService()); + ServiceLocator.registerGameEndService(new GameEndService()); renderer = RenderFactory.createRenderer(); renderer.getCamera().getEntity().setPosition(CAMERA_POSITION); @@ -122,6 +122,11 @@ public void render(float delta) { physicsEngine.update(); ServiceLocator.getEntityService().update(); + // Check if the game has ended + if (ServiceLocator.getGameEndService().hasGameEnded()) { + ui.getEvents().trigger("lose"); + } + batch.setProjectionMatrix(camera.combined); batch.begin(); batch.draw(backgroundTexture, 0, 0, viewportWidth, viewportHeight); @@ -189,7 +194,7 @@ private void createUI() { InputComponent inputComponent = ServiceLocator.getInputService().getInputFactory().createForTerminal(); - Entity ui = new Entity(); + ui = new Entity(); ui.addComponent(new InputDecorator(stage, 10)) .addComponent(new PerformanceDisplay()) .addComponent(new MainGameActions(this.game)) diff --git a/source/core/src/main/com/csse3200/game/services/GameEndService.java b/source/core/src/main/com/csse3200/game/services/GameEndService.java new file mode 100644 index 000000000..8b853343a --- /dev/null +++ b/source/core/src/main/com/csse3200/game/services/GameEndService.java @@ -0,0 +1,67 @@ +package com.csse3200.game.services; + +import com.csse3200.game.components.gamearea.EngineerCountDisplay; + +public class GameEndService { + + private int engineerCount; + + private boolean gameOver = false; + + private EngineerCountDisplay display; + + /** + * Constructor for the Game End Service + */ + public GameEndService() { + this.engineerCount = 5; + this.display = new EngineerCountDisplay(); + } + + /** + * Set the engineer limit. During instantiation, limit defaults to 5. + * @param newLimit as an integer representing the maximum number of engineer deaths + */ + public void setEngineerCount(int newLimit) { + if (newLimit > 0) { + engineerCount = newLimit; + } + } + + /** + * Returns the number of engineers left + * @return (int) engineer count + */ + + public int getEngineerCount() { + return engineerCount; + } + + /** + * Updates engineer count and the UI display + * If engineer count is 0, the game is over. + */ + public void updateEngineerCount() { + engineerCount -= 1; + display.updateCount(); + + if (engineerCount == 0) { + gameOver = true; + } + } + + /** + * Returns the game over state + * @return (boolean) true if the game is over; false otherwise + */ + public boolean hasGameEnded() { + return gameOver; + } + + /** + * Returns the Engineer Count UI component + */ + public EngineerCountDisplay getDisplay() { + return display; + } +} diff --git a/source/core/src/main/com/csse3200/game/services/ServiceLocator.java b/source/core/src/main/com/csse3200/game/services/ServiceLocator.java index 5bbe956cc..5683715e4 100644 --- a/source/core/src/main/com/csse3200/game/services/ServiceLocator.java +++ b/source/core/src/main/com/csse3200/game/services/ServiceLocator.java @@ -24,6 +24,7 @@ public class ServiceLocator { private static GameTime timeSource; private static InputService inputService; private static ResourceService resourceService; + private static GameEndService gameEndService; public static CurrencyService getCurrencyService() { return currencyService; @@ -53,6 +54,10 @@ public static ResourceService getResourceService() { return resourceService; } + public static GameEndService getGameEndService() { + return gameEndService; + } + public static void registerCurrencyService(CurrencyService service) { logger.debug("Registering currency service {}", service); currencyService = service; @@ -88,6 +93,11 @@ public static void registerResourceService(ResourceService source) { resourceService = source; } + public static void registerGameEndService(GameEndService source) { + logger.debug("Registering game end service service {}", source); + gameEndService = source; + } + public static void clear() { entityService = null; renderService = null; @@ -95,6 +105,7 @@ public static void clear() { timeSource = null; inputService = null; resourceService = null; + gameEndService = null; } private ServiceLocator() { diff --git a/source/core/src/main/com/csse3200/game/utils/math/GridPoint2Utils.java b/source/core/src/main/com/csse3200/game/utils/math/GridPoint2Utils.java index af3b20287..332e029af 100644 --- a/source/core/src/main/com/csse3200/game/utils/math/GridPoint2Utils.java +++ b/source/core/src/main/com/csse3200/game/utils/math/GridPoint2Utils.java @@ -6,9 +6,14 @@ * Contains additional utility constants and functions for common GridPoint2 operations. */ public class GridPoint2Utils { - public static final GridPoint2 ZERO = new GridPoint2(0, 4); + public static final GridPoint2 ZERO = new GridPoint2(0, 0); + private GridPoint2Utils() { throw new IllegalStateException("Instantiating static util class"); } + + public static GridPoint2Utils createInstance() { + return new GridPoint2Utils(); + } } diff --git a/source/core/src/test/com/csse3200/game/components/RicochetComponentTest.java b/source/core/src/test/com/csse3200/game/components/RicochetComponentTest.java new file mode 100644 index 000000000..c92371344 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/RicochetComponentTest.java @@ -0,0 +1,197 @@ +package com.csse3200.game.components; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.BeforeEach; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +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 RicochetComponentTest { + Entity projectile; + Entity mob; + + private final String[] atlas = { + "images/projectiles/mobProjectile.atlas", + "images/projectiles/basic_projectile.atlas", + "images/projectiles/mobKing_projectile.atlas", + "images/projectiles/engineer_projectile.atlas" + }; + + @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.loadTextureAtlases(atlas); + resourceService.loadAll(); + ServiceLocator.registerEntityService(new EntityService()); + + // For the time being, NPC is treated as an enemy. + projectile = createProjectile(PhysicsLayer.NPC, 0); + mob = createMobTarget(PhysicsLayer.NPC); + ServiceLocator.getEntityService().register(projectile); + ServiceLocator.getEntityService().register(mob); + } + + @Test + public void shouldNotBeNull() { + assertNotNull(projectile, "Ricochet projectile does not exist"); + } + + @Test + public void shouldHaveRicochetComponent() { + assertNotNull(projectile.getComponent(RicochetComponent.class), + "Projectile does not contain RicochetComponent"); + } + + @Test + public void shouldDisposeAferCollision() { + int currentEntities = ServiceLocator.getEntityService().getEntities().size; + + triggerCollisionEnd(projectile, mob); + + assertTrue("projectile entity flag should be true after collision", + projectile.getFlagForDelete()); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertEquals("Projectile should be deleted after collision upon update", currentEntities - 1, + ServiceLocator.getEntityService().getEntities().size); + } + + // @Ignore + @Test + public void shouldSpawnAnotherProjWithinMapBounds() { + projectile.setPosition(3, 3); + int currentEntities = ServiceLocator.getEntityService().getEntities().size; + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertEquals("Should spawn another ricochet projectile within map bounds", currentEntities, + ServiceLocator.getEntityService().getEntities().size); + } + + @Test + public void shouldNotSpawnAnotherProjOutOfMapBounds() { + projectile.setPosition(-1, -1); + int currentEntities = ServiceLocator.getEntityService().getEntities().size; + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertNotEquals(currentEntities, + ServiceLocator.getEntityService().getEntities().size, + "Should not have spawned another projectile upon collision"); + } + + @Test + public void testWithinRangeSpawnedProjectile() { + projectile.setPosition(3, 3); + mob.setPosition(3, 3); + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + // For the time being, 2f seems to be the justifiable range + // for the new projectile to be spawned. + assertEquals("Projectile should be spawned within the range provided.", 1, + ServiceLocator.getEntityService().getNearbyEntities(mob, 2f).size); + } + + @Test + public void testNotWithinRangeShouldNotSpawnProjectile() { + projectile.setPosition(3, 3); + mob.setPosition(3, 3); + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertEquals("Projectile should not be spawned too close to the original (now disposed) projectile and mob", 0, + ServiceLocator.getEntityService().getNearbyEntities(mob, 0.5f).size); + } + + @Test + public void shouldNotSpawnAnotherProjWithMaxBounceCount() { + Entity newProjectile = createProjectile(PhysicsLayer.NPC, 3); + ServiceLocator.getEntityService().register(newProjectile); + int currentEntities = ServiceLocator.getEntityService().getEntities().size; + + newProjectile.setPosition(3, 3); + mob.setPosition(3, 3); + + triggerCollisionEnd(newProjectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertNotEquals(currentEntities, + ServiceLocator.getEntityService().getEntities().size, + "Should not have spawned another projectile upon collision with a max bounce count"); + } + + Entity createProjectile(short targetLayer, int bounceCount) { + Entity projectile = ProjectileFactory.createRicochetFireball(targetLayer, new Vector2(0.1f, 0.1f), + new Vector2(2f, 2f), bounceCount); + + return projectile; + } + + Entity createMobTarget(short layer) { + Entity target = new Entity(); + + target + .addComponent(new CombatStatsComponent(100, 0)) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent().setLayer(layer)); + + return target; + } + + /** + * Assumes both entity has hitbox components. + * + * @param projectile + * @param mob + */ + void triggerCollisionEnd(Entity projectile, Entity mob) { + projectile.getEvents().trigger("collisionEnd", + projectile.getComponent(HitboxComponent.class).getFixture(), + mob.getComponent(HitboxComponent.class).getFixture()); + } +} diff --git a/source/core/src/test/com/csse3200/game/components/SplitFireworksComponentTest.java b/source/core/src/test/com/csse3200/game/components/SplitFireworksComponentTest.java new file mode 100644 index 000000000..3a25dbd68 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/SplitFireworksComponentTest.java @@ -0,0 +1,214 @@ +package com.csse3200.game.components; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +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 SplitFireworksComponentTest { + Entity projectile; + Entity mob; + static double OFFSET_X = 1.75; + + private final String[] atlas = { + "images/projectiles/mobProjectile.atlas", + "images/projectiles/basic_projectile.atlas", + "images/projectiles/mobKing_projectile.atlas", + "images/projectiles/engineer_projectile.atlas", + "images/projectiles/firework_anim.atlas" + }; + + @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.loadTextureAtlases(atlas); + resourceService.loadAll(); + ServiceLocator.registerEntityService(new EntityService()); + + // For the time being, NPC is treated as an enemy. + projectile = createSplitFireworkProjectile(PhysicsLayer.NPC, 3); + mob = createMobTarget(PhysicsLayer.NPC); + ServiceLocator.getEntityService().register(projectile); + ServiceLocator.getEntityService().register(mob); + } + + @Test + public void shouldNotBeNull() { + assertNotNull(projectile, "Ricochet projectile does not exist"); + } + + @Test + public void shouldHaveSplitFireworksComponent() { + assertNotNull(projectile.getComponent(SplitFireworksComponent.class), + "Projectile does not contain SplitFireworksComponent"); + } + + @Test + public void shouldDisposeAferCollision() { + triggerCollisionEnd(projectile, mob); + + assertTrue("original projectile entity flag should be true after collision", + projectile.getFlagForDelete()); + } + + @Test + void shouldSpawnCorrectNumberOfProjs() { + projectile.setPosition(3, 3); + + int initialNumEntities = ServiceLocator.getEntityService().getEntities().size; + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + // initialNumEntities + 2 to account for the dispose of the original projectile. + assertEquals("Should spawn correct number of projectiles after collision based on amount given", + initialNumEntities + 2, ServiceLocator.getEntityService().getEntities().size); + } + + @Test + public void shouldSpawnMultProjWithinMapBounds() { + projectile.setPosition(3, 3); + mob.setPosition(3, 3); + + int initialNumEntities = ServiceLocator.getEntityService().getEntities().size; + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertTrue("SplitFireWorks projectile should spawn multiple projectile out of map bounds", + ServiceLocator.getEntityService().getEntities().size > initialNumEntities); + } + + @Test + public void shouldNotSpawnMultProjOutOfMapBounds() { + projectile.setPosition(22, 22); + mob.setPosition(22, 22); + + int initialNumEntities = ServiceLocator.getEntityService().getEntities().size; + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertFalse(ServiceLocator.getEntityService().getEntities().size > initialNumEntities, + "SplitFireWorks projectile should not spawn multiple projectile out of map bounds"); + } + + @Test + public void testWithinRangeSpawnedProjectiles() { + projectile.setPosition(3, 3); + mob.setPosition(3, 3); + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertEquals("Projectiles should be spawned within the range provided.", 3, + ServiceLocator.getEntityService().getNearbyEntities(mob, 2f).size); + } + + @Test + public void testTooCloseRangeSpawnedProjectiles() { + projectile.setPosition(3, 3); + mob.setPosition(3, 3); + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + assertNotEquals(3, + ServiceLocator.getEntityService().getNearbyEntities(mob, 0.5f).size, + "Projectiles should not be spawned too close upon impact."); + } + + @Test + public void shouldSpawnAtSpecifiedLocation() { + projectile.setPosition(3, 3); + mob.setPosition(3, 3); + float currPosition = projectile.getPosition().x; + + triggerCollisionEnd(projectile, mob); + + ServiceLocator.getPhysicsService().getPhysics().update(); + ServiceLocator.getEntityService().update(); + + float newXPosition = (float) (currPosition + OFFSET_X); + + Array allEntities = ServiceLocator.getEntityService().getEntities(); + + for (Entity entity : allEntities) { + if (entity == mob) + continue; + + assertEquals("Projectiles were not spawned at the right offset x placement", newXPosition, entity.getPosition().x, + 0.02); + } + } + + Entity createSplitFireworkProjectile(short targetLayer, int amount) { + Entity projectile = ProjectileFactory.createSplitFireWorksFireball(targetLayer, new Vector2(100, 3), + new Vector2(2f, 2f), amount); + + return projectile; + } + + Entity createMobTarget(short layer) { + Entity target = new Entity(); + + target + .addComponent(new CombatStatsComponent(100, 0)) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent().setLayer(layer)); + + return target; + } + + /** + * Assumes both entity has hitbox components. + * + * @param projectile + * @param mob + */ + void triggerCollisionEnd(Entity projectile, Entity mob) { + projectile.getEvents().trigger("collisionEnd", + projectile.getComponent(HitboxComponent.class).getFixture(), + mob.getComponent(HitboxComponent.class).getFixture()); + } +} diff --git a/source/core/src/test/com/csse3200/game/components/player/HumanAnimationControllerTest.java b/source/core/src/test/com/csse3200/game/components/player/HumanAnimationControllerTest.java index 99019845a..1f8f063d6 100644 --- a/source/core/src/test/com/csse3200/game/components/player/HumanAnimationControllerTest.java +++ b/source/core/src/test/com/csse3200/game/components/player/HumanAnimationControllerTest.java @@ -58,6 +58,7 @@ void setUp() { resourceService.loadSounds(sounds); resourceService.loadAll(); engineer = EngineerFactory.createEngineer(); + engineer.create(); } @AfterEach @@ -70,46 +71,45 @@ void shouldHaveAnimationController() { "Created Engineer entity should have a HumanAnimationController"); } -// @Test -// void shouldAnimateIdleRight() { -// engineer.getEvents().trigger("idleStart"); -// when(gameTime.getDeltaTime()).thenReturn(0.1f); -// assertEquals("idle_right", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), -// "'idleStart' event should trigger 'idle_right' animation'"); -// } -// -// @Test -// void animateLeftWalk() { -// engineer.getEvents().trigger("walkLeftStart"); -// assertEquals("walk_left", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), -// "'walkLeftStart' event should trigger 'walk_left' animation'"); -// } -// -// @Test -// void animateRightWalk() { -// engineer.getEvents().trigger("walkRightStart"); -// assertEquals("walk_right", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), -// "'walkRightStart' event should trigger 'walk_right' animation'"); -// } -// -// @Test -// void animateFiring() { -// engineer.getEvents().trigger("firingSingleStart"); -// assertEquals("firing_single", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), -// "'firingSingleStart' event should trigger 'firing_single' animation'"); -// } -// -// @Test -// void animateHit() { -// engineer.getEvents().trigger("hitStart"); -// assertEquals("hit", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), -// "'hitStart' event should trigger 'hit' animation'"); -// } -// -// @Test -// void animateDeath() { -// engineer.getEvents().trigger("hitStart"); -// assertEquals("death", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), -// "'deathStart' event should trigger 'death' animation'"); -// } + @Test + void shouldAnimateIdleRight() { + engineer.getEvents().trigger("idleRight"); + assertEquals("idle_right", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), + "'idleRight' event should trigger 'idle_right' animation'"); + } + + @Test + void shouldAnimateLeftWalk() { + engineer.getEvents().trigger("walkLeftStart"); + assertEquals("walk_left", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), + "'walkLeftStart' event should trigger 'walk_left' animation'"); + } + + @Test + void shouldAnimateRightWalk() { + engineer.getEvents().trigger("walkRightStart"); + assertEquals("walk_right", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), + "'walkRightStart' event should trigger 'walk_right' animation'"); + } + + @Test + void shoudlAnimateFiring() { + engineer.getEvents().trigger("firingSingleStart"); + assertEquals("firing_single", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), + "'firingSingleStart' event should trigger 'firing_single' animation'"); + } + + @Test + void shouldAnimateHit() { + engineer.getEvents().trigger("hitStart"); + assertEquals("hit", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), + "'hitStart' event should trigger 'hit' animation'"); + } + + @Test + void shouldAnimateDeath() { + engineer.getEvents().trigger("deathStart"); + assertEquals("death", engineer.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), + "'deathStart' event should trigger 'death' animation'"); + } } \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java new file mode 100644 index 000000000..f03282584 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java @@ -0,0 +1,148 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +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.services.GameTime; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.entities.factories.ProjectileFactory; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class DroidCombatTaskTest { + DroidCombatTask droidCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + droidCombatTask = new DroidCombatTask(1, 4); + } + + @Test + public void testStartTriggersWalkEvent() { + Entity entity = createDroid(); + EventListener0 walkListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(DroidCombatTask.WALK, walkListener); + droidCombatTask.start(); + verify(walkListener).handle(); + } + + @Test + public void testUpdateTowerStateWithTargetInRange() { + Entity entity = createDroid(); + entity.setPosition(10,10); + + Entity Target = createNPC(); + Target.setPosition(12,10); + + EventListener0 attackUp = mock(EventListener0.class); + EventListener0 attackDown = mock(EventListener0.class); + EventListener0 switchDown = mock(EventListener0.class); + EventListener0 shootUp = mock(EventListener0.class); + EventListener0 shootDown = mock(EventListener0.class); + entity.getEvents().addListener(DroidCombatTask.ATTACK_UP, attackUp); + entity.getEvents().addListener(DroidCombatTask.SHOOT_UP,shootUp); + entity.getEvents().addListener(DroidCombatTask.ATTACK_DOWN, attackDown); + entity.getEvents().addListener(DroidCombatTask.GO_DOWN,switchDown); + entity.getEvents().addListener(DroidCombatTask.SHOOT_DOWN,shootDown); + //Jump to IDLE state + droidCombatTask.start(); + droidCombatTask.towerState = DroidCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + + assertTrue(droidCombatTask.isTargetVisible()); + + droidCombatTask.updateTowerState(); + // By default, Droid aims from top, so shoot from top + verify(attackUp).handle(); + // shoot projectiles from top + verify(shootUp).handle(); + assertEquals(DroidCombatTask.STATE.DOWN, droidCombatTask.getState()); + + droidCombatTask.updateTowerState(); + // switch to aim downwards + verify(switchDown).handle(); + assertEquals(DroidCombatTask.STATE.SHOOT_DOWN, droidCombatTask.getState()); + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + // check if the target is still there to shoot from below + assertTrue(droidCombatTask.isTargetVisible()); + + droidCombatTask.updateTowerState(); + // Shoot from below + verify(attackDown).handle(); + //shoot projectiles from below + verify(shootUp).handle(); + // switch back to aim from top + assertEquals(DroidCombatTask.STATE.UP, droidCombatTask.getState()); + } + + @Test + public void testUpdateTowerStateWithTargetNotInRange() { + Entity entity = createDroid(); + entity.setPosition(10, 10); + + Entity Target = createNPC(); + Target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attackUp = mock(EventListener0.class); + entity.getEvents().addListener(DroidCombatTask.IDLE, idle); + entity.getEvents().addListener(DroidCombatTask.ATTACK_UP,attackUp); + //Jump to IDLE state + droidCombatTask.towerState = DroidCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + // Target out of range + assertFalse(droidCombatTask.isTargetVisible()); + + droidCombatTask.updateTowerState(); + // Droid will remain in Idle and will not shoot + verify(idle).handle(); + verifyNoInteractions(attackUp); + assertEquals(DroidCombatTask.STATE.IDLE, droidCombatTask.getState()); + + } + + + Entity createDroid() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(droidCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new CombatStatsComponent(100,10)); + entity.create(); + return entity; + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + Target.create(); + return Target; + } +} diff --git a/source/core/src/test/com/csse3200/game/components/tasks/EngineerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/EngineerCombatTaskTest.java new file mode 100644 index 000000000..8a5954efd --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/EngineerCombatTaskTest.java @@ -0,0 +1,211 @@ +package com.csse3200.game.entities.factories; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.player.HumanAnimationController; +import com.csse3200.game.components.tasks.FinalBossMovementTask; +import com.csse3200.game.components.tasks.human.EngineerCombatTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +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.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +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.AfterEach; +import org.junit.jupiter.api.BeforeAll; +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 java.util.ArrayList; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(GameExtension.class) +class EngineerCombatTaskTest { + private final int MAX_RANGE = 10; + private EngineerCombatTask COMBAT_TASK; + private Entity MOCK_ENGINEER; + + private final String[] atlas = {"images/engineers/engineer.atlas"}; + private final String[] projectileAtlas = {"images/projectiles/engineer_projectile.atlas"}; + private static final String[] sounds = { + "sounds/engineers/firing_auto.mp3", + "sounds/engineers/firing_single.mp3" + }; + + private final String[] animations = { + "idle_right", + "walk_left", + "walk_right", + "walk_prep", + "prep", + "firing_auto", + "firing_single", + "hit", + "death" + }; + + @BeforeEach + 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); + resourceService.loadTextureAtlases(atlas); + resourceService.loadTextureAtlases(projectileAtlas); + resourceService.loadSounds(sounds); + resourceService.loadAll(); + + // Create a mock engineer and add the combat task. + COMBAT_TASK = new EngineerCombatTask(MAX_RANGE); + MOCK_ENGINEER = EngineerFactory.createEngineer(); + MOCK_ENGINEER.getComponent(AITaskComponent.class).addTask(COMBAT_TASK); + COMBAT_TASK.create(MOCK_ENGINEER.getComponent(AITaskComponent.class)); + } + + /** + * Tests that the task correctly triggers an idle event. + */ + @Test + void testIdleEvent() { + + // Add a listener to the engineer to test for the idle animation trigger. + EventListener0 idleListener = mock(EventListener0.class); + MOCK_ENGINEER.getEvents().addListener("idleRight", idleListener); + + // Start the task. + COMBAT_TASK.start(); + + // Update physics and check whether the event is triggered. + updatePhysics(); + verify(idleListener).handle(); + } + + /** + * Adapted from EngineerFactoryTest: Author - The-AhmadAA + * + * Tests that the task correctly switches to the + * firing state and triggers a firing event. + */ + @Test + void testFiringEvent() { + + // Create an attacker entity. + Entity MOCK_ATTACKER = createAttacker(PhysicsLayer.NPC); + + // Add a listener to the engineer to test for the firing animation trigger. + EventListener0 firingListener = mock(EventListener0.class); + MOCK_ENGINEER.getEvents().addListener("firingSingleStart", firingListener); + + // Set testing positions for the engineer and the attacker and start the task. + MOCK_ENGINEER.setPosition(10,10); + MOCK_ATTACKER.setPosition(12,10); + COMBAT_TASK.start(); + + // Update physics and check whether the event is triggered. + updatePhysics(); + verify(firingListener).handle(); + } + + /** + * Adapted from EngineerFactoryTest: Author - The-AhmadAA + * + * Test that the task can switch the state back to idle after mob isn't visible. + */ + @Test + void testIdleAfterMobKilled() { + // Create an attacker entity. + Entity MOCK_ATTACKER = createAttacker(PhysicsLayer.NPC); + + // Set testing positions for the engineer and the attacker and start the task. + MOCK_ENGINEER.setPosition(10,10); + MOCK_ATTACKER.setPosition(12,10); + COMBAT_TASK.start(); + + // Update physics, move the attacker out of range + // and check whether the event is triggered. + updatePhysics(); + MOCK_ATTACKER.dispose(); + EventListener0 idleListener = mock(EventListener0.class); + MOCK_ENGINEER.getEvents().addListener("idleRight", idleListener); + updatePhysics(); + + // Verify the idle animation trigger's occurrence if the attacker is gone. + if (!COMBAT_TASK.isTargetVisible()) { + verify(idleListener).handle(); + + // Fail the test if the attacked remains visible after deletion. + } else { + fail(); + } + } + + @Test + void testProjectileFired() { + // Create an attacker entity. + Entity MOCK_ATTACKER = createAttacker(PhysicsLayer.NPC); + + // Set testing positions for the engineer and the attacker and start the task. + MOCK_ENGINEER.setPosition(10,10); + MOCK_ATTACKER.setPosition(12,10); + COMBAT_TASK.start(); + + // Update physics and check whether a projectile is fired. + updatePhysics(); + EventListener0 projectileListener = mock(EventListener0.class); + MOCK_ENGINEER.getEvents().addListener("engineerProjectileFired", projectileListener); + updatePhysics(); + + verify(projectileListener).handle(); + + } + + /** + * Adapted from EngineerFactoryTest: Author - The-AhmadAA + */ + Entity createAttacker(short targetLayer) { + Entity entity = + new Entity() + .addComponent(new TouchAttackComponent(targetLayer)) + .addComponent(new CombatStatsComponent(0, 10)) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent().setLayer(targetLayer)); + entity.create(); + return entity; + } + + /** + * Update the game physics and the task state. + */ + void updatePhysics() { + ServiceLocator.getPhysicsService().getPhysics().update(); + COMBAT_TASK.updateEngineerState(); + } +} \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java new file mode 100644 index 000000000..dc7394399 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java @@ -0,0 +1,139 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +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.services.GameTime; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.entities.factories.ProjectileFactory; + +import jdk.jfr.Event; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class FireTowerCombatTaskTest { + FireTowerCombatTask fireTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + fireTowerCombatTask = new FireTowerCombatTask(1, 4); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testStartTriggersIdleEvent() { + Entity entity = createFireTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(FireTowerCombatTask.IDLE, idleListener); + fireTowerCombatTask.start(); + verify(idleListener).handle(); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetInRange() { + Entity entity = createFireTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 prepAttack = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(FireTowerCombatTask.PREP_ATTACK, prepAttack); + entity.getEvents().addListener(FireTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + fireTowerCombatTask.start(); + fireTowerCombatTask.towerState = FireTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + + assertTrue(fireTowerCombatTask.isTargetVisible()); + + fireTowerCombatTask.updateTowerState(); + verify(prepAttack).handle(); + assertEquals(FireTowerCombatTask.STATE.PREP_ATTACK, fireTowerCombatTask.getState()); + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertTrue(fireTowerCombatTask.isTargetVisible()); + + fireTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(FireTowerCombatTask.STATE.ATTACK, fireTowerCombatTask.getState()); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetNotInRange() { + Entity entity = createFireTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 prepAttack = mock(EventListener0.class); + entity.getEvents().addListener(FireTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(FireTowerCombatTask.PREP_ATTACK, prepAttack); + + fireTowerCombatTask.towerState = FireTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(fireTowerCombatTask.isTargetVisible()); + + fireTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(prepAttack); + assertEquals(FireTowerCombatTask.STATE.IDLE, fireTowerCombatTask.getState()); + } + + Entity createFireTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(fireTowerCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new CombatStatsComponent(100,10)); + entity.create(); + return entity; + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + Target.create(); + return Target; + } +} diff --git a/source/core/src/test/com/csse3200/game/components/tasks/WanderTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/MobWanderTaskTest.java similarity index 61% rename from source/core/src/test/com/csse3200/game/components/tasks/WanderTaskTest.java rename to source/core/src/test/com/csse3200/game/components/tasks/MobWanderTaskTest.java index 28fedd6c5..46b2a2ab9 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/WanderTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/MobWanderTaskTest.java @@ -19,7 +19,7 @@ @ExtendWith(GameExtension.class) @ExtendWith(MockitoExtension.class) -class WanderTaskTest { +class MobWanderTaskTest { @Mock GameTime gameTime; @@ -28,20 +28,20 @@ void beforeEach() { ServiceLocator.registerTimeSource(gameTime); } - @Test - void shouldTriggerEvent() { - WanderTask wanderTask = new WanderTask(Vector2Utils.ONE, 1f); - - AITaskComponent aiTaskComponent = new AITaskComponent().addTask(wanderTask); - Entity entity = new Entity().addComponent(aiTaskComponent).addComponent(new PhysicsMovementComponent()); - entity.create(); - - // Register callbacks - EventListener0 callback = mock(EventListener0.class); - entity.getEvents().addListener("wanderStart", callback); - - wanderTask.start(); - - verify(callback).handle(); - } +// @Test +// void shouldTriggerEvent() { +// MobWanderTask mobWanderTask = new MobWanderTask(Vector2Utils.ONE, 1f); +// +// AITaskComponent aiTaskComponent = new AITaskComponent().addTask(mobWanderTask); +// Entity entity = new Entity().addComponent(aiTaskComponent).addComponent(new PhysicsMovementComponent()); +// entity.create(); +// +// // Register callbacks +// EventListener0 callback = mock(EventListener0.class); +// entity.getEvents().addListener("wanderStart", callback); +// +// mobWanderTask.start(); +// +// verify(callback).handle(); +// } } \ No newline at end of file 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/StunTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/StunTowerCombatTaskTest.java new file mode 100644 index 000000000..37c87158c --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/StunTowerCombatTaskTest.java @@ -0,0 +1,129 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +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.services.GameTime; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.entities.factories.ProjectileFactory; + +import jdk.jfr.Event; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class StunTowerCombatTaskTest { + StunTowerCombatTask stunTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + stunTowerCombatTask = new StunTowerCombatTask(1, 4); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testStartTriggersIdleEvent() { + Entity entity = createStunTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(StunTowerCombatTask.IDLE, idleListener); + stunTowerCombatTask.start(); + verify(idleListener).handle(); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetInRange() { + Entity entity = createStunTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(StunTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + stunTowerCombatTask.start(); + stunTowerCombatTask.towerState = StunTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + + assertTrue(stunTowerCombatTask.isTargetVisible()); + + stunTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(StunTowerCombatTask.STATE.ATTACK, stunTowerCombatTask.getState()); + } + + /** + * this test has been implement using the same logic as the tests implemented + * in DroidCombatTaskTest by Mohamad Dabboussi + */ + @Test + public void testUpdateTowerStateWithTargetNotInRange() { + Entity entity = createStunTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(StunTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(StunTowerCombatTask.ATTACK, attack); + + stunTowerCombatTask.towerState = StunTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(stunTowerCombatTask.isTargetVisible()); + + stunTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(attack); + assertEquals(StunTowerCombatTask.STATE.IDLE, stunTowerCombatTask.getState()); + } + + Entity createStunTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(stunTowerCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent) + .addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new CombatStatsComponent(100,10)); + entity.create(); + return entity; + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + Target.create(); + return Target; + } +} diff --git a/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java new file mode 100644 index 000000000..65a0e4724 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java @@ -0,0 +1,144 @@ +package com.csse3200.game.components.tasks; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.events.listeners.EventListener0; +import com.csse3200.game.physics.PhysicsLayer; +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.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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class TNTTowerCombatTaskTest { + + + TNTTowerCombatTask tntTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + + tntTowerCombatTask = new TNTTowerCombatTask(2,4); + } + + @Test + public void testStartTriggersDefaultEvent() { + + Entity entity = createTNT(); + + EventListener0 defaultStartListener = mock(EventListener0.class); + entity.getEvents().addListener(TNTTowerCombatTask.DEFAULT, defaultStartListener); + + tntTowerCombatTask.start(); + + verify(defaultStartListener).handle(); + } + + @Test + public void testUpdateTowerStateWithTargetInRange() { + + Entity entity = createTNT(); + entity.setPosition(10,10); + + Entity Target = createNPC(); + Target.setPosition(12,10); + + EventListener0 dig = mock(EventListener0.class); + EventListener0 explode = mock(EventListener0.class); + EventListener0 damage = mock(EventListener0.class); + // still in idle + assertEquals(TNTTowerCombatTask.STATE.IDLE, tntTowerCombatTask.getState()); + entity.getEvents().addListener(TNTTowerCombatTask.DIG, dig); + entity.getEvents().addListener(TNTTowerCombatTask.EXPLOSION,explode); + entity.getEvents().addListener(TNTTowerCombatTask.DAMAGE,damage); + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + // TNT saw the target + assertTrue(tntTowerCombatTask.isTargetVisible()); + + tntTowerCombatTask.updateTowerState(); + // TNT just Dug into the ground + verify(dig).handle(); + // READY TO EXPLODE !!! + assertEquals(TNTTowerCombatTask.STATE.EXPLODE, tntTowerCombatTask.getState()); + + tntTowerCombatTask.updateTowerState(); + + // BOOOOOOOOM !! + verify(explode).handle(); + // Apply Damage and Knock-back to Target + verify(damage).handle(); + + // Ready to dispose TNT + assertEquals(TNTTowerCombatTask.STATE.REMOVE, tntTowerCombatTask.getState()); + + tntTowerCombatTask.updateTowerState(); + // Set flag to dispose + assertTrue(tntTowerCombatTask.isReadyToDelete()); + + } + + @Test + public void testStayAtIdleWhenNoTargetInRange() { + + Entity entity = createTNT(); + entity.setPosition(10,10); + + Entity Target = createNPC(); + Target.setPosition(15,10); + + EventListener0 defaultStartListener = mock(EventListener0.class); + // still in idle + assertEquals(TNTTowerCombatTask.STATE.IDLE, tntTowerCombatTask.getState()); + entity.getEvents().addListener(TNTTowerCombatTask.DIG, defaultStartListener); + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + // Target not in range + assertFalse(tntTowerCombatTask.isTargetVisible()); + + tntTowerCombatTask.updateTowerState(); + + verifyNoInteractions(defaultStartListener); + // still in idle + assertEquals(TNTTowerCombatTask.STATE.IDLE, tntTowerCombatTask.getState()); + + } + + Entity createTNT() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(tntTowerCombatTask); + Entity entity = new Entity().addComponent(aiTaskComponent). + addComponent(new PhysicsComponent()) + .addComponent(new HitboxComponent()) + .addComponent(new ColliderComponent()); + entity.create(); + return entity; + + } + + Entity createNPC() { + Entity Target = new Entity().addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new ColliderComponent()) + .addComponent(new PhysicsComponent()); + + Target.create(); + return Target; + } + + +} 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/components/tasks/human/HumanMovementTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/human/HumanMovementTaskTest.java deleted file mode 100644 index 4deb83386..000000000 --- a/source/core/src/test/com/csse3200/game/components/tasks/human/HumanMovementTaskTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.csse3200.game.components.tasks.human; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class HumanMovementTaskTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void start() { - } - - @Test - void update() { - } - - @Test - void setTarget() { - } - - @Test - void stop() { - } -} \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tasks/human/HumanWanderTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/human/HumanWanderTaskTest.java index 5a24deb7c..6e7c9eaeb 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/human/HumanWanderTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/human/HumanWanderTaskTest.java @@ -1,30 +1,104 @@ package com.csse3200.game.components.tasks.human; +import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.EngineerFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +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.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +@ExtendWith(GameExtension.class) +@ExtendWith(MockitoExtension.class) class HumanWanderTaskTest { + /** + * Class for testing the HumanWanderTask, adapted from WanderTaskTest by + * Jonathan Tang + */ + + Entity owner; + + private final String[] atlas = {"images/engineers/engineer.atlas"}; + private static final String[] sounds = { + "sounds/engineers/firing_auto.mp3", + "sounds/engineers/firing_single.mp3" + }; @BeforeEach void setUp() { + GameTime gameTime = new GameTime(); + PhysicsService physics = new PhysicsService(); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(physics); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = new ResourceService(); + ServiceLocator.registerResourceService(resourceService); + resourceService.loadTextureAtlases(atlas); + resourceService.loadSounds(sounds); + resourceService.loadAll(); + owner = EngineerFactory.createEngineer(); + owner.create(); } @AfterEach void tearDown() { } + @Test + void start() { + + } + @Test void getPriority() { } @Test - void start() { + void shouldStartWaiting() { + + } + + @Test + void shouldStartMoving() { + + } + + @Test + void shouldStartCombat() { + + } + + @Test + void shouldSwapTask() { + } @Test void update() { } + + Entity createEnemy() { + Entity enemy = mock(Entity.class); + enemy + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC)) + .addComponent(new PhysicsComponent()) + .addComponent(new TouchAttackComponent(PhysicsLayer.ENGINEER)); + return enemy; + } } \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tower/FireTowerAnimationControllerTest.java b/source/core/src/test/com/csse3200/game/components/tower/FireTowerAnimationControllerTest.java new file mode 100644 index 000000000..6f62eb3be --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tower/FireTowerAnimationControllerTest.java @@ -0,0 +1,66 @@ +package com.csse3200.game.components.tower; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.TowerFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.rendering.DebugRenderer; +import com.csse3200.game.rendering.RenderService; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ResourceService; +import com.csse3200.game.services.ServiceLocator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(GameExtension.class) +public class FireTowerAnimationControllerTest { + + private Entity mockEntity; + private final String[] texture = {"images/towers/fire_tower_atlas.png"}; + private final String[] atlas = {"images/towers/fire_tower_atlas.atlas"}; + + @BeforeEach + public void setUp() { + ServiceLocator.registerPhysicsService(new PhysicsService()); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = new ResourceService(); + ServiceLocator.registerResourceService(resourceService); + resourceService.loadTextures(texture); + resourceService.loadTextureAtlases(atlas); + resourceService.loadAll(); + + mockEntity = TowerFactory.createFireTower(); // Replace with actual Droid Tower creation logic + mockEntity.create(); + } + + @Test + public void testAnimateWalk() { + mockEntity.getEvents().trigger("startIdle"); + assertEquals("idle", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateDefault() { + mockEntity.getEvents().trigger("startAttackPrep"); + assertEquals("prepAttack", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateGoUp() { + mockEntity.getEvents().trigger("startAttack"); + assertEquals("attack", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateDeath() { + mockEntity.getEvents().trigger("startDeath"); + assertEquals("death", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } +} diff --git a/source/core/src/test/com/csse3200/game/components/tower/StunTowerAnimationControllerTest.java b/source/core/src/test/com/csse3200/game/components/tower/StunTowerAnimationControllerTest.java new file mode 100644 index 000000000..dbb7df351 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tower/StunTowerAnimationControllerTest.java @@ -0,0 +1,60 @@ +package com.csse3200.game.components.tower; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.TowerFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.rendering.DebugRenderer; +import com.csse3200.game.rendering.RenderService; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ResourceService; +import com.csse3200.game.services.ServiceLocator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(GameExtension.class) +public class StunTowerAnimationControllerTest { + + private Entity mockEntity; + private final String[] texture = {"images/towers/stun_tower.png"}; + private final String[] atlas = {"images/towers/stun_tower.atlas"}; + + @BeforeEach + public void setUp() { + ServiceLocator.registerPhysicsService(new PhysicsService()); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = new ResourceService(); + ServiceLocator.registerResourceService(resourceService); + resourceService.loadTextures(texture); + resourceService.loadTextureAtlases(atlas); + resourceService.loadAll(); + + mockEntity = TowerFactory.createStunTower(); // Replace with actual Droid Tower creation logic + mockEntity.create(); + } + + @Test + public void testAnimateWalk() { + mockEntity.getEvents().trigger("startIdle"); + assertEquals("idle", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateDefault() { + mockEntity.getEvents().trigger("startAttack"); + assertEquals("attack", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateGoUp() { + mockEntity.getEvents().trigger("startDeath"); + assertEquals("death", mockEntity.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } +} diff --git a/source/core/src/test/com/csse3200/game/components/xenos/XenoAnimationControllerTest.java b/source/core/src/test/com/csse3200/game/components/xenos/XenoAnimationControllerTest.java new file mode 100644 index 000000000..b8317ff2f --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/xenos/XenoAnimationControllerTest.java @@ -0,0 +1,70 @@ +package com.csse3200.game.components.xenos; + + +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.NPCFactory; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.rendering.DebugRenderer; +import com.csse3200.game.rendering.RenderService; +import com.csse3200.game.services.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 java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + + +@ExtendWith(GameExtension.class) +public class XenoAnimationControllerTest { + + private Entity xenoGrunt; + private Entity target; + private final String[] atlas = {"images/mobs/xenoGrunt.atlas"}; + + @BeforeEach + public void setUp() { + ServiceLocator.registerPhysicsService(new PhysicsService()); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = new ResourceService(); + ServiceLocator.registerResourceService(resourceService); + resourceService.loadTextureAtlases(atlas); + resourceService.loadAll(); + + xenoGrunt = NPCFactory.createXenoGrunt(target); // Replace with actual Droid Tower creation logic + xenoGrunt.create(); + } + + @Test + public void testAnimateWander() { + xenoGrunt.getEvents().trigger("wanderStart"); + assertEquals("xeno_run", xenoGrunt.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateHurt() { + xenoGrunt.getEvents().trigger("runHurt"); + assertEquals("xeno_hurt", xenoGrunt.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + + @Test + public void testAnimateMelee1() { + xenoGrunt.getEvents().trigger("meleeStart"); + assert(Objects.equals(xenoGrunt.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), "xeno_melee_1") + || Objects.equals(xenoGrunt.getComponent(AnimationRenderComponent.class).getCurrentAnimation(), "xeno_melee_2")); + } + + @Test + public void testAnimateDie() { + xenoGrunt.getEvents().trigger("dieStart"); + assertEquals("xeno_die", xenoGrunt.getComponent(AnimationRenderComponent.class).getCurrentAnimation()); + } + +} diff --git a/source/core/src/test/com/csse3200/game/entities/factories/EngineerFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/EngineerFactoryTest.java index 3d80accb0..d130ae5c7 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/EngineerFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/EngineerFactoryTest.java @@ -55,7 +55,7 @@ class EngineerFactoryTest { "firing_single", "hit", "death" - };; + }; @BeforeEach void setUp() { 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..9673617c1 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java @@ -0,0 +1,114 @@ +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.physics.components.PhysicsMovementComponent; +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 final String[] texture = { + "images/towers/turret_deployed.png", + "images/towers/turret01.png", + "images/towers/wallTower.png" + }; + private final 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), + "Xeno Grunt should have AnimationRenderComponent"); + } + + @Test + public void testCreateXenoGruntHasPhysicsMovementComponent() { + assertNotNull(xenoGrunt.getComponent(PhysicsMovementComponent.class), + "Xeno Grunt should have PhysicsMovementComponent"); + } + +} diff --git a/source/core/src/test/com/csse3200/game/entities/factories/ProjectileFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/ProjectileFactoryTest.java index 925f44e03..9c4129a40 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/ProjectileFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/ProjectileFactoryTest.java @@ -10,13 +10,13 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; -import com.csse3200.game.components.CombatStatsComponent; -import com.csse3200.game.components.CostComponent; -import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.*; +import com.csse3200.game.components.projectile.*; import com.csse3200.game.entities.Entity; 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.physics.components.HitboxComponent; import com.csse3200.game.physics.components.PhysicsComponent; import com.csse3200.game.physics.components.PhysicsMovementComponent; @@ -37,7 +37,25 @@ @ExtendWith(GameExtension.class) class ProjectileFactoryTest { - private Entity projectile; + + private final String[] atlas = { + "images/projectiles/mobProjectile.atlas", + "images/projectiles/basic_projectile.atlas", + "images/projectiles/mobKing_projectile.atlas", + "images/projectiles/engineer_projectile.atlas", + "images/projectiles/stun_effect.atlas", + "images/projectiles/burn_effect.atlas", + "images/projectiles/snow_ball.atlas", + "images/projectiles/firework_anim.atlas" + }; + + private final String[] animations = { + "rotate", + "projectile", + "projectileFinal", + "mob_boss", + "mob_bossFinal" + }; @BeforeEach public void setUp() { @@ -50,37 +68,232 @@ public void setUp() { ServiceLocator.registerRenderService(render); ResourceService resourceService = new ResourceService(); ServiceLocator.registerResourceService(resourceService); - // resourceService.loadTextures(texture); - // resourceService.loadTextureAtlases(atlas); - resourceService.loadAll(); - // ServiceLocator.getResourceService() - // .getAsset("images/projectiles/basic_projectile.atlas", TextureAtlas.class); - Vector2 destination = new Vector2(0.1f, 0.1f); - short targetLayer = PhysicsLayer.HUMANS; - Vector2 speed = new Vector2(2f, 2f); - projectile = ProjectileFactory.createBaseProjectile(targetLayer, destination, speed); + resourceService.loadTextureAtlases(atlas); + resourceService.loadAll(); + } + + @Test + public void createBaseProjectile() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(projectile); + } + + @Test + public void testBaseProjectileColliderComponent() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(projectile.getComponent(ColliderComponent.class), + "Projectile does not have a ColliderComponent"); + } + + @Test + public void testBaseProjectileTouchAttackComponent() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(projectile.getComponent(TouchAttackComponent.class), + "Projectile does not have a TouchAttackComponent"); } @Test - public void testBaseProjectileNotNull() { - assertNotNull(projectile, "Base projectile is null"); + public void testBaseProjectileDeleteOnMapEdgeComponent() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(projectile.getComponent(DeleteOnMapEdgeComponent.class), + "Projectile does not have a DeleteOnMapEdgeComponent"); } + @Test + public void testBaseProjectileSpeed() { + Vector2 testSpeed = new Vector2(1f, 1f); + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), testSpeed); + assertEquals(testSpeed, projectile.getComponent(PhysicsMovementComponent.class).getSpeed(), + "Projectile speed does not match testSpeed"); + } + @Test public void testBaseProjectileHitbox() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); assertNotNull(projectile.getComponent(HitboxComponent.class), "Projectile does not contain Hotbox component"); } @Test public void testBaseProjectilePhysics() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); assertNotNull(projectile.getComponent(PhysicsComponent.class), - "Projectile does not have Physics component"); + "Projectile does not have Physics component"); } - + @Test public void testBaseProjectilePhysicsMovement() { + Entity projectile = ProjectileFactory.createBaseProjectile(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); assertNotNull(projectile.getComponent(PhysicsMovementComponent.class), "Projectile does not have PhysicsMovement component"); } + + @Test + public void testFireBallProjectileCreation() { + Entity fireBall = ProjectileFactory.createFireBall(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(fireBall); + } + + @Test + public void testFireBallAnimationRenderComponent() { + Entity fireBall = ProjectileFactory.createFireBall(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(fireBall.getComponent(AnimationRenderComponent.class), + "Fire Ball does not have an AnimationRenderComponent"); + } + @Test + public void testFireBallAnimationController() { + Entity fireBall = ProjectileFactory.createFireBall(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(fireBall.getComponent(ProjectileAnimationController.class), + "Fire Ball does not have an Animation Controller"); + } + + @Test + public void createMobBallProjectile() { + Entity mobBallProjectile = ProjectileFactory.createMobBall(PhysicsLayer.HUMANS, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(mobBallProjectile, "Mob King Ball is Null"); + } + + @Test + public void testMobBallProjectileAnimationRenderComponent() { + Entity mobBallProjectile = ProjectileFactory.createMobBall(PhysicsLayer.HUMANS, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(mobBallProjectile.getComponent(AnimationRenderComponent.class), + "Mob Ball Projectile does not have an AnimationRenderComponent"); + } + + @Test + public void testMobBallProjectileAnimationController() { + Entity mobBallProjectile = ProjectileFactory.createMobBall(PhysicsLayer.HUMANS, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(mobBallProjectile.getComponent(MobProjectileAnimationController.class), + "Mob Ball Projectile does not have an AnimationController"); + } + + @Test + public void testMobKingBallCreation() { + Entity mobKingBall = ProjectileFactory.createMobKingBall(PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(mobKingBall, "Mob King Ball is null"); + } + + @Test + public void testMobKingBallAnimationRenderComponent() { + Entity mobKingBall = ProjectileFactory.createMobKingBall(PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(mobKingBall.getComponent(AnimationRenderComponent.class), + "Mob King Ball does not have AnimationRenderComponent"); + } + + @Test + public void testMobKingBallAnimationController() { + Entity mobKingBall = ProjectileFactory.createMobKingBall(PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(mobKingBall.getComponent(MobKingProjectAnimController.class), + "Mob King Ball does not have Animation Controller"); + } + + @Test + public void testEngineerBulletCreation() { + Entity engineerBullet = ProjectileFactory.createEngineerBullet(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(engineerBullet, "engineerBullet is null"); + } + + @Test + public void testEngineerBulletAnimationRenderComponent() { + Entity engineerBulllet = ProjectileFactory.createEngineerBullet(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(engineerBulllet.getComponent(AnimationRenderComponent.class), + "Engineer Bullet does not have AnimationRenderComponent"); + } + + @Test + public void testEngineerAnimationController() { + Entity engineerBullet = ProjectileFactory.createEngineerBullet(PhysicsLayer.NPC, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(engineerBullet.getComponent(EngineerBulletsAnimationController.class), + "Engineer Bullet does not have Animation Controller"); + } + + @Test + public void testStunProjectileCreation() { + Entity stunProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(0.1f, + 0.1f), new Vector2(2,2), ProjectileEffects.STUN, false); + assertNotNull(stunProjectile, "stunProjectile is null"); + } + + @Test + public void testStunProjectileAnimationRenderComponent() { + Entity stunProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(0.1f,01f), + new Vector2(2,2), ProjectileEffects.STUN, false); + assertNotNull(stunProjectile.getComponent(AnimationRenderComponent.class), + "Stun Projectile does not have AnimationRenderComponent"); + } + + @Test + public void testStunProjectileAnimationController() { + Entity stunProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f) + , new Vector2(2,2), ProjectileEffects.STUN, false); + assertNotNull(stunProjectile.getComponent(StunEffectProjectileAnimationController.class), + "Stun Projectile does not have Animation Controller"); + } + + @Test + public void testBurnProjectileCreation() { + Entity burnProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(0.1f, + 0.1f), new Vector2(2,2), ProjectileEffects.BURN, false); + assertNotNull(burnProjectile, "burnProjectile is null"); + } + + @Test + public void testBurnProjectileAnimationRenderComponent() { + Entity burnProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(0.1f,01f), + new Vector2(2,2), ProjectileEffects.BURN, false); + assertNotNull(burnProjectile.getComponent(AnimationRenderComponent.class), + "Burn Projectile does not have AnimationRenderComponent"); + } + @Test + public void testBurnProjectileAnimationController() { + Entity burnProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f) + , new Vector2(2,2), ProjectileEffects.BURN, false); + assertNotNull(burnProjectile.getComponent(BurnEffectProjectileAnimationController.class), + "Burn Projectile does not have Animation Controller"); + } + + @Test + public void testSlowProjectileCreation() { + Entity slowProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(0.1f, + 0.1f), new Vector2(2,2), ProjectileEffects.SLOW, false); + assertNotNull(slowProjectile, "slowProjectile is null"); + } + + @Test + public void testSlowProjectileAnimationRenderComponent() { + Entity slowProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, new Vector2(0.1f,01f), + new Vector2(2,2), ProjectileEffects.SLOW, false); + assertNotNull(slowProjectile.getComponent(AnimationRenderComponent.class), + "Slow Projectile does not have AnimationRenderComponent"); + } + @Test + public void testSlowProjectileAnimationController() { + Entity slowProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f) + , new Vector2(2,2), ProjectileEffects.SLOW, false); + assertNotNull(slowProjectile.getComponent(SnowBallProjectileAnimationController.class), + "Slow Projectile does not have Animation Controller"); + } + + @Test + public void testFireworkProjectileCreation() { + Entity fireworkProjectile = ProjectileFactory.createFireworks( + PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(fireworkProjectile, "fireworkProjectile is null"); + } + + @Test + public void testFireworkProjectileAnimationRenderComponent() { + Entity fireworkProjectile = ProjectileFactory.createFireworks( + PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(fireworkProjectile.getComponent(AnimationRenderComponent.class), + "Fire Projectile does not have AnimationRenderComponent"); + } + @Test + public void testFireworkProjectileAnimationController() { + Entity fireworkProjectile = ProjectileFactory.createFireworks( + PhysicsLayer.TOWER, new Vector2(0.1f, 0.1f), new Vector2(1f, 1f)); + assertNotNull(fireworkProjectile.getComponent(FireworkAnimationController.class), + "Fire Projectile does not have Animation Controller"); + } } + diff --git a/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java index 0924d2667..1ab5d51cc 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java @@ -39,12 +39,26 @@ public class TowerFactoryTest { private Entity baseTower; private Entity weaponTower; private Entity wallTower; + private Entity stunTower; + private Entity fireTower; + private Entity tntTower; + private Entity droidTower; private String[] texture = { "images/towers/turret_deployed.png", "images/towers/turret01.png", - "images/towers/wallTower.png" + "images/towers/wallTower.png", + "images/towers/fire_tower_atlas.png", + "images/towers/stun_tower.png", + "images/towers/DroidTower.png", + "images/towers/TNTTower.png" + }; + private String[] atlas = { + "images/towers/turret01.atlas", + "images/towers/stun_tower.atlas", + "images/towers/fire_tower_atlas.atlas", + "images/towers/DroidTower.atlas", + "images/towers/TNTTower.atlas" }; - private String[] atlas = {"images/towers/turret01.atlas"}; private static final String[] sounds = { "sounds/towers/gun_shot_trimmed.mp3", "sounds/towers/deploy.mp3", @@ -71,6 +85,10 @@ public void setUp() { baseTower = TowerFactory.createBaseTower(); weaponTower = TowerFactory.createWeaponTower(); wallTower = TowerFactory.createWallTower(); + fireTower = TowerFactory.createFireTower(); + stunTower = TowerFactory.createFireTower(); + tntTower = TowerFactory.createTNTTower(); + droidTower = TowerFactory.createDroidTower(); } @Test @@ -79,6 +97,10 @@ public void testCreateBaseTowerNotNull() { assertNotNull(baseTower, "Base tower should not be null"); assertNotNull(weaponTower, "Weaponry tower should not be null"); assertNotNull(wallTower, "Wall tower should not be null"); + assertNotNull(stunTower, "Stun tower must not be null"); + assertNotNull(fireTower, "Fire tower must not be null"); + assertNotNull(tntTower, "TNT tower must not be null"); + assertNotNull(droidTower, "Droid tower must not be null"); } @Test @@ -89,6 +111,14 @@ public void testCreateBaseTowerHasColliderComponent() { "Weaponry tower should have ColliderComponent"); assertNotNull(wallTower.getComponent(ColliderComponent.class), "Wall tower should have ColliderComponent"); + assertNotNull(stunTower.getComponent(ColliderComponent.class), + "Stun Tower should have ColliderComponent"); + assertNotNull(fireTower.getComponent(ColliderComponent.class), + "Fire tower should have ColliderComponent"); + assertNotNull(tntTower.getComponent(ColliderComponent.class), + "TNT tower should have ColliderComponent"); + assertNotNull(droidTower.getComponent(ColliderComponent.class), + "Droid tower should have ColliderComponent"); } @Test @@ -99,6 +129,14 @@ public void testCreateBaseTowerHasHitboxComponent() { "Weaponry tower should have HitboxComponent"); assertNotNull(wallTower.getComponent(HitboxComponent.class), "Wall tower should have HitboxComponent"); + assertNotNull(stunTower.getComponent(HitboxComponent.class), + "Stun tower should have HitboxComponent"); + assertNotNull(fireTower.getComponent(HitboxComponent.class), + "Fire tower should have HitboxComponent"); + assertNotNull(tntTower.getComponent(HitboxComponent.class), + "TNT tower should have HitboxComponent"); + assertNotNull(droidTower.getComponent(HitboxComponent.class), + "Droid tower should have HitboxComponent"); } @Test @@ -109,6 +147,14 @@ public void testCreateBaseTowerHasPhysicsComponent() { "Weaponry tower should have PhysicsComponent"); assertNotNull(wallTower.getComponent(PhysicsComponent.class), "Wall tower should have PhysicsComponent"); + assertNotNull(stunTower.getComponent(PhysicsComponent.class), + "Stun tower should have PhysicsComponent"); + assertNotNull(fireTower.getComponent(PhysicsComponent.class), + "Fire tower should have PhysicsComponent"); + assertNotNull(tntTower.getComponent(PhysicsComponent.class), + "TNT tower should have PhysicsComponent"); + assertNotNull(droidTower.getComponent(PhysicsComponent.class), + "Droid tower should have PhysicsComponent"); } @Test @@ -116,6 +162,10 @@ public void testCreateBaseTowerPhysicsComponentStaticBody() { PhysicsComponent physicsComponent = baseTower.getComponent(PhysicsComponent.class); PhysicsComponent physicsComponent1 = weaponTower.getComponent(PhysicsComponent.class); PhysicsComponent physicsComponent2 = wallTower.getComponent(PhysicsComponent.class); + PhysicsComponent physicsComponent3 = stunTower.getComponent(PhysicsComponent.class); + PhysicsComponent physicsComponent4 = fireTower.getComponent(PhysicsComponent.class); + PhysicsComponent physicsComponent5 = tntTower.getComponent(PhysicsComponent.class); + PhysicsComponent physicsComponent6 = droidTower.getComponent(PhysicsComponent.class); assertTrue(physicsComponent.getBody().getType() == BodyType.StaticBody, "PhysicsComponent should be of type StaticBody"); @@ -123,18 +173,49 @@ public void testCreateBaseTowerPhysicsComponentStaticBody() { "PhysicsComponent1 should be of type StaticBody"); assertTrue(physicsComponent2.getBody().getType() == BodyType.StaticBody, "PhysicsComponent2 should be of type StaticBody"); + assertTrue(physicsComponent3.getBody().getType() == BodyType.StaticBody, + "StunTower's PhysicsComponent should be of type StaticBody"); + assertTrue(physicsComponent4.getBody().getType() == BodyType.StaticBody, + "FireTower's PhysicsComponent should be of type StaticBody"); + assertTrue(physicsComponent5.getBody().getType() == BodyType.StaticBody, + "TNT tower's PhysicsComponent should be of type StaticBody"); + assertTrue(physicsComponent6.getBody().getType() == BodyType.StaticBody, + "Droid Tower's PhysicsComponent should be of type StaticBody"); } @Test public void testWeaponTowerCombatStatsComponentAndCostComponent() { - assertTrue(weaponTower.getComponent(CombatStatsComponent.class).getHealth() == 10, + assertEquals(10, weaponTower.getComponent(CombatStatsComponent.class).getHealth(), "Health should be 10"); - assertTrue(weaponTower.getComponent(CombatStatsComponent.class).getBaseAttack() == 10, + assertEquals(10, weaponTower.getComponent(CombatStatsComponent.class).getBaseAttack(), "BaseAttack should be 10"); - assertTrue(weaponTower.getComponent(CostComponent.class).getCost() == 10, + assertEquals(10, weaponTower.getComponent(CostComponent.class).getCost(), "Cost should be 10"); - + assertEquals(10, fireTower.getComponent(CombatStatsComponent.class).getHealth(), + "Fire Tower health must be 10"); + assertEquals(10, fireTower.getComponent(CombatStatsComponent.class).getBaseAttack(), + "Fire Tower base attack must be 10"); + assertEquals(10, fireTower.getComponent(CostComponent.class).getCost(), + "Fire Tower cost must 10"); + assertEquals(10, stunTower.getComponent(CombatStatsComponent.class).getHealth(), + "Stun Tower health must be 10"); + assertEquals(10, stunTower.getComponent(CombatStatsComponent.class).getBaseAttack(), + "Stun Tower base attack must be 10"); + assertEquals(10, stunTower.getComponent(CostComponent.class).getCost(), + "Stun Tower cost must 10"); + assertEquals(10, tntTower.getComponent(CombatStatsComponent.class).getHealth(), + "TNT Tower health must be 10"); + assertEquals(5, tntTower.getComponent(CombatStatsComponent.class).getBaseAttack(), + "TNT Tower base attack must be 5"); + assertEquals(1, tntTower.getComponent(CostComponent.class).getCost(), + "TNT Tower cost must 1"); + assertEquals(50, droidTower.getComponent(CombatStatsComponent.class).getHealth(), + "TNT Tower health must be 50"); + assertEquals(5, droidTower.getComponent(CombatStatsComponent.class).getBaseAttack(), + "Droid Tower base attack must be 5"); + assertEquals(1, droidTower.getComponent(CostComponent.class).getCost(), + "Droid Tower cost must 1"); } @Test @@ -152,6 +233,10 @@ public void testWallTowerCombatStatsComponentAndCostComponent() { @Test public void weaponTowerHasAnimationComponent() { assertNotNull(weaponTower.getComponent(AnimationRenderComponent.class)); + assertNotNull(stunTower.getComponent(AnimationRenderComponent.class)); + assertNotNull(fireTower.getComponent(AnimationRenderComponent.class)); + assertNotNull(tntTower.getComponent(AnimationRenderComponent.class)); + assertNotNull(droidTower.getComponent(AnimationRenderComponent.class)); } @Test diff --git a/source/core/src/test/com/csse3200/game/utils/math/GridPoint2UtilsTest.java b/source/core/src/test/com/csse3200/game/utils/math/GridPoint2UtilsTest.java new file mode 100644 index 000000000..354b7dfe9 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/utils/math/GridPoint2UtilsTest.java @@ -0,0 +1,30 @@ +package com.csse3200.game.utils.math; + +import com.badlogic.gdx.math.GridPoint2; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class GridPoint2UtilsTest { + + /** + * testZero() ensures that GridPoint2Utils.ZERO constant is (0, 0). + * GridPoint2Utils.ZERO is a constant representing the point (0, 0). + */ + @Test + public void testZero() { + GridPoint2 zero = GridPoint2Utils.ZERO; + assertEquals(0, zero.x); + assertEquals(0, zero.y); + } + + /** + * testInstantiation() checks if private constructor of GridPoint2Utils throws an exception + * while creating an instance. + */ + @Test + public void testInstantiation() { + assertThrows(IllegalStateException.class, () -> { + GridPoint2Utils.createInstance(); + }); + } +} \ No newline at end of file diff --git a/source/wiki/team-2/EngineerFactoryGapScannerFactorySequenceDiagram.png b/source/wiki/team-2/EngineerFactoryGapScannerFactorySequenceDiagram.png new file mode 100644 index 000000000..b8a09a56e Binary files /dev/null and b/source/wiki/team-2/EngineerFactoryGapScannerFactorySequenceDiagram.png differ diff --git a/source/wiki/team-2/EngineerFactoryUML.png b/source/wiki/team-2/EngineerFactoryUML.png new file mode 100644 index 000000000..3aa08fefb Binary files /dev/null and b/source/wiki/team-2/EngineerFactoryUML.png differ diff --git a/source/wiki/team-2/GameEndServiceSequenceDiagram.png b/source/wiki/team-2/GameEndServiceSequenceDiagram.png new file mode 100644 index 000000000..55ca66c2e Binary files /dev/null and b/source/wiki/team-2/GameEndServiceSequenceDiagram.png differ diff --git a/source/wiki/team-2/GameEndServiceUML.png b/source/wiki/team-2/GameEndServiceUML.png new file mode 100644 index 000000000..9fc33c803 Binary files /dev/null and b/source/wiki/team-2/GameEndServiceUML.png differ diff --git a/source/wiki/team-2/GapScannerFactory UML.png b/source/wiki/team-2/GapScannerFactory UML.png new file mode 100644 index 000000000..9230a3e5a Binary files /dev/null and b/source/wiki/team-2/GapScannerFactory UML.png differ diff --git a/source/wiki/team-2/HumanWanderTaskSequenceDiagram.png b/source/wiki/team-2/HumanWanderTaskSequenceDiagram.png new file mode 100644 index 000000000..c23337d2e Binary files /dev/null and b/source/wiki/team-2/HumanWanderTaskSequenceDiagram.png differ