From b6c3f2c1e0efe800e404d709cf55d6e37d710065 Mon Sep 17 00:00:00 2001 From: jannis Date: Mon, 9 Nov 2026 21:35:28 +0100 Subject: [PATCH] rebalance towerdefense: welcome title, spectator switcher, projectile/aim fixes, HP scaling --- build.gradle | 1 - .../types/towerdefense/Towerdefense.java | 73 +++++++++++- .../types/towerdefense/TowerdefenseRoom.java | 110 +++++++++++++----- .../towerdefense/enemies/WaveGenerator.java | 51 ++++++-- .../towerdefense/towers/EnderDragonTower.java | 2 +- .../towerdefense/towers/ShootingTower.java | 18 ++- .../towerdefense/towers/SkeletonTower.java | 2 +- .../types/towerdefense/towers/Tower.java | 34 ++++-- src/main/resources/config.yml | 3 +- 9 files changed, 231 insertions(+), 63 deletions(-) diff --git a/build.gradle b/build.gradle index 9a890d3..0a8d395 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,3 @@ - //for jar packaging see https://github.com/Protonull/BasicMinestomServer/blob/master/build.gradle.kts plugins { 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 b35f0c7..89256a8 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 @@ -6,27 +6,38 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enem import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.*; import eu.mhsl.minenet.minigames.score.LastWinsScore; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.title.Title; import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Pos; 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.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.sound.SoundEvent; import net.minestom.server.timer.Task; import net.minestom.server.timer.TaskSchedule; +import java.time.Duration; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; public class Towerdefense extends StatelessGame { private final Random random = new Random(); private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch(); private final List mazePath = new ArrayList<>(); private final List instances = new ArrayList<>(); + private final Set spectators = new HashSet<>(); private final WaveGenerator waveGenerator; private Task waveTask; + public static final Component SWITCHER_NAME = Component.text("Spieler wechseln", NamedTextColor.AQUA); private final Map> availableTowers = Map.of( Material.SKELETON_SPAWN_EGG, SkeletonTower.class, Material.ZOMBIE_SPAWN_EGG, ZombieTower.class, @@ -59,7 +70,9 @@ public class Towerdefense extends StatelessGame { @Override protected void checkAbandoned() { this.scheduleNextTick((instance) -> { - if(this.instances.stream().allMatch(room -> room.getPlayers().isEmpty()) && this.getPlayers().isEmpty()) { + boolean noAlive = this.instances.stream().allMatch(room -> room.getPlayers().isEmpty()); + boolean noSpectators = this.spectators.stream().noneMatch(Player::isOnline); + if(noAlive && this.getPlayers().isEmpty() && noSpectators) { this.waveTask.cancel(); this.unload(); } @@ -71,15 +84,67 @@ public class Towerdefense extends StatelessGame { this.getPlayers().forEach(player -> { TowerdefenseRoom newRoom = new TowerdefenseRoom(player, this); this.instances.add(newRoom); - player.setInstance(newRoom, new Pos(0, 1, 0)); + player.setInstance(newRoom, new Pos(0, 1, 0)).thenRun(() -> this.welcome(player)); }); this.waveTask = MinecraftServer.getSchedulerManager().scheduleTask( this.waveGenerator::startNextWave, - TaskSchedule.seconds(3), - TaskSchedule.seconds(25) + TaskSchedule.seconds(8), + TaskSchedule.seconds(15) ); } + private void welcome(Player player) { + player.showTitle(Title.title( + Component.text("Tower Defense", NamedTextColor.GOLD), + Component.text("Verteidige dich gegen die Mobs!", NamedTextColor.YELLOW), + Title.Times.times(Duration.ofMillis(400), Duration.ofSeconds(3), Duration.ofMillis(600)) + )); + player.playSound(Sound.sound(SoundEvent.UI_TOAST_CHALLENGE_COMPLETE, Sound.Source.MASTER, 1f, 1f)); + player.sendMessage(Component.text("Die erste Welle kommt in 8 Sekunden...", NamedTextColor.GRAY)); + } + + public List getAliveInstances() { + synchronized (this.instances) { + return new ArrayList<>(this.instances); + } + } + + public void removeInstance(TowerdefenseRoom room) { + synchronized (this.instances) { + this.instances.remove(room); + } + } + + public Set getSpectators() { + return this.spectators; + } + + public void beginSpectating(Player player) { + this.spectators.add(player); + ItemStack switcher = ItemStack.of(Material.COMPASS).withCustomName(SWITCHER_NAME); + player.getInventory().clear(); + player.getInventory().setItemStack(4, switcher); + player.sendMessage(Component.text("Du bist gestorben! Nutze den Kompass, um andere Spieler zu beobachten.", NamedTextColor.RED)); + this.cycleSpectator(player); + } + + public void cycleSpectator(Player player) { + List alive = this.getAliveInstances(); + if(alive.isEmpty()) { + player.sendMessage(Component.text("Kein Spieler mehr am Leben.", NamedTextColor.GRAY)); + return; + } + TowerdefenseRoom current = alive.stream() + .filter(r -> r == player.getInstance()) + .findFirst().orElse(null); + int nextIdx = current == null ? 0 : (alive.indexOf(current) + 1) % alive.size(); + TowerdefenseRoom target = alive.get(nextIdx); + player.setInstance(target, new Pos(0, 2, 0)).thenRun(() -> { + player.setGameMode(net.minestom.server.entity.GameMode.SPECTATOR); + player.sendActionBar(Component.text("Du beobachtest: " + target.getPlayer().getUsername(), NamedTextColor.AQUA)); + }); + } + private void generateMaze() { Pos position = new Pos(0, 0, 0); this.addMazePosition(position, Block.GREEN_WOOL); 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 0c26667..7bae398 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 @@ -10,6 +10,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.collision.Aerodynamics; +import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.*; import net.minestom.server.entity.attribute.Attribute; @@ -42,6 +43,7 @@ public class TowerdefenseRoom extends InstanceContainer { private final List towers = new ArrayList<>(); private final Cursor cursor; private int money = 0; + private boolean dead = false; public TowerdefenseRoom(Player player, Towerdefense game) { super(UUID.randomUUID(), Dimension.OVERWORLD.key); @@ -60,10 +62,12 @@ public class TowerdefenseRoom extends InstanceContainer { this.player.getInventory().addItemStack( ItemStack.of(Material.BARRIER).withCustomName(Component.text("Löschen", TextColor.color(255,0,0))) ); - this.game.getAvailableTowers().forEach((material, tower) -> { - int price = this.game.getPrices().get(tower); - this.player.getInventory().addItemStack(ItemStack.of(material).withMaxStackSize(price).withAmount(price)); - }); + this.game.getAvailableTowers().entrySet().stream() + .sorted(java.util.Comparator.comparingInt(e -> this.game.getPrices().get(e.getValue()))) + .forEach(entry -> { + int price = this.game.getPrices().get(entry.getValue()); + this.player.getInventory().addItemStack(ItemStack.of(entry.getKey()).withMaxStackSize(price).withAmount(price)); + }); this.setGenerator(new MazeGenerator()); BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {}); @@ -77,11 +81,14 @@ public class TowerdefenseRoom extends InstanceContainer { if(!(event.getEntity() instanceof EntityCreature enemy)) return; this.enemies.remove(enemy); }) - .addListener(PlayerTickEvent.class, event -> this.cursor.updateCursorPosition(this.player)) - .addListener(PlayerUseItemEvent.class, event -> this.useItem()) - .addListener(PlayerUseItemOnBlockEvent.class, event -> this.useItem()) - .addListener(PlayerEntityInteractEvent.class, event -> this.useItem()) - .addListener(PlayerHandAnimationEvent.class, event -> this.useItem()) + .addListener(PlayerTickEvent.class, event -> { + if(event.getPlayer() != this.player || this.dead) return; + this.cursor.updateCursorPosition(this.player); + }) + .addListener(PlayerUseItemEvent.class, event -> this.handleUse(event.getPlayer())) + .addListener(PlayerUseItemOnBlockEvent.class, event -> this.handleUse(event.getPlayer())) + .addListener(PlayerEntityInteractEvent.class, event -> this.handleUse(event.getPlayer())) + .addListener(PlayerHandAnimationEvent.class, event -> this.handleUse(event.getPlayer())) .addListener(ItemDropEvent.class, CommonEventHandles::cancel) .addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel) .addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel) @@ -90,6 +97,17 @@ public class TowerdefenseRoom extends InstanceContainer { }); } + private void handleUse(Player eventPlayer) { + if(this.game.getSpectators().contains(eventPlayer)) { + if(eventPlayer.getItemInMainHand().material() == Material.COMPASS) { + this.game.cycleSpectator(eventPlayer); + } + return; + } + if(eventPlayer != this.player || this.dead) return; + this.useItem(); + } + private void useItem() { Material itemInHand = this.player.getItemInMainHand().material(); if(itemInHand.equals(Material.BOW)) { @@ -108,23 +126,31 @@ public class TowerdefenseRoom extends InstanceContainer { Player p = this.player; this.lastPlayerAttack = System.currentTimeMillis(); 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, 1)); + projectile.setAerodynamics(new Aerodynamics(0, 0.99, 0.99)); + projectile.setView(p.getPosition().yaw(), p.getPosition().pitch()); - Vec projectileVelocity = p.getPosition().direction().normalize().mul(20); - projectile.setVelocity(projectileVelocity); + Pos spawn = p.getPosition().add(0, p.getEyeHeight() - 0.2, 0); projectile.scheduleRemove(Duration.ofSeconds(5)); - projectile.setInstance(this, p.getPosition().add(0, p.getEyeHeight()-0.5, 0)); projectile.eventNode() .addListener(ProjectileCollideWithEntityEvent.class, hitEvent -> { - if(!(hitEvent.getTarget() instanceof EntityCreature target)) return; - if(!this.getEnemies().stream().filter(entityCreature -> !entityCreature.isDead()).toList().contains(target)) return; - target.damage(DamageType.PLAYER_ATTACK, 0.1f); - if(target.isDead()) this.addMoney((int) target.getAttribute(Attribute.MAX_HEALTH).getBaseValue()); - projectile.remove(); + if(hitEvent.getTarget() == projectile.getShooter()) return; + try { + if(hitEvent.getTarget() instanceof EntityCreature target + && !target.isDead() + && this.enemies.contains(target)) { + target.damage(DamageType.PLAYER_ATTACK, 1.2f); + if(target.isDead()) this.addMoney(Math.max(1, (int) target.getAttribute(Attribute.MAX_HEALTH).getBaseValue() / 3)); + } + } finally { + projectile.remove(); + } }) .addListener(ProjectileCollideWithBlockEvent.class, collisionEvent -> projectile.remove()); + projectile.setInstance(this, spawn).thenRun(() -> { + Vec projectileVelocity = p.getPosition().direction().normalize().mul(60); + projectile.setVelocity(projectileVelocity); + }); } public void addMoney(int amount) { @@ -163,9 +189,18 @@ public class TowerdefenseRoom extends InstanceContainer { } private void removeTower() { - Entity entity = this.player.getLineOfSightEntity(reach, entity1 -> true); - if(!(entity instanceof Tower tower)) return; - if(!this.towers.contains(tower)) return; + Tower tower = null; + Entity entity = this.player.getLineOfSightEntity(reach, e -> e instanceof Tower); + if(entity instanceof Tower t && this.towers.contains(t)) { + tower = t; + } else if(this.cursor.getTargetBlockPosition() != null) { + Pos target = new Pos(this.cursor.getTargetBlockPosition()).add(0.5, 0.5, 0.5); + tower = this.towers.stream() + .filter(t -> t.getPosition().distanceSquared(target) <= 4) + .min(java.util.Comparator.comparingDouble(t -> t.getPosition().distanceSquared(target))) + .orElse(null); + } + if(tower == null) return; this.setBlock(tower.getPosition().sub(0, 0.5, 0), Block.BLACK_WOOL); this.addMoney(tower.getSellingPrice(this.game.getPrices().get(tower.getClass()))); @@ -207,15 +242,10 @@ public class TowerdefenseRoom extends InstanceContainer { if(positionIndex == this.game.getMazePath().size()-1) { this.enemies.remove(enemy); enemy.remove(); + if(this.dead) return; float damage = (float) Math.ceil(enemy.getHealth()/10); 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); + this.handleDeath(); return; } this.player.damage(DamageType.PLAYER_ATTACK, damage); @@ -224,6 +254,28 @@ public class TowerdefenseRoom extends InstanceContainer { enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1)); } + private void handleDeath() { + this.dead = true; + this.game.removeInstance(this); + new ArrayList<>(this.enemies).forEach(Entity::remove); + this.enemies.clear(); + new ArrayList<>(this.towers).forEach(Entity::remove); + this.towers.clear(); + 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.heal(); + this.game.getScore().insertResult(this.player); + if(!this.game.getAliveInstances().isEmpty()) { + this.game.beginSpectating(this.player); + } else { + this.player.setInstance(this.game).thenRun(() -> MinecraftServer.getInstanceManager().unregisterInstance(this)); + } + } + + public boolean isDead() { + return this.dead; + } + public Towerdefense getGame() { return this.game; } 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 index 1db8032..c67a73a 100644 --- 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 @@ -2,10 +2,12 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.ene import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.Towerdefense; import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.EntityCreature; import net.minestom.server.entity.EntityType; import net.minestom.server.timer.TaskSchedule; import java.util.*; +import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; @@ -32,44 +34,71 @@ public class WaveGenerator { this.currentLevel = startLevel - 1; } + private static final int MAX_ENEMIES_PER_WAVE = 60; + private static final int MAX_ALIVE_PER_ROOM = 150; + public void startNextWave() { this.currentLevel += 1; + if(this.game.getAliveInstances().isEmpty()) return; List enemyList = this.chooseEnemies(); + if(enemyList.isEmpty()) return; Collections.shuffle(enemyList); int averageDelay = Math.min(20000 / enemyList.size(), 1500); AtomicInteger delay = new AtomicInteger(0); - enemyList.forEach(enemy -> { + for (EnemyFactory enemy : enemyList) { delay.addAndGet(averageDelay); MinecraftServer.getSchedulerManager().scheduleTask( - () -> this.spawnEnemy(enemyList.removeFirst()), + () -> this.spawnEnemy(enemy), 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())); + float hpMultiplier = this.getHealthMultiplier(); + this.game.getAliveInstances().forEach(instance -> { + if(instance.isDead()) return; + if(instance.getEnemies().size() >= MAX_ALIVE_PER_ROOM) return; + try { + EntityCreature entity = enemy.buildEntity(); + if(hpMultiplier > 1f) { + float scaledHp = (float) Math.min(1024, entity.getAttribute(net.minestom.server.entity.attribute.Attribute.MAX_HEALTH).getBaseValue() * hpMultiplier); + entity.getAttribute(net.minestom.server.entity.attribute.Attribute.MAX_HEALTH).setBaseValue(scaledHp); + entity.setHealth(scaledHp); + } + instance.addEnemy(entity); + } catch (Exception ignored) { + } + }); + } + + private float getHealthMultiplier() { + // Levels 1-3: unchanged. Then +12% HP per level, capped so tower + // damage still matters at very high levels. + return Math.min(5f, 1f + Math.max(0, this.currentLevel - 3) * 0.12f); } private int getCurrentLevelStrength() { - return (int) (0.5 * Math.pow(2, this.currentLevel)); + // Linear scaling (was 0.5 * 2^level — exponential blow-up). + int alive = Math.max(1, this.game.getAliveInstances().size()); + return (int) (15 + this.currentLevel * 6 * Math.sqrt(alive)); } private List chooseEnemies() { int strength = this.getCurrentLevelStrength(); final List usedEnemies = new ArrayList<>(); - while(strength > 0) { + while(strength > 0 && usedEnemies.size() < MAX_ENEMIES_PER_WAVE) { int finalStrength = strength; - Map.Entry chosenEnemy = this.availableEnemies.entrySet().stream() + Optional> 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(); + .findAny(); + if(chosenEnemy.isEmpty()) break; + usedEnemies.add(chosenEnemy.get().getKey()); + strength -= chosenEnemy.get().getKey().getStrength(); } return usedEnemies; diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/EnderDragonTower.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/EnderDragonTower.java index 12e3f96..4904329 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/EnderDragonTower.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/EnderDragonTower.java @@ -5,7 +5,7 @@ import net.minestom.server.entity.EntityType; public class EnderDragonTower extends ShootingTower{ public EnderDragonTower() { - super(EntityType.ENDER_DRAGON, 40, 7, 20); + super(EntityType.ENDER_DRAGON, 20, 3, 16); } @Override 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 15bbccd..3c82832 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 @@ -43,12 +43,18 @@ public abstract class ShootingTower extends Tower { projectile.setInstance(this.getInstance(), startingPoint); projectile.eventNode() .addListener(ProjectileCollideWithEntityEvent.class, event -> { - if(!(event.getTarget() instanceof EntityCreature target)) return; - if(!this.getRoomInstance().getEnemies().contains(target)) return; - this.causeDamage(target); - this.onProjectileHit(target); - target.setFlyingWithElytra(true); - projectile.remove(); + if(event.getTarget() == projectile.getShooter()) return; + try { + if(event.getTarget() instanceof EntityCreature target + && !target.isDead() + && this.getRoomInstance().getEnemies().contains(target)) { + this.causeDamage(target); + this.onProjectileHit(target); + if(target.isDead()) target.setFlyingWithElytra(true); + } + } finally { + projectile.remove(); + } }) .addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove()); } 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 d1c10e9..045ec69 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 @@ -7,7 +7,7 @@ import net.minestom.server.item.Material; public class SkeletonTower extends ShootingTower { public SkeletonTower() { - super(EntityType.SKELETON, 1, 2, 10); + super(EntityType.SKELETON, 3, 2.5, 12); this.setItemInMainHand(ItemStack.of(Material.BOW)); } 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 a0d9d03..94783c5 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 @@ -87,14 +87,15 @@ public abstract class Tower extends EntityCreature { .filter(enemy -> !enemy.isDead()) .filter(enemy -> enemy.getPosition().distance(this.getPosition()) <= this.range); return switch (this.priority.getType()) { - case PATH_DISTANCE -> enemyStream - .sorted((o1, o2) -> { - Pos endPoint = this.getRoomInstance().getGame().getMazePath().getLast(); - return Double.compare( - o1.getPosition().distance(endPoint), - o2.getPosition().distance(endPoint) - ); - }).toList(); + case PATH_DISTANCE -> { + List path = this.getRoomInstance().getGame().getMazePath(); + yield enemyStream + .sorted((o1, o2) -> Integer.compare( + pathProgress(o1, path), + pathProgress(o2, path) + )) + .toList(); + } case TOWER_DISTANCE -> enemyStream .sorted((o1, o2) -> Double.compare( o1.getPosition().distance(this.getPosition()), @@ -106,6 +107,21 @@ public abstract class Tower extends EntityCreature { }; } + private static int pathProgress(EntityCreature enemy, List path) { + // Highest index == furthest along the path. Returned as negative so a + // natural ascending sort puts the most-advanced enemy first. + double bestDist = Double.MAX_VALUE; + int bestIdx = 0; + for(int i = 0; i < path.size(); i++) { + double d = enemy.getPosition().distanceSquared(path.get(i)); + if(d < bestDist) { + bestDist = d; + bestIdx = i; + } + } + return -bestIdx; + } + protected TowerdefenseRoom getRoomInstance() { return (TowerdefenseRoom) this.getInstance(); } @@ -128,7 +144,7 @@ public abstract class Tower extends EntityCreature { protected void causeDamage(EntityCreature enemy, float amount) { enemy.damage(DamageType.PLAYER_ATTACK, amount); - if(enemy.isDead()) this.getRoomInstance().addMoney((int) enemy.getAttribute(Attribute.MAX_HEALTH).getBaseValue()); + if(enemy.isDead()) this.getRoomInstance().addMoney(Math.max(1, (int) enemy.getAttribute(Attribute.MAX_HEALTH).getBaseValue() / 3)); } protected abstract void attack(EntityCreature enemy); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f7c93d6..76b6a65 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -20,4 +20,5 @@ api: admins: - minetec - - 28Pupsi28 \ No newline at end of file + - 28Pupsi28 + - jannis_mg \ No newline at end of file