added wave mechanic

This commit is contained in:
2025-07-19 01:10:57 +02:00
parent 3b55f16b24
commit b5d6d7bac4
6 changed files with 115 additions and 27 deletions

View File

@ -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<Pos> mazePath = new ArrayList<>();
private final List<TowerdefenseRoom> instances = new ArrayList<>();
private final WaveGenerator waveGenerator;
private Task waveTask;
private final Map<Material, Class<? extends Tower>> 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<TowerdefenseRoom> 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<Class<? extends Tower>, Integer> getPrices() {

View File

@ -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);

View File

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

View File

@ -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(() -> {

View File

@ -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<EnemyFactory, Integer> 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<EnemyFactory> 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<EnemyFactory> chooseEnemies() {
int strength = this.getCurrentLevelStrength();
final List<EnemyFactory> usedEnemies = new ArrayList<>();
while(strength > 0) {
int finalStrength = strength;
Map.Entry<EnemyFactory, Integer> 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;
}
}

View File

@ -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;