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); + } +}