diff --git a/source/core/assets/images/attack.png b/source/core/assets/images/attack.png new file mode 100644 index 000000000..c9dfff668 Binary files /dev/null and b/source/core/assets/images/attack.png differ diff --git a/source/core/assets/images/damage_upgrade.png b/source/core/assets/images/damage_upgrade.png new file mode 100644 index 000000000..043750ffb Binary files /dev/null and b/source/core/assets/images/damage_upgrade.png differ diff --git a/source/core/assets/images/engineers/engineer_outline.atlas b/source/core/assets/images/engineers/engineer_outline.atlas new file mode 100644 index 000000000..7ccef54b0 --- /dev/null +++ b/source/core/assets/images/engineers/engineer_outline.atlas @@ -0,0 +1,391 @@ + +engineer_outline.png +size: 2048, 128 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +death_outline + rotate: false + xy: 503, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 6 +death_outline + rotate: false + xy: 902, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +death_outline + rotate: false + xy: 1358, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 5 +death_outline + rotate: false + xy: 1814, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +death_outline + rotate: false + xy: 454, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 4 +death_outline + rotate: false + xy: 796, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +firing_auto_outline + rotate: false + xy: 25, 80 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 2 +firing_auto_outline + rotate: false + xy: 25, 44 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 4 +firing_auto_outline + rotate: false + xy: 102, 80 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 1 +firing_auto_outline + rotate: false + xy: 25, 8 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 3 +firing_single_outline + rotate: false + xy: 102, 42 + size: 52, 33 + orig: 45, 33 + offset: 0, 0 + index: 2 +firing_single_outline + rotate: false + xy: 179, 78 + size: 52, 33 + orig: 45, 33 + offset: 0, 0 + index: 4 +firing_single_outline + rotate: false + xy: 249, 78 + size: 52, 33 + orig: 45, 33 + offset: 0, 0 + index: 1 +firing_single_outline + rotate: false + xy: 319, 78 + size: 52, 33 + orig: 45, 33 + offset: 0, 0 + index: 3 +firing_single_outline + rotate: false + xy: 343, 40 + size: 52, 33 + orig: 29, 33 + offset: 0, 0 + index: 5 +hit_outline + rotate: false + xy: 560, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +hit_outline + rotate: false + xy: 1415, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +hit_outline + rotate: false + xy: 511, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_left_outline + rotate: false + xy: 674, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +idle_left_outline + rotate: false + xy: 1130, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 4 +idle_left_outline + rotate: false + xy: 1529, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_left_outline + rotate: false + xy: 102, 5 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +idle_right_outline + rotate: false + xy: 1016, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +idle_right_outline + rotate: false + xy: 1586, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 4 +default_outline + rotate: false + xy: 1928, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_right_outline + rotate: false + xy: 1928, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_right_outline + rotate: false + xy: 568, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +prep_outline + rotate: false + xy: 343, 40 + size: 52, 33 + orig: 29, 33 + offset: 0, 0 + index: 4 +prep_outline + rotate: false + xy: 853, 41 + size: 52, 33 + orig: 29, 33 + offset: 0, 0 + index: 1 +prep_outline + rotate: false + xy: 907, 41 + size: 52, 33 + orig: 29, 33 + offset: 0, 0 + index: 3 +prep_outline + rotate: false + xy: 961, 41 + size: 52, 33 + orig: 29, 33 + offset: 0, 0 + index: 2 +walk_left_outline + rotate: false + xy: 446, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 6 +walk_left_outline + rotate: false + xy: 845, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +walk_left_outline + rotate: false + xy: 959, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 8 +walk_left_outline + rotate: false + xy: 1301, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 5 +walk_left_outline + rotate: false + xy: 1700, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +walk_left_outline + rotate: false + xy: 1871, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 7 +walk_left_outline + rotate: false + xy: 397, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 4 +walk_left_outline + rotate: false + xy: 739, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +walk_prep_outline + rotate: false + xy: 389, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 8 +walk_prep_outline + rotate: false + xy: 731, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 5 +walk_prep_outline + rotate: false + xy: 1073, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +walk_prep_outline + rotate: false + xy: 1244, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 7 +walk_prep_outline + rotate: false + xy: 1643, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 4 +walk_prep_outline + rotate: false + xy: 1985, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +walk_prep_outline + rotate: false + xy: 229, 41 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 6 +walk_prep_outline + rotate: false + xy: 625, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +walk_right_outline + rotate: false + xy: 617, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 2 +walk_right_outline + rotate: false + xy: 788, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 7 +walk_right_outline + rotate: false + xy: 1187, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 4 +walk_right_outline + rotate: false + xy: 1472, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 1 +walk_right_outline + rotate: false + xy: 1757, 79 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 6 +walk_right_outline + rotate: false + xy: 172, 41 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 3 +walk_right_outline + rotate: false + xy: 286, 41 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 8 +walk_right_outline + rotate: false + xy: 682, 42 + size: 52, 32 + orig: 32, 32 + offset: 0, 0 + index: 5 diff --git a/source/core/assets/images/engineers/engineer_outline.png b/source/core/assets/images/engineers/engineer_outline.png new file mode 100644 index 000000000..620f4cd24 Binary files /dev/null and b/source/core/assets/images/engineers/engineer_outline.png differ diff --git a/source/core/assets/images/glass.png b/source/core/assets/images/glass.png new file mode 100644 index 000000000..f9d9d87ea Binary files /dev/null and b/source/core/assets/images/glass.png differ diff --git a/source/core/assets/images/hammer.png b/source/core/assets/images/hammer.png new file mode 100644 index 000000000..f9119a9aa Binary files /dev/null and b/source/core/assets/images/hammer.png differ diff --git a/source/core/assets/images/health.png b/source/core/assets/images/health.png new file mode 100644 index 000000000..3273c9a50 Binary files /dev/null and b/source/core/assets/images/health.png differ diff --git a/source/core/assets/images/heart_upgrade.png b/source/core/assets/images/heart_upgrade.png new file mode 100644 index 000000000..40dec728e Binary files /dev/null and b/source/core/assets/images/heart_upgrade.png differ diff --git a/source/core/assets/images/hourglass.png b/source/core/assets/images/hourglass.png new file mode 100644 index 000000000..2c2640912 Binary files /dev/null and b/source/core/assets/images/hourglass.png differ diff --git a/source/core/assets/images/hourglass_upgrade.png b/source/core/assets/images/hourglass_upgrade.png new file mode 100644 index 000000000..45e83baa2 Binary files /dev/null and b/source/core/assets/images/hourglass_upgrade.png differ diff --git a/source/core/assets/images/scrap_upgrade.png b/source/core/assets/images/scrap_upgrade.png new file mode 100644 index 000000000..01f9fb6a7 Binary files /dev/null and b/source/core/assets/images/scrap_upgrade.png differ diff --git a/source/core/assets/images/towers/imageedit_9_7173145411.png b/source/core/assets/images/towers/imageedit_9_7173145411.png new file mode 100644 index 000000000..c43b4675b Binary files /dev/null and b/source/core/assets/images/towers/imageedit_9_7173145411.png differ diff --git a/source/core/assets/images/towers/wallTower.png b/source/core/assets/images/towers/wall_tower.png similarity index 100% rename from source/core/assets/images/towers/wallTower.png rename to source/core/assets/images/towers/wall_tower.png diff --git a/source/core/assets/images/turret-select/Weapon-Tower-Clicked.png b/source/core/assets/images/turret-select/Weapon-Tower-Clicked.png new file mode 100644 index 000000000..fbfbedccb Binary files /dev/null and b/source/core/assets/images/turret-select/Weapon-Tower-Clicked.png differ diff --git a/source/core/assets/images/turret-select/Weapon-Tower-Default.png b/source/core/assets/images/turret-select/Weapon-Tower-Default.png new file mode 100644 index 000000000..fff06381b Binary files /dev/null and b/source/core/assets/images/turret-select/Weapon-Tower-Default.png differ diff --git a/source/core/assets/images/turret-select/droid-tower-clicked.png b/source/core/assets/images/turret-select/droid-tower-clicked.png new file mode 100644 index 000000000..4d182496d Binary files /dev/null and b/source/core/assets/images/turret-select/droid-tower-clicked.png differ diff --git a/source/core/assets/images/turret-select/droid-tower-default.png b/source/core/assets/images/turret-select/droid-tower-default.png new file mode 100644 index 000000000..958470e53 Binary files /dev/null and b/source/core/assets/images/turret-select/droid-tower-default.png differ diff --git a/source/core/assets/images/turret-select/fire-tower-clicked.png b/source/core/assets/images/turret-select/fire-tower-clicked.png new file mode 100644 index 000000000..ce1969010 Binary files /dev/null and b/source/core/assets/images/turret-select/fire-tower-clicked.png differ diff --git a/source/core/assets/images/turret-select/fire-tower-default.png b/source/core/assets/images/turret-select/fire-tower-default.png new file mode 100644 index 000000000..318b0f138 Binary files /dev/null and b/source/core/assets/images/turret-select/fire-tower-default.png differ diff --git a/source/core/assets/images/turret-select/imageedit_15_5627113584.png b/source/core/assets/images/turret-select/imageedit_15_5627113584.png new file mode 100644 index 000000000..14412cd66 Binary files /dev/null and b/source/core/assets/images/turret-select/imageedit_15_5627113584.png differ diff --git a/source/core/assets/images/turret-select/imageedit_28_4047785594.png b/source/core/assets/images/turret-select/imageedit_28_4047785594.png new file mode 100644 index 000000000..a7f312043 Binary files /dev/null and b/source/core/assets/images/turret-select/imageedit_28_4047785594.png differ diff --git a/source/core/assets/images/turret-select/imageedit_2_8132799771.png b/source/core/assets/images/turret-select/imageedit_2_8132799771.png new file mode 100644 index 000000000..6fafcf62f Binary files /dev/null and b/source/core/assets/images/turret-select/imageedit_2_8132799771.png differ diff --git a/source/core/assets/images/turret-select/imageedit_4_5616741474.png b/source/core/assets/images/turret-select/imageedit_4_5616741474.png new file mode 100644 index 000000000..f877adaec Binary files /dev/null and b/source/core/assets/images/turret-select/imageedit_4_5616741474.png differ diff --git a/source/core/assets/images/turret-select/mine-tower-clicked.png b/source/core/assets/images/turret-select/mine-tower-clicked.png new file mode 100644 index 000000000..cb8eebc75 Binary files /dev/null and b/source/core/assets/images/turret-select/mine-tower-clicked.png differ diff --git a/source/core/assets/images/turret-select/mine-tower-default.png b/source/core/assets/images/turret-select/mine-tower-default.png new file mode 100644 index 000000000..64bb31cae Binary files /dev/null and b/source/core/assets/images/turret-select/mine-tower-default.png differ diff --git a/source/core/assets/images/turret-select/stun-tower-clicked.png b/source/core/assets/images/turret-select/stun-tower-clicked.png new file mode 100644 index 000000000..d620b84e7 Binary files /dev/null and b/source/core/assets/images/turret-select/stun-tower-clicked.png differ diff --git a/source/core/assets/images/turret-select/stun-tower-default.png b/source/core/assets/images/turret-select/stun-tower-default.png new file mode 100644 index 000000000..5674c9020 Binary files /dev/null and b/source/core/assets/images/turret-select/stun-tower-default.png differ diff --git a/source/core/assets/images/turret-select/tnt-tower-clicked.png b/source/core/assets/images/turret-select/tnt-tower-clicked.png new file mode 100644 index 000000000..9abee203c Binary files /dev/null and b/source/core/assets/images/turret-select/tnt-tower-clicked.png differ diff --git a/source/core/assets/images/turret-select/tnt-tower-default.png b/source/core/assets/images/turret-select/tnt-tower-default.png new file mode 100644 index 000000000..09ee3b6de Binary files /dev/null and b/source/core/assets/images/turret-select/tnt-tower-default.png differ diff --git a/source/core/assets/images/turret-select/wall-tower-clicked.png b/source/core/assets/images/turret-select/wall-tower-clicked.png new file mode 100644 index 000000000..35cf07460 Binary files /dev/null and b/source/core/assets/images/turret-select/wall-tower-clicked.png differ diff --git a/source/core/assets/images/turret-select/wall-tower-default.png b/source/core/assets/images/turret-select/wall-tower-default.png new file mode 100644 index 000000000..bc7ba42f8 Binary files /dev/null and b/source/core/assets/images/turret-select/wall-tower-default.png differ diff --git a/source/core/assets/images/upgrade.png b/source/core/assets/images/upgrade.png new file mode 100644 index 000000000..d45aa5768 Binary files /dev/null and b/source/core/assets/images/upgrade.png differ diff --git a/source/core/assets/sounds/turret-select/Modern4.wav b/source/core/assets/sounds/turret-select/Modern4.wav new file mode 100644 index 000000000..98b9386fa Binary files /dev/null and b/source/core/assets/sounds/turret-select/Modern4.wav differ diff --git a/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java b/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java index a0a91095b..93798e8d4 100644 --- a/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java +++ b/source/core/src/main/com/csse3200/game/ai/tasks/AITaskComponent.java @@ -1,6 +1,7 @@ package com.csse3200.game.ai.tasks; import com.csse3200.game.components.Component; +import com.csse3200.game.components.ComponentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +36,24 @@ public AITaskComponent addTask(PriorityTask task) { return this; } + /** + * Get a task from the list of tasks. This can be used to get a reference to + * a task to modify it. This is inspired from Entity.getComponent(). + * + * @param task The task to get + * @return A reference to a task with the given class + * @param The type of task to get + */ + public T getTask(Class task) { + for (PriorityTask priorityTask : priorityTasks) { + if (priorityTask.getClass() == task) { + return (T) priorityTask; + } + } + logger.info("Task {} not found", task); + return null; + } + /** * On update, run the current highest priority task. If it's a different one, stop the old one and * start the new one. If the highest priority task has negative priority, no task will be run. @@ -86,7 +105,7 @@ public void restore() { this.update(); } - private PriorityTask getHighestPriorityTask() { + public PriorityTask getHighestPriorityTask() { try { return Collections.max(priorityTasks, Comparator.comparingInt(PriorityTask::getPriority)); } catch (NoSuchElementException e) { 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 358f615bd..b74fa46ac 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -45,10 +45,10 @@ public class ForestGameArea extends GameArea { private static final GridPoint2 PLAYER_SPAWN = new GridPoint2(2, 4); // Temporary spawn point for testing private static final float WALL_WIDTH = 0.1f; - + // Required to load assets before using them private static final String[] forestTextures = { - "images/desert_bg.png", + "images/desert_bg.png", "images/ice_bg.png", "images/lava_bg.png", "images/projectiles/projectile.png", @@ -87,7 +87,7 @@ public class ForestGameArea extends GameArea { "images/mobs/Hurt.png", "images/mobs/Idle.png", "images/mobs/rangeBossRight.png", - "images/towers/wallTower.png", + "images/towers/wall_tower.png", "images/background/building2.png", "images/iso_grass_3.png", "images/Dusty_MoonBG.png", @@ -158,7 +158,7 @@ public class ForestGameArea extends GameArea { "images/mobs/fire_worm.atlas", "images/mobs/dragon_knight.atlas", "images/mobs/skeleton.atlas", - "images/mobs/wizard.atlas", + "images/mobs/wizard.atlas", "images/mobs/water_queen.atlas", "images/mobs/water_slime.atlas", "images/mobboss/patrick.atlas", @@ -207,12 +207,12 @@ public class ForestGameArea extends GameArea { }; private static final String backgroundMusic = "sounds/background/Sci-Fi1.ogg"; private static final String[] forestMusic = {backgroundMusic}; - + // private final TerrainFactory terrainFactory; - + private Entity player; private Entity waves; - + // Variables to be used with spawn projectile methods. This is the variable // that should occupy the direction param. private static final int towardsMobs = 100; @@ -259,29 +259,29 @@ private void spawnWave() { switch (wave) { case 1: case 2: - spawnWaterQueen(); - spawnWizard(); - logger.info("Lol"); - - break; + spawnWaterQueen(); + spawnWizard(); + logger.info("Lol"); + + break; case 3: - spawnWaterSlime(); - spawnIceBaby(); - logger.info("Lol"); - // mobBoss2 = spawnMobBoss2(); - break; + spawnWaterSlime(); + spawnIceBaby(); + logger.info("Lol"); + // mobBoss2 = spawnMobBoss2(); + break; case 4: - spawnFireWorm(); - // spawnDragonKnight(); - logger.info("Lol"); - // mobBoss2 = spawnMobBoss2(); - break; + spawnFireWorm(); + // spawnDragonKnight(); + logger.info("Lol"); + // mobBoss2 = spawnMobBoss2(); + break; case 5: case 6: - spawnSkeleton(); + spawnSkeleton(); case 7: - spawnDemonBoss(); - spawnPatrick(); + spawnDemonBoss(); + spawnPatrick(); default: // Handle other wave scenarios if needed break; @@ -301,7 +301,7 @@ public void create() { logger.info("Lol"); spawnTerrain(); logger.info("Lol"); - + // Set up infrastructure for end game tracking // player = spawnPlayer(); @@ -312,12 +312,12 @@ public void create() { playMusic(); spawnScrap(); spawnTNTTower(); - //spawnWeaponTower(); + //spawnWeaponTower(); spawnGapScanners(); spawnDroidTower(); } - + private void displayUI() { Entity ui = new Entity(); ui.addComponent(new GameAreaDisplay("Box Forest")); @@ -325,17 +325,17 @@ private void displayUI() { ui.addComponent(ServiceLocator.getCurrencyService().getDisplay()); spawnEntity(ui); } - + private void spawnTerrain() { terrain = ServiceLocator.getMapService().getComponent(); spawnEntity(ServiceLocator.getMapService().getEntity()); - + // Terrain walls float tileSize = terrain.getTileSize(); GridPoint2 tileBounds = terrain.getMapBounds(0); Vector2 worldBounds = new Vector2(tileBounds.x * tileSize, tileBounds.y * tileSize); - + // Left spawnEntityAt( ObstacleFactory.createWall(WALL_WIDTH, worldBounds.y), @@ -362,11 +362,11 @@ private void spawnTerrain() { false); } - + private void spawnBuilding2() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < NUM_BUILDINGS; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity building2 = ObstacleFactory.createBuilding2(); @@ -379,7 +379,7 @@ private Entity spawnPlayer() { spawnEntityAt(newPlayer, PLAYER_SPAWN, true, true); return newPlayer; } - + // Spawn player at a specific position private Entity spawnPlayer(GridPoint2 position) { Entity newPlayer = PlayerFactory.createPlayer(); @@ -421,7 +421,7 @@ private void spawnProjectile(Vector2 position, short targetLayer, int direction, Projectile.setPosition(position); spawnEntity(Projectile); } - + /** * Spawns a projectile specifically for general mobs/xenohunters * @@ -435,7 +435,7 @@ private void spawnProjectileTest(Vector2 position, short targetLayer, int direct Projectile.setPosition(position); spawnEntity(Projectile); } - + /** * Spawns a projectile to be used for multiple projectile function. * @@ -524,7 +524,7 @@ private void spawnDeflectWizard(int x, int y) { xenoGrunt.setScale(1.5f, 1.5f); spawnEntityAt(xenoGrunt, pos, true, true); } - + private void spawnFireWorm() { int[] pickedLanes = rand.ints(0, ServiceLocator.getMapService().getHeight() ) @@ -633,7 +633,7 @@ private void spawnMultiProjectile(Vector2 position, short targetLayer, int direc --half; } } - + /** * Returns projectile that can do an area of effect damage * @@ -650,7 +650,7 @@ private void spawnEffectProjectile(Vector2 position, short targetLayer, int dire Projectile.setPosition(position); spawnEntity(Projectile); } - + /** * Spawns a pierce fireball. * Pierce fireball can go through targetlayers without disappearing but damage @@ -666,7 +666,7 @@ private void spawnPierceFireBall(Vector2 position, short targetLayer, int direct projectile.setPosition(position); spawnEntity(projectile); } - + /** * Spawns a ricochet fireball * Ricochet fireballs bounce off targets with a specified maximum count of 3 @@ -683,7 +683,7 @@ private void spawnRicochetFireball(Vector2 position, short targetLayer, int dire projectile.setPosition(position); spawnEntity(projectile); } - + /** * Spawns a split firework fireball. * Splits into mini projectiles that spreads out after collision. @@ -700,7 +700,7 @@ private void spawnSplitFireWorksFireBall(Vector2 position, short targetLayer, in spawnEntity(projectile); } - + private void spawnWeaponTower() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(5, 1); @@ -729,17 +729,17 @@ private void spawnDroidTowerAt(int x, int y) { spawnEntityAt(droidTower, pos, true, true); } - + private void spawnTNTTower() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(5, 1); - + for (int i = 0; i < NUM_WEAPON_TOWERS + 5; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity weaponTower = TowerFactory.createTNTTower(); spawnEntityAt(weaponTower, randomPos, true, true); } - + } private void spawnDroidTower() { @@ -752,15 +752,15 @@ private void spawnDroidTower() { spawnEntityAt(weaponTower, randomPos, true, false); } } - - + + private void playMusic() { Music music = ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class); music.setLooping(true); music.setVolume(0.3f); music.play(); } - + private void loadAssets() { logger.debug("Loading assets"); ResourceService resourceService = ServiceLocator.getResourceService(); @@ -768,13 +768,13 @@ private void loadAssets() { resourceService.loadTextureAtlases(forestTextureAtlases); resourceService.loadSounds(forestSounds); resourceService.loadMusic(forestMusic); - + while (!resourceService.loadForMillis(10)) { // This could be upgraded to a loading screen logger.info("Loading... {}%", resourceService.getProgress()); } } - + private void unloadAssets() { logger.debug("Unloading assets"); ResourceService resourceService = ServiceLocator.getResourceService(); @@ -783,7 +783,7 @@ private void unloadAssets() { resourceService.unloadAssets(forestSounds); resourceService.unloadAssets(forestMusic); } - + @Override public void dispose() { super.dispose(); @@ -791,28 +791,28 @@ public void dispose() { this.unloadAssets(); stopWaveTimer(); } - + private void spawnScrap() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < 5; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity scrap = DropFactory.createScrapDrop(); spawnEntityAt(scrap, randomPos, true, false); } - + for (int i = 0; i < 5; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity crystal = DropFactory.createCrystalDrop(); spawnEntityAt(crystal, randomPos, true, false); } } - + private void spawnIncome() { GridPoint2 minPos = new GridPoint2(0, 0); GridPoint2 maxPos = terrain.getMapBounds(0).sub(2, 2); - + for (int i = 0; i < 50; i++) { GridPoint2 randomPos = RandomUtils.random(minPos, maxPos); Entity towerfactory = TowerFactory.createIncomeTower(); diff --git a/source/core/src/main/com/csse3200/game/areas/GameArea.java b/source/core/src/main/com/csse3200/game/areas/GameArea.java index 414396c24..38cfad74c 100644 --- a/source/core/src/main/com/csse3200/game/areas/GameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/GameArea.java @@ -53,7 +53,7 @@ protected void spawnEntity(Entity entity) { * @param centerY true to center entity Y on the tile, false to align the bottom left corner */ protected void spawnEntityAt( - Entity entity, GridPoint2 tilePos, boolean centerX, boolean centerY) { + Entity entity, GridPoint2 tilePos, boolean centerX, boolean centerY) { Vector2 worldPos = terrain.tileToWorldPosition(tilePos); float tileSize = terrain.getTileSize(); @@ -67,4 +67,4 @@ protected void spawnEntityAt( entity.setPosition(worldPos); spawnEntity(entity); } -} +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java b/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java index 83582470b..47096ea75 100644 --- a/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java +++ b/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java @@ -76,7 +76,7 @@ public void create() { for (int i = 0; i < 2; i++) { // Use "building1" for the first tower and "building2" for the second tower skin.add("default", new Label.LabelStyle(new BitmapFont(), Color.WHITE)); - skin.add("building1", new Texture("images/towers/WallTower.png")); + skin.add("building1", new Texture("images/towers/wall_tower.png")); // Load textures for building1 and building2 towers1[i] = new Image(skin, "building1"); towers1[i].setBounds(Gdx.graphics.getWidth() * 40f / 100f, Gdx.graphics.getHeight() * 80f / 100f, 100, 100); @@ -131,7 +131,7 @@ public void touchUp(InputEvent event, float x, float y, int pointer, int button) for (int i = 0; i < 2; i++) { // Use "building1" for the first tower and "building2" for the second tower skin.add("default", new Label.LabelStyle(new BitmapFont(), Color.WHITE)); - skin.add("building2", new Texture("images/towers/WallTower.png")); + skin.add("building2", new Texture("images/towers/wall_tower.png")); towers2[i] = new Image(skin, "building2"); towers2[i].setBounds(Gdx.graphics.getWidth() * 50f / 100f, Gdx.graphics.getHeight() * 80f / 100f, 100, 100); stage.addActor(towers2[i]); diff --git a/source/core/src/main/com/csse3200/game/components/npc/EngineerMenuComponent.java b/source/core/src/main/com/csse3200/game/components/npc/EngineerMenuComponent.java new file mode 100644 index 000000000..096fb5962 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/npc/EngineerMenuComponent.java @@ -0,0 +1,150 @@ +package com.csse3200.game.components.npc; + +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.scenes.scene2d.utils.Drawable; +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; +import com.badlogic.gdx.utils.Array; +import com.csse3200.game.components.player.HumanAnimationController; +import com.csse3200.game.components.tower.TowerUpgraderComponent; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.input.EngineerInputComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.ui.UIComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EngineerMenuComponent extends UIComponent { + private Logger logger = LoggerFactory.getLogger(EngineerMenuComponent.class); + Table table; + + @Override + public void create() { + super.create(); + } + + @Override + public void draw(SpriteBatch batch) { + // draw is handled by the stage + } + + /** + * Creates a menu for the engineer + * @param x cursor x coordinate + * @param y cursor y coordinate + * @param camera camera of the game + */ + public void createMenu(float x, float y, Camera camera) { + this.table = createTable(x, y, camera); + + // add buttons + TextButton moveButton = createButton("Move"); + TextButton repairButton = createButton("Repair"); + + // add listeners to buttons + AnimationRenderComponent animator = getEntity().getComponent(AnimationRenderComponent.class); + HumanAnimationController controller = getEntity().getComponent(HumanAnimationController.class); + EngineerInputComponent input = ServiceLocator.getInputService().getEngineerInput(); + + moveButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + input.setMoveClicked(true); + controller.deselectEngineer(animator.getCurrentAnimation()); + + //logger.info("Move button clicked"); + } + }); + + repairButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + controller.deselectEngineer(animator.getCurrentAnimation()); + EntityService entityService = ServiceLocator.getEntityService(); + Array tower = entityService.getEntitiesInLayer(getEntity(), 0.1f, PhysicsLayer.TOWER); + if (tower.size == 0) { + logger.info("No tower to repair"); + return; + } + logger.info("repairing"); + tower.get(0).getComponent(TowerUpgraderComponent.class).repairTower(); + //logger.info("Repair button clicked"); + } + }); + + table.add(moveButton).grow(); + table.row(); + table.add(repairButton).grow(); + table.row(); + stage.addActor(table); + + } + + /** + * Creates a table for the menu + * @param x cursor x coordinate + * @param y cursor y coordinate + * @param camera camera of the game + * @return table for the menu + */ + private Table createTable(float x, float y, Camera camera) { + Table table = new Table(); + table.top(); + table.defaults().pad(0).space(0); + table.setSize(90, 60); // fixed table size + + // convert cursor position to stage coordinates + Vector3 entityCoordinates = new Vector3(x, y, 0); + Vector3 entityScreenCoordinate = camera.project(entityCoordinates); + Vector2 stageCoordinates = stage.screenToStageCoordinates( + new Vector2(entityScreenCoordinate.x, entityScreenCoordinate.y)); + stage.getViewport().unproject(stageCoordinates); + table.setPosition(stageCoordinates.x, stageCoordinates.y); + + // set table background + String imageFilePath = "images/ui/Sprites/UI_Glass_Frame_Standard_01a.png"; + Drawable drawable = new TextureRegionDrawable(new TextureRegion(new Texture(imageFilePath))); + table.setBackground(drawable); + + return table; + } + + /** + * Creates a button for the menu + * @param text text to be displayed on the button + * @return the button + */ + private TextButton createButton(String text) { + String upImageFilePath = "images/ui/Sprites/UI_Glass_Button_Medium_Lock_01a2.png"; + String downImageFilePath = "images/ui/Sprites/UI_Glass_Button_Medium_Press_01a2.png"; + + Drawable upDrawable = new TextureRegionDrawable(new TextureRegion(new Texture(upImageFilePath))); + Drawable downDrawable = new TextureRegionDrawable(new TextureRegion(new Texture(downImageFilePath))); + TextButton button = new TextButton(text, + new TextButton.TextButtonStyle(upDrawable, downDrawable, null, new BitmapFont())); + + button.setTransform(true); + return button; + } + + /** + * Removes the menu from the stage + */ + public void removeMenu() { + table.clear(); + table.remove(); + } + +} diff --git a/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java b/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java index c6231f29e..bcaf10f1f 100644 --- a/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java +++ b/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java @@ -2,6 +2,7 @@ import com.badlogic.gdx.audio.Sound; import com.csse3200.game.components.Component; +import com.csse3200.game.components.npc.EngineerMenuComponent; import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.ServiceLocator; @@ -31,6 +32,18 @@ public class HumanAnimationController extends Component { private static final String FIRE_SINGLE_ANIM = "firing_single"; private static final String HIT_ANIM = "hit"; private static final String DEATH_ANIM = "death"; + + private static final String IDLEL_ANIM_OUTLINE = "idle_left_outline"; + private static final String IDLER_ANIM_OUTLINE = "idle_right_outline"; + private static final String WALKL_ANIM_OUTLINE = "walk_left_outline"; + private static final String WALKR_ANIM_OUTLINE = "walk_right_outline"; + private static final String WALK_PREP_ANIM_OUTLINE = "walk_prep_outline"; + private static final String FIRE_AUTO_ANIM_OUTLINE = "firing_auto_outline"; + private static final String FIRE_SINGLE_ANIM_OUTLINE = "firing_single_outline"; + private static final String PREP_ANIM_OUTLINE = "prep_outline"; + private static final String HIT_ANIM_OUTLINE = "hit_outline"; + private static final String DEATH_ANIM_OUTLINE = "death_outline"; + // Sound effects constants private static final String FIRE_AUTO_SFX = "sounds/engineers/firing_auto.mp3"; private static final String FIRE_SINGLE_SFX = "sounds/engineers/firing_single.mp3"; @@ -41,6 +54,9 @@ public class HumanAnimationController extends Component { private final Sound fireSingleSound = ServiceLocator.getResourceService().getAsset( FIRE_SINGLE_SFX, Sound.class); + private boolean clicked = false; + + /** * Instantiates a HumanAnimationController and adds all the event listeners for the * Human entity - Just engineers at this stage. @@ -65,21 +81,33 @@ public void create() { * Callback that starts the idle animation facing left */ void animateIdleLeft() { - animator.startAnimation(IDLEL_ANIM); + if (clicked) { + animator.startAnimation(IDLEL_ANIM_OUTLINE); + } else { + animator.startAnimation(IDLEL_ANIM); + } } /** * Callback that starts the idle animation facing right */ void animateIdleRight() { - animator.startAnimation(IDLER_ANIM); + if (clicked) { + animator.startAnimation(IDLER_ANIM_OUTLINE); + } else { + animator.startAnimation(IDLER_ANIM); + } } /** * Callback that starts the walk animation for left movement */ void animateLeftWalk() { - animator.startAnimation(WALKL_ANIM); + if (clicked) { + animator.startAnimation(WALKL_ANIM_OUTLINE); + } else { + animator.startAnimation(WALKL_ANIM); + } // runSound.play(); } @@ -87,7 +115,11 @@ void animateLeftWalk() { * Callback that starts the walk animation for right movement */ void animateRightWalk() { - animator.startAnimation(WALKR_ANIM); + if (clicked) { + animator.startAnimation(WALKR_ANIM_OUTLINE); + } else { + animator.startAnimation(WALKR_ANIM); + } } /** @@ -95,14 +127,22 @@ void animateRightWalk() { * unused, but intended to be incorporated as engineer functionality expands */ void animatePrepWalk() { - animator.startAnimation(WALK_PREP_ANIM); + if (clicked) { + animator.startAnimation(WALK_PREP_ANIM_OUTLINE); + } else { + animator.startAnimation(WALK_PREP_ANIM); + } } /** * Callback that starts the shoot animation in single fire mode, and plays the single fire sound */ void animateSingleFiring() { - animator.startAnimation(FIRE_SINGLE_ANIM); + if (clicked) { + animator.startAnimation(FIRE_SINGLE_ANIM_OUTLINE); + } else { + animator.startAnimation(FIRE_SINGLE_ANIM); + } fireSingleSound.play(); } @@ -111,7 +151,11 @@ void animateSingleFiring() { * Currently unused, but intended to be incorporated as engineer functionality expands. */ void animateFiringAuto() { - animator.startAnimation(FIRE_AUTO_ANIM); + if (clicked) { + animator.startAnimation(FIRE_AUTO_ANIM_OUTLINE); + } else { + animator.startAnimation(FIRE_AUTO_ANIM); + } fireAutoSound.play(); } @@ -119,20 +163,61 @@ void animateFiringAuto() { * Callback that starts the 'prep' animation, i.e., raising weapon in preparation for firing */ void animatePrep() { - animator.startAnimation(PREP); + if (clicked) { + animator.startAnimation(PREP_ANIM_OUTLINE); + } else { + animator.startAnimation(PREP); + } } /** * Callback that starts the 'hit' animation when engineer is damaged */ void animateHit() { - animator.startAnimation(HIT_ANIM); + if (clicked) { + animator.startAnimation(HIT_ANIM_OUTLINE); + } else { + animator.startAnimation(HIT_ANIM); + } } /** * Callback that starts the 'death' animation when the engineer entity's health reaches zero. */ void animateDeath() { - animator.startAnimation(DEATH_ANIM); + if (clicked) { + animator.startAnimation(DEATH_ANIM_OUTLINE); + } else { + animator.startAnimation(DEATH_ANIM); + } + } + + /** + * @return true if the entity has been clicked/selected, false otherwise + */ + public boolean isClicked() { + return clicked; + } + + /** + * Sets the clicked state of the entity + * @param clicked true if the entity has been clicked/selected, false otherwise + */ + public void setClicked(boolean clicked) { + this.clicked = clicked; + } + + /** + * Deseelects the engineer entity by starting the appropriate animation without an outline + * and removes the engineer menu from the stage + * @param currentAnimation the current animation of the entity + */ + public void deselectEngineer(String currentAnimation) { + AnimationRenderComponent animator = this.entity.getComponent(AnimationRenderComponent.class); + EngineerMenuComponent menu = this.entity.getComponent(EngineerMenuComponent.class); + + animator.startAnimation(currentAnimation.substring(0, currentAnimation.lastIndexOf('_'))); + menu.removeMenu(); + setClicked(false); } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/components/tasks/CurrencyTask.java b/source/core/src/main/com/csse3200/game/components/tasks/CurrencyTask.java index c9e21d846..c280db0bd 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/CurrencyTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/CurrencyTask.java @@ -39,7 +39,8 @@ public CurrencyTask(int priority, int interval) { @Override public void start() { super.start(); - endTime = timeSource.getTime() + (30 * 1000); + owner.getEntity().getEvents().addListener("addIncome",this::changeInterval); + endTime = timeSource.getTime() + (30 * 1000L); owner.getEntity().getEvents().trigger(IDLE); } @@ -53,7 +54,8 @@ public void update() { if (timeSource.getTime() >= endTime) { owner.getEntity().getEvents().trigger(MOVE); updateCurrency(); // update currency - endTime = timeSource.getTime() + (30 * 1000); // reset end time + logger.info(String.format("Interval: %d", interval)); + endTime = timeSource.getTime() + (interval * 1000L); // reset end time } } @@ -89,6 +91,11 @@ public int getPriority() { return priority; } + public void changeInterval(int newInterval) { + interval = newInterval; + logger.info("Interval changed to: " + interval); + } + public void setInterval(int interval) { this.interval = interval; } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java index 9f1159de4..384549af7 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java @@ -11,6 +11,8 @@ import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import static java.lang.Math.round; + /** * The DroidCombatTask runs the AI for the DroidTower class. The tower will scan for targets in a straight line * from its center point until a point at (x + maxRange, y), where x,y are the cooridinates of the tower's center @@ -35,6 +37,7 @@ public class DroidCombatTask extends DefaultTask implements PriorityTask { // class attributes private final int priority; // The active priority this task will have private final float maxRange; + private float fireRateInterval; private Vector2 towerPosition = new Vector2(10, 10); // initial placeholder value - will be overwritten private final Vector2 maxRangePosition = new Vector2(); private PhysicsEngine physics; @@ -54,6 +57,7 @@ public enum STATE { public DroidCombatTask(int priority, float maxRange) { this.priority = priority; this.maxRange = maxRange; + this.fireRateInterval = 1; physics = ServiceLocator.getPhysicsService().getPhysics(); timeSource = ServiceLocator.getTimeSource(); } @@ -69,7 +73,7 @@ public void start() { this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); // Default to idle mode owner.getEntity().getEvents().trigger(WALK); - + owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); endTime = timeSource.getTime() + (INTERVAL * 500); } @@ -81,8 +85,12 @@ public void start() { public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); - endTime = timeSource.getTime() + (INTERVAL * 1000); - } + if (towerState == STATE.SHOOT_UP || towerState == STATE.SHOOT_DOWN) { + endTime = timeSource.getTime() + round(fireRateInterval * 1000); + } else { + endTime = timeSource.getTime() + (INTERVAL * 1000); + } + } } /** @@ -193,5 +201,17 @@ public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + private void changeFireRateInterval(int newInterval) { + fireRateInterval = 1 / ((float) newInterval / 5); + } + + /** + * Function for getting the turret's fire rate. + * + * @return The fireRateInterval variable + */ + public float getFireRateInterval() { + return fireRateInterval; + } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java index e2f1327ef..abf01bb79 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -14,6 +14,8 @@ import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import static java.lang.Math.round; + /** * The FireTowerCombatTask runs the AI for the FireTower class. The tower implementing this task will scan for enemies * in a straight line from the current position to a maxRange, and change the state of the tower. @@ -28,10 +30,12 @@ public class FireTowerCombatTask extends DefaultTask implements PriorityTask { public static final String ATTACK = "startAttack"; public static final String DEATH = "startDeath"; + //Class attributes private final int priority; private final float maxRange; + private float fireRateInterval; private Vector2 towerPosition = new Vector2(10, 10); private final Vector2 maxRangePosition = new Vector2(); private PhysicsEngine physics; @@ -50,6 +54,7 @@ public enum STATE { public FireTowerCombatTask(int priority, float maxRange) { this.priority = priority; this.maxRange = maxRange; + this.fireRateInterval = 1; physics = ServiceLocator.getPhysicsService().getPhysics(); timeSource = ServiceLocator.getTimeSource(); } @@ -63,6 +68,7 @@ public void start() { // get the tower coordinates this.towerPosition = owner.getEntity().getCenterPosition(); this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); //default to idle state owner.getEntity().getEvents().trigger(IDLE); @@ -76,7 +82,10 @@ public void start() { public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); - endTime = timeSource.getTime() + (INTERVAL * 1000); + if (towerState == STATE.ATTACK) { + endTime = timeSource.getTime() + round(fireRateInterval * 1000); + } else + endTime = timeSource.getTime() + (INTERVAL * 1000); } } @@ -172,4 +181,18 @@ private int getInactivePriority() { public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + + private void changeFireRateInterval(int newInterval) { + fireRateInterval = 1 / ((float) newInterval / 5); + } + + /** + * Function for getting the turret's fire rate. + * + * @return The fireRateInterval variable + */ + public float getFireRateInterval() { + return fireRateInterval; + } + } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java index be56625f7..cce8ec833 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -14,6 +14,8 @@ import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import static java.lang.Math.round; + /** * The StunTowerCombatTask runs the AI for the StunTower class. The tower scans for mobs and targets in a straight line @@ -31,6 +33,7 @@ public class StunTowerCombatTask extends DefaultTask implements PriorityTask { //Following are the class constants private final int priority; private final float maxRange; + private float fireRateInterval; private Vector2 towerPosition = new Vector2(10, 10); private final Vector2 maxRangePosition = new Vector2(); private PhysicsEngine physics; @@ -51,6 +54,7 @@ public enum STATE { public StunTowerCombatTask(int priority, float maxRange) { this.priority = priority; this.maxRange = maxRange; + this.fireRateInterval = 1; physics = ServiceLocator.getPhysicsService().getPhysics(); timeSource = ServiceLocator.getTimeSource(); } @@ -64,6 +68,7 @@ public void start() { //get the tower coordinates this.towerPosition = owner.getEntity().getCenterPosition(); this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); + owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); //set the default state to IDLE state owner.getEntity().getEvents().trigger(IDLE); @@ -77,7 +82,11 @@ public void start() { public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); - endTime = timeSource.getTime() + (INTERVAL * 1000); + if (towerState == STATE.ATTACK) { + endTime = timeSource.getTime() + round(fireRateInterval * 1000); + } else { + endTime = timeSource.getTime() + (INTERVAL * 1000); + } } } @@ -160,4 +169,17 @@ public int getInactivePriority() { public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + + private void changeFireRateInterval(int newInterval) { + fireRateInterval = 1 / ((float) newInterval / 5); + } + + /** + * Function for getting the turret's fire rate. + * + * @return The fireRateInterval variable + */ + public float getFireRateInterval() { + return fireRateInterval; + } } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java index f67214ae7..f1d0eb9ca 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java @@ -3,6 +3,7 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.areas.ForestGameArea; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.ProjectileFactory; import com.csse3200.game.physics.PhysicsEngine; @@ -10,6 +11,9 @@ import com.csse3200.game.physics.raycast.RaycastHit; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import static java.lang.Math.round; /** @@ -37,6 +41,7 @@ public class TowerCombatTask extends DefaultTask implements PriorityTask { private GameTime timeSource; private long endTime; private final RaycastHit hit = new RaycastHit(); + private static final Logger logger = LoggerFactory.getLogger(ForestGameArea.class); private enum STATE { IDLE, DEPLOY, FIRING, STOW @@ -48,6 +53,7 @@ private enum STATE { * @param maxRange Maximum effective range of the weapon tower. This determines the detection distance of targets */ public TowerCombatTask(int priority, float maxRange) { + this.priority = priority; this.maxRange = maxRange; this.fireRateInterval = 1; @@ -56,6 +62,7 @@ public TowerCombatTask(int priority, float maxRange) { } /** + * THIS IS REDUNDANT AND NOT USED * @param priority Task priority when targets are detected (0 when nothing detected). Must be a positive integer. * @param maxRange Maximum effective range of the weapon tower. This determines the detection distance of targets * @param fireRate The number of times per second this tower should fire its weapon @@ -81,7 +88,7 @@ public void start() { owner.getEntity().getEvents().trigger(IDLE); // Set up listener to change fire rate owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); - + logger.info("TowerCombatTask started"); endTime = timeSource.getTime() + (INTERVAL * 500); } @@ -216,18 +223,13 @@ private boolean isTargetVisible() { } /** - * Increases the fireRateInterval, changing how frequently the turret fires. Will decrease if the argument is negative. + * Changes the tower's fire rate. * - * @param perMinute The number of times per minute the turret's fire rate should increase + * @param newInterval The rate at which the tower should fire projectiles in shots per second. */ - private void changeFireRateInterval(int perMinute) { - float oldFireSpeed = 1/fireRateInterval; - float newFireSpeed = oldFireSpeed + perMinute/60f; - if (newFireSpeed == 0) { - return; - } else { - fireRateInterval = 1 / newFireSpeed; - } + private void changeFireRateInterval(int newInterval) { + logger.info("Changing fire rate to: " + newInterval); + fireRateInterval = 1 / ((float) newInterval / 5); } /** @@ -238,4 +240,5 @@ private void changeFireRateInterval(int perMinute) { public float getFireRateInterval() { return fireRateInterval; } + } diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java index fa589460f..933ffacc1 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java @@ -2,6 +2,7 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.ai.tasks.PriorityTask; import com.csse3200.game.physics.components.PhysicsMovementComponent; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; @@ -10,7 +11,7 @@ * Move a human entity to a given position, finishing when you get close enough. Requires an entity with a * PhysicsMovementComponent. */ -public class HumanMovementTask extends DefaultTask { +public class HumanMovementTask extends DefaultTask implements PriorityTask { private final GameTime gameTime; private Vector2 target; private float stopDistance = 0.01f; @@ -18,6 +19,8 @@ public class HumanMovementTask extends DefaultTask { private Vector2 lastPos; private PhysicsMovementComponent movementComponent; + private int priority = 1000; // default priority + public HumanMovementTask(Vector2 target) { this.target = target; this.gameTime = ServiceLocator.getTimeSource(); @@ -85,4 +88,9 @@ private void checkIfStuck() { private boolean didMove() { return owner.getEntity().getPosition().dst2(lastPos) > 0.001f; } + + @Override + public int getPriority() { + return priority; + } } 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 528348b8a..499ce2937 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 @@ -31,6 +31,8 @@ public class HumanWanderTask extends DefaultTask implements PriorityTask { private Task currentTask; private boolean isDead = false; + private boolean isSelected = false; + private boolean hasDied = false; /** @@ -105,35 +107,35 @@ public void update() { // otherwise doing engineer things since engineer is alive else if (!isDead){ doEngineerThings(); - currentTask.update(); } } private void doEngineerThings() { - if (currentTask.getStatus() != Status.ACTIVE) { - - // if the engineer is in move state and update has been called, engineer has arrived at destination - if (currentTask == movementTask) { - startWaiting(); - owner.getEntity().getEvents().trigger(IDLE_EVENT); - - } else if (combatTask.isTargetVisible()) { - float engY = owner.getEntity().getCenterPosition().y; - float targetY = combatTask.fetchTarget().y; - // if the engineer is positioned within the tolerance range of the mob's y position, enter combat state - if (engY < targetY + TOLERANCE && - engY > targetY - TOLERANCE) { - startCombat(); - - // move into position for targeting mob - } else { - Vector2 newPos = new Vector2(owner.getEntity().getPosition().x, combatTask.fetchTarget().y); - startMoving(newPos); - } + if (currentTask.getStatus() == Status.ACTIVE) { + return; + } + + // if the engineer is in move state and update has been called, engineer has arrived at destination + if (currentTask == movementTask) { + startWaiting(); + owner.getEntity().getEvents().trigger(IDLE_EVENT); + } else if (combatTask.isTargetVisible()) { + float engY = owner.getEntity().getCenterPosition().y; + float targetY = combatTask.fetchTarget().y; + // if the engineer is positioned within the tolerance range of the mob's y position, enter combat state + if (engY < targetY + TOLERANCE && + engY > targetY - TOLERANCE) { + startCombat(); + + // move into position for targeting mob + } else if (!this.isSelected()) { + Vector2 newPos = new Vector2(owner.getEntity().getPosition().x, combatTask.fetchTarget().y); + startMoving(newPos); } } } + /** * 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) @@ -150,7 +152,7 @@ private void startDying() { /** * Starts the wait task. */ - private void startWaiting() { + public void startWaiting() { swapTask(waitTask); } @@ -158,7 +160,7 @@ private void startWaiting() { * 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) { + public void startMoving(Vector2 destination) { movementTask.setTarget(destination); swapTask(movementTask); } @@ -166,7 +168,7 @@ private void startMoving(Vector2 destination) { /** * Starts the combat task. */ - private void startCombat() { + public void startCombat() { swapTask(combatTask); } @@ -181,4 +183,12 @@ private void swapTask(Task newTask) { currentTask = newTask; currentTask.start(); } + + private boolean isSelected() { + return isSelected; + } + + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + } } diff --git a/source/core/src/main/com/csse3200/game/components/tower/IncomeUpgradeComponent.java b/source/core/src/main/com/csse3200/game/components/tower/IncomeUpgradeComponent.java new file mode 100644 index 000000000..ffbb93e12 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/IncomeUpgradeComponent.java @@ -0,0 +1,19 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; + +public class IncomeUpgradeComponent extends Component { + private float incomeRate; + + public IncomeUpgradeComponent(float incomeRate) { + setIncomeRate(incomeRate); + } + + public void setIncomeRate(float incomeRate) { + this.incomeRate = incomeRate; + } + + public float getIncomeRate() { + return incomeRate; + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java b/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java index bc04c1d1d..e83210b48 100644 --- a/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java +++ b/source/core/src/main/com/csse3200/game/components/tower/TowerUpgraderComponent.java @@ -3,13 +3,15 @@ import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.Component; +import static com.csse3200.game.screens.TowerType.INCOME; + /** * Listens for an event from the popup menu to upgrade * the turret entity this component is attached to. */ public class TowerUpgraderComponent extends Component { public enum UPGRADE { - ATTACK, MAXHP, FIRERATE, REPAIR + ATTACK, MAXHP, FIRERATE, REPAIR, INCOME } @Override @@ -25,12 +27,14 @@ public void create() { * @param upgradeType An enum indicating the type of upgrade to do * @param value How much the upgrade should change the tower's stats, if applicable */ - void upgradeTower(UPGRADE upgradeType, int value) { + public void upgradeTower(UPGRADE upgradeType, int value) { switch (upgradeType) { + case INCOME -> {getEntity().getEvents().trigger("addIncome", value);} case ATTACK -> {upgradeTowerAttack(value);} - case MAXHP -> {upgradeTowerMaxHealth(value);} + case MAXHP -> {upgradeTowerMaxHealth( value);} case FIRERATE -> {getEntity().getEvents().trigger("addFireRate", value);} case REPAIR -> {repairTower();} + } } @@ -58,7 +62,7 @@ void upgradeTowerMaxHealth(int increase) { /** * Restores the tower's health to its maximum health. */ - void repairTower() { + public void repairTower() { int maxHealth = getEntity().getComponent(CombatStatsComponent.class).getMaxHealth(); getEntity().getComponent(CombatStatsComponent.class).setHealth(maxHealth); } diff --git a/source/core/src/main/com/csse3200/game/components/tower/UpgradableStatsComponent.java b/source/core/src/main/com/csse3200/game/components/tower/UpgradableStatsComponent.java new file mode 100644 index 000000000..91fcba6ff --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tower/UpgradableStatsComponent.java @@ -0,0 +1,19 @@ +package com.csse3200.game.components.tower; + +import com.csse3200.game.components.Component; + +public class UpgradableStatsComponent extends Component { + private float attackRate; + + public UpgradableStatsComponent(float attackRate) { + setAttackRate(attackRate); + } + + public void setAttackRate(float attackRate) { + this.attackRate = attackRate; + } + + public float getAttackRate() { + return attackRate; + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/Entity.java b/source/core/src/main/com/csse3200/game/entities/Entity.java index 46d4ffda6..4373da4a9 100644 --- a/source/core/src/main/com/csse3200/game/entities/Entity.java +++ b/source/core/src/main/com/csse3200/game/entities/Entity.java @@ -206,6 +206,16 @@ public Entity addComponent(Component component) { return this; } + public void removeComponent(Class componentClass) { + ComponentType componentType = ComponentType.getFrom(componentClass); + int id = componentType.getId(); + + if (components.containsKey(id)) { + Component removedComponent = components.remove(id); + removedComponent.dispose(); + } + } + /** Dispose of the entity. This will dispose of all components on this entity. */ public void dispose() { for (Component component : createdComponents) { diff --git a/source/core/src/main/com/csse3200/game/entities/EntityService.java b/source/core/src/main/com/csse3200/game/entities/EntityService.java index 7adb4fc8e..3511d598b 100644 --- a/source/core/src/main/com/csse3200/game/entities/EntityService.java +++ b/source/core/src/main/com/csse3200/game/entities/EntityService.java @@ -173,4 +173,17 @@ private boolean entityContainsPosition(Entity entity, float x, float y) { return (x >= entityX && x <= entityX + entityWidth && y >= entityY && y <= entityY + entityHeight); } + public Entity getEntityAtPositionLayer(float x, float y, short layer) { + for (int i = 0; i < entities.size; i++) { + Entity entity = entities.get(i); + if (entityContainsPosition(entity, x, y)) { + HitboxComponent hitBox = entity.getComponent(HitboxComponent.class); + if (hitBox != null && PhysicsLayer.contains(layer, hitBox.getLayer())) { + return entity; + } + } + } + return null; + } + } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java index 18ce675d5..3349a149c 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/DroidTowerConfig.java @@ -7,4 +7,6 @@ public class DroidTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + + public float attackRate = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java index 7e697040b..cc5b75284 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/FireTowerConfig.java @@ -4,4 +4,6 @@ public class FireTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + + public float attackRate = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/IncomeTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/IncomeTowerConfig.java index 65dee1def..9e79376e2 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/IncomeTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/IncomeTowerConfig.java @@ -7,4 +7,8 @@ public class IncomeTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + + public float attackRate = 0; + public float incomeRate = 30; + } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java index fc711e70f..adf01c4f4 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/StunTowerConfig.java @@ -4,4 +4,6 @@ public class StunTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + + public float attackRate = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java index 2dbd2e53d..9385fc512 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/TNTTowerConfigs.java @@ -7,4 +7,6 @@ public class TNTTowerConfigs { public int health = 1; public int baseAttack = 0; public int cost = 1; + + public float attackRate = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/WallTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/WallTowerConfig.java index 452eaf7e0..59c830d4f 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/WallTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/WallTowerConfig.java @@ -7,4 +7,6 @@ public class WallTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + + public float attackRate = 0; } diff --git a/source/core/src/main/com/csse3200/game/entities/configs/WeaponTowerConfig.java b/source/core/src/main/com/csse3200/game/entities/configs/WeaponTowerConfig.java index 7d00ccb65..7f0547769 100644 --- a/source/core/src/main/com/csse3200/game/entities/configs/WeaponTowerConfig.java +++ b/source/core/src/main/com/csse3200/game/entities/configs/WeaponTowerConfig.java @@ -7,4 +7,5 @@ public class WeaponTowerConfig { public int health = 1; public int baseAttack = 0; public int cost = 1; + public float attackRate = 1; } diff --git a/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java index 7bdd6396a..e9feaf2aa 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java @@ -5,6 +5,7 @@ import com.csse3200.game.ai.tasks.AITaskComponent; import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.npc.EngineerMenuComponent; import com.csse3200.game.components.player.HumanAnimationController; import com.csse3200.game.components.tasks.human.HumanWanderTask; import com.csse3200.game.entities.Entity; @@ -50,24 +51,14 @@ public static Entity createEngineer() { Entity engineer = createBaseHumanNPC(); BaseEntityConfig config = configs.engineer; - AnimationRenderComponent animator = new AnimationRenderComponent( - new TextureAtlas("images/engineers/engineer.atlas")); - animator.addAnimation("walk_left", 0.2f, Animation.PlayMode.LOOP); - animator.addAnimation("walk_right", 0.2f, Animation.PlayMode.LOOP); - animator.addAnimation("walk_prep", 0.2f, Animation.PlayMode.LOOP); - animator.addAnimation("idle_right", 0.2f, Animation.PlayMode.LOOP); - animator.addAnimation("firing_auto", 0.05f, Animation.PlayMode.NORMAL); - animator.addAnimation("firing_single", 0.05f, Animation.PlayMode.NORMAL); - animator.addAnimation("prep", 0.05f, Animation.PlayMode.NORMAL); - animator.addAnimation("hit", 0.01f, Animation.PlayMode.NORMAL); - animator.addAnimation("death", 0.1f, Animation.PlayMode.NORMAL); - AITaskComponent aiComponent = new AITaskComponent(); + AnimationRenderComponent animator = createAnimationRenderComponent(); engineer .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) .addComponent(animator) .addComponent(new HumanAnimationController()) + .addComponent(new EngineerMenuComponent()) .addComponent(aiComponent); engineer.getComponent(AITaskComponent.class).addTask(new HumanWanderTask(COMBAT_TASK_PRIORITY, ENGINEER_RANGE)); @@ -76,6 +67,33 @@ public static Entity createEngineer() { return engineer; } + public static AnimationRenderComponent createAnimationRenderComponent() { + String atlasPath = "images/engineers/engineer.atlas"; + String atlasOutlinePath = "images/engineers/engineer_outline.atlas"; + AnimationRenderComponent animator = new AnimationRenderComponent( + new TextureAtlas(atlasPath), new TextureAtlas(atlasOutlinePath)); + animator.addAnimation("walk_left", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("walk_right", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("walk_prep", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("idle_right", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("firing_auto", 0.05f, Animation.PlayMode.NORMAL); + animator.addAnimation("firing_single", 0.05f, Animation.PlayMode.NORMAL); + animator.addAnimation("prep", 0.05f, Animation.PlayMode.NORMAL); + animator.addAnimation("hit", 0.01f, Animation.PlayMode.NORMAL); + animator.addAnimation("death", 0.1f, Animation.PlayMode.NORMAL); + + animator.addAnimation("walk_left_outline", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("walk_right_outline", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("walk_prep_outline", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("idle_right_outline", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("firing_auto_outline", 0.05f, Animation.PlayMode.NORMAL); + animator.addAnimation("firing_single_outline", 0.05f, Animation.PlayMode.NORMAL); + animator.addAnimation("prep_outline", 0.05f, Animation.PlayMode.NORMAL); + animator.addAnimation("hit_outline", 0.01f, Animation.PlayMode.NORMAL); + animator.addAnimation("death_outline", 0.1f, Animation.PlayMode.NORMAL); + return animator; + } + /** * Creates a generic human npc to be used as a base entity by more specific NPC creation methods. * diff --git a/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java index b8371a08f..d07b91497 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/PlayerFactory.java @@ -26,36 +26,36 @@ * the properties stores in 'PlayerConfig'. */ public class PlayerFactory { - private static final PlayerConfig stats = - FileLoader.readClass(PlayerConfig.class, "configs/player.json"); + private static final PlayerConfig stats = + FileLoader.readClass(PlayerConfig.class, "configs/player.json"); - /** - * Create a player entity. - * @return entity - */ - public static Entity createPlayer() { - InputComponent inputComponent = - ServiceLocator.getInputService().getInputFactory().createForPlayer(); - Entity player = - new Entity() - .addComponent(new TextureRenderComponent("images/box_boy_leaf.png")) - .addComponent(new PhysicsComponent()) - .addComponent(new ColliderComponent()) - .addComponent(new TouchAttackComponent(PhysicsLayer.NPC)) - .addComponent(new HitboxComponent().setLayer(PhysicsLayer.HUMANS)) - .addComponent(new PlayerActions()) - .addComponent(new CombatStatsComponent(1000, 0)) - .addComponent(new InventoryComponent(stats.gold)) - .addComponent(inputComponent) - .addComponent(new PlayerStatsDisplay()); + /** + * Create a player entity. + * @return entity + */ + public static Entity createPlayer() { + InputComponent inputComponent = + ServiceLocator.getInputService().getInputFactory().createForPlayer(); + Entity player = + new Entity() + .addComponent(new TextureRenderComponent("images/box_boy_leaf.png")) + .addComponent(new PhysicsComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new TouchAttackComponent(PhysicsLayer.NPC)) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.HUMANS)) + .addComponent(new PlayerActions()) + .addComponent(new CombatStatsComponent(1000, 0)) + .addComponent(new InventoryComponent(stats.gold)) + .addComponent(inputComponent) + .addComponent(new PlayerStatsDisplay()); - PhysicsUtils.setScaledCollider(player, 0.6f, 0.3f); - player.getComponent(ColliderComponent.class).setDensity(1.5f); - player.getComponent(TextureRenderComponent.class).scaleEntity(); - return player; - } + PhysicsUtils.setScaledCollider(player, 0.6f, 0.3f); + player.getComponent(ColliderComponent.class).setDensity(1.5f); + player.getComponent(TextureRenderComponent.class).scaleEntity(); + return player; + } - private PlayerFactory() { - throw new IllegalStateException("Instantiating static util class"); - } -} + private PlayerFactory() { + 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 6302c0d8c..00f113b2f 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 @@ -24,7 +24,7 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.rendering.TextureRenderComponent; import com.csse3200.game.services.ServiceLocator; - +import com.csse3200.game.input.UpgradeUIComponent; /** * Factory to create a tower entity. * @@ -38,7 +38,7 @@ public class TowerFactory { private static final int TNT_TOWER_MAX_RANGE = 6; private static final int TNT_TOWER_RANGE = 6; private static final int TNT_KNOCK_BACK_FORCE = 10; - private static final String WALL_IMAGE = "images/towers/wallTower.png"; + private static final String WALL_IMAGE = "images/towers/wall_tower.png"; private static final String RESOURCE_TOWER = "images/towers/mine_tower.png"; private static final String TURRET_ATLAS = "images/towers/turret01.atlas"; private static final String FIRE_TOWER_ATLAS = "images/towers/fire_tower_atlas.atlas"; @@ -98,10 +98,8 @@ public static Entity createIncomeTower() { IncomeTowerConfig config = configs.income; // Create the CurrencyIncomeTask and add it to the AITaskComponent - CurrencyTask currencyTask = new CurrencyTask(INCOME_TASK_PRIORITY, INCOME_INTERVAL); + CurrencyTask currencyTask = new CurrencyTask(INCOME_TASK_PRIORITY, (int) config.incomeRate); - int updatedInterval = 1; - currencyTask.setInterval(updatedInterval); AITaskComponent aiTaskComponent = new AITaskComponent().addTask(currencyTask); @@ -115,7 +113,9 @@ public static Entity createIncomeTower() { income .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) + .addComponent(new IncomeUpgradeComponent(config.incomeRate)) .addComponent(aiTaskComponent) .addComponent(animator) .addComponent(new EconTowerAnimationController()); @@ -129,6 +129,7 @@ public static Entity createWallTower() { wall .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) .addComponent(new TextureRenderComponent(WALL_IMAGE)); return wall; @@ -158,6 +159,7 @@ public static Entity createTNTTower() { TNTTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) .addComponent(new TNTDamageComponent(PhysicsLayer.NPC,TNT_KNOCK_BACK_FORCE,TNT_TOWER_RANGE)) .addComponent(aiTaskComponent) @@ -199,6 +201,7 @@ public static Entity createDroidTower() { DroidTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) .addComponent(new DroidAnimationController()) .addComponent(animator) @@ -233,7 +236,9 @@ public static Entity createWeaponTower() { weapon .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(aiTaskComponent) .addComponent(animator) .addComponent(new TowerAnimationController()); @@ -265,6 +270,7 @@ public static Entity createFireTower() { fireTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent(new CostComponent(config.cost)) .addComponent(aiTaskComponent) .addComponent(animator) @@ -294,6 +300,7 @@ public static Entity createStunTower() { stunTower .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(new UpgradableStatsComponent(config.attackRate)) .addComponent((new CostComponent(config.cost))) .addComponent(aiTaskComponent) .addComponent(animator) @@ -315,7 +322,7 @@ public static Entity createBaseTower() { .addComponent(new HitboxComponent().setLayer(PhysicsLayer.TOWER)) // TODO: we might have to change the names of the layers .addComponent(new PhysicsComponent().setBodyType(BodyType.StaticBody)) .addComponent(new TowerUpgraderComponent()); - + tower.setLayer(1); // Set priority to 1, which is 1 below scrap (which is 0) return tower; } } \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/input/EngineerInputComponent.java b/source/core/src/main/com/csse3200/game/input/EngineerInputComponent.java new file mode 100644 index 000000000..4b2388878 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/input/EngineerInputComponent.java @@ -0,0 +1,131 @@ +package com.csse3200.game.input; +import com.badlogic.gdx.Game; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.ai.tasks.PriorityTask; +import com.csse3200.game.ai.tasks.Task; +import com.csse3200.game.components.npc.EngineerMenuComponent; +import com.csse3200.game.components.player.HumanAnimationController; +import com.csse3200.game.components.tasks.human.HumanMovementTask; +import com.csse3200.game.components.tasks.human.HumanWanderTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.entities.factories.EngineerFactory; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EngineerInputComponent extends InputComponent { + + private static final Logger logger = LoggerFactory.getLogger(EngineerInputComponent.class); + private Game game; + private Camera camera; + private EntityService entityService; + + private Entity selectedEngineer = null; + private boolean moveClicked = false; + + public EngineerInputComponent(Game game, Camera camera) { + this.game = game; + this.camera = camera; + this.entityService = ServiceLocator.getEntityService(); + } + + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + Vector3 worldCoordinates = new Vector3((float) screenX , (float) screenY, 0); + camera.unproject(worldCoordinates); + Vector2 cursorPosition = new Vector2(worldCoordinates.x, worldCoordinates.y); + camera.project(worldCoordinates); + Entity engineer = entityService.getEntityAtPositionLayer(cursorPosition.x, cursorPosition.y, PhysicsLayer.ENGINEER); + //logger.info("Clicked entity: " + engineer); + + // Case when engineer is not clicked + if (engineer == null || engineer.getComponent(HumanAnimationController.class) == null) { + if (selectedEngineer != null && moveClicked) { + moveEngineer(cursorPosition); + selectedEngineer = null; + moveClicked = false; + return true; + } else { + return false; + } + } + // Case when engineer is clicked + AnimationRenderComponent animator = engineer.getComponent(AnimationRenderComponent.class); + String currentAnimation = animator.getCurrentAnimation(); + HumanAnimationController controller = engineer.getComponent(HumanAnimationController.class); + EngineerMenuComponent menu = engineer.getComponent(EngineerMenuComponent.class); + + if (engineer.equals(selectedEngineer)) { + // Deselect the engineer by clicking on itself + this.getWanderTask().setSelected(false); + selectedEngineer = null; + moveClicked = false; + if (currentAnimation.contains("_outline")) { + controller.deselectEngineer(currentAnimation); + //logger.info("Engineer deselected"); + } + } else { + this.selectedEngineer = engineer; + this.getWanderTask().setSelected(true); + moveClicked = false; + logger.info("Engineer size: {}", engineer.getScale()); + + // outline image if it is not already outlined and vice versa + if (!currentAnimation.contains("_outline")) { + animator.startAnimation(currentAnimation + "_outline"); + menu.createMenu(cursorPosition.x, cursorPosition.y, camera); + controller.setClicked(true); + } + } + return true; + } + + @Override + public boolean keyDown(int keycode) { + if (selectedEngineer == null) { + return false; + } + + + if (Gdx.input.isKeyPressed(Input.Keys.P)) { + manualShoot(); + } + return true; + } + + private void manualShoot() { + HumanWanderTask wander = this.getWanderTask(); + wander.startWaiting(); + wander.startCombat(); + } + + private HumanWanderTask getWanderTask() { + AITaskComponent movementTask = selectedEngineer.getComponent(AITaskComponent.class); + return movementTask.getTask(HumanWanderTask.class); + } + + private void moveEngineer(Vector2 cursorPosition) { + if (selectedEngineer == null) { + logger.info("Trying to move an engineer that is not selected"); + } + + HumanWanderTask wander = this.getWanderTask(); + wander.startWaiting(); + Vector2 enggpos = selectedEngineer.getPosition(); + Vector2 offset = new Vector2(cursorPosition.x > enggpos.x ? 0.17f : -0.6f , cursorPosition.y > enggpos.y ? 0.0f : -0.5f); + Vector2 dest = cursorPosition.add(offset); + wander.startMoving(dest); + } + + public void setMoveClicked(boolean moveClicked) { + this.moveClicked = moveClicked; + } + +} diff --git a/source/core/src/main/com/csse3200/game/input/InputService.java b/source/core/src/main/com/csse3200/game/input/InputService.java index 6c2e8d2ae..bb9a8dbe0 100644 --- a/source/core/src/main/com/csse3200/game/input/InputService.java +++ b/source/core/src/main/com/csse3200/game/input/InputService.java @@ -27,11 +27,13 @@ public class InputService implements InputProcessor, GestureDetector.GestureList private final List inputHandlers = new ArrayList<>(); private final InputFactory inputFactory; + private EngineerInputComponent engineerInputComponent; public InputService() { this(InputFactory.createFromInputType(inputType)); } + public InputService(InputFactory inputFactory) { this.inputFactory = inputFactory; Gdx.input.setInputProcessor(this); @@ -46,6 +48,10 @@ public InputFactory getInputFactory() { return inputFactory; } + public EngineerInputComponent getEngineerInput() { + return engineerInputComponent; + } + /** * Register an input handler based on its priority and reorder inputHandlers. * @@ -53,6 +59,9 @@ public InputFactory getInputFactory() { */ public void register(InputComponent inputHandler) { logger.debug("Registering input handler {}", inputHandler); + if (inputHandler instanceof EngineerInputComponent) { + engineerInputComponent = (EngineerInputComponent) inputHandler; + } inputHandlers.add(inputHandler); inputHandlers.sort(comparator); } diff --git a/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java b/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java new file mode 100644 index 000000000..cffd36cc6 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/input/UpgradeUIComponent.java @@ -0,0 +1,374 @@ +package com.csse3200.game.input; + +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.*; +import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.scenes.scene2d.utils.Drawable; +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; +import com.badlogic.gdx.scenes.scene2d.ui.Image; +import com.badlogic.gdx.utils.Scaling; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.ai.tasks.PriorityTask; +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.entities.Entity; +import com.csse3200.game.entities.EntityService; +import com.csse3200.game.services.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class UpgradeUIComponent extends InputComponent { + private static final Logger logger = LoggerFactory.getLogger(ForestGameArea.class); + private final EntityService entityService; + private final Camera camera; + private final Stage stage; + + private int value; + + // Create a map to store upgrade tables for each turret entity + private Map upgradeTables = new HashMap<>(); + + /** + * Constructor for the UpgradeUIComponent + * @param camera the camera to be used, this is the camera that the game is rendered with + */ + public UpgradeUIComponent(Camera camera, Stage stage) { + this.value = ServiceLocator.getCurrencyService().getScrap().getAmount(); + this.entityService = ServiceLocator.getEntityService(); + this.camera = camera; + this.stage = stage; + } + + /** + * Getter for the camera + * @return the camera + */ + public Camera getCamera() { + return camera; + } + + public Stage getStage() { + return stage; + } + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + // Clear all existing upgrade tables + + + Vector3 worldCoordinates = new Vector3((float) screenX, (float) screenY, 0); + getCamera().unproject(worldCoordinates); + Vector2 cursorPosition = new Vector2(worldCoordinates.x, worldCoordinates.y); + Entity clickedEntity = entityService.getEntityAtPosition(cursorPosition.x, cursorPosition.y); + + if (clickedEntity != null && clickedEntity.getComponent(TowerUpgraderComponent.class) != null && clickedEntity.getComponent(TNTDamageComponent.class) == null) { +// logger.info("clicked a turret that is upgradable!"); + clearUpgradeTables(); + // Check if there is an existing upgrade table for this turret entity + Table existingUpgradeTable = upgradeTables.get(clickedEntity); + + if (existingUpgradeTable != null) { + // If an upgrade table already exists, show it + stage.addActor(existingUpgradeTable); + } else { + // If no upgrade table exists, create and store a new one + Table newUpgradeTable = createUpgradeTable(clickedEntity); + Vector2 UICoordinates = stage.screenToStageCoordinates(new Vector2(screenX, screenY)); + newUpgradeTable.setPosition(UICoordinates.x, UICoordinates.y); + stage.addActor(newUpgradeTable); + + // Store the new upgrade table in the map + upgradeTables.put(clickedEntity, newUpgradeTable); + } + + return true; + } + return false; + } + + // Create a method to clear all existing upgrade tables + private void clearUpgradeTables() { + for (Table upgradeTable : upgradeTables.values()) { + upgradeTable.remove(); + } + upgradeTables.clear(); + } + + private Table createUpgradeTable(Entity turretEntity) { + Table upgradeTable = new Table(); + upgradeTable.top(); + upgradeTable.defaults().pad(0).space(0); + upgradeTable.setSize(60, 60); + Table innerUpgradeTable = new Table(); + innerUpgradeTable.top(); + innerUpgradeTable.defaults().pad(10).space(0).padBottom(1); + innerUpgradeTable.setSize(60, 60); + // set table background + String imageFilePath = "images/ui/Sprites/UI_Glass_Frame_Standard_01a.png"; + String upgradeButtonFilePath = "images/economy/scrapBanner.png"; + Drawable drawableBackground = new TextureRegionDrawable(new TextureRegion(new Texture(imageFilePath))); + innerUpgradeTable.setBackground(drawableBackground); + + Drawable drawable = new TextureRegionDrawable(new TextureRegion(new Texture("images/ui/Sprites/UI_Glass_Button_Small_Lock_01a2.png"))); + Drawable econDrawable = new TextureRegionDrawable(new TextureRegion(new Texture(upgradeButtonFilePath))); + TextButton.TextButtonStyle style = new TextButton.TextButtonStyle( + drawable, drawable, drawable, new BitmapFont()); + TextButton.TextButtonStyle econStyle = new TextButton.TextButtonStyle( + econDrawable, econDrawable, econDrawable, new BitmapFont()); + // create button + int maxHealth = turretEntity.getComponent(CombatStatsComponent.class).getMaxHealth(); + int currentHealth = turretEntity.getComponent(CombatStatsComponent.class).getHealth(); + turretEntity.getComponent(CombatStatsComponent.class).setHealth(5); // for testing + int attack = turretEntity.getComponent(CombatStatsComponent.class).getBaseAttack(); + float fireRate = turretEntity.getComponent(UpgradableStatsComponent.class).getAttackRate(); + Label healthLabel = new Label(String.format("%d/%d", currentHealth, maxHealth), createLabelStyle()); + Label attackLabel = new Label(String.format("%d", attack), createLabelStyle()); + Label fireRateLabel = new Label(String.format("%.2f", fireRate), createLabelStyle()); + Table costDisplay = new Table(); + costDisplay.setWidth(0); + costDisplay.setBackground(drawableBackground); + // Create an Image for the scrap icon + Drawable costDrawable = new TextureRegionDrawable(new TextureRegion(new Texture("images/economy/scrap.png"))); + Image costImage = new Image(costDrawable); + costDisplay.add(costImage).center(); + costImage.setScaling(Scaling.none); + Label costDisplayLabel = new Label("100", createLabelStyle()); + costDisplay.add(costDisplayLabel).padLeft(0); + + TextButton closeButton = new TextButton("X", style); + closeButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + upgradeTable.remove(); + // Remove the upgrade table from the map + upgradeTables.remove(turretEntity); + } + }); + + // Create an Image for the icons + Drawable healthIconDrawable = new TextureRegionDrawable(new TextureRegion(new Texture("images/health.png"))); + Image healthIconImage = new Image(healthIconDrawable); + + Drawable attackIconDrawable = new TextureRegionDrawable(new TextureRegion(new Texture("images/attack.png"))); + Image attackIconImage = new Image(attackIconDrawable); + + Drawable fireRateDrawable = new TextureRegionDrawable(new TextureRegion(new Texture("images/hourglass.png"))); + Image fireRateImage = new Image(fireRateDrawable); + + + Drawable healthStyle = new TextureRegionDrawable(new TextureRegion(new Texture("images/heart_upgrade.png"))); + ImageButton upgradeHealth = new ImageButton(healthStyle); + upgradeHealth.setScale(0.8f); + upgradeHealth.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + value = ServiceLocator.getCurrencyService().getScrap().getAmount(); + logger.info("clicked"); + if (value >= 100) { + value -= 100; + ServiceLocator.getCurrencyService().getScrap().setAmount(value); + ServiceLocator.getCurrencyService().getDisplay().updateScrapsStats(); + + turretEntity.getComponent(TowerUpgraderComponent.class).upgradeTower(TowerUpgraderComponent.UPGRADE.MAXHP, 10); + int currentHealth = turretEntity.getComponent(CombatStatsComponent.class).getHealth(); + int maxHealth = turretEntity.getComponent(CombatStatsComponent.class).getMaxHealth(); + healthLabel.setText(String.format("%d/%d", currentHealth, maxHealth)); + } + } + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + costDisplayLabel.setText("10"); + costDisplay.setVisible(true); + } + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) { + costDisplay.setVisible(false); + } + }); + + Drawable attackStyle = new TextureRegionDrawable(new TextureRegion(new Texture("images/damage_upgrade.png"))); + ImageButton upgradeAttack = new ImageButton(attackStyle); + upgradeAttack.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + value = ServiceLocator.getCurrencyService().getScrap().getAmount(); + if (value >= 10) { + value -= 10; + ServiceLocator.getCurrencyService().getScrap().setAmount(value); + ServiceLocator.getCurrencyService().getDisplay().updateScrapsStats(); + turretEntity.getComponent(TowerUpgraderComponent.class).upgradeTower(TowerUpgraderComponent.UPGRADE.ATTACK, 5); + + int attack = turretEntity.getComponent(CombatStatsComponent.class).getBaseAttack(); + attackLabel.setText(String.format("%d", attack)); + } + } + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + costDisplayLabel.setText("10"); + costDisplay.setVisible(true); + } + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) { + costDisplay.setVisible(false); + } + }); + + + + Drawable asStyle = new TextureRegionDrawable(new TextureRegion(new Texture("images/hourglass_upgrade.png"))); + ImageButton upgradeFireRate = new ImageButton(asStyle); + upgradeFireRate.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + value = ServiceLocator.getCurrencyService().getScrap().getAmount(); + if (value >= 10) { + value -= 10; + ServiceLocator.getCurrencyService().getScrap().setAmount(value); + ServiceLocator.getCurrencyService().getDisplay().updateScrapsStats(); + float newFireRate = turretEntity.getComponent(UpgradableStatsComponent.class).getAttackRate() + 0.2f; + turretEntity.getComponent(UpgradableStatsComponent.class).setAttackRate(newFireRate); + turretEntity.getComponent(TowerUpgraderComponent.class).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, (int) newFireRate * 5); + + float fireRate = turretEntity.getComponent(UpgradableStatsComponent.class).getAttackRate(); + fireRateLabel.setText(String.format("%.2f", fireRate)); + } + + } + + + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + costDisplayLabel.setText("10"); + costDisplay.setVisible(true); + } + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) { + costDisplay.setVisible(false); + } + }); + + Drawable repair = new TextureRegionDrawable(new TextureRegion(new Texture("images/hammer.png"))); + ImageButton repairButton = new ImageButton(repair); + repairButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + value = ServiceLocator.getCurrencyService().getScrap().getAmount(); + if (value >= 100) { + value -= 100; + ServiceLocator.getCurrencyService().getScrap().setAmount(value); + ServiceLocator.getCurrencyService().getDisplay().updateScrapsStats(); + turretEntity.getComponent(TowerUpgraderComponent.class).upgradeTower(TowerUpgraderComponent.UPGRADE.REPAIR, 0); + int currentHealth = turretEntity.getComponent(CombatStatsComponent.class).getHealth(); + healthLabel.setText(String.format("%d/%d", currentHealth, maxHealth)); + } + } + + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + costDisplayLabel.setText("10"); + costDisplay.setVisible(true); + } + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) { + costDisplay.setVisible(false); + } + + + }); + + innerUpgradeTable.row(); + innerUpgradeTable.add(healthIconImage).padRight(5).width(32).height(32); // Add health icon + innerUpgradeTable.add(healthLabel).expandX().left(); + innerUpgradeTable.row(); + TextButton upgradeIncome = null; + if (turretEntity.getComponent(IncomeUpgradeComponent.class) != null) { + Drawable incomeDrawable = new TextureRegionDrawable(new TextureRegion(new Texture("images/economy/scrap.png"))); + Image incomeImage = new Image(incomeDrawable); + Label incomeLabel = new Label(String.format("%.2f", turretEntity.getComponent(IncomeUpgradeComponent.class).getIncomeRate()), createLabelStyle()); + innerUpgradeTable.add(incomeImage).padRight(5).width(32).height(32); + innerUpgradeTable.add(incomeLabel).expandX().left(); + innerUpgradeTable.row(); + + Drawable income = new TextureRegionDrawable(new TextureRegion(new Texture("images/scrap_upgrade.png"))); + ImageButton attackStyleButton = new ImageButton(asStyle); + attackStyleButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + value = ServiceLocator.getCurrencyService().getScrap().getAmount(); + if (value >= 10 && turretEntity.getComponent(IncomeUpgradeComponent.class).getIncomeRate() >= 10) { + value -= 10; + ServiceLocator.getCurrencyService().getScrap().setAmount(value); + ServiceLocator.getCurrencyService().getDisplay().updateScrapsStats(); + float newIncome = turretEntity.getComponent(IncomeUpgradeComponent.class).getIncomeRate() - 5; + turretEntity.getComponent(IncomeUpgradeComponent.class).setIncomeRate(newIncome); + turretEntity.getComponent(TowerUpgraderComponent.class).upgradeTower(TowerUpgraderComponent.UPGRADE.INCOME, (int) newIncome); + incomeLabel.setText(String.format("%.2f", newIncome)); + } + + } + + + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + costDisplayLabel.setText("10"); + costDisplay.setVisible(true); + } + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) { + costDisplay.setVisible(false); + } + }); + } + if (attack != 0) { + innerUpgradeTable.add(attackIconImage).padRight(5).width(32).height(32); // Add attack icon + innerUpgradeTable.add(attackLabel).expandX().left(); + innerUpgradeTable.row(); + + innerUpgradeTable.add(fireRateImage).padRight(5).width(32).height(32); // Add fire rate icon + innerUpgradeTable.add(fireRateLabel).expandX().right(); + innerUpgradeTable.row(); + } + innerUpgradeTable.add(upgradeHealth).expandX().fillX(); + if (attack != 0) { + innerUpgradeTable.add(upgradeAttack).expandX().fillX(); + innerUpgradeTable.add(upgradeFireRate).expandX().fillX(); + } + if (upgradeIncome != null) { + innerUpgradeTable.add(upgradeIncome).expandX().fillX(); + } + innerUpgradeTable.add(repairButton).expandX().fillX(); + upgradeTable.add(closeButton).right().row(); + upgradeTable.add(innerUpgradeTable).center().expand().row(); + + upgradeTable.add(costDisplay).left(); + costDisplay.setVisible(false); + upgradeTable.setVisible(true); + + return upgradeTable; + } + + private LabelStyle createLabelStyle() { + LabelStyle style = new LabelStyle(); + style.font = new BitmapFont(); + style.fontColor = Color.WHITE; + return style; + } +} diff --git a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java index 0143567f3..62de022a8 100644 --- a/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java +++ b/source/core/src/main/com/csse3200/game/rendering/AnimationRenderComponent.java @@ -37,6 +37,7 @@ public class AnimationRenderComponent extends RenderComponent { private static final Logger logger = LoggerFactory.getLogger(AnimationRenderComponent.class); private final GameTime timeSource; private final TextureAtlas atlas; + private final TextureAtlas atlas2; private final Map> animations; private Animation currentAnimation; private String currentAnimationName; @@ -48,6 +49,14 @@ public class AnimationRenderComponent extends RenderComponent { */ public AnimationRenderComponent(TextureAtlas atlas) { this.atlas = atlas; + this.atlas2 = null; + this.animations = new HashMap<>(4); + timeSource = ServiceLocator.getTimeSource(); + } + + public AnimationRenderComponent(TextureAtlas atlas1, TextureAtlas atlas2) { + this.atlas = atlas1; + this.atlas2 = atlas2; this.animations = new HashMap<>(4); timeSource = ServiceLocator.getTimeSource(); } @@ -73,20 +82,37 @@ public boolean addAnimation(String name, float frameDuration) { */ public boolean addAnimation(String name, float frameDuration, PlayMode playMode) { Array regions = atlas.findRegions(name); + Array regions2; + if (regions == null || regions.size == 0) { - logger.warn("Animation {} not found in texture atlas", name); - return false; + // check if there is a second atlas file + if (atlas2 != null) { + regions2 = atlas2.findRegions(name); + + if (regions2 == null || regions2.size == 0) { + logger.warn("Animation {} not found in texture atlas 2", name); + return false; + } else { + Animation animation = new Animation<>(frameDuration, regions2, playMode); + animations.put(name, animation); + logger.debug("Adding animation {}", name); + return true; + } + } else { + logger.warn("Animation {} not found in texture atlas", name); + return false; + } } else if (animations.containsKey(name)) { logger.warn( - "Animation {} already added in texture atlas. Animations should only be added once.", - name); + "Animation {} already added in texture atlas. Animations should only be added once.", + name); return false; + } else { + Animation animation = new Animation<>(frameDuration, regions, playMode); + animations.put(name, animation); + logger.debug("Adding animation {}", name); + return true; } - - Animation animation = new Animation<>(frameDuration, regions, playMode); - animations.put(name, animation); - logger.debug("Adding animation {}", name); - return true; } /** Scale the entity to a width of 1 and a height matching the texture's ratio */ diff --git a/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java b/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java index a86e63e09..dc7f8d293 100644 --- a/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java +++ b/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java @@ -18,10 +18,7 @@ import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.EntityService; import com.csse3200.game.entities.factories.RenderFactory; -import com.csse3200.game.input.DropInputComponent; -import com.csse3200.game.input.InputComponent; -import com.csse3200.game.input.InputDecorator; -import com.csse3200.game.input.InputService; +import com.csse3200.game.input.*; import com.csse3200.game.physics.PhysicsEngine; import com.csse3200.game.physics.PhysicsService; import com.csse3200.game.rendering.RenderService; @@ -95,7 +92,12 @@ public MainGameScreen(GdxGame game) { renderer.getCamera().getEntity().setPosition(CAMERA_POSITION); renderer.getDebug().renderPhysicsWorld(physicsEngine.getWorld()); InputComponent inputHandler = new DropInputComponent(renderer.getCamera().getCamera()); + InputComponent UpgradedInputHandler = new UpgradeUIComponent(renderer.getCamera().getCamera(), renderer.getStage()); + + InputComponent engineerInputHnadler = new EngineerInputComponent(game, renderer.getCamera().getCamera()); ServiceLocator.getInputService().register(inputHandler); + ServiceLocator.getInputService().register(engineerInputHnadler); + ServiceLocator.getInputService().register(UpgradedInputHandler); ServiceLocator.getCurrencyService().getDisplay().setCamera(renderer.getCamera().getCamera()); loadAssets(); @@ -220,18 +222,18 @@ private void createUI() { logger.debug("Creating ui"); Stage stage = ServiceLocator.getRenderService().getStage(); InputComponent inputComponent = - ServiceLocator.getInputService().getInputFactory().createForTerminal(); + ServiceLocator.getInputService().getInputFactory().createForTerminal(); ui = new Entity(); ui.addComponent(new InputDecorator(stage, 10)) - .addComponent(new PerformanceDisplay()) - .addComponent(new MainGameActions(this.game)) - .addComponent(new MainGameExitDisplay()) + .addComponent(new PerformanceDisplay()) + .addComponent(new MainGameActions(this.game)) + .addComponent(new MainGameExitDisplay()) .addComponent(new MainGameLoseDisplay()) - .addComponent(new Terminal()) - .addComponent(inputComponent) - .addComponent(new TerminalDisplay()); + .addComponent(new Terminal()) + .addComponent(inputComponent) + .addComponent(new TerminalDisplay()); ServiceLocator.getEntityService().register(ui); } -} +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/screens/TowerType.java b/source/core/src/main/com/csse3200/game/screens/TowerType.java index 1dd3eadf5..a6f79dbdf 100644 --- a/source/core/src/main/com/csse3200/game/screens/TowerType.java +++ b/source/core/src/main/com/csse3200/game/screens/TowerType.java @@ -1,35 +1,48 @@ package com.csse3200.game.screens; public enum TowerType { - WEAPON("images/towers/turret_deployed.png", "Weapon Tower", - "The Weapon Tower is a simple and basic turret that fires rapid shots at enemies dealing damage over time."), - TNT("images/towers/turret_deployed.png", "TNT Tower", - "The TNT Tower launches explosive projectiles, dealing area damage to groups of enemies."), - DROID("images/towers/turret_deployed.png", "Droid Tower", - "Droid Towers deploy robotic helpers that assist in combat and provide support to nearby turrets."), - WALL("images/towers/turret_deployed.png", "Wall Tower", - "The Wall Tower creates barriers to block enemy paths, slowing down their progress."), - FIRE("images/towers/turret_deployed.png", "Fire Tower", - "The Fire Tower emits flames, causing damage over time to enemies caught in its fiery radius."), - STUN("images/towers/turret_deployed.png", "Stun Tower", - "The Stun Tower releases electric shocks that temporarily immobilize and damage enemies."), - INCOME("images/towers/turret_deployed.png", "Income Tower", - "The Income Tower generates additional in-game currency over time."); - - private final String imagePath; + WEAPON("Weapon Tower", "The Weapon Tower is a simple and basic turret that fires rapid shots at enemies dealing damage over time.", + 0, "0", "images/turret-select/Weapon-Tower-Default.png", "images/turret-select/Weapon-Tower-Clicked.png"), + TNT("TNT Tower", "The TNT Tower launches explosive projectiles, dealing area damage to groups of enemies.", + 1, "0", "images/turret-select/tnt-tower-default.png", "images/turret-select/tnt-tower-clicked.png"), + DROID("Droid Tower", "Droid Towers deploy robotic helpers that assist in combat and provide support to nearby turrets.", + 2, "0", "images/turret-select/droid-tower-default.png", "images/turret-select/droid-tower-clicked.png"), + WALL("Wall Tower", "The Wall Tower creates barriers to block enemy paths, slowing down their progress.", + 3, "100", "images/turret-select/wall-tower-default.png", "images/turret-select/wall-tower-clicked.png"), + FIRE("Fire Tower", "The Fire Tower emits flames, causing damage over time to enemies caught in its fiery radius.", + 4, "0", "images/turret-select/fire-tower-default.png", "images/turret-select/fire-tower-clicked.png"), + STUN("Stun Tower", "The Stun Tower releases electric shocks that temporarily immobilize and damage enemies.", + 5, "1000", "images/turret-select/stun-tower-default.png", "images/turret-select/stun-tower-clicked.png"), + INCOME("Income Tower", "The Income Tower generates additional in-game currency over time.", + 5, "0", "images/turret-select/mine-tower-default.png", "images/turret-select/mine-tower-clicked.png"); + private final String towerName; private final String description; + private final int id; + private final String cost; + private final String defaultImage; + private final String clickedImage; + - TowerType(String imagePath, String towerName, String description) { - this.imagePath = imagePath; + TowerType(String towerName, String description, int id, String cost, String defaultImage, String clickedImage) { this.towerName = towerName; this.description = description; + this.id = id; + this.cost = cost; + this.defaultImage = defaultImage; + this.clickedImage = clickedImage; } - public String getImagePath() { return imagePath; } + public int getID() { return id; } public String getTowerName() { return towerName; } public String getDescription() { return description; } + public String getPrice() { return cost; } + + public String getDefaultImage() {return defaultImage;} + + public String getClickedImage() {return clickedImage;} + } diff --git a/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java b/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java index d120d8854..a679dd30c 100644 --- a/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java +++ b/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java @@ -2,22 +2,33 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.ScreenAdapter; +import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.scenes.scene2d.utils.Drawable; +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; +import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.viewport.ScreenViewport; import com.csse3200.game.GdxGame; import com.badlogic.gdx.scenes.scene2d.ui.Image; +import com.csse3200.game.services.ResourceService; +import com.csse3200.game.services.ServiceLocator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Text; import java.util.*; @@ -26,18 +37,23 @@ public class TurretSelectionScreen extends ScreenAdapter { private static final int MAX_SELECTED_TURRETS = 5; private Stage stage; + private Label descText; private List turretList; private TextButton confirmButton; + private String turretDescriptionText; private GdxGame game; private SpriteBatch batch; + private String turretDescription; + private String turretName; + TextButton turretDescriptionLabel; private Sprite introSprite; private Label message; - private Label turretsPicked; private Table table; + private TextButton descriptionLabel; private static final String TEXTURE = "planets/background.png"; private Set selectedTurrets = new HashSet<>(); @@ -60,12 +76,15 @@ public TurretSelectionScreen(GdxGame game) { turretList.addAll(Arrays.asList(TowerType.values())); // Restrictions can be added to the arrays i.e. map == "Forest" && level == 1 using for loop - Skin skin = new Skin(Gdx.files.internal("flat-earth/skin/flat-earth-ui.json")); message = new Label("Select your turrets", skin); - turretsPicked = new Label("Turrets picked: ", skin); - confirmButton = new TextButton("Continue", skin); + confirmButton = createButton("images/turret-select/imageedit_4_5616741474.png", + "images/ui/Sprites/UI_Glass_Button_Large_Press_01a1.png", "Continue", "", ""); + Drawable pressDrawable = new TextureRegionDrawable(new TextureRegion( + new Texture("images/ui/Sprites/UI_Glass_Button_Large_Press_01a1.png"))); + confirmButton.getStyle().down = pressDrawable; + confirmButton.pad(0,0,6,0); confirmButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { @@ -73,9 +92,20 @@ public void clicked(InputEvent event, float x, float y) { } }); + turretDescriptionLabel = createButton("images/turret-select/imageedit_28_4047785594.png", + "images/turret-select/imageedit_28_4047785594.png", "", "", turretDescriptionText); + + BitmapFont font = new BitmapFont(); + Label.LabelStyle labelStyle = new Label.LabelStyle(); + labelStyle.font = font; // Set your desired BitmapFont + descText = new Label(turretDescriptionText, labelStyle); + descText.setWrap(true); + descText.setWidth(190f); + + turretName = ""; + // Centered the message and turrets label table.add(message).center().colspan(4).row(); - table.add(turretsPicked).center().colspan(4).row(); int towersPerRow = 4; // Set the number of towers to display per row int numRows = (int) Math.ceil((double)turretList.size() / towersPerRow); // Calculate the number of rows @@ -92,23 +122,19 @@ public void clicked(InputEvent event, float x, float y) { Table turretTable = new Table(); turretTable.center(); // Center the contents of the nested table - // Load the turret image - Texture turretTexture = new Texture(Gdx.files.internal(turret.getImagePath())); - Image turretImage = new Image(turretTexture); + descriptionLabel = createButton("images/turret-select/imageedit_15_5627113584.png", + "images/turret-select/imageedit_15_5627113584.png", "Description: ", turretName, ""); - // Add the image to the nested table - turretTable.add(turretImage).pad(10).row(); + //turretDescriptionText = createButton("images/turret-select/imageedit_20_9050213399.png", + // "images/turret-select/imageedit_20_9050213399.png", ) - // Create a label for the turret description - Label turretDescriptionLabel = new Label(turret.getDescription(), skin); - turretDescriptionLabel.setWrap(true); // Wrap text if it's too long - // Add the description label to the nested table - turretTable.add(turretDescriptionLabel).center().width(200).pad(10).row(); // Adjust width if needed + TextButton button = createButton(turret.getDefaultImage(), + turret.getClickedImage(), turret.getPrice(), turret.getTowerName(), turret.getDescription()); + + button.pad(103, 15, 0, 0); + button.addListener(new ClickListener() { - // Create a TextButton for the turret name - TextButton turretButton = new TextButton(turret.getTowerName(), skin); - turretButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { logger.info(String.valueOf(selectedTurrets.size())); @@ -122,20 +148,17 @@ public void clicked(InputEvent event, float x, float y) { selectedTurrets.remove(turret); // You can also change the button appearance to indicate unselection logger.info(selectedTurrets.toString()); - turretsPicked.setText("Turrets picked: " + selectedTurrets.toString()); } else if (selectedTurrets.size() == MAX_SELECTED_TURRETS) { // Turret is not selected, but the max number of turrets has been reached message.setText("You can only select up to 5 turrets."); } else if (selectedTurrets.size() < MAX_SELECTED_TURRETS) { // Turret is not selected, select it selectedTurrets.add(turret); - turretsPicked.setText("Turrets picked: " + selectedTurrets.toString()); logger.info(selectedTurrets.toString()); } else { // Turret is not selected, select it selectedTurrets.add(turret); - turretsPicked.setText("Turrets picked: " + selectedTurrets.toString()); //logger.info(selectedTurrets.toString()); // You can change the button appearance to indicate selection @@ -143,8 +166,8 @@ public void clicked(InputEvent event, float x, float y) { } }); - // Add the turret name button to the nested table - turretTable.add(turretButton).center(); + // Add the image to the nested table + turretTable.add(button).pad(10).row(); // Add the nested table to the main table table.add(turretTable).pad(10).center(); @@ -161,6 +184,21 @@ public void clicked(InputEvent event, float x, float y) { // Centered the "continue" button table.add(confirmButton).center().colspan(4).padBottom(20).row(); + descriptionLabel.setPosition(Gdx.graphics.getWidth() - descriptionLabel.getWidth(), Gdx.graphics.getHeight() - descriptionLabel.getHeight()); + float turretDescriptionLabelX = Gdx.graphics.getWidth() - turretDescriptionLabel.getWidth() - 11; + float turretDescriptionLabelY = Gdx.graphics.getHeight() - descriptionLabel.getHeight() - turretDescriptionLabel.getHeight() - 7; // Adjusted vertical position + +// Set the position for turretDescriptionLabel + turretDescriptionLabel.setPosition(turretDescriptionLabelX, turretDescriptionLabelY); + descText.setPosition(turretDescriptionLabelX + 18, turretDescriptionLabelY + 70); + +// Add the actors to the stage + stage.addActor(turretDescriptionLabel); + stage.addActor(descriptionLabel); + stage.addActor(descText); + + + // Center the table within the stage table.center(); stage.addActor(table); @@ -172,16 +210,84 @@ public void clicked(InputEvent event, float x, float y) { public void render(float delta) { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + batch.begin(); introSprite.draw(batch); batch.end(); - stage.draw(); + + stage.act(); // Update the stage + stage.draw(); // Draw the stage } public List getTurretList() { return turretList; } + private TextButton createButton(String defaultImageFilePath, String alternateImageFilePath, String cost, + String towerName, String turretDesc) { + Drawable defaultDrawable = new TextureRegionDrawable(new TextureRegion(new Texture(defaultImageFilePath))); + Drawable alternateDrawable = new TextureRegionDrawable(new TextureRegion(new Texture(alternateImageFilePath))); + + TextButton.TextButtonStyle buttonStyle = new TextButton.TextButtonStyle(); + buttonStyle.font = new BitmapFont(); // Set your desired font + buttonStyle.up = defaultDrawable; // Default state + + // Create button + TextButton tb = new TextButton(cost, buttonStyle); + + final boolean[] isDefaultImage = {true}; // Keep track of the image state + + tb.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + + // Toggle the image + if (isDefaultImage[0]) { + tb.getStyle().up = alternateDrawable; + } else { + tb.getStyle().up = defaultDrawable; + } + + // Update the image state + isDefaultImage[0] = !isDefaultImage[0]; + } + }); + + // Add a hover listener to update turretName when hovered over + tb.addListener(new InputListener() { + @Override + public void enter(InputEvent event, float x, float y, int pointer, com.badlogic.gdx.scenes.scene2d.Actor fromActor) { + super.enter(event, x, y, pointer, fromActor); + turretDescription = towerName; // Update turretDescription when hovering over the button + turretDescriptionText = turretDesc; + updateDescriptionLabel(); + updateDescriptionText(); + } + + @Override + public void exit(InputEvent event, float x, float y, int pointer, com.badlogic.gdx.scenes.scene2d.Actor toActor) { + super.exit(event, x, y, pointer, toActor); + turretDescription = ""; // Reset turretDescription when exiting the button + turretDescriptionText = ""; + updateDescriptionLabel(); + updateDescriptionText(); + } + }); + + tb.setDisabled(true); + + return tb; + } + + private void updateDescriptionLabel() { + descriptionLabel.setText("Description: " + turretDescription); + } + + private void updateDescriptionText() { + descText.setText(turretDescriptionText); + } + @Override public void dispose() { stage.dispose(); diff --git a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java index 703a1299b..973937fb1 100644 --- a/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java +++ b/source/core/src/test/com/csse3200/game/components/tower/TowerUpgraderComponentTest.java @@ -60,7 +60,7 @@ void increaseFireRate() { entity.create(); entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, 60); verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, 60); - assertEquals(0.5, towerCombatTask.getFireRateInterval()); + assertEquals(((float) 1 /12), towerCombatTask.getFireRateInterval()); } @Test @@ -74,8 +74,8 @@ void divideByZeroDefaultToIgnore() { entity.addComponent(aiTaskComponent); towerCombatTask.start(); entity.create(); - entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, -60); - verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, -60); - assertEquals(1., towerCombatTask.getFireRateInterval()); + entity.getEvents().trigger("upgradeTower", TowerUpgraderComponent.UPGRADE.FIRERATE, 60); + verify(towerUpgraderComponent).upgradeTower(TowerUpgraderComponent.UPGRADE.FIRERATE, 60); + assertEquals((1/12f), towerCombatTask.getFireRateInterval()); } } diff --git a/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java index 355828fad..e07093e74 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/NPCFactoryTest.java @@ -56,7 +56,7 @@ public class NPCFactoryTest { private final String[] texture = { "images/towers/turret_deployed.png", "images/towers/turret01.png", - "images/towers/wallTower.png" + "images/towers/wall_tower.png" }; private final String[] atlas = { "images/towers/turret01.atlas", 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 4d6188c88..7d2f75c65 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 @@ -38,7 +38,7 @@ public class TowerFactoryTest { private String[] texture = { "images/towers/turret_deployed.png", "images/towers/turret01.png", - "images/towers/wallTower.png", + "images/towers/wall_tower.png", "images/towers/fire_tower_atlas.png", "images/towers/stun_tower.png", "images/towers/DroidTower.png",