diff --git a/source/core/assets/configs/Bombship.json b/source/core/assets/configs/Bombship.json new file mode 100644 index 000000000..f776b4224 --- /dev/null +++ b/source/core/assets/configs/Bombship.json @@ -0,0 +1,6 @@ +{ + "Bombship" : { + "health": 100, + "baseAttack": 20 +} +} \ No newline at end of file diff --git a/source/core/assets/images/bombship/bombship.atlas b/source/core/assets/images/bombship/bombship.atlas new file mode 100644 index 000000000..ec9415a7c --- /dev/null +++ b/source/core/assets/images/bombship/bombship.atlas @@ -0,0 +1,89 @@ +bombship.png +size: 32, 286 +format: RGBA8888 +filter: Linear,Linear +repeat: none +destroy + rotate: false + xy: 0, 0 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 32 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 64 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 96 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +destroy + rotate: false + xy: 0, 128 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 0, 160 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +idle + rotate: false + xy: 0, 160 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +idle + rotate: false + xy: 0, 181 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +idle + rotate: false + xy: 0, 202 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +start + rotate: false + xy: 0, 223 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +start + rotate: false + xy: 0, 244 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 +start + rotate: false + xy: 0, 265 + size: 26, 21 + orig: 26, 21 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/bombship/bombship.png b/source/core/assets/images/bombship/bombship.png new file mode 100644 index 000000000..ba743448f Binary files /dev/null and b/source/core/assets/images/bombship/bombship.png differ diff --git a/source/core/assets/images/towers/PierceTower.atlas b/source/core/assets/images/towers/PierceTower.atlas new file mode 100644 index 000000000..85d6d35d1 --- /dev/null +++ b/source/core/assets/images/towers/PierceTower.atlas @@ -0,0 +1,299 @@ +PierceTower.png +size: 896, 192 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Attack + rotate: false + xy: 0, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 64, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 128, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 192, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 256, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 320, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 384, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 448, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 512, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 576, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 640, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 704, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 768, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 832, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 0, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 64, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 128, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 192, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 256, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 320, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 384, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 448, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 512, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 576, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 640, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 704, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 768, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 832, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 0, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 64, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 128, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 192, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 256, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 320, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 384, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 256, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 448, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 512, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 576, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 640, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 704, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Warning + rotate: false + xy: 768, 128 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/PierceTower.png b/source/core/assets/images/towers/PierceTower.png new file mode 100644 index 000000000..3d0d0e5b4 Binary files /dev/null and b/source/core/assets/images/towers/PierceTower.png differ diff --git a/source/core/assets/images/towers/RicochetTower.atlas b/source/core/assets/images/towers/RicochetTower.atlas new file mode 100644 index 000000000..1bafec97b --- /dev/null +++ b/source/core/assets/images/towers/RicochetTower.atlas @@ -0,0 +1,187 @@ +RicochetTower.png +size: 832, 128 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Attack + rotate: false + xy: 0, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 64, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 128, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 192, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 256, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 320, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 384, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 448, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 512, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 576, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 640, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 704, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 768, 0 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 0, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 64, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 128, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 192, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 256, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 320, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 384, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 448, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 512, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 576, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 640, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 704, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 448, 64 + size: 64, 64 + orig: 64, 64 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/RicochetTower.png b/source/core/assets/images/towers/RicochetTower.png new file mode 100644 index 000000000..aa7b66318 Binary files /dev/null and b/source/core/assets/images/towers/RicochetTower.png differ diff --git a/source/core/assets/images/towers/barrier.atlas b/source/core/assets/images/towers/barrier.atlas new file mode 100644 index 000000000..c3327a790 --- /dev/null +++ b/source/core/assets/images/towers/barrier.atlas @@ -0,0 +1,76 @@ +barrier.png +size: 64, 35 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Death + rotate: false + xy: 0, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: 2 +Death + rotate: false + xy: 32, 0 + size: 32, 17 + orig: 32, 17 + offset: 0, 0 + index: 3 +Death + rotate: false + xy: 0, 0 + size: 32, 12 + orig: 32, 12 + offset: 0, 0 + index: 4 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 32, 17 + size: 32, 18 + orig: 32, 18 + offset: 0, 0 + index: -1 + diff --git a/source/core/assets/images/towers/barrier.png b/source/core/assets/images/towers/barrier.png new file mode 100644 index 000000000..715fa4acc Binary files /dev/null and b/source/core/assets/images/towers/barrier.png differ diff --git a/source/core/assets/images/towers/fireworks_tower.atlas b/source/core/assets/images/towers/fireworks_tower.atlas new file mode 100644 index 000000000..1c42d0b25 --- /dev/null +++ b/source/core/assets/images/towers/fireworks_tower.atlas @@ -0,0 +1,355 @@ +fireworks_tower.png +size: 1700, 420 +format: RGBA8888 +filter: Linear,Linear +repeat: none +Attack + rotate: false + xy: 0, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 100, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 200, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 300, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 400, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 500, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 600, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 700, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 800, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 900, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1000, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1100, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1200, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1300, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1400, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1500, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 1600, 0 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 0, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 100, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 200, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 300, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 400, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 500, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 600, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Attack + rotate: false + xy: 700, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 800, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 900, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1000, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1100, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1200, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1300, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1400, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1500, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 1600, 140 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 0, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 100, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Death + rotate: false + xy: 200, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 300, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 400, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 500, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 600, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 700, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 800, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 900, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1000, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1100, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1200, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1300, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Idle + rotate: false + xy: 1400, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 +Default + rotate: false + xy: 1400, 280 + size: 100, 140 + orig: 100, 140 + offset: 0, 0 + index: -1 diff --git a/source/core/assets/images/towers/fireworks_tower.png b/source/core/assets/images/towers/fireworks_tower.png new file mode 100644 index 000000000..fe5029278 Binary files /dev/null and b/source/core/assets/images/towers/fireworks_tower.png differ diff --git a/source/core/assets/images/towers/wall_tower.png b/source/core/assets/images/towers/wall_tower.png index e31daa176..1b5a43650 100644 Binary files a/source/core/assets/images/towers/wall_tower.png and b/source/core/assets/images/towers/wall_tower.png differ diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index 6d420090d..6d12bf198 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -112,6 +112,11 @@ public class ForestGameArea extends GameArea { "images/mobboss/demon2.png", "images/mobs/fire_worm.png", "images/mobboss/patrick.png", + "images/towers/fireworks_tower.png", + "images/towers/barrier.png", + "images/towers/wall_tower.png", + "images/towers/PierceTower.png", + "images/towers/RicochetTower.png", "images/GrassTile/grass_tile_1.png", "images/GrassTile/grass_tile_2.png", "images/GrassTile/grass_tile_3.png", @@ -120,7 +125,8 @@ public class ForestGameArea extends GameArea { "images/GrassTile/grass_tile_6.png", "images/GrassTile/grass_tile_7.png", "images/highlight_tile.png", - "images/mobboss/iceBaby.png" + "images/mobboss/iceBaby.png", + "images/bombship/bombship.png" }; private static final String[] forestTextureAtlases = { @@ -162,7 +168,12 @@ public class ForestGameArea extends GameArea { "images/mobs/water_queen.atlas", "images/mobs/water_slime.atlas", "images/mobboss/patrick.atlas", - "images/mobboss/iceBaby.atlas" + "images/towers/fireworks_tower.atlas", + "images/towers/barrier.atlas", + "images/towers/PierceTower.atlas", + "images/towers/RicochetTower.atlas", + "images/mobboss/iceBaby.atlas", + "images/bombship/bombship.atlas" }; private static final String[] forestSounds = { "sounds/Impact4.ogg", @@ -305,11 +316,14 @@ public void create() { playMusic(); spawnScrap(); - spawnTNTTower(); - //spawnWeaponTower(); - spawnGapScanners(); - spawnDroidTower(); - +// spawnTNTTower(); +// spawnWeaponTower(); +// spawnGapScanners(); +// spawnDroidTower(); + spawnFireWorksTower(); + spawnPierceTower(); + spawnRicochetTower(); +// spawnBombship(); } private void displayUI() { @@ -799,6 +813,7 @@ private void spawnWeaponTower() { Entity stunTower = TowerFactory.createStunTower(); spawnEntityAt(fireTower, randomPos1, true, true); spawnEntityAt(stunTower, randomPos2, true, true); + spawnEntityAt(wallTower, randomPos2, true, true); } } @@ -827,6 +842,46 @@ private void spawnTNTTower() { } } + private void spawnFireWorksTower() { + GridPoint2 minPos = new GridPoint2(0, 2); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(1, 1); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity FireWorksTower = TowerFactory.createFireworksTower(); + spawnEntityAt(FireWorksTower, randomPos, true, true); + } + + } + private void spawnPierceTower() { + GridPoint2 minPos = new GridPoint2(0, 2); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(3, 3); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity PierceTower = TowerFactory.createPierceTower(); + spawnEntityAt(PierceTower, randomPos, true, true); + } + + } + private void spawnRicochetTower() { + GridPoint2 minPos = new GridPoint2(0, 2); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(0, 3); + + for (int i = 0; i < NUM_WEAPON_TOWERS; i++) { + GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); + Entity RicochetTower = TowerFactory.createRicochetTower(); + spawnEntityAt(RicochetTower, randomPos, true, true); + } + + } + + private void spawnBombship() { + GridPoint2 minPos = new GridPoint2(0, 0); + GridPoint2 maxPos = terrain.getMapBounds(0).sub(5, 1); + Entity bombship = BombshipFactory.createBombship(); + spawnEntityAt(bombship, minPos, true, true); + } private void spawnDroidTower() { GridPoint2 minPos = new GridPoint2(0, 0); diff --git a/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java b/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java new file mode 100644 index 000000000..a8bdf5b40 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/player/BombShipAnimationController.java @@ -0,0 +1,34 @@ +package com.csse3200.game.components.player; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to the SHip entity's state and plays the animation when one + * of the events is triggered. + */ +public class BombShipAnimationController extends Component { + AnimationRenderComponent animator; + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("start", this::animateStart); + entity.getEvents().addListener("destroy", this::animateDestroy); + entity.getEvents().addListener("idle", this::animateIdle); + } + + + void animateIdle() { + animator.startAnimation("ship_Idle"); + } + + void animateDestroy() { + animator.startAnimation("ship_Destroy"); + } + + void animateStart() { + animator.startAnimation("ship_Start"); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java new file mode 100644 index 000000000..ef7c74f89 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -0,0 +1,157 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.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; + + +/** + * The FireworksTowerCombatTask runs the AI for the FireworksTower class. The tower scans for mobs and targets in a + * straight line from its centre coordinate and executes the trigger phrases for animations depeending on the current + * state of tower. + */ +public class FireworksTowerCombatTask extends DefaultTask implements PriorityTask { + // constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + private static final short TARGET = PhysicsLayer.NPC; + //Following constants are names of events that will be triggered in the state machine + public static final String IDLE = "idleStart"; + public static final String ATTACK = "attackStart"; + public static final String DEATH = "deathStart"; + + + // Class attributes + private final int priority; + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + public enum STATE { + IDLE, ATTACK, DEATH + } + public STATE towerState = STATE.IDLE; + + /** + * @param priority Task priority when targets are detected (0 when nothing is present) + * @param maxRange Maximum effective range of the StunTower. + */ + public FireworksTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @Override + public void start() { + super.start(); + // Get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Set the default state to IDLE state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 5000); + } + + /** + * updates the current state of the tower based on the current state of the game. If enemies are detected, attack + * state is activated and otherwise idle state remains. + */ + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * This method acts is the state machine for StunTower. Relevant animations are triggered based on relevant state + * 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.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + switch (towerState) { + case IDLE -> { + if(isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } + } + case ATTACK -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), 3); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), + (float) (owner.getEntity().getPosition().y + 0.25)); + ServiceLocator.getEntityService().register(newProjectile); + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState=STATE.IDLE; + } + } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + } + } + + /** + * Returns the state that the tower is currently in + * @return this.towerState + */ + public STATE getState() { + return this.towerState; + } + + /** + * stops the current animation and switches back the state of the tower to IDLE. + */ + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + /** + * returns the current priority of the task + * @return (int) active priority if target is visible and inactive priority otherwise + */ + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + /** + * Searches for enemies/mobs in a straight line from the centre of the tower to maxRange in a straight line. + * @return true if targets are detected, false otherwise + */ + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java new file mode 100644 index 000000000..8d8723f65 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -0,0 +1,158 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.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; + +/** + * The PierceTowerCombatTask runs the AI for the PierceTower class. The tower scans for mobs and targets in a straight + * line from its centre coordinate and executes the trigger phrases for animations depeending on the current state of + * tower. + */ +public class PierceTowerCombatTask extends DefaultTask implements PriorityTask { + // constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + private static final short TARGET = PhysicsLayer.NPC; + public static final String IDLE = "startIdle"; + public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; + public static final String ALERT = "startAlert"; + + // Class attributes + private final int priority; + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + public enum STATE { + IDLE, ATTACK, DEATH + } + public STATE towerState = STATE.IDLE; + + /** + * @param priority Task priority when targets are detected (0 when nothing is present) + * @param maxRange Maximum effective range of the StunTower. + */ + public PierceTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @Override + public void start() { + super.start(); + // Get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Set the default state to IDLE state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 5000); + } + + /** + * updates the current state of the tower based on the current state of the game. If enemies are detected, attack + * state is activated and otherwise idle state remains. + */ + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * This method acts is the state machine for StunTower. Relevant animations are triggered based on relevant state + * 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.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + switch (towerState) { + case IDLE -> { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ALERT); + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } + } + case ATTACK -> { + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(ALERT); + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createPierceFireBall(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + 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); + } + } + } + } + + /** + * Returns the state that the tower is currently in + * @return this.towerState + */ + public STATE getState() { + return this.towerState; + } + + /** + * stops the current animation and switches back the state of the tower to IDLE. + */ + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + /** + * returns the current priority of the task + * @return (int) active priority if target is visible and inactive priority otherwise + */ + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + /** + * Searches for enemies/mobs in a straight line from the centre of the tower to maxRange in a straight line. + * @return true if targets are detected, false otherwise + */ + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java new file mode 100644 index 000000000..d057c1ab0 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java @@ -0,0 +1,155 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.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; + + +/** + * The RicochetTowerCombatTask runs the AI for the RicochetTower class. The tower scans for mobs and targets in a + * straight line from its centre coordinate and executes the trigger phrases for animations depending on the current + * state of tower. + */ +public class RicochetTowerCombatTask extends DefaultTask implements PriorityTask { + //constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + private static final short TARGET = PhysicsLayer.NPC; + // Following constants are names of events that will be triggered in the state machine + public static final String IDLE = "startIdle"; + public static final String ATTACK = "startAttack"; + public static final String DEATH = "startDeath"; + + // Class constants + private final int priority; + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + //enums for the state triggers + public enum STATE { + IDLE, ATTACK, DEATH + } + public STATE towerState = STATE.IDLE; + + /** + * @param priority Task priority when targets are detected (0 when nothing is present) + * @param maxRange Maximum effective range of the StunTower. + */ + public RicochetTowerCombatTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @Override + public void start() { + super.start(); + // Get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Set the default state to IDLE state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 5000); + } + + /** + * updates the current state of the tower based on the current state of the game. If enemies are detected, attack + * state is activated and otherwise idle state remains. + */ + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * This method acts is the state machine for StunTower. Relevant animations are triggered based on relevant state + * 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.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + switch (towerState) { + case IDLE -> { + if(isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } + } + case ATTACK -> { + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createRicochetFireball(PhysicsLayer.NPC, + // NEED TO DO USER TESTING TO FIGURE OUT THE BOUNCE COUNT + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), 3); + 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); + } + } + } + } + + public STATE getState() { + return this.towerState; + } + + /** + * stops the current animation and switches back the state of the tower to IDLE. + */ + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + /** + * returns the current priority of the task + * @return (int) active priority if target is visible and inactive priority otherwise + */ + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + /** + * Searches for enemies/mobs in a straight line from the centre of the tower to maxRange in a straight line. + * @return true if targets are detected, false otherwise + */ + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java b/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java new file mode 100644 index 000000000..988ff0071 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/WallTowerDestructionTask.java @@ -0,0 +1,141 @@ +package com.csse3200.game.components.tasks; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.components.CombatStatsComponent; +import com.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; + + +/** + * The FireworksTowerCombatTask runs the AI for the FireworksTower class. The tower scans for mobs and targets in a + * straight line from its centre coordinate and executes the trigger phrases for animations depeending on the current + * state of tower. + */ +public class WallTowerDestructionTask extends DefaultTask implements PriorityTask { + // constants + // Time interval (in seconds) to scan for enemies + private static final int INTERVAL = 1; + // The type of targets this tower will detect + private static final short TARGET = PhysicsLayer.NPC; + //Following constants are names of events that will be triggered in the state machine + public static final String IDLE = "startIdle"; + public static final String DEATH = "startDeath"; + + + // Class attributes + private final int priority; + private final float maxRange; + private Vector2 towerPosition = new Vector2(10, 10); + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private final RaycastHit hit = new RaycastHit(); + + public enum STATE { + IDLE, ATTACK, DEATH + } + public STATE towerState = STATE.IDLE; + + /** + * @param priority Task priority when targets are detected (0 when nothing is present) + * @param maxRange Maximum effective range of the StunTower. + */ + public WallTowerDestructionTask(int priority, float maxRange) { + this.priority = priority; + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Starts the task running and starts the Idle animation + */ + @Override + public void start() { + super.start(); + // Get the tower coordinates + this.towerPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + // Set the default state to IDLE state + owner.getEntity().getEvents().trigger(IDLE); + + endTime = timeSource.getTime() + (INTERVAL * 5000); + } + + /** + * updates the current state of the tower based on the current state of the game. If enemies are detected, attack + * state is activated and otherwise idle state remains. + */ + public void update() { + if (timeSource.getTime() >= endTime) { + updateTowerState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * This method acts is the state machine for StunTower. Relevant animations are triggered based on relevant state + * 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.DEATH) { + owner.getEntity().getEvents().trigger(DEATH); + towerState = STATE.DEATH; + return; + } + + switch (towerState) { + case IDLE -> { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.ATTACK; + } + case DEATH -> { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } + } + } + + /** + * Returns the state that the tower is currently in + * @return this.towerState + */ + public STATE getState() { + return this.towerState; + } + + /** + * stops the current animation and switches back the state of the tower to IDLE. + */ + public void stop() { + super.stop(); + owner.getEntity().getEvents().trigger(IDLE); + } + + /** + * returns the current priority of the task + * @return (int) active priority if target is visible and inactive priority otherwise + */ + public int getPriority() { + return !isTargetVisible() ? 0 : priority; + } + + /** + * Searches for enemies/mobs in a straight line from the centre of the tower to maxRange in a straight line. + * @return true if targets are detected, false otherwise + */ + public boolean isTargetVisible() { + return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java new file mode 100644 index 000000000..0c044d89f --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipCombatTask.java @@ -0,0 +1,172 @@ +package com.csse3200.game.components.tasks.bombship; + + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.factories.ProjectileFactory; +import com.csse3200.game.physics.PhysicsEngine; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.raycast.RaycastHit; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + + +import java.util.ArrayList; + +/** + * The AI Task for the Engineer entity. The Engineer will scan for targets within its detection range + * and trigger events to change its state accordingly. This task must be called once the Engineer has + * appropiately moved into position. + */ +public class BombshipCombatTask extends DefaultTask implements PriorityTask { + + private static final int INTERVAL = 1; // The time interval for each target scan from the Engineer. + private static final int PRIORITY = 3; // Default priority of the combat task when mobs are in range. + private static final short TARGET1 = PhysicsLayer.BOSS; // The type of targets that the Engineer will detect. + private static final short TARGET2 = PhysicsLayer.XENO; + + // Animation event names for the Engineer's state machine. + private static final String START = "start"; + private static final String IDLE = "idle"; + private static final String DESTROY = "destroy"; + + // The Engineer's attributes. + private final float maxRange; // The maximum range of the bombship. + + private Vector2 bombShipPosition = new Vector2(0, 0); // Placeholder value for the Bombship's position. + private final Vector2 maxRangePosition = new Vector2(); + private PhysicsEngine physics; + private GameTime timeSource; + private long endTime; + private long reloadTime; +/** + private ArrayList hits = new ArrayList<>(); + private final RaycastHit hit = new RaycastHit(); + private ArrayList targets = new ArrayList<>(); +*/ + /** The Engineer's states. */ + private enum STATE { + IDLE, START , DESTROY + } + private STATE bombshipState = STATE.IDLE; + + public BombshipCombatTask(float maxRange) { + this.maxRange = maxRange; + physics = ServiceLocator.getPhysicsService().getPhysics(); + timeSource = ServiceLocator.getTimeSource(); + } + + /** + * Runs the task and triggers Bombship's idle animation. + */ + @Override + public void start() { + super.start(); + this.bombShipPosition = owner.getEntity().getCenterPosition(); + this.maxRangePosition.set(bombShipPosition.x + maxRange, bombShipPosition.y); + // Default to idle mode + owner.getEntity().getEvents().trigger(IDLE); + endTime = timeSource.getTime() + (INTERVAL * 500); + } + + /** + * The update method is what is run every time the TaskRunner in the AiTaskComponent calls update(). + * triggers events depending on the presence or otherwise of targets in the detection range + */ + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + updateBombshipState(); + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } + + /** + * Bombship state machine + */ + public void updateBombshipState() { + // configure engineer state depending on target visibility + switch (bombshipState) { + case IDLE -> { + // targets detected in idle mode - start deployment + if (isEngineerDied()) { + combatState(); + } + } + case START -> { + // targets gone - stop firing + if (!isEngineerDied()) { + owner.getEntity().getEvents().trigger(IDLE); + bombshipState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(START); + } + } + case DESTROY -> { + owner.getEntity().getEvents().trigger(DESTROY); + } + } + } + + /** + * Puts the BombshipCombatTask state into combat mode + */ + private void combatState() { + owner.getEntity().getEvents().trigger(START); + bombshipState = STATE.START; + } + /** + * For stopping the running task + */ + @Override + public void stop() { + super.stop(); + } + + /** + * Simplified getPriority function, returns the priority of the task + * @return priority as an integer value. If mobs are visible, return the current priority, otherwise return 0. + */ + @Override + public int getPriority() { + return isEngineerDied() ? PRIORITY : 0; + } + + /** + * Uses a raycast to determine whether there are any targets in detection range. Performs multiple raycasts + * to a range of points at x = engineer.x + maxRange, and a range of y values above and below current y position. + * Allows the bombship entity to detect mobs in adjacent lanes. + * @return true if a target is detected, false otherwise + */ + public boolean isEngineerDied() { + //if (engineerCount < maxEngineers) { + return true; + //} + } + + /** + * Fetches the nearest target from the array of detected target positions created during the last call of + * this could be done in the next sprint , the scan doesnt work as of now ! + * @return a Vector2 position of the nearest mob detected. + */ + /** public Vector2 fetchTarget() { + // Initial nearest position for comparison + int lowest = 10; + + Vector2 nearest = new Vector2(owner.getEntity().getCenterPosition().x, + owner.getEntity().getCenterPosition().y); + + // Find the nearest target from the array of targets + for (Vector2 tgt : targets){ + if (Math.abs(tgt.y - nearest.y) < lowest) { + lowest = (int)Math.abs(tgt.y - nearest.y); + nearest = tgt; + } + } + return nearest; + } + */ +} + diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java new file mode 100644 index 000000000..4ba3d89af --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipMovementTask.java @@ -0,0 +1,84 @@ +package com.csse3200.game.components.tasks.bombship; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * Move the ship entity to a given position, finishing when you get close enough. Requires an entity with a + * PhysicsMovementComponent. + */ +public class BombshipMovementTask extends DefaultTask { + + private final GameTime gameTime; + private Vector2 target; + private float stopDistance = 0.01f; + private long lastTimeMoved; + private Vector2 lastPos; + private PhysicsMovementComponent movementComponent; + + public BombshipMovementTask(Vector2 target) { + this.target = target; + this.gameTime = ServiceLocator.getTimeSource(); + } + + public BombshipMovementTask(Vector2 target, float stopDistance) { + this(target); + this.stopDistance = stopDistance; + } + + @Override + public void start() { + super.start(); + this.movementComponent = owner.getEntity().getComponent(PhysicsMovementComponent.class); + movementComponent.setTarget(target); + movementComponent.setMoving(true); + //making the ship move + owner.getEntity().getEvents().trigger("start"); + + lastTimeMoved = gameTime.getTime(); + lastPos = owner.getEntity().getPosition(); + } + + @Override + public void update() { + if (isAtTarget()) { + movementComponent.setMoving(false); + owner.getEntity().getEvents().trigger("idle"); + status = Status.FINISHED; + } else { + checkIfStuck(); + } + } + + public void setTarget(Vector2 target) { + this.target = target; + movementComponent.setTarget(target); + } + + @Override + public void stop() { + super.stop(); + movementComponent.setMoving(false); + } + + private boolean isAtTarget() { + return owner.getEntity().getPosition().dst(target) <= stopDistance; + } + + private void checkIfStuck() { + if (didMove()) { + lastTimeMoved = gameTime.getTime(); + lastPos = owner.getEntity().getPosition(); + } else if (gameTime.getTimeSince(lastTimeMoved) > 500L) { + movementComponent.setMoving(false); + status = Status.FAILED; + } + } + + private boolean didMove() { + return owner.getEntity().getPosition().dst2(lastPos) > 0.001f; + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java new file mode 100644 index 000000000..895225131 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWaitTask.java @@ -0,0 +1,39 @@ +package com.csse3200.game.components.tasks.bombship; + +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * Task that does nothing other than waiting for a given time. Status is Finished + * after the time has passed. + */ +public class BombshipWaitTask extends DefaultTask { + private final GameTime totalTime; + private final float duration; + private long endTime; + + /** + * @param duration How long to wait for, in seconds. + */ + public BombshipWaitTask(float duration) { + totalTime = ServiceLocator.getTimeSource(); + this.duration = duration; + } + + /** + * Start waiting from now until duration has passed. + */ + @Override + public void start() { + super.start(); + endTime = totalTime.getTime() + (int)(duration * 1000); + } + + @Override + public void update() { + if (totalTime.getTime() >= endTime) { + status = Status.FINISHED; + } + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java new file mode 100644 index 000000000..63c611885 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/bombship/BombshipWanderTask.java @@ -0,0 +1,167 @@ +package com.csse3200.game.components.tasks.bombship; + +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.components.CombatStatsComponent; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.components.ColliderComponent; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +/** + * BombshipWanderTask is the entry point for the engineer entity's behaviour. Instantiates subtasks HumanWaitTask, + * BombshipMovementTask and BombshipCombatTask, and manages transitions between the tasks. Bombship damage and destruction is + * handled in this class. + */ +public class BombshipWanderTask extends DefaultTask implements PriorityTask { + 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 START = "start"; + private static final String DESTROY = "destroy"; + private static final String IDLE = "idle"; + private AnimationRenderComponent animator; + private final float maxRange; + private final float waitTime; + private BombshipMovementTask movementTask; + private BombshipWaitTask waitTask; + private BombshipCombatTask combatTask; + private Task currentTask; + private boolean isDestroyed = false; + + /** + * Constructor of BombshipWanderTask + * + * @param waitTime How long in seconds to wait between wandering. + * @param maxRange Maximum of the entity to fight + */ + public BombshipWanderTask(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 DEFAULT_PRIORITY; // Low priority task + } + + /** + * Starts the BombshipWanderTask instance and instantiates subtasks (BombshipWaitTask, BombshipWanderTask, BombshipCombatTask). + */ + @Override + public void start() { + super.start(); + Vector2 startPos = owner.getEntity().getCenterPosition(); + waitTask = new BombshipWaitTask(waitTime); + waitTask.create(owner); + + movementTask = new BombshipMovementTask(startPos, STOP_DISTANCE); + movementTask.create(owner); + movementTask.start(); + + combatTask = new BombshipCombatTask(maxRange); + combatTask.create(owner); + 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() { + if (!isDestroyed) { + startDestroying(); + } + + // Check if bombship has destroyed since last update + if (!isDestroyed) { + startDestroying(); + } else if (isDestroyed && animator.isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + + // otherwise doing engineer things since engineer is alive + else if (!isDestroyed){ + doBombshipThings(); + + currentTask.update(); + } + } + + private void doBombshipThings() { + 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); + + } else if (combatTask.isEngineerDied()) { + owner.getEntity().getEvents().trigger(START); + } + } + } + /** + * 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 startDestroying() { + owner.getEntity().getEvents().trigger(DESTROY); + owner.getEntity().getComponent(ColliderComponent.class).setLayer(PhysicsLayer.NONE); + owner.getEntity().getComponent(HitboxComponent.class).setLayer(PhysicsLayer.NONE); + currentTask.stop(); + isDestroyed = true; + } + + /** + * Starts the wait task. + */ + private void startWaiting() { + 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) { + movementTask.setTarget(destination); + swapTask(movementTask); + } + + /** + * Starts the combat task. + */ + private void startCombat() { + 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(); + } + currentTask = newTask; + currentTask.start(); + } +} 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 499ce2937..421fbf6ce 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 @@ -29,7 +29,7 @@ public class HumanWanderTask extends DefaultTask implements PriorityTask { private HumanWaitTask waitTask; private EngineerCombatTask combatTask; private Task currentTask; - private boolean isDead = false; + private boolean isDead = false; private boolean isSelected = false; diff --git a/source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java new file mode 100644 index 000000000..cd701b0db --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/FireworksTowerAnimationController.java @@ -0,0 +1,50 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to DroidTower entity's state and plays the animation when one + * of the events is triggered. + */ +public class FireworksTowerAnimationController extends Component { + private AnimationRenderComponent animator; + + /** + * Creation call for a DroidAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations and sounds. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("idleStart", this::animateDefault); + entity.getEvents().addListener("attackStart", this::animateAttack); + entity.getEvents().addListener("deathStart", this::animateDeath); + + } + + + /** + * THIS IS TO SHOW THE TOWER DEATH IN THIS SITUATION THE TOWER GETS BLASTED + */ + void animateAttack() { + animator.startAnimation("Attack"); + } + + + void animateDeath() { + animator.startAnimation("Death"); + } + + + /** + * Triggers the "default" or "Idle animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { + animator.startAnimation("Idle"); + } + + +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/components/tower/PierceTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/PierceTowerAnimationController.java new file mode 100644 index 000000000..9daa39acb --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/PierceTowerAnimationController.java @@ -0,0 +1,66 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * Listens to triggers phrases and executes the required animations. + */ +public class PierceTowerAnimationController extends Component { + //Event name constants + private static final String IDLE = "startIdle"; + private static final String ATTACK = "startAttack"; + private static final String DEATH = "startDeath"; + private static final String ALERT = "startAlert"; + //animation name constants + private static final String IDLE_ANIM = "Idle"; + private static final String ATTACK_ANIM = "Attack"; + private static final String DEATH_ANIM = "Death"; + private static final String ALERT_ANIM = "Warning"; + + //further sounds can be added for the tower attacks/movement + + AnimationRenderComponent animator; + + /** + * Creation method for StunTowerAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations. + */ + @Override + public void create() { + super.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); + entity .getEvents().addListener(ALERT,this::animateAlert); + } + + /** + * Starts the idle animation + */ + void animateIdle() { + animator.startAnimation(IDLE_ANIM); + } + + /** + * starts the attack animation + */ + void animateAttack() { + animator.startAnimation(ATTACK_ANIM); + } + + /** + * starts the death animation + */ + void animateDeath() { + animator.startAnimation(DEATH_ANIM); + } + + /** + * starts the alert animation when enemy in range + */ + void animateAlert() { + animator.startAnimation(ALERT_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java new file mode 100644 index 000000000..1a197868b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/RicochetTowerAnimationController.java @@ -0,0 +1,50 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * This class listens to events relevant to DroidTower entity's state and plays the animation when one + * of the events is triggered. + */ +public class RicochetTowerAnimationController extends Component { + private AnimationRenderComponent animator; + + /** + * Creation call for a DroidAnimationController, fetches the animationRenderComponent that this controller will + * be attached to and registers all the event listeners required to trigger the animations and sounds. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener("startIdle", this::animateDefault); + entity.getEvents().addListener("startAttack", this::animateAttack); + entity.getEvents().addListener("startDeath", this::animateDeath); + + } + + + /** + * THIS IS TO SHOW THE TOWER DEATH IN THIS SITUATION THE TOWER GETS BLASTED + */ + void animateAttack() { + animator.startAnimation("Attack"); + } + + + void animateDeath() { + animator.startAnimation("Death"); + } + + + /** + * Triggers the "default" or "Idle animation for the entity. + * This method should be invoked when the entity returns to its default state. + */ + void animateDefault() { + animator.startAnimation("Idle"); + } + + +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java b/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java new file mode 100644 index 000000000..4f22bfaf5 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/WallTowerAnimationController.java @@ -0,0 +1,41 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * Listens for events relevant to a weapon tower state. + * Each event will have a trigger phrase and have a certain animation attached. + */ +public class WallTowerAnimationController extends Component{ + //Event name constants + private static final String DEATH = "startDeath"; + private static final String IDLE = "startIdle"; + + //animation name constants + private static final String DEATH_ANIM = "Death"; + private static final String Idle_ANIM = "Idle"; + //here we can add the sounds for the implemented animations + + AnimationRenderComponent animator; + + /** + * Create method for FireTowerAnimationController. + */ + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(DEATH, this::animateDeath); + entity.getEvents().addListener(IDLE, this::animateIdle); + } + /** + * Starts the idle animation. + */ + void animateDeath() { + animator.startAnimation(DEATH_ANIM); + } + void animateIdle(){ + animator.startAnimation(Idle_ANIM); + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java new file mode 100644 index 000000000..4869dbb9b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/BombshipConfigs.java @@ -0,0 +1,10 @@ +package com.csse3200.game.entities.configs; + +/** + * Defines the properties stored in Bombship config files to be loaded by the Bombship Factory. + */ +public class BombshipConfigs extends BaseEntityConfig { + public BaseEntityConfig bombship = new BaseEntityConfig(); + public int health = 100; + public int baseAttack = 20; +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java new file mode 100644 index 000000000..c1bde5dd3 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/FireworksTowerConfig.java @@ -0,0 +1,9 @@ +package com.csse3200.game.entities.configs; + +public class FireworksTowerConfig { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; + public int attackRate =0; + public int incomeRate =0; +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java new file mode 100644 index 000000000..0eb826bcc --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/HealTowerConfig.java @@ -0,0 +1,8 @@ +package com.csse3200.game.entities.configs; + +public class HealTowerConfig { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; + //comment to commit message +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/PierceTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/PierceTowerConfig.java new file mode 100644 index 000000000..8baf1cb52 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/PierceTowerConfig.java @@ -0,0 +1,7 @@ +package com.csse3200.game.entities.configs; + +public class PierceTowerConfig { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/RicochetTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/RicochetTowerConfig.java new file mode 100644 index 000000000..3a637c7e2 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/RicochetTowerConfig.java @@ -0,0 +1,7 @@ +package com.csse3200.game.entities.configs; + +public class RicochetTowerConfig { + public int health = 1; + public int baseAttack = 0; + public int cost = 1; +} 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 968f4ec93..c1b3f997a 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 @@ -11,4 +11,8 @@ public class baseTowerConfigs { public StunTowerConfig stunTower = new StunTowerConfig(); public TNTTowerConfigs TNTTower = new TNTTowerConfigs(); public DroidTowerConfig DroidTower = new DroidTowerConfig(); + public FireworksTowerConfig fireworksTower = new FireworksTowerConfig(); + public PierceTowerConfig pierceTower = new PierceTowerConfig(); + public RicochetTowerConfig ricochetTower = new RicochetTowerConfig(); + public HealTowerConfig HealTower = new HealTowerConfig(); } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java new file mode 100644 index 000000000..b96ad1e59 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/factories/BombshipFactory.java @@ -0,0 +1,94 @@ +package com.csse3200.game.entities.factories; + +import com.badlogic.gdx.graphics.g2d.Animation; +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.BombShipAnimationController; +import com.csse3200.game.components.tasks.bombship.BombshipWanderTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.configs.*; +import com.csse3200.game.files.FileLoader; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsUtils; +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; + +/** + * Factory to create non-playable human character (NPC) entities with predefined components. + * + * These may be modified to become controllable characters in future sprints. + * + *

Each NPC entity type should have a creation method that returns a corresponding entity. + * Predefined entity properties can be loaded from configs stored as json files which are defined in + * "NPCConfigs". + * + *

If needed, this factory can be separated into more specific factories for entities with + * similar characteristics. + */ +public class BombshipFactory { + + private static final int COMBAT_TASK_PRIORITY = 2; + private static final int BOMBSHIP_RANGE = 30; + private static final BombshipConfigs configs = + FileLoader.readClass(BombshipConfigs.class, "configs/Bombship.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 + * + * + * @return entity + */ + public static Entity createBombship() { + Entity bombship = createBaseshipNPC(); + BaseEntityConfig config = configs.bombship; + + AnimationRenderComponent animator = new AnimationRenderComponent( + new TextureAtlas("images/bombship/bombship.atlas")); + animator.addAnimation("start", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("destroy", 0.1f, Animation.PlayMode.NORMAL); + animator.addAnimation("idle", 0.2f, Animation.PlayMode.LOOP); + AITaskComponent aiComponent = new AITaskComponent(); + + bombship + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(animator) + .addComponent(new BombShipAnimationController()) + .addComponent(aiComponent); + + bombship.getComponent(AITaskComponent.class).addTask(new BombshipWanderTask(COMBAT_TASK_PRIORITY, BOMBSHIP_RANGE)); + bombship.getComponent(AnimationRenderComponent.class).scaleEntity(); + bombship.setScale(HUMAN_SCALE_X, HUMAN_SCALE_Y); + return bombship; + } + + /** + * Creates a generic human npc to be used as a base entity by more specific NPC creation methods. + * + * @return entity + */ + public static Entity createBaseshipNPC() { + + + Entity ship = + new Entity() + .addComponent(new PhysicsComponent()) + .addComponent(new PhysicsMovementComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.BOMBSHIP)) + .addComponent(new TouchAttackComponent(PhysicsLayer.NPC, 1.5f)); + + return ship; + } + + private BombshipFactory() { + 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/TowerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/TowerFactory.java index 00f113b2f..59fb98b9c 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,15 @@ 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.tasks.*; 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.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.CostComponent; -import com.csse3200.game.components.tasks.TowerCombatTask; -import com.csse3200.game.components.tasks.CurrencyTask; import com.csse3200.game.entities.Entity; import com.csse3200.game.physics.PhysicsLayer; import com.csse3200.game.physics.PhysicsUtils; @@ -24,7 +20,9 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.rendering.TextureRenderComponent; import com.csse3200.game.services.ServiceLocator; -import com.csse3200.game.input.UpgradeUIComponent; +import com.csse3200.game.input.UpgradeUIComponent;import java.util.HashSet; +import java.util.Set; + /** * Factory to create a tower entity. * @@ -32,6 +30,8 @@ * the properties stores in 'baseTowerConfigs'. */ public class TowerFactory { + // Define a set to keep track of occupied lanes + private static final Set occupiedLanes = new HashSet<>(); private static final int COMBAT_TASK_PRIORITY = 2; private static final int WEAPON_TOWER_MAX_RANGE = 40; @@ -43,7 +43,11 @@ public class TowerFactory { 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 FIREWORKS_TOWER_ATLAS = "images/towers/fireworks_tower.atlas"; + private static final String PIERCE_TOWER_ATLAS = "images/towers/PierceTower.atlas"; + private static final String RICOCHET_TOWER_ATLAS = "images/towers/RicochetTower.atlas"; private static final String TNT_ATLAS = "images/towers/TNTTower.atlas"; + private static final String WALL_ATLAS = "images/towers/barrier.atlas"; private static final String DROID_ATLAS = "images/towers/DroidTower.atlas"; private static final float DROID_SPEED = 0.25f; private static final String DEFAULT_ANIM = "default"; @@ -75,11 +79,27 @@ public class TowerFactory { 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 String WALL_TOWER_DEATH_ANIM = "Death"; + private static final String WALL_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 String FIREWORKS_TOWER_DEATH_ANIM ="Death"; + private static final float FIREWORKS_TOWER_ANIM_ATTACK_SPEED = 0.12f; + private static final float FIREWORKS_TOWER_ANIM_SPEED = 0.06f; + private static final String FIREWORKS_TOWER_IDLE_ANIM ="Idle"; + private static final String FIREWORKS_TOWER_ATTACK_ANIM ="Attack"; + private static final String PIERCE_TOWER_IDLE_ANIM ="Idle"; + private static final String PIERCE_TOWER_ATTACK_ANIM ="Attack"; + private static final String PIERCE_TOWER_DEATH_ANIM ="Death"; + private static final String RICOCHET_TOWER_IDLE_ANIM ="Idle"; + private static final String RICOCHET_TOWER_ATTACK_ANIM ="Attack"; + private static final String RICOCHET_TOWER_DEATH_ANIM ="Death"; + private static final float RICOCHET_TOWER_ANIM_ATTACK_SPEED = 0.12f; + private static final String PIERCE_TOWER_ALERT_ANIM ="Warning"; + private static final float PIERCE_TOWER_ANIM_ATTACK_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"; @@ -126,12 +146,27 @@ public static Entity createIncomeTower() { public static Entity createWallTower() { Entity wall = createBaseTower(); WallTowerConfig config = configs.wall; + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new WallTowerDestructionTask(COMBAT_TASK_PRIORITY,TNT_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(WALL_ATLAS, TextureAtlas.class)); + + animator.addAnimation(WALL_TOWER_DEATH_ANIM,0.5f, Animation.PlayMode.NORMAL); + animator.addAnimation(WALL_TOWER_IDLE_ANIM,0.12f, Animation.PlayMode.LOOP); wall + .addComponent(aiTaskComponent) .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) - .addComponent(new TextureRenderComponent(WALL_IMAGE)); + .addComponent(animator) + .addComponent(new WallTowerAnimationController()); + + wall.setScale(0.5f,0.5f); + PhysicsUtils.setScaledCollider(wall, 0.5f, 0.5f); return wall; } @@ -146,7 +181,7 @@ public static Entity createTNTTower() { TNTTowerConfigs config = configs.TNTTower; AITaskComponent aiTaskComponent = new AITaskComponent() - .addTask(new TNTTowerCombatTask(COMBAT_TASK_PRIORITY, TNT_TOWER_MAX_RANGE)); + .addTask(new TNTTowerCombatTask(COMBAT_TASK_PRIORITY,TNT_TOWER_MAX_RANGE)); AnimationRenderComponent animator = new AnimationRenderComponent( @@ -311,6 +346,119 @@ public static Entity createStunTower() { return stunTower; } + /** + * Creates the FireworksTower entity which shoots at mobs traversing in a straight line. + * @return FireworksTower entity with relevant components. + */ + public static Entity createFireworksTower() { + Entity fireworksTower = createBaseTower(); + FireworksTowerConfig config = configs.fireworksTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new FireworksTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(FIREWORKS_TOWER_ATLAS, TextureAtlas.class)); + animator.addAnimation(FIREWORKS_TOWER_ATTACK_ANIM, FIREWORKS_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(FIREWORKS_TOWER_IDLE_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(FIREWORKS_TOWER_DEATH_ANIM, FIREWORKS_TOWER_ANIM_SPEED, Animation.PlayMode.NORMAL); + + fireworksTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent) + .addComponent(animator) + .addComponent(new FireworksTowerAnimationController()); + + fireworksTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(fireworksTower, 0.2f, 0.2f); + return fireworksTower; + } + + /** + * Creates the PierceTower entity which shoots at mobs traversing in a straight line. + * @return PierceTower entity with relevant components. + */ + public static Entity createPierceTower() { + Entity pierceTower = createBaseTower(); + PierceTowerConfig config = configs.pierceTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new PierceTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = + new AnimationRenderComponent( + ServiceLocator.getResourceService() + .getAsset(PIERCE_TOWER_ATLAS, TextureAtlas.class)); + animator.addAnimation(PIERCE_TOWER_ATTACK_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(PIERCE_TOWER_IDLE_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.LOOP); + animator.addAnimation(PIERCE_TOWER_DEATH_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.NORMAL); + animator.addAnimation(PIERCE_TOWER_ALERT_ANIM, PIERCE_TOWER_ANIM_ATTACK_SPEED, Animation.PlayMode.NORMAL); + + + pierceTower + .addComponent(animator) + .addComponent(new PierceTowerAnimationController()) + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + + pierceTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(pierceTower, 0.5f, 0.5f); + return pierceTower; + } + + /** + * Creates the RicochetTower entity which shoots at mobs traversing in a straight line. + * @return RicochetTower entity with relevant components. + */ + public static Entity createRicochetTower() { + Entity ricochetTower = createBaseTower(); + RicochetTowerConfig config = configs.ricochetTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new RicochetTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + AnimationRenderComponent animator = new AnimationRenderComponent( + ServiceLocator.getResourceService().getAsset(RICOCHET_TOWER_ATLAS,TextureAtlas.class)); + animator.addAnimation(RICOCHET_TOWER_ATTACK_ANIM,RICOCHET_TOWER_ANIM_ATTACK_SPEED,Animation.PlayMode.LOOP); + animator.addAnimation(RICOCHET_TOWER_DEATH_ANIM,RICOCHET_TOWER_ANIM_ATTACK_SPEED,Animation.PlayMode.NORMAL); + animator.addAnimation(RICOCHET_TOWER_IDLE_ANIM,RICOCHET_TOWER_ANIM_ATTACK_SPEED,Animation.PlayMode.LOOP); + ricochetTower + .addComponent(animator) + .addComponent(new RicochetTowerAnimationController()) + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + // ADD ANIMATION COMPONENTS + + ricochetTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(ricochetTower, 0.5f, 0.5f); + return ricochetTower; + } + public static Entity createHealTower() { + Entity ricochetTower = createBaseTower(); + HealTowerConfig config = configs.HealTower; + + AITaskComponent aiTaskComponent = new AITaskComponent() + .addTask(new RicochetTowerCombatTask(COMBAT_TASK_PRIORITY, WEAPON_TOWER_MAX_RANGE)); + + // ADD AnimationRenderComponent + + ricochetTower + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent((new CostComponent(config.cost))) + .addComponent(aiTaskComponent); + // ADD ANIMATION COMPONENTS + + ricochetTower.setScale(1.5f, 1.5f); + PhysicsUtils.setScaledCollider(ricochetTower, 0.5f, 0.5f); + return ricochetTower; + } + /** * Creates a generic tower entity to be used as a base entity by more specific tower creation methods. * @return entity @@ -325,4 +473,27 @@ public static Entity createBaseTower() { tower.setLayer(1); // Set priority to 1, which is 1 below scrap (which is 0) return tower; } + public static Entity createAndPlaceTower(int lane) { + if (isLaneOccupied(lane)) { + System.out.println("Lane " + lane + " is already occupied by a tower"); + return null; + } + + Entity tower = createBaseTower(); + // Customize the tower creation here based on the chosen tower type + + // Add the lane to the set of occupied lanes + occupiedLanes.add(lane); + + return tower; + } + + /** + * Checks if a lane is already occupied by a tower. + * @param lane The lane to check. + * @return True if the lane is occupied, false otherwise. + */ + public static boolean isLaneOccupied(int lane) { + return occupiedLanes.contains(lane); + } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java b/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java index cffd36cc6..f9d4ca67e 100644 --- a/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java +++ b/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java @@ -22,10 +22,7 @@ import com.csse3200.game.areas.ForestGameArea; import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.tasks.TowerCombatTask; -import com.csse3200.game.components.tower.IncomeUpgradeComponent; -import com.csse3200.game.components.tower.TNTDamageComponent; -import com.csse3200.game.components.tower.TowerUpgraderComponent; -import com.csse3200.game.components.tower.UpgradableStatsComponent; +import com.csse3200.game.components.tower.*; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.EntityService; import com.csse3200.game.services.ServiceLocator; @@ -79,6 +76,15 @@ public boolean touchDown(int screenX, int screenY, int pointer, int button) { Vector2 cursorPosition = new Vector2(worldCoordinates.x, worldCoordinates.y); Entity clickedEntity = entityService.getEntityAtPosition(cursorPosition.x, cursorPosition.y); + //temp fix to prevent upgrading of new towers + if (clickedEntity!= null && (clickedEntity.getComponent(RicochetTowerAnimationController.class) != null || + clickedEntity.getComponent(PierceTowerAnimationController.class) != null || + clickedEntity.getComponent(FireworksTowerAnimationController.class) != null)) { + return false; + } + // + + if (clickedEntity != null && clickedEntity.getComponent(TowerUpgraderComponent.class) != null && clickedEntity.getComponent(TNTDamageComponent.class) == null) { // logger.info("clicked a turret that is upgradable!"); clearUpgradeTables(); 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 37d4b64c5..32487b9a8 100644 --- a/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java +++ b/source/core/src/main/com/csse3200/game/physics/PhysicsLayer.java @@ -4,6 +4,7 @@ public class PhysicsLayer { public static final short NONE = 0; public static final short DEFAULT = (1 << 0); public static final short ENGINEER = (1 << 1); + public static final short BOMBSHIP = (1 << 1); // Terrain obstacle, e.g. trees public static final short OBSTACLE = (1 << 2); // NPC (Non-Playable Character) colliders diff --git a/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java new file mode 100644 index 000000000..9310e8229 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java @@ -0,0 +1,126 @@ +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 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 FireworksTowerCombatTaskTest { + FireworksTowerCombatTask fireworksTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + fireworksTowerCombatTask = new FireworksTowerCombatTask(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 = createFireworksTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(FireworksTowerCombatTask.IDLE, idleListener); + fireworksTowerCombatTask.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 = createFireworksTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + fireworksTowerCombatTask.start(); + fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertTrue(fireworksTowerCombatTask.isTargetVisible()); + + fireworksTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(FireworksTowerCombatTask.STATE.ATTACK, fireworksTowerCombatTask.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 = createFireworksTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(FireworksTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); + + fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(fireworksTowerCombatTask.isTargetVisible()); + + fireworksTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(attack); + assertEquals(FireworksTowerCombatTask.STATE.IDLE, fireworksTowerCombatTask.getState()); + } + + Entity createFireworksTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(fireworksTowerCombatTask); + 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; + } +} \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java new file mode 100644 index 000000000..a1a6ff11c --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java @@ -0,0 +1,127 @@ +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 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 PierceTowerCombatTaskTest { + PierceTowerCombatTask pierceTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + pierceTowerCombatTask = new PierceTowerCombatTask(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 = createPierceTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(PierceTowerCombatTask.IDLE, idleListener); + pierceTowerCombatTask.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 = createPierceTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(PierceTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + pierceTowerCombatTask.start(); + pierceTowerCombatTask.towerState = PierceTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + + assertTrue(pierceTowerCombatTask.isTargetVisible()); + + pierceTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(PierceTowerCombatTask.STATE.ATTACK, pierceTowerCombatTask.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 = createPierceTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(PierceTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(PierceTowerCombatTask.ATTACK, attack); + + pierceTowerCombatTask.towerState = PierceTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(pierceTowerCombatTask.isTargetVisible()); + + pierceTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(attack); + assertEquals(PierceTowerCombatTask.STATE.IDLE, pierceTowerCombatTask.getState()); + } + + Entity createPierceTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(pierceTowerCombatTask); + 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/RicochetTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java new file mode 100644 index 000000000..7f78761c8 --- /dev/null +++ b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java @@ -0,0 +1,126 @@ +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 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 RicochetTowerCombatTaskTest { + RicochetTowerCombatTask ricochetTowerCombatTask; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + ServiceLocator.registerEntityService(new EntityService()); + ricochetTowerCombatTask = new RicochetTowerCombatTask(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 = createRicochetTower(); + EventListener0 idleListener = mock(EventListener0.class); + // Deploy Droid in the walking state + entity.getEvents().addListener(RicochetTowerCombatTask.IDLE, idleListener); + ricochetTowerCombatTask.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 = createRicochetTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(12, 10); + + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(RicochetTowerCombatTask.ATTACK, attack); + //Jump to IDLE state + ricochetTowerCombatTask.start(); + ricochetTowerCombatTask.towerState = RicochetTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertTrue(ricochetTowerCombatTask.isTargetVisible()); + + ricochetTowerCombatTask.updateTowerState(); + verify(attack).handle(); + assertEquals(RicochetTowerCombatTask.STATE.ATTACK, ricochetTowerCombatTask.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 = createRicochetTower(); + entity.setPosition(10, 10); + + Entity target = createNPC(); + target.setPosition(15, 10); + + EventListener0 idle = mock(EventListener0.class); + EventListener0 attack = mock(EventListener0.class); + entity.getEvents().addListener(RicochetTowerCombatTask.IDLE, idle); + entity.getEvents().addListener(RicochetTowerCombatTask.ATTACK, attack); + + ricochetTowerCombatTask.towerState = RicochetTowerCombatTask.STATE.IDLE; + + ServiceLocator.getPhysicsService().getPhysics().update(); + entity.update(); + assertFalse(ricochetTowerCombatTask.isTargetVisible()); + + ricochetTowerCombatTask.updateTowerState(); + + verify(idle).handle(); + verifyNoInteractions(attack); + assertEquals(RicochetTowerCombatTask.STATE.IDLE, ricochetTowerCombatTask.getState()); + } + + Entity createRicochetTower() { + AITaskComponent aiTaskComponent = new AITaskComponent().addTask(ricochetTowerCombatTask); + 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/entities/factories/TowerFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/TowerFactoryTest.java index 7d2f75c65..3b72c699a 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 @@ -49,7 +49,8 @@ public class TowerFactoryTest { "images/towers/stun_tower.atlas", "images/towers/fire_tower_atlas.atlas", "images/towers/DroidTower.atlas", - "images/towers/TNTTower.atlas" + "images/towers/TNTTower.atlas", + "images/towers/barrier.atlas" }; private static final String[] sounds = { "sounds/towers/gun_shot_trimmed.mp3",