added tower value calculation and delete tower mechanic

This commit is contained in:
Lars Neuhaus 2025-04-13 18:35:50 +02:00
parent e3068be160
commit e77879c657
7 changed files with 74 additions and 22 deletions

View File

@ -8,15 +8,15 @@ record EnemyFactory(EntityType entityType, float health, double speed) {
/** /**
* Factory for a tower defense enemy. * Factory for a tower defense enemy.
* @param entityType type of enemy * @param entityType type of enemy
* @param health base health, between 0 and 1024 * @param health base health (between 0 and 1024, default 10)
* @param speed walk speed of enemy * @param speed walk speed (default 0.1)
*/ */
public EnemyFactory { public EnemyFactory {
if(health > 1024 || health <= 0) throw new IllegalArgumentException("Enemy health has to be between 0 and 1024"); if(health > 1024 || health <= 0) throw new IllegalArgumentException("Enemy health has to be between 0 and 1024");
} }
public EnemyFactory(EntityType entityType) { public EnemyFactory(EntityType entityType) {
this(entityType, 20, 0.3); this(entityType, 10, 0.1);
} }
public EntityCreature buildEntity() { public EntityCreature buildEntity() {

View File

@ -31,9 +31,9 @@ public class Towerdefense extends StatelessGame {
Material.BLAZE_SPAWN_EGG, BlazeTower.class Material.BLAZE_SPAWN_EGG, BlazeTower.class
); );
private final Map<Class<? extends Tower>, Integer> prices = Map.of( private final Map<Class<? extends Tower>, Integer> prices = Map.of(
ZombieTower.class, 5, ZombieTower.class, 14,
SkeletonTower.class, 10, SkeletonTower.class, 10,
BlazeTower.class, 15 BlazeTower.class, 30
); );
private static final int pathLength = 10; private static final int pathLength = 10;
@ -45,6 +45,18 @@ public class Towerdefense extends StatelessGame {
this.generateMaze(); this.generateMaze();
} }
@Override
protected void onStart() {
this.getPlayers().forEach(player -> {
TowerdefenseRoom newRoom = new TowerdefenseRoom(player, this);
this.instances.add(newRoom);
player.setInstance(newRoom);
newRoom.startWave(List.of(
new GroupFactory(new EnemyFactory(EntityType.VILLAGER), 1, 800)
));
});
}
private void generateMaze() { private void generateMaze() {
Pos position = new Pos(0, 0, 0); Pos position = new Pos(0, 0, 0);
this.addMazePosition(position, Block.GREEN_WOOL); this.addMazePosition(position, Block.GREEN_WOOL);
@ -90,14 +102,6 @@ public class Towerdefense extends StatelessGame {
@Override @Override
protected boolean onPlayerJoin(Player p) { protected boolean onPlayerJoin(Player p) {
TowerdefenseRoom newRoom = new TowerdefenseRoom(p, this);
this.instances.add(newRoom);
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.SNIFFER, 1000, 0.02), 1, 0)
));
return false; return false;
} }

View File

@ -5,6 +5,8 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.gene
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower; 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.BatchUtil;
import eu.mhsl.minenet.minigames.util.CommonEventHandles; import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.collision.Aerodynamics; import net.minestom.server.collision.Aerodynamics;
import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Point;
@ -23,6 +25,7 @@ import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.Duration; import java.time.Duration;
@ -38,7 +41,7 @@ public class TowerdefenseRoom extends InstanceContainer {
private final List<EntityCreature> enemies = new ArrayList<>(); private final List<EntityCreature> enemies = new ArrayList<>();
private final List<Tower> towers = new ArrayList<>(); private final List<Tower> towers = new ArrayList<>();
private final Entity cursor; private final Entity cursor;
private int money = 1000; private int money = 0;
public TowerdefenseRoom(Player player, Towerdefense game) { public TowerdefenseRoom(Player player, Towerdefense game) {
super(UUID.randomUUID(), Dimension.OVERWORLD.key); super(UUID.randomUUID(), Dimension.OVERWORLD.key);
@ -51,7 +54,8 @@ public class TowerdefenseRoom extends InstanceContainer {
this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach); this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach);
this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach); this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach);
this.player.getInventory().addItemStack(ItemStack.of(Material.SPECTRAL_ARROW)); this.player.getInventory().addItemStack(ItemStack.of(Material.SPECTRAL_ARROW).withCustomName(Component.text("Schießen", TextColor.color(255, 180, 0))));
this.player.getInventory().addItemStack(ItemStack.of(Material.BARRIER).withCustomName(Component.text("Löschen", TextColor.color(255,0,0))));
this.game.getAvailableTowers().keySet().forEach(material -> this.player.getInventory().addItemStack(ItemStack.of(material))); this.game.getAvailableTowers().keySet().forEach(material -> this.player.getInventory().addItemStack(ItemStack.of(material)));
this.setGenerator(new MazeGenerator()); this.setGenerator(new MazeGenerator());
@ -61,6 +65,16 @@ public class TowerdefenseRoom extends InstanceContainer {
this.cursor.setInstance(this); this.cursor.setInstance(this);
this.cursor.setBoundingBox(0,0,0); this.cursor.setBoundingBox(0,0,0);
MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of(
new GroupFactory(new EnemyFactory(EntityType.VILLAGER), 2, 800)
)), TaskSchedule.seconds(20), TaskSchedule.stop());
MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of(
new GroupFactory(new EnemyFactory(EntityType.VILLAGER), 4, 800)
)), TaskSchedule.seconds(50), TaskSchedule.stop());
MinecraftServer.getSchedulerManager().scheduleTask(() -> this.startWave(List.of(
new GroupFactory(new EnemyFactory(EntityType.VILLAGER), 6, 800)
)), TaskSchedule.seconds(90), TaskSchedule.stop());
this.eventNode() this.eventNode()
.addListener(EntityDeathEvent.class, event -> { .addListener(EntityDeathEvent.class, event -> {
if(!(event.getEntity() instanceof EntityCreature enemy)) return; if(!(event.getEntity() instanceof EntityCreature enemy)) return;
@ -77,10 +91,15 @@ public class TowerdefenseRoom extends InstanceContainer {
} }
private void useItem() { private void useItem() {
if(this.player.getItemInMainHand().material().equals(Material.SPECTRAL_ARROW)) { Material itemInHand = this.player.getItemInMainHand().material();
if(itemInHand.equals(Material.SPECTRAL_ARROW)) {
this.playerAttack(); this.playerAttack();
return; return;
} }
if(itemInHand.equals(Material.BARRIER)) {
this.removeTower();
return;
}
this.placeTower(); this.placeTower();
} }
@ -127,13 +146,25 @@ public class TowerdefenseRoom extends InstanceContainer {
this.setBlock(this.cursor.getPosition().sub(0, 0.5, 0), Block.BLUE_WOOL); this.setBlock(this.cursor.getPosition().sub(0, 0.5, 0), Block.BLUE_WOOL);
tower.setInstance(this, this.cursor.getPosition()); tower.setInstance(this, this.cursor.getPosition());
this.towers.add(tower); this.towers.add(tower);
this.addMoney(-this.game.getPrices().get(tower.getClass()));
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
private void removeTower() {
Entity entity = this.player.getLineOfSightEntity(reach, entity1 -> true);
if(!(entity instanceof Tower tower)) return;
if(!this.towers.contains(tower)) return;
this.setBlock(tower.getPosition().sub(0, 0.5, 0), Block.BLACK_WOOL);
this.addMoney(tower.getSellingPrice(this.game.getPrices().get(tower.getClass())));
this.towers.remove(tower);
tower.remove();
}
private void setCursorPosition(PlayerTickEvent event) { private void setCursorPosition(PlayerTickEvent event) {
Point newPosition = this.player.getTargetBlockPosition(reach); Point newPosition = this.player.getTargetBlockPosition(reach);
if(newPosition == null) return; if(newPosition == null) newPosition = this.cursor.getPosition().withY(-2);
newPosition = newPosition.add(0.5,1,0.5); newPosition = newPosition.add(0.5,1,0.5);
this.cursor.teleport(new Pos(newPosition)); this.cursor.teleport(new Pos(newPosition));
this.cursor.setInvisible(!(this.canPlaceTower((Material) null) && this.game.getAvailableTowers().containsKey(this.player.getItemInMainHand().material()))); this.cursor.setInvisible(!(this.canPlaceTower((Material) null) && this.game.getAvailableTowers().containsKey(this.player.getItemInMainHand().material())));
@ -147,7 +178,7 @@ public class TowerdefenseRoom extends InstanceContainer {
private boolean canPlaceTower(@Nullable Class<? extends Tower> towerClass) { private boolean canPlaceTower(@Nullable Class<? extends Tower> towerClass) {
boolean blockAllowed = this.getBlock(this.cursor.getPosition().sub(0, 1, 0)).equals(Block.BLACK_WOOL); boolean blockAllowed = this.getBlock(this.cursor.getPosition().sub(0, 1, 0)).equals(Block.BLACK_WOOL);
if(towerClass == null) return blockAllowed; if(towerClass == null) return blockAllowed;
boolean enoughMoney = this.money > this.game.getPrices().get(towerClass); boolean enoughMoney = this.money >= this.game.getPrices().get(towerClass);
return blockAllowed && enoughMoney; return blockAllowed && enoughMoney;
} }
@ -165,7 +196,13 @@ public class TowerdefenseRoom extends InstanceContainer {
if(positionIndex == this.game.getMazePath().size()-1) { if(positionIndex == this.game.getMazePath().size()-1) {
this.enemies.remove(enemy); this.enemies.remove(enemy);
enemy.remove(); enemy.remove();
this.player.damage(DamageType.PLAYER_ATTACK, enemy.getHealth()/10); float damage = (float) Math.ceil(enemy.getHealth()/10);
if(this.player.getHealth() - damage <= 0) {
this.player.setInstance(this.game);
this.game.getScore().insertResult(this.player);
return;
}
this.player.damage(DamageType.PLAYER_ATTACK, damage);
return; return;
} }
enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1)); enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1));

View File

@ -5,7 +5,7 @@ import net.minestom.server.entity.EntityType;
public class BlazeTower extends ShootingTower { public class BlazeTower extends ShootingTower {
public BlazeTower() { public BlazeTower() {
super(EntityType.BLAZE, 1, 1, 20); super(EntityType.BLAZE, 2, 2, 15);
} }
@Override @Override

View File

@ -7,7 +7,7 @@ import net.minestom.server.item.Material;
public class SkeletonTower extends ShootingTower { public class SkeletonTower extends ShootingTower {
public SkeletonTower() { public SkeletonTower() {
super(EntityType.SKELETON, 1, 3, 15); super(EntityType.SKELETON, 1, 2, 10);
this.setItemInMainHand(ItemStack.of(Material.BOW)); this.setItemInMainHand(ItemStack.of(Material.BOW));
} }

View File

@ -43,13 +43,16 @@ public abstract class Tower extends EntityCreature {
private Priority priority = Priority.FIRST; private Priority priority = Priority.FIRST;
protected float damage; protected float damage;
protected int range; protected int range;
protected double attacksPerSecond;
protected TaskSchedule attackDelay; protected TaskSchedule attackDelay;
private final Task attackTask; private final Task attackTask;
private float sellingPriceMultiplier = 1;
public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) { public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
super(entityType); super(entityType);
this.damage = (float) damage / damageDivider; this.damage = (float) damage / damageDivider;
this.range = range; this.range = range;
this.attacksPerSecond = attacksPerSecond;
this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond)); this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond));
this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> { this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
EntityCreature nextEnemy = this.getNextEnemy(); EntityCreature nextEnemy = this.getNextEnemy();
@ -104,6 +107,14 @@ public abstract class Tower extends EntityCreature {
return (TowerdefenseRoom) this.getInstance(); return (TowerdefenseRoom) this.getInstance();
} }
public int getSellingPrice(int buyPrice) {
return (int) (this.sellingPriceMultiplier * buyPrice);
}
public double getValue() {
return this.damage * this.attacksPerSecond * this.range * 2;
}
protected void causeDamage(EntityCreature enemy) { protected void causeDamage(EntityCreature enemy) {
this.causeDamage(enemy, this.damage); this.causeDamage(enemy, this.damage);
} }

View File

@ -7,7 +7,7 @@ import net.minestom.server.item.Material;
public class ZombieTower extends Tower { public class ZombieTower extends Tower {
public ZombieTower() { public ZombieTower() {
super(EntityType.ZOMBIE, 2, 0.7, 4); super(EntityType.ZOMBIE, 7, 1, 4);
this.setItemInMainHand(ItemStack.of(Material.IRON_SWORD)); this.setItemInMainHand(ItemStack.of(Material.IRON_SWORD));
} }