From b5d6d7bac4cb550daa1883bc026ee20c1af006cc Mon Sep 17 00:00:00 2001 From: lars Date: Sat, 19 Jul 2025 01:10:57 +0200 Subject: [PATCH] added wave mechanic --- .../types/towerdefense/Towerdefense.java | 30 +++++-- .../types/towerdefense/TowerdefenseRoom.java | 19 +---- .../{ => enemies}/EnemyFactory.java | 8 +- .../{ => enemies}/GroupFactory.java | 5 +- .../towerdefense/enemies/WaveGenerator.java | 78 +++++++++++++++++++ .../towerdefense/towers/ShootingTower.java | 2 +- 6 files changed, 115 insertions(+), 27 deletions(-) rename src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/{ => enemies}/EnemyFactory.java (83%) rename src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/{ => enemies}/GroupFactory.java (72%) create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/WaveGenerator.java 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 86be4c3..fc4f180 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 @@ -2,18 +2,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.enemies.WaveGenerator; 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.MinecraftServer; 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 net.minestom.server.timer.Task; +import net.minestom.server.timer.TaskSchedule; import java.util.ArrayList; import java.util.List; @@ -25,6 +28,8 @@ public class Towerdefense extends StatelessGame { private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch(); private final List mazePath = new ArrayList<>(); private final List instances = new ArrayList<>(); + private final WaveGenerator waveGenerator; + private Task waveTask; private final Map> availableTowers = Map.of( Material.SKELETON_SPAWN_EGG, SkeletonTower.class, Material.ZOMBIE_SPAWN_EGG, ZombieTower.class, @@ -35,7 +40,6 @@ public class Towerdefense extends StatelessGame { SkeletonTower.class, 3, BlazeTower.class, 8 ); - private static final int pathLength = 10; public Towerdefense() { @@ -43,12 +47,17 @@ public class Towerdefense extends StatelessGame { this.setGenerator(new MazeGenerator()); this.generateMaze(); + + this.waveGenerator = new WaveGenerator(this); } @Override protected void checkAbandoned() { this.scheduleNextTick((instance) -> { - if(this.instances.stream().allMatch(room -> room.getPlayers().isEmpty()) && this.getPlayers().isEmpty()) this.unload(); + if(this.instances.stream().allMatch(room -> room.getPlayers().isEmpty()) && this.getPlayers().isEmpty()) { + this.waveTask.cancel(); + this.unload(); + } }); } @@ -58,10 +67,12 @@ public class Towerdefense extends StatelessGame { TowerdefenseRoom newRoom = new TowerdefenseRoom(player, this); this.instances.add(newRoom); player.setInstance(newRoom, new Pos(0, 1, 0)); - newRoom.startWave(List.of( - new GroupFactory(new EnemyFactory(EntityType.VILLAGER, 2, 0.1), 1, 800) - )); }); + this.waveTask = MinecraftServer.getSchedulerManager().scheduleTask( + this.waveGenerator::startNextWave, + TaskSchedule.seconds(3), + TaskSchedule.seconds(25) + ); } private void generateMaze() { @@ -107,9 +118,14 @@ public class Towerdefense extends StatelessGame { return this.mazePath; } + public List getInstances() { + return this.instances; + } + @Override protected boolean onPlayerJoin(Player p) { - return false; + MinecraftServer.getSchedulerManager().scheduleNextTick(() -> p.teleport(new Pos((long) -this.getPlayers().size(), 1, 0))); + return super.onPlayerJoin(p); } public Map, Integer> getPrices() { 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 67552ec..0c26667 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 @@ -1,6 +1,7 @@ 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.enemies.GroupFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower; import eu.mhsl.minenet.minigames.util.BatchUtil; @@ -25,7 +26,6 @@ import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemAnimation; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import net.minestom.server.timer.TaskSchedule; import org.jetbrains.annotations.Nullable; import java.time.Duration; @@ -72,19 +72,6 @@ public class TowerdefenseRoom extends InstanceContainer { this.cursor.setInstance(this); this.cursor.setBoundingBox(0,0,0); - MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of( - new GroupFactory(new EnemyFactory(EntityType.VILLAGER, 3, 0.1), 1, 800) - )), TaskSchedule.seconds(20), TaskSchedule.stop()); - MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of( - new GroupFactory(new EnemyFactory(EntityType.VILLAGER, 3, 0.1), 2, 800) - )), TaskSchedule.seconds(50), TaskSchedule.stop()); - MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of( - new GroupFactory(new EnemyFactory(EntityType.VILLAGER), 2, 800) - )), TaskSchedule.seconds(90), TaskSchedule.stop()); - MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of( - new GroupFactory(new EnemyFactory(EntityType.EGG), 200, 800) - )), TaskSchedule.seconds(150), TaskSchedule.stop()); - this.eventNode() .addListener(EntityDeathEvent.class, event -> { if(!(event.getEntity() instanceof EntityCreature enemy)) return; @@ -123,7 +110,7 @@ public class TowerdefenseRoom extends InstanceContainer { EntityProjectile projectile = new EntityProjectile(p, EntityType.SPECTRAL_ARROW); projectile.setView(p.getPosition().yaw(), p.getPosition().pitch()); projectile.setNoGravity(true); - projectile.setAerodynamics(new Aerodynamics(0, 1, 0)); + projectile.setAerodynamics(new Aerodynamics(0, 1, 1)); Vec projectileVelocity = p.getPosition().direction().normalize().mul(20); projectile.setVelocity(projectileVelocity); @@ -224,6 +211,8 @@ public class TowerdefenseRoom extends InstanceContainer { if(this.player.getHealth() - damage <= 0) { this.getEnemies().forEach(Entity::remove); this.towers.forEach(Entity::remove); + this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).attribute().defaultValue()); + this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).attribute().defaultValue()); this.player.setInstance(this.game).thenRun(() -> MinecraftServer.getInstanceManager().unregisterInstance(this)); this.player.heal(); this.game.getScore().insertResult(this.player); 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/enemies/EnemyFactory.java similarity index 83% rename from src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java rename to src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/EnemyFactory.java index a869dd6..cd8921c 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/enemies/EnemyFactory.java @@ -1,10 +1,10 @@ -package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense; +package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies; import net.minestom.server.entity.EntityCreature; import net.minestom.server.entity.EntityType; import net.minestom.server.entity.attribute.Attribute; -record EnemyFactory(EntityType entityType, float health, double speed) { +public record EnemyFactory(EntityType entityType, float health, double speed) { /** * Factory for a tower defense enemy. * @param entityType type of enemy @@ -26,4 +26,8 @@ record EnemyFactory(EntityType entityType, float health, double speed) { entity.setHealth(this.health); return entity; } + + public int getStrength() { + return (int) Math.floor(this.health * this.speed * 5); + } } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/GroupFactory.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/GroupFactory.java similarity index 72% rename from src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/GroupFactory.java rename to src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/GroupFactory.java index 94decd9..021864b 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/GroupFactory.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/GroupFactory.java @@ -1,9 +1,10 @@ -package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense; +package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies; +import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom; import net.minestom.server.MinecraftServer; import net.minestom.server.timer.TaskSchedule; -record GroupFactory(EnemyFactory enemyFactory, int count, long delay) { +public record GroupFactory(EnemyFactory enemyFactory, int count, long delay) { public void summonGroup(TowerdefenseRoom instance) { for (int i = 0; i < this.count; i++) { MinecraftServer.getSchedulerManager().scheduleTask(() -> { diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/WaveGenerator.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/WaveGenerator.java new file mode 100644 index 0000000..41a6757 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/enemies/WaveGenerator.java @@ -0,0 +1,78 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies; + +import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.Towerdefense; +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.EntityType; +import net.minestom.server.timer.TaskSchedule; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; + +public class WaveGenerator { + final Towerdefense game; + int currentLevel; + Random random = new Random(); + private final Map availableEnemies = Map.of( + new EnemyFactory(EntityType.PIG, 2, 0.1), new EnemyFactory(EntityType.PIG, 2, 0.1).getStrength(), + new EnemyFactory(EntityType.VILLAGER, 6, 0.1), new EnemyFactory(EntityType.VILLAGER, 6, 0.1).getStrength(), + new EnemyFactory(EntityType.SHEEP, 4, 0.2), new EnemyFactory(EntityType.SHEEP, 4, 0.2).getStrength(), + new EnemyFactory(EntityType.COW, 10, 0.1), new EnemyFactory(EntityType.COW, 10, 0.1).getStrength(), + new EnemyFactory(EntityType.CAMEL, 10, 0.2), new EnemyFactory(EntityType.CAMEL, 10, 0.2).getStrength(), + new EnemyFactory(EntityType.ARMADILLO, 20, 0.3), new EnemyFactory(EntityType.ARMADILLO, 20, 0.3).getStrength(), + new EnemyFactory(EntityType.CHICKEN, 8, 0.6), new EnemyFactory(EntityType.CHICKEN, 8, 0.6).getStrength() + ); + + public WaveGenerator(Towerdefense game) { + this(game, 1); + } + + public WaveGenerator(Towerdefense game, int startLevel) { + this.game = game; + this.currentLevel = startLevel - 1; + } + + public void startNextWave() { + this.currentLevel += 1; + List enemyList = this.chooseEnemies(); + Collections.shuffle(enemyList); + System.out.println(enemyList); + + int averageDelay = Math.min(20000 / enemyList.size(), 1500); + AtomicInteger delay = new AtomicInteger(0); + enemyList.forEach(enemy -> { + delay.addAndGet(averageDelay); + MinecraftServer.getSchedulerManager().scheduleTask( + () -> this.spawnEnemy(enemyList.removeFirst()), + TaskSchedule.millis(delay.get() + this.random.nextInt(averageDelay-200, averageDelay+200)), + TaskSchedule.stop() + ); + }); + } + + private void spawnEnemy(EnemyFactory enemy) { + this.game.getInstances().forEach(instance -> instance.addEnemy(enemy.buildEntity())); + } + + private int getCurrentLevelStrength() { + return (int) (0.5 * Math.pow(2, this.currentLevel)); + } + + private List chooseEnemies() { + int strength = this.getCurrentLevelStrength(); + final List usedEnemies = new ArrayList<>(); + + while(strength > 0) { + int finalStrength = strength; + Map.Entry chosenEnemy = this.availableEnemies.entrySet().stream() + .filter(e -> e.getValue() <= finalStrength) + .sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2)) + .findAny() + .get(); + usedEnemies.add(chosenEnemy.getKey()); + strength -= chosenEnemy.getKey().getStrength(); + } + + return usedEnemies; + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ShootingTower.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ShootingTower.java index 41f2994..15bbccd 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ShootingTower.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ShootingTower.java @@ -23,7 +23,7 @@ public abstract class ShootingTower extends Tower { EntityProjectile projectile = new EntityProjectile(this, projectileType); projectile.setView(this.getPosition().yaw(), this.getPosition().pitch()); projectile.setNoGravity(true); - projectile.setAerodynamics(new Aerodynamics(0, 1, 0)); + projectile.setAerodynamics(new Aerodynamics(0, 1, 1)); Pos startingPoint = this.getPosition().add(0, this.getEyeHeight(), 0); double enemySpeed = enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue() / 0.05;