From 893923cc5690c43b1b55d2c265e575af9e8a6f1c Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Sat, 12 Apr 2025 23:48:06 +0200
Subject: [PATCH] added towers and added tower spawn mechanics

---
 .../types/towerdefense/EnemyFactory.java      | 11 +++++
 .../types/towerdefense/Towerdefense.java      | 18 ++++++-
 .../types/towerdefense/TowerdefenseRoom.java  | 49 ++++++++++++-------
 .../types/towerdefense/towers/BlazeTower.java | 15 ++++++
 .../towerdefense/towers/SkeletonTower.java    | 10 ++--
 .../types/towerdefense/towers/Tower.java      | 16 +++---
 .../towerdefense/towers/ZombieTower.java      | 20 ++++++++
 7 files changed, 106 insertions(+), 33 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/BlazeTower.java
 create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ZombieTower.java

diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java
index a1a6d49..02b23a0 100644
--- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java
@@ -5,6 +5,16 @@ import net.minestom.server.entity.EntityType;
 import net.minestom.server.entity.attribute.Attribute;
 
 record EnemyFactory(EntityType entityType, float health, double speed) {
+    /**
+     * Factory for a tower defense enemy.
+     * @param entityType type of enemy
+     * @param health base health, between 0 and 1024
+     * @param speed walk speed of enemy
+     */
+    public EnemyFactory {
+        if(health > 1024 || health <= 0) throw new IllegalArgumentException("Enemy health has to be between 0 and 1024");
+    }
+
     public EnemyFactory(EntityType entityType) {
         this(entityType, 20, 0.3);
     }
@@ -12,6 +22,7 @@ record EnemyFactory(EntityType entityType, float health, double speed) {
     public EntityCreature buildEntity() {
         EntityCreature entity = new EntityCreature(this.entityType);
         entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(this.speed);
+        entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(this.health);
         entity.setHealth(this.health);
         return entity;
     }
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/Towerdefense.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/Towerdefense.java
index f51b4a5..8880ac0 100644
--- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/Towerdefense.java
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/Towerdefense.java
@@ -3,15 +3,21 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
 import eu.mhsl.minenet.minigames.instance.Dimension;
 import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
 import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
+import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.BlazeTower;
+import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.SkeletonTower;
+import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
+import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.ZombieTower;
 import eu.mhsl.minenet.minigames.score.LastWinsScore;
 import net.minestom.server.coordinate.Pos;
 import net.minestom.server.entity.EntityType;
 import net.minestom.server.entity.Player;
 import net.minestom.server.instance.batch.AbsoluteBlockBatch;
 import net.minestom.server.instance.block.Block;
+import net.minestom.server.item.Material;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 
 public class Towerdefense extends StatelessGame {
@@ -19,6 +25,11 @@ public class Towerdefense extends StatelessGame {
     private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
     private final List<Pos> mazePath = new ArrayList<>();
     private final List<TowerdefenseRoom> instances = new ArrayList<>();
+    private final Map<Material, Class<? extends Tower>> availableTowers = Map.of(
+        Material.SKELETON_SPAWN_EGG, SkeletonTower.class,
+        Material.ZOMBIE_SPAWN_EGG, ZombieTower.class,
+        Material.BLAZE_SPAWN_EGG, BlazeTower.class
+    );
 
     private static final int pathLength = 10;
 
@@ -79,8 +90,13 @@ public class Towerdefense extends StatelessGame {
         p.setInstance(newRoom);
         newRoom.startWave(List.of(
             new GroupFactory(new EnemyFactory(EntityType.VILLAGER, 20, 0.1), 5, 800),
-            new GroupFactory(new EnemyFactory(EntityType.BEE, 10, 1), 3, 400)
+            new GroupFactory(new EnemyFactory(EntityType.BEE, 10, 1), 3, 400),
+            new GroupFactory(new EnemyFactory(EntityType.SNIFFER, 1000, 0.02), 1, 0)
         ));
         return false;
     }
+
+    public Map<Material, Class<? extends Tower>> getAvailableTowers() {
+        return this.availableTowers;
+    }
 }
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/TowerdefenseRoom.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/TowerdefenseRoom.java
index 8dca24d..786efaf 100644
--- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/TowerdefenseRoom.java
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/TowerdefenseRoom.java
@@ -2,7 +2,6 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
 
 import eu.mhsl.minenet.minigames.instance.Dimension;
 import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
-import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.SkeletonTower;
 import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
 import eu.mhsl.minenet.minigames.util.BatchUtil;
 import eu.mhsl.minenet.minigames.util.CommonEventHandles;
@@ -13,7 +12,10 @@ import net.minestom.server.entity.*;
 import net.minestom.server.entity.attribute.Attribute;
 import net.minestom.server.entity.damage.DamageType;
 import net.minestom.server.event.entity.EntityDeathEvent;
+import net.minestom.server.event.inventory.InventoryPreClickEvent;
 import net.minestom.server.event.item.ItemDropEvent;
+import net.minestom.server.event.player.PlayerEntityInteractEvent;
+import net.minestom.server.event.player.PlayerSwapItemEvent;
 import net.minestom.server.event.player.PlayerTickEvent;
 import net.minestom.server.event.player.PlayerUseItemEvent;
 import net.minestom.server.instance.InstanceContainer;
@@ -40,13 +42,15 @@ public class TowerdefenseRoom extends InstanceContainer {
         this.player.setGameMode(GameMode.ADVENTURE);
         this.player.setAllowFlying(true);
         this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(200);
-        this.player.getInventory().setItemStack(0, ItemStack.of(Material.SKELETON_SPAWN_EGG));
+
+        this.game.getAvailableTowers().keySet().forEach(material -> this.player.getInventory().addItemStack(ItemStack.of(material)));
 
         this.setGenerator(new MazeGenerator());
         BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});
 
         this.cursor = new Entity(EntityType.ARMOR_STAND);
         this.cursor.setInstance(this);
+        this.cursor.setBoundingBox(0,0,0);
 
         this.eventNode()
             .addListener(EntityDeathEvent.class, event -> {
@@ -54,19 +58,27 @@ public class TowerdefenseRoom extends InstanceContainer {
                 this.enemies.remove(enemy);
             })
             .addListener(PlayerTickEvent.class, this::setCursorPosition)
-            .addListener(PlayerUseItemEvent.class, this::setTower)
-            .addListener(ItemDropEvent.class, CommonEventHandles::cancel);
+            .addListener(PlayerUseItemEvent.class, event -> this.setTower(event.getItemStack().material()))
+            .addListener(PlayerEntityInteractEvent.class, event -> this.setTower(event.getPlayer().getItemInMainHand().material()))
+            .addListener(ItemDropEvent.class, CommonEventHandles::cancel)
+            .addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel)
+            .addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel);
     }
 
-    private void setTower(PlayerUseItemEvent event) {
-        if(event.getItemStack().material().equals(Material.SKELETON_SPAWN_EGG)) {
-            Point newPosition = this.player.getTargetBlockPosition(20);
-            if(newPosition == null) return;
-            if(!this.getBlock(newPosition).equals(Block.BLACK_WOOL)) return;
-            this.setBlock(newPosition, Block.BLUE_WOOL);
-            newPosition = newPosition.add(0.5,1,0.5);
-            SkeletonTower tower = new SkeletonTower();
-            tower.setInstance(this, newPosition);
+    private void setTower(Material usedMaterial) {
+        if(!this.canPlaceTower()) return;
+        try {
+            Tower tower = this.game.getAvailableTowers().entrySet().stream()
+                .filter(materialClassEntry -> materialClassEntry.getKey().equals(usedMaterial))
+                .findFirst()
+                .orElseThrow()
+                .getValue()
+                .getConstructor()
+                .newInstance();
+            this.setBlock(this.cursor.getPosition().sub(0, 0.5, 0), Block.BLUE_WOOL);
+            tower.setInstance(this, this.cursor.getPosition());
+            this.towers.add(tower);
+        } catch (Exception ignored) {
         }
     }
 
@@ -77,14 +89,13 @@ public class TowerdefenseRoom extends InstanceContainer {
     private void setCursorPosition(PlayerTickEvent event) {
         Point newPosition = this.player.getTargetBlockPosition(20);
         if(newPosition == null) return;
-        if(this.getBlock(newPosition).equals(Block.BLACK_WOOL)) {
-            this.cursor.setInvisible(false);
-        } else {
-            this.cursor.setInvisible(true);
-            return;
-        }
         newPosition = newPosition.add(0.5,1,0.5);
         this.cursor.teleport(new Pos(newPosition));
+        this.cursor.setInvisible(!this.canPlaceTower());
+    }
+
+    private boolean canPlaceTower() {
+        return this.getBlock(this.cursor.getPosition().sub(0, 1, 0)).equals(Block.BLACK_WOOL);
     }
 
     void startWave(List<GroupFactory> enemyGroups) {
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/BlazeTower.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/BlazeTower.java
new file mode 100644
index 0000000..b652667
--- /dev/null
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/BlazeTower.java
@@ -0,0 +1,15 @@
+package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
+
+import net.minestom.server.entity.EntityCreature;
+import net.minestom.server.entity.EntityType;
+
+public class BlazeTower extends Tower {
+    public BlazeTower() {
+        super(EntityType.BLAZE, 2, 1, 20);
+    }
+
+    @Override
+    protected void attack(EntityCreature enemy) {
+        this.shootProjectile(enemy, EntityType.FIREBALL, 2, 0);
+    }
+}
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/SkeletonTower.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/SkeletonTower.java
index 30c012c..82ffee3 100644
--- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/SkeletonTower.java
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/SkeletonTower.java
@@ -2,23 +2,23 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.tow
 
 import net.minestom.server.entity.EntityCreature;
 import net.minestom.server.entity.EntityType;
-import net.minestom.server.entity.attribute.Attribute;
 import net.minestom.server.item.ItemStack;
 import net.minestom.server.item.Material;
 
 public class SkeletonTower extends Tower {
     public SkeletonTower() {
-        super(EntityType.SKELETON, 1, 300, 20);
+        super(EntityType.SKELETON, 1, 3, 15);
         this.setItemInMainHand(ItemStack.of(Material.BOW));
     }
 
     @Override
-    protected void shoot(EntityCreature enemy) {
+    protected void attack(EntityCreature enemy) {
         this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
     }
 
     @Override
-    protected void onEnemyHit(EntityCreature enemy) {
-        enemy.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue()*0.5);
+    protected void onProjectileHit(EntityCreature enemy) {
+        super.onProjectileHit(enemy);
+//        enemy.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue()*0.5);
     }
 }
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/Tower.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/Tower.java
index c26e09c..79895c5 100644
--- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/Tower.java
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/Tower.java
@@ -45,22 +45,23 @@ public abstract class Tower extends EntityCreature {
         }
     }
 
+    private final static int damageDivider = 10;
     private Priority priority = Priority.FIRST;
     protected float damage;
     protected int range;
     protected TaskSchedule attackDelay;
     private final Task shootTask;
 
-    public Tower(@NotNull EntityType entityType, float damage, int attackDelay, int range) {
+    public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
         super(entityType);
-        this.damage = damage;
+        this.damage = (float) damage / damageDivider;
         this.range = range;
-        this.attackDelay = TaskSchedule.millis(attackDelay);
+        this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond));
         this.shootTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
             EntityCreature nextEnemy = this.getNextEnemy();
             if(nextEnemy == null) return this.attackDelay;
             this.lookAt(nextEnemy);
-            this.shoot(nextEnemy);
+            this.attack(nextEnemy);
             return this.attackDelay;
         }, TaskSchedule.immediate());
     }
@@ -92,14 +93,13 @@ public abstract class Tower extends EntityCreature {
                 if(!(event.getTarget() instanceof EntityCreature target)) return;
                 if(!this.getRoomInstance().getEnemies().contains(target)) return;
                 target.damage(DamageType.PLAYER_ATTACK, this.damage);
-                this.onEnemyHit(target);
+                this.onProjectileHit(target);
                 projectile.remove();
             })
             .addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove());
     }
 
-    protected void onEnemyHit(EntityCreature enemy) {
-
+    protected void onProjectileHit(EntityCreature enemy) {
     }
 
     protected TowerdefenseRoom getRoomInstance() {
@@ -146,5 +146,5 @@ public abstract class Tower extends EntityCreature {
         super.remove(permanent);
     }
 
-    protected abstract void shoot(EntityCreature enemy);
+    protected abstract void attack(EntityCreature enemy);
 }
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ZombieTower.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ZombieTower.java
new file mode 100644
index 0000000..84e4544
--- /dev/null
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ZombieTower.java
@@ -0,0 +1,20 @@
+package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
+
+import net.minestom.server.entity.EntityCreature;
+import net.minestom.server.entity.EntityType;
+import net.minestom.server.entity.damage.DamageType;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+
+public class ZombieTower extends Tower {
+    public ZombieTower() {
+        super(EntityType.ZOMBIE, 3, 0.7, 4);
+        this.setItemInMainHand(ItemStack.of(Material.IRON_SWORD));
+    }
+
+    @Override
+    protected void attack(EntityCreature enemy) {
+        this.swingMainHand();
+        enemy.damage(DamageType.PLAYER_ATTACK, this.damage);
+    }
+}