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 8880ac0..d5d9237 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 @@ -30,6 +30,11 @@ public class Towerdefense extends StatelessGame { Material.ZOMBIE_SPAWN_EGG, ZombieTower.class, Material.BLAZE_SPAWN_EGG, BlazeTower.class ); + private final Map, Integer> prices = Map.of( + ZombieTower.class, 5, + SkeletonTower.class, 10, + BlazeTower.class, 15 + ); private static final int pathLength = 10; @@ -96,6 +101,10 @@ public class Towerdefense extends StatelessGame { return false; } + public Map, Integer> getPrices() { + return this.prices; + } + public Map> 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 786efaf..966d759 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 @@ -6,33 +6,39 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towe import eu.mhsl.minenet.minigames.util.BatchUtil; import eu.mhsl.minenet.minigames.util.CommonEventHandles; import net.minestom.server.MinecraftServer; +import net.minestom.server.collision.Aerodynamics; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; 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.entity.projectile.ProjectileCollideWithBlockEvent; +import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent; 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.event.player.*; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import org.jetbrains.annotations.Nullable; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class TowerdefenseRoom extends InstanceContainer { + private final static int reach = 30; + private long lastPlayerAttack = 0; private final Player player; private final Towerdefense game; private final List enemies = new ArrayList<>(); private final List towers = new ArrayList<>(); private final Entity cursor; + private int money = 1000; public TowerdefenseRoom(Player player, Towerdefense game) { super(UUID.randomUUID(), Dimension.OVERWORLD.key); @@ -41,8 +47,11 @@ public class TowerdefenseRoom extends InstanceContainer { this.game = game; this.player.setGameMode(GameMode.ADVENTURE); this.player.setAllowFlying(true); - this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(200); + this.addMoney(0); + this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach); + this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach); + this.player.getInventory().addItemStack(ItemStack.of(Material.SPECTRAL_ARROW)); this.game.getAvailableTowers().keySet().forEach(material -> this.player.getInventory().addItemStack(ItemStack.of(material))); this.setGenerator(new MazeGenerator()); @@ -58,15 +67,55 @@ public class TowerdefenseRoom extends InstanceContainer { this.enemies.remove(enemy); }) .addListener(PlayerTickEvent.class, this::setCursorPosition) - .addListener(PlayerUseItemEvent.class, event -> this.setTower(event.getItemStack().material())) - .addListener(PlayerEntityInteractEvent.class, event -> this.setTower(event.getPlayer().getItemInMainHand().material())) + .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(ItemDropEvent.class, CommonEventHandles::cancel) .addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel) .addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel); } - private void setTower(Material usedMaterial) { - if(!this.canPlaceTower()) return; + private void useItem() { + if(this.player.getItemInMainHand().material().equals(Material.SPECTRAL_ARROW)) { + this.playerAttack(); + return; + } + this.placeTower(); + } + + private void playerAttack() { + if(this.lastPlayerAttack > System.currentTimeMillis()-200) return; + 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, 0)); + + Vec projectileVelocity = p.getPosition().direction().normalize().mul(20); + projectile.setVelocity(projectileVelocity); + projectile.scheduleRemove(Duration.ofSeconds(5)); + projectile.setInstance(this, p.getPosition().add(0, p.getEyeHeight(), 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(); + }) + .addListener(ProjectileCollideWithBlockEvent.class, collisionEvent -> projectile.remove()); + } + + public void addMoney(int amount) { + this.money += amount; + this.player.setLevel(this.money); + } + + private void placeTower() { + Material usedMaterial = this.player.getItemInMainHand().material(); + if(!this.canPlaceTower(usedMaterial)) return; try { Tower tower = this.game.getAvailableTowers().entrySet().stream() .filter(materialClassEntry -> materialClassEntry.getKey().equals(usedMaterial)) @@ -82,20 +131,24 @@ public class TowerdefenseRoom extends InstanceContainer { } } - public List getEnemies() { - return this.enemies; - } - private void setCursorPosition(PlayerTickEvent event) { - Point newPosition = this.player.getTargetBlockPosition(20); + Point newPosition = this.player.getTargetBlockPosition(reach); if(newPosition == null) return; newPosition = newPosition.add(0.5,1,0.5); this.cursor.teleport(new Pos(newPosition)); - this.cursor.setInvisible(!this.canPlaceTower()); + this.cursor.setInvisible(!(this.canPlaceTower((Material) null) && this.game.getAvailableTowers().containsKey(this.player.getItemInMainHand().material()))); } - private boolean canPlaceTower() { - return this.getBlock(this.cursor.getPosition().sub(0, 1, 0)).equals(Block.BLACK_WOOL); + private boolean canPlaceTower(@Nullable Material usedMaterial) { + if(usedMaterial == null) return this.canPlaceTower((Class) null); + return this.canPlaceTower(this.game.getAvailableTowers().get(usedMaterial)); + } + + private boolean canPlaceTower(@Nullable Class towerClass) { + boolean blockAllowed = this.getBlock(this.cursor.getPosition().sub(0, 1, 0)).equals(Block.BLACK_WOOL); + if(towerClass == null) return blockAllowed; + boolean enoughMoney = this.money > this.game.getPrices().get(towerClass); + return blockAllowed && enoughMoney; } void startWave(List enemyGroups) { @@ -108,10 +161,6 @@ public class TowerdefenseRoom extends InstanceContainer { this.changeEnemyGoal(enemy, 0); } - public Towerdefense getGame() { - return this.game; - } - private void changeEnemyGoal(EntityCreature enemy, int positionIndex) { if(positionIndex == this.game.getMazePath().size()-1) { this.enemies.remove(enemy); @@ -121,4 +170,12 @@ public class TowerdefenseRoom extends InstanceContainer { } enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1)); } + + public Towerdefense getGame() { + return this.game; + } + + public List getEnemies() { + return this.enemies; + } } 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 index b652667..f711661 100644 --- 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 @@ -3,13 +3,14 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.tow import net.minestom.server.entity.EntityCreature; import net.minestom.server.entity.EntityType; -public class BlazeTower extends Tower { +public class BlazeTower extends ShootingTower { public BlazeTower() { - super(EntityType.BLAZE, 2, 1, 20); + super(EntityType.BLAZE, 1, 1, 20); } @Override protected void attack(EntityCreature enemy) { + this.swingMainHand(); this.shootProjectile(enemy, EntityType.FIREBALL, 2, 0); } } 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 new file mode 100644 index 0000000..9c9343b --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/ShootingTower.java @@ -0,0 +1,62 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers; + +import net.minestom.server.collision.Aerodynamics; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.EntityProjectile; +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent; +import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent; +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; + +public abstract class ShootingTower extends Tower { + public ShootingTower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) { + super(entityType, damage, attacksPerSecond, range); + } + + protected void shootProjectile(EntityCreature enemy, EntityType projectileType, double power, double spread) { + EntityProjectile projectile = new EntityProjectile(this, projectileType); + projectile.setView(this.getPosition().yaw(), this.getPosition().pitch()); + projectile.setNoGravity(true); + projectile.setAerodynamics(new Aerodynamics(0, 1, 0)); + Pos startingPoint = this.getPosition().add(0, this.getEyeHeight(), 0); + + double enemySpeed = enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue() / 0.05; + Point enemyGoal = enemy.getNavigator().getGoalPosition(); + if(enemyGoal == null) enemyGoal = enemy.getPosition(); + Pos enemyPosition = enemy.getPosition(); + Vec enemyMovement = Vec.fromPoint(enemyGoal.sub(enemyPosition)).normalize().mul(enemySpeed); + + // just an approximation, not calculated correctly: + double projectileSpeed = 20 * power; + double enemyTowerDistance = startingPoint.distance(enemyPosition); + double estimatedFlightTime = (enemyTowerDistance / projectileSpeed); + Pos targetPosition = enemyPosition.add(enemyMovement.mul(estimatedFlightTime)).withY(enemyPosition.y()+enemy.getEyeHeight()); + + projectile.shoot(targetPosition, power, spread); + projectile.scheduleRemove(Duration.ofSeconds(5)); + 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); + projectile.remove(); + }) + .addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove()); + } + + protected void onProjectileHit(EntityCreature enemy) { + } + + @Override + protected void attack(EntityCreature enemy) { + this.shootProjectile(enemy, EntityType.ARROW, 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 82ffee3..7c8f9b4 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 @@ -5,7 +5,7 @@ import net.minestom.server.entity.EntityType; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -public class SkeletonTower extends Tower { +public class SkeletonTower extends ShootingTower { public SkeletonTower() { super(EntityType.SKELETON, 1, 3, 15); this.setItemInMainHand(ItemStack.of(Material.BOW)); @@ -13,6 +13,7 @@ public class SkeletonTower extends Tower { @Override protected void attack(EntityCreature enemy) { + this.swingMainHand(); this.shootProjectile(enemy, EntityType.ARROW, 2, 0); } 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 79895c5..002af1d 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 @@ -2,21 +2,15 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.tow import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom; import net.minestom.server.MinecraftServer; -import net.minestom.server.collision.Aerodynamics; -import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; -import net.minestom.server.coordinate.Vec; 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.projectile.ProjectileCollideWithBlockEvent; -import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent; import net.minestom.server.timer.Task; import net.minestom.server.timer.TaskSchedule; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.time.Duration; import java.util.List; import java.util.stream.Stream; @@ -50,14 +44,14 @@ public abstract class Tower extends EntityCreature { protected float damage; protected int range; protected TaskSchedule attackDelay; - private final Task shootTask; + private final Task attackTask; public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) { super(entityType); this.damage = (float) damage / damageDivider; this.range = range; this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond)); - this.shootTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> { + this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> { EntityCreature nextEnemy = this.getNextEnemy(); if(nextEnemy == null) return this.attackDelay; this.lookAt(nextEnemy); @@ -66,44 +60,10 @@ public abstract class Tower extends EntityCreature { }, TaskSchedule.immediate()); } - protected void shootProjectile(EntityCreature enemy, EntityType projectileType, double power, double spread) { - EntityProjectile projectile = new EntityProjectile(this, projectileType); - projectile.setView(this.getPosition().yaw(), this.getPosition().pitch()); - projectile.setNoGravity(true); - projectile.setAerodynamics(new Aerodynamics(0, 1, 0)); - Pos startingPoint = this.getPosition().add(0, this.getEyeHeight(), 0); - - double enemySpeed = enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue() / 0.05; - Point enemyGoal = enemy.getNavigator().getGoalPosition(); - if(enemyGoal == null) enemyGoal = enemy.getPosition(); - Pos enemyPosition = enemy.getPosition(); - Vec enemyMovement = Vec.fromPoint(enemyGoal.sub(enemyPosition)).normalize().mul(enemySpeed); - - // just an approximation, not calculated correctly: - double projectileSpeed = 20 * power; - double enemyTowerDistance = startingPoint.distance(enemyPosition); - double estimatedFlightTime = (enemyTowerDistance / projectileSpeed); - Pos targetPosition = enemyPosition.add(enemyMovement.mul(estimatedFlightTime)).withY(enemyPosition.y()+enemy.getEyeHeight()); - - projectile.shoot(targetPosition, power, spread); - projectile.scheduleRemove(Duration.ofSeconds(5)); - 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; - target.damage(DamageType.PLAYER_ATTACK, this.damage); - this.onProjectileHit(target); - projectile.remove(); - }) - .addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove()); - } - - protected void onProjectileHit(EntityCreature enemy) { - } - - protected TowerdefenseRoom getRoomInstance() { - return (TowerdefenseRoom) this.getInstance(); + @Override + protected void remove(boolean permanent) { + this.attackTask.cancel(); + super.remove(permanent); } private @Nullable EntityCreature getNextEnemy() { @@ -140,10 +100,17 @@ public abstract class Tower extends EntityCreature { }; } - @Override - protected void remove(boolean permanent) { - this.shootTask.cancel(); - super.remove(permanent); + protected TowerdefenseRoom getRoomInstance() { + return (TowerdefenseRoom) this.getInstance(); + } + + protected void causeDamage(EntityCreature enemy) { + this.causeDamage(enemy, this.damage); + } + + 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()); } 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 index 84e4544..a9c6262 100644 --- 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 @@ -2,19 +2,18 @@ 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.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); + super(EntityType.ZOMBIE, 2, 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); + this.causeDamage(enemy); } }