Compare commits
8 Commits
develop
...
develop-to
Author | SHA1 | Date | |
---|---|---|---|
3b55f16b24 | |||
5ef254b75f | |||
5e5f36153e | |||
e77879c657 | |||
e3068be160 | |||
867bee1c5a | |||
893923cc56 | |||
38c944e6c1 |
@ -2,7 +2,6 @@ package eu.mhsl.minenet.minigames.handler.global;
|
||||
|
||||
import eu.mhsl.minenet.minigames.Main;
|
||||
import eu.mhsl.minenet.minigames.api.QueuedPlayerRooms;
|
||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory;
|
||||
import eu.mhsl.minenet.minigames.instance.room.Room;
|
||||
import eu.mhsl.minenet.minigames.instance.transfer.Transfer;
|
||||
import eu.mhsl.minenet.minigames.skin.SkinCache;
|
||||
@ -49,15 +48,7 @@ public class PlayerLoginHandler implements EventListener<AsyncPlayerConfiguratio
|
||||
if(pushQueue != null) {
|
||||
Room.setRoom(p, Room.getRoom(pushQueue).orElseThrow());
|
||||
} else {
|
||||
if(p.getUsername().equals("28Pupsi28")) {
|
||||
try {
|
||||
MoveInstance.move(p, new TowerdefenseFactory().manufacture(Room.createRoom(p)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
MoveInstance.move(p, Hub.INSTANCE);
|
||||
}
|
||||
MoveInstance.move(p, Hub.INSTANCE);
|
||||
}
|
||||
},
|
||||
TaskSchedule.seconds(1),
|
||||
|
@ -0,0 +1,89 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
||||
|
||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.server.SendablePacket;
|
||||
import net.minestom.server.network.packet.server.play.ParticlePacket;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class Cursor extends Entity {
|
||||
private boolean cursorActive = false;
|
||||
private final int reach;
|
||||
|
||||
public Cursor(int reach) {
|
||||
super(EntityType.ARMOR_STAND);
|
||||
this.setBoundingBox(0,0,0);
|
||||
this.reach = reach;
|
||||
}
|
||||
|
||||
public void updateCursorPosition(Player player) {
|
||||
Point targetPosition = player.getTargetBlockPosition(this.reach);
|
||||
this.moveCursorTo(targetPosition);
|
||||
}
|
||||
|
||||
private void moveCursorTo(@Nullable Point newTargetBlockPosition) {
|
||||
this.setCursorEnabled(true);
|
||||
if(newTargetBlockPosition == null) {
|
||||
this.setCursorEnabled(false);
|
||||
return;
|
||||
}
|
||||
if(!this.getInstance().getBlock(newTargetBlockPosition).equals(Block.BLACK_WOOL)) this.setCursorEnabled(false);
|
||||
Material holdingMaterial = this.getInstance().getPlayerHandMaterial();
|
||||
if(!this.getInstance().getGame().getAvailableTowers().containsKey(holdingMaterial)) this.setCursorEnabled(false);
|
||||
this.teleport(new Pos(newTargetBlockPosition.add(0.5,1,0.5)));
|
||||
this.showRange(this.getInstance().getGame().getAvailableTowers().get(holdingMaterial));
|
||||
}
|
||||
|
||||
private void setCursorEnabled(boolean enabled) {
|
||||
this.cursorActive = enabled;
|
||||
this.setInvisible(!enabled);
|
||||
}
|
||||
|
||||
private void showRange(@Nullable Class<? extends Tower> towerClass) {
|
||||
if(towerClass == null) return;
|
||||
if(!this.isCursorActive()) return;
|
||||
try {
|
||||
int range = towerClass.getConstructor().newInstance().getRange();
|
||||
Collection<SendablePacket> particles = new ArrayList<>();
|
||||
double circumference = 2 * Math.PI * range;
|
||||
int count = (int) (circumference * 1.5);
|
||||
for (int i = 0; i < count; i++) {
|
||||
double radians = ((2 * Math.PI) / count) * i;
|
||||
Vec relativePosition = new Vec(Math.sin(radians)*range, 0, Math.cos(radians)*range);
|
||||
ParticlePacket particle = new ParticlePacket(Particle.COMPOSTER, this.position.add(relativePosition), Pos.ZERO, 0, 1);
|
||||
particles.add(particle);
|
||||
}
|
||||
this.getInstance().getPlayer().sendPackets(particles);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TowerdefenseRoom getInstance() {
|
||||
return (TowerdefenseRoom) super.getInstance();
|
||||
}
|
||||
|
||||
public boolean isCursorActive() {
|
||||
return this.cursorActive;
|
||||
}
|
||||
|
||||
public Block getTargetBlock() {
|
||||
return this.getInstance().getBlock(this.getTargetBlockPosition());
|
||||
}
|
||||
|
||||
public Pos getTargetBlockPosition() {
|
||||
return this.getPosition().sub(0, 0.5, 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
||||
|
||||
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) {
|
||||
/**
|
||||
* Factory for a tower defense enemy.
|
||||
* @param entityType type of enemy
|
||||
* @param health base health (between 0 and 1024, default 10)
|
||||
* @param speed walk speed (default 0.1)
|
||||
*/
|
||||
public EnemyFactory {
|
||||
if(health > 1024 || health <= 0) throw new IllegalArgumentException("Enemy health has to be between 0 and 1024");
|
||||
}
|
||||
|
||||
public EnemyFactory(EntityType entityType) {
|
||||
this(entityType, 10, 0.1);
|
||||
}
|
||||
|
||||
public EntityCreature buildEntity() {
|
||||
EntityCreature entity = new EntityCreature(this.entityType);
|
||||
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(this.speed);
|
||||
entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(this.health);
|
||||
entity.setHealth(this.health);
|
||||
return entity;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.timer.TaskSchedule;
|
||||
|
||||
record GroupFactory(EnemyFactory enemyFactory, int count, long delay) {
|
||||
public void summonGroup(TowerdefenseRoom instance) {
|
||||
for (int i = 0; i < this.count; i++) {
|
||||
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
||||
instance.addEnemy(this.enemyFactory.buildEntity());
|
||||
return TaskSchedule.stop();
|
||||
}, TaskSchedule.millis(this.delay*i));
|
||||
}
|
||||
}
|
||||
}
|
@ -3,38 +3,76 @@ 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.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.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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class Towerdefense extends StatelessGame {
|
||||
private final Random random = new Random();
|
||||
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
|
||||
private final List<Pos> mazePath = new ArrayList<>();
|
||||
private List<TowerdefenseRoom> instances = new ArrayList<>();
|
||||
private final List<TowerdefenseRoom> instances = new ArrayList<>();
|
||||
private final Map<Material, Class<? extends Tower>> availableTowers = Map.of(
|
||||
Material.SKELETON_SPAWN_EGG, SkeletonTower.class,
|
||||
Material.ZOMBIE_SPAWN_EGG, ZombieTower.class,
|
||||
Material.BLAZE_SPAWN_EGG, BlazeTower.class
|
||||
);
|
||||
private final Map<Class<? extends Tower>, Integer> prices = Map.of(
|
||||
ZombieTower.class, 4,
|
||||
SkeletonTower.class, 3,
|
||||
BlazeTower.class, 8
|
||||
);
|
||||
|
||||
private static final int pathLength = 10;
|
||||
|
||||
public Towerdefense() {
|
||||
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
|
||||
|
||||
setGenerator(new MazeGenerator());
|
||||
this.setGenerator(new MazeGenerator());
|
||||
this.generateMaze();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkAbandoned() {
|
||||
this.scheduleNextTick((instance) -> {
|
||||
if(this.instances.stream().allMatch(room -> room.getPlayers().isEmpty()) && this.getPlayers().isEmpty()) this.unload();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
this.getPlayers().forEach(player -> {
|
||||
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)
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
private void generateMaze() {
|
||||
Pos position = new Pos(0, 0, 0);
|
||||
this.addMazePosition(position, Block.GREEN_WOOL);
|
||||
position = position.add(0,0,2);
|
||||
|
||||
List<Integer> previousDirections = new ArrayList<>();
|
||||
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
|
||||
for (int i = 0; i < 9; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < pathLength; i++) {
|
||||
for (int j = 0; j < 9; j++) {
|
||||
position = position.add(direction-1,0,direction%2);
|
||||
this.addMazePosition(position, Block.WHITE_WOOL);
|
||||
}
|
||||
@ -44,16 +82,20 @@ public class Towerdefense extends StatelessGame {
|
||||
long rightLeftDifference = previousDirections.stream().filter(integer -> integer == 0).count() - previousDirections.stream().filter(integer -> integer == 2).count();
|
||||
if(rightLeftDifference >= 2 || direction == 2) origin = 1;
|
||||
if(rightLeftDifference <= -2 || direction == 0) bound = 2;
|
||||
direction = random.nextInt(origin, bound);
|
||||
direction = this.random.nextInt(origin, bound);
|
||||
previousDirections.add(direction);
|
||||
}
|
||||
this.addMazePosition(position, Block.WHITE_WOOL);
|
||||
this.addMazePosition(position.add(0,0,1), Block.WHITE_WOOL);
|
||||
this.addMazePosition(position.add(0,0,2), Block.RED_WOOL);
|
||||
this.addMazePosition(position.add(0,0,3), Block.WHITE_WOOL);
|
||||
this.addMazePosition(position.add(0,0,6), Block.RED_WOOL);
|
||||
}
|
||||
|
||||
private void addMazePosition(Pos position, Block pathBlock) {
|
||||
this.mazeBatch.setBlock(position, pathBlock);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
this.mazeBatch.setBlock(position.add(i-1,0,j-1), pathBlock);
|
||||
}
|
||||
}
|
||||
this.mazePath.add(position.add(0.5,1,0.5));
|
||||
}
|
||||
|
||||
@ -67,10 +109,14 @@ public class Towerdefense extends StatelessGame {
|
||||
|
||||
@Override
|
||||
protected boolean onPlayerJoin(Player p) {
|
||||
TowerdefenseRoom newRoom = new TowerdefenseRoom(p, this);
|
||||
this.instances.add(newRoom);
|
||||
p.setInstance(newRoom);
|
||||
newRoom.startWave(List.of(EntityType.ENDERMAN, EntityType.BLAZE, EntityType.PLAYER, EntityType.HORSE, EntityType.ARMOR_STAND, EntityType.SKELETON));
|
||||
return false;
|
||||
}
|
||||
|
||||
public Map<Class<? extends Tower>, Integer> getPrices() {
|
||||
return this.prices;
|
||||
}
|
||||
|
||||
public Map<Material, Class<? extends Tower>> getAvailableTowers() {
|
||||
return this.availableTowers;
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,46 @@ 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.generator.MazeGenerator;
|
||||
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.CommonEventHandles;
|
||||
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.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.item.PlayerBeginItemUseEvent;
|
||||
import net.minestom.server.event.player.*;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
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;
|
||||
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<EntityCreature> enemies = new ArrayList<>();
|
||||
private final List<Tower> towers = new ArrayList<>();
|
||||
private final Cursor cursor;
|
||||
private int money = 0;
|
||||
|
||||
public TowerdefenseRoom(Player player, Towerdefense game) {
|
||||
super(UUID.randomUUID(), Dimension.OVERWORLD.key);
|
||||
@ -25,34 +50,200 @@ public class TowerdefenseRoom extends InstanceContainer {
|
||||
this.game = game;
|
||||
this.player.setGameMode(GameMode.ADVENTURE);
|
||||
this.player.setAllowFlying(true);
|
||||
this.player.getInventory().setItemStack(0, ItemStack.of(Material.ARMOR_STAND));
|
||||
this.addMoney(0);
|
||||
this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach);
|
||||
this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach);
|
||||
|
||||
setGenerator(new MazeGenerator());
|
||||
this.player.getInventory().addItemStack(
|
||||
ItemStack.of(Material.BOW).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().forEach((material, tower) -> {
|
||||
int price = this.game.getPrices().get(tower);
|
||||
this.player.getInventory().addItemStack(ItemStack.of(material).withMaxStackSize(price).withAmount(price));
|
||||
});
|
||||
|
||||
this.setGenerator(new MazeGenerator());
|
||||
BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});
|
||||
|
||||
this.cursor = new Cursor(reach);
|
||||
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;
|
||||
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(ItemDropEvent.class, CommonEventHandles::cancel)
|
||||
.addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel)
|
||||
.addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel)
|
||||
.addListener(PlayerBeginItemUseEvent.class, event -> {
|
||||
if(event.getAnimation().equals(ItemAnimation.BOW)) event.setCancelled(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void startWave(List<EntityType> entities) {
|
||||
int counter = 0;
|
||||
for(EntityType entityType : entities) {
|
||||
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
||||
this.addEntity(new EntityCreature(entityType));
|
||||
return TaskSchedule.stop();
|
||||
}, TaskSchedule.millis(800L*counter));
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntity(EntityCreature entity) {
|
||||
entity.setInstance(this, this.game.getMazePath().getFirst());
|
||||
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.15);
|
||||
entity.getNavigator().setPathTo(this.game.getMazePath().get(1), 0.7, () -> changeEntityGoal(entity, 1));
|
||||
}
|
||||
|
||||
private void changeEntityGoal(EntityCreature entity, int positionIndex) {
|
||||
if(positionIndex == this.game.getMazePath().size()-1) {
|
||||
private void useItem() {
|
||||
Material itemInHand = this.player.getItemInMainHand().material();
|
||||
if(itemInHand.equals(Material.BOW)) {
|
||||
this.playerAttack();
|
||||
return;
|
||||
}
|
||||
entity.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.7, () -> changeEntityGoal(entity, positionIndex+1));
|
||||
if(itemInHand.equals(Material.BARRIER)) {
|
||||
this.removeTower();
|
||||
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.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();
|
||||
})
|
||||
.addListener(ProjectileCollideWithBlockEvent.class, collisionEvent -> projectile.remove());
|
||||
}
|
||||
|
||||
public void addMoney(int amount) {
|
||||
this.money += amount;
|
||||
this.player.setLevel(this.money);
|
||||
}
|
||||
|
||||
public Material getPlayerHandMaterial() {
|
||||
return this.player.getItemInMainHand().material();
|
||||
}
|
||||
|
||||
private synchronized void placeTower() {
|
||||
if(!this.canPlaceActiveTower()) {
|
||||
if(this.cursor.isCursorActive() && !this.enoughMoneyForActiveTower()) {
|
||||
this.player.sendActionBar(Component.text("Nicht genug Geld!", TextColor.color(255,0,0)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Material usedMaterial = this.player.getItemInMainHand().material();
|
||||
Tower tower = this.game.getAvailableTowers().entrySet().stream()
|
||||
.filter(materialClassEntry -> materialClassEntry.getKey().equals(usedMaterial))
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.getValue()
|
||||
.getConstructor()
|
||||
.newInstance();
|
||||
this.setBlock(this.cursor.getTargetBlockPosition(), Block.BLUE_WOOL);
|
||||
tower.setInstance(this, this.cursor.getPosition());
|
||||
this.towers.add(tower);
|
||||
tower.startShooting();
|
||||
this.addMoney(-this.game.getPrices().get(tower.getClass()));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
this.cursor.updateCursorPosition(this.player);
|
||||
}
|
||||
|
||||
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 boolean canPlaceActiveTower() {
|
||||
Material usedMaterial = this.player.getItemInMainHand().material();
|
||||
return this.canPlaceTower(this.game.getAvailableTowers().get(usedMaterial));
|
||||
}
|
||||
|
||||
private boolean canPlaceTower(@Nullable Class<? extends Tower> towerClass) {
|
||||
if(towerClass == null) return false;
|
||||
return this.cursor.isCursorActive() && this.enoughMoney(towerClass);
|
||||
}
|
||||
|
||||
private boolean enoughMoneyForActiveTower() {
|
||||
Material usedMaterial = this.player.getItemInMainHand().material();
|
||||
return this.enoughMoney(this.game.getAvailableTowers().get(usedMaterial));
|
||||
}
|
||||
|
||||
private boolean enoughMoney(@Nullable Class<? extends Tower> towerClass) {
|
||||
if(towerClass == null) return true;
|
||||
return this.money >= this.game.getPrices().get(towerClass);
|
||||
}
|
||||
|
||||
void startWave(List<GroupFactory> enemyGroups) {
|
||||
enemyGroups.forEach(groupFactory -> groupFactory.summonGroup(this));
|
||||
}
|
||||
|
||||
public void addEnemy(EntityCreature enemy) {
|
||||
enemy.setInstance(this, this.game.getMazePath().getFirst());
|
||||
this.enemies.add(enemy);
|
||||
this.changeEnemyGoal(enemy, 0);
|
||||
}
|
||||
|
||||
private synchronized void changeEnemyGoal(EntityCreature enemy, int positionIndex) {
|
||||
if(positionIndex == this.game.getMazePath().size()-1) {
|
||||
this.enemies.remove(enemy);
|
||||
enemy.remove();
|
||||
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.setInstance(this.game).thenRun(() -> MinecraftServer.getInstanceManager().unregisterInstance(this));
|
||||
this.player.heal();
|
||||
this.game.getScore().insertResult(this.player);
|
||||
return;
|
||||
}
|
||||
this.player.damage(DamageType.PLAYER_ATTACK, damage);
|
||||
return;
|
||||
}
|
||||
enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1));
|
||||
}
|
||||
|
||||
public Towerdefense getGame() {
|
||||
return this.game;
|
||||
}
|
||||
|
||||
public List<EntityCreature> getEnemies() {
|
||||
return this.enemies;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
|
||||
public class BlazeTower extends ShootingTower {
|
||||
public BlazeTower() {
|
||||
super(EntityType.BLAZE, 2, 2, 15);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attack(EntityCreature enemy) {
|
||||
this.swingMainHand();
|
||||
this.shootProjectile(enemy, EntityType.FIREBALL, 2, 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
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);
|
||||
target.setFlyingWithElytra(true);
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
|
||||
public class SkeletonTower extends ShootingTower {
|
||||
public SkeletonTower() {
|
||||
super(EntityType.SKELETON, 1, 2, 10);
|
||||
this.setItemInMainHand(ItemStack.of(Material.BOW));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attack(EntityCreature enemy) {
|
||||
this.swingMainHand();
|
||||
this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProjectileHit(EntityCreature enemy) {
|
||||
super.onProjectileHit(enemy);
|
||||
// enemy.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue()*0.5);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||
|
||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.*;
|
||||
import net.minestom.server.entity.attribute.Attribute;
|
||||
import net.minestom.server.entity.damage.DamageType;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.timer.TaskSchedule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class Tower extends EntityCreature {
|
||||
public enum Priority {
|
||||
FIRST(Type.PATH_DISTANCE),
|
||||
LAST(Type.PATH_DISTANCE),
|
||||
STRONG(Type.HEALTH),
|
||||
WEAK(Type.HEALTH),
|
||||
CLOSE(Type.TOWER_DISTANCE),
|
||||
FAR(Type.TOWER_DISTANCE);
|
||||
|
||||
final Type type;
|
||||
Priority(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
PATH_DISTANCE,
|
||||
HEALTH,
|
||||
TOWER_DISTANCE
|
||||
}
|
||||
}
|
||||
|
||||
private final static int damageDivider = 10;
|
||||
private Priority priority = Priority.FIRST;
|
||||
protected float damage;
|
||||
protected int range;
|
||||
protected double attacksPerSecond;
|
||||
protected TaskSchedule attackDelay;
|
||||
private @Nullable Task attackTask;
|
||||
private float sellingPriceMultiplier = 1;
|
||||
|
||||
public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
|
||||
super(entityType);
|
||||
this.damage = (float) damage / damageDivider;
|
||||
this.range = range;
|
||||
this.attacksPerSecond = attacksPerSecond;
|
||||
this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond));
|
||||
}
|
||||
|
||||
public void startShooting() {
|
||||
this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
||||
EntityCreature nextEnemy = this.getNextEnemy();
|
||||
if(nextEnemy == null) return this.attackDelay;
|
||||
this.lookAt(nextEnemy);
|
||||
this.attack(nextEnemy);
|
||||
return this.attackDelay;
|
||||
}, TaskSchedule.immediate());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void remove(boolean permanent) {
|
||||
if(this.attackTask != null) this.attackTask.cancel();
|
||||
super.remove(permanent);
|
||||
}
|
||||
|
||||
private @Nullable EntityCreature getNextEnemy() {
|
||||
List<EntityCreature> enemies = this.getSortedEnemies();
|
||||
if(enemies.isEmpty()) return null;
|
||||
|
||||
return switch (this.priority) {
|
||||
case LAST, STRONG, FAR -> enemies.getLast();
|
||||
case FIRST, WEAK, CLOSE -> enemies.getFirst();
|
||||
};
|
||||
}
|
||||
|
||||
private List<EntityCreature> getSortedEnemies() {
|
||||
Stream<EntityCreature> enemyStream = this.getRoomInstance().getEnemies().stream().parallel()
|
||||
.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 TOWER_DISTANCE -> enemyStream
|
||||
.sorted((o1, o2) -> Double.compare(
|
||||
o1.getPosition().distance(this.getPosition()),
|
||||
o2.getPosition().distance(this.getPosition())
|
||||
)).toList();
|
||||
case HEALTH -> enemyStream
|
||||
.sorted((o1, o2) -> Float.compare(o1.getHealth(), o2.getHealth()))
|
||||
.toList();
|
||||
};
|
||||
}
|
||||
|
||||
protected TowerdefenseRoom getRoomInstance() {
|
||||
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;
|
||||
}
|
||||
|
||||
public int getRange() {
|
||||
return this.range;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
|
||||
public class ZombieTower extends Tower {
|
||||
public ZombieTower() {
|
||||
super(EntityType.ZOMBIE, 7, 1, 4);
|
||||
this.setItemInMainHand(ItemStack.of(Material.IRON_SWORD));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attack(EntityCreature enemy) {
|
||||
this.swingMainHand();
|
||||
this.causeDamage(enemy);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user