added basic towerdefense logic
This commit is contained in:
parent
36c6c93edb
commit
38c944e6c1
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense
18
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java
Normal file
18
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/EnemyFactory.java
Normal file
@ -0,0 +1,18 @@
|
||||
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) {
|
||||
public EnemyFactory(EntityType entityType) {
|
||||
this(entityType, 20, 0.3);
|
||||
}
|
||||
|
||||
public EntityCreature buildEntity() {
|
||||
EntityCreature entity = new EntityCreature(this.entityType);
|
||||
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(this.speed);
|
||||
entity.setHealth(this.health);
|
||||
return entity;
|
||||
}
|
||||
}
|
15
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/GroupFactory.java
Normal file
15
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/GroupFactory.java
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
28
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/Towerdefense.java
28
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/Towerdefense.java
@ -18,23 +18,26 @@ 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 static final int pathLength = 10;
|
||||
|
||||
public Towerdefense() {
|
||||
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
|
||||
|
||||
setGenerator(new MazeGenerator());
|
||||
this.setGenerator(new MazeGenerator());
|
||||
this.generateMaze();
|
||||
}
|
||||
|
||||
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 +47,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));
|
||||
}
|
||||
|
||||
@ -70,7 +77,10 @@ public class Towerdefense extends StatelessGame {
|
||||
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));
|
||||
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)
|
||||
));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,35 @@ 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.SkeletonTower;
|
||||
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.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
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.event.entity.EntityDeathEvent;
|
||||
import net.minestom.server.event.item.ItemDropEvent;
|
||||
import net.minestom.server.event.player.PlayerTickEvent;
|
||||
import net.minestom.server.event.player.PlayerUseItemEvent;
|
||||
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 net.minestom.server.timer.TaskSchedule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TowerdefenseRoom extends InstanceContainer {
|
||||
private final Player player;
|
||||
private final Towerdefense game;
|
||||
private final List<EntityCreature> enemies = new ArrayList<>();
|
||||
private final List<Tower> towers = new ArrayList<>();
|
||||
private final Entity cursor;
|
||||
|
||||
public TowerdefenseRoom(Player player, Towerdefense game) {
|
||||
super(UUID.randomUUID(), Dimension.OVERWORLD.key);
|
||||
@ -25,34 +39,75 @@ 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.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(200);
|
||||
this.player.getInventory().setItemStack(0, ItemStack.of(Material.SKELETON_SPAWN_EGG));
|
||||
|
||||
setGenerator(new MazeGenerator());
|
||||
this.setGenerator(new MazeGenerator());
|
||||
BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});
|
||||
|
||||
this.cursor = new Entity(EntityType.ARMOR_STAND);
|
||||
this.cursor.setInstance(this);
|
||||
|
||||
this.eventNode()
|
||||
.addListener(EntityDeathEvent.class, event -> {
|
||||
if(!(event.getEntity() instanceof EntityCreature enemy)) return;
|
||||
this.enemies.remove(enemy);
|
||||
})
|
||||
.addListener(PlayerTickEvent.class, this::setCursorPosition)
|
||||
.addListener(PlayerUseItemEvent.class, this::setTower)
|
||||
.addListener(ItemDropEvent.class, CommonEventHandles::cancel);
|
||||
}
|
||||
|
||||
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 setTower(PlayerUseItemEvent event) {
|
||||
if(event.getItemStack().material().equals(Material.SKELETON_SPAWN_EGG)) {
|
||||
Point newPosition = this.player.getTargetBlockPosition(20);
|
||||
if(newPosition == null) return;
|
||||
if(!this.getBlock(newPosition).equals(Block.BLACK_WOOL)) return;
|
||||
this.setBlock(newPosition, Block.BLUE_WOOL);
|
||||
newPosition = newPosition.add(0.5,1,0.5);
|
||||
SkeletonTower tower = new SkeletonTower();
|
||||
tower.setInstance(this, newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
public List<EntityCreature> getEnemies() {
|
||||
return this.enemies;
|
||||
}
|
||||
|
||||
private void changeEntityGoal(EntityCreature entity, int positionIndex) {
|
||||
if(positionIndex == this.game.getMazePath().size()-1) {
|
||||
private void setCursorPosition(PlayerTickEvent event) {
|
||||
Point newPosition = this.player.getTargetBlockPosition(20);
|
||||
if(newPosition == null) return;
|
||||
if(this.getBlock(newPosition).equals(Block.BLACK_WOOL)) {
|
||||
this.cursor.setInvisible(false);
|
||||
} else {
|
||||
this.cursor.setInvisible(true);
|
||||
return;
|
||||
}
|
||||
entity.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.7, () -> changeEntityGoal(entity, positionIndex+1));
|
||||
newPosition = newPosition.add(0.5,1,0.5);
|
||||
this.cursor.teleport(new Pos(newPosition));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public Towerdefense getGame() {
|
||||
return this.game;
|
||||
}
|
||||
|
||||
private void changeEnemyGoal(EntityCreature enemy, int positionIndex) {
|
||||
if(positionIndex == this.game.getMazePath().size()-1) {
|
||||
this.enemies.remove(enemy);
|
||||
enemy.remove();
|
||||
this.player.damage(DamageType.PLAYER_ATTACK, enemy.getHealth()/10);
|
||||
return;
|
||||
}
|
||||
enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1));
|
||||
}
|
||||
}
|
||||
|
24
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/SkeletonTower.java
Normal file
24
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/SkeletonTower.java
Normal file
@ -0,0 +1,24 @@
|
||||
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.entity.attribute.Attribute;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
|
||||
public class SkeletonTower extends Tower {
|
||||
public SkeletonTower() {
|
||||
super(EntityType.SKELETON, 1, 300, 20);
|
||||
this.setItemInMainHand(ItemStack.of(Material.BOW));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shoot(EntityCreature enemy) {
|
||||
this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnemyHit(EntityCreature enemy) {
|
||||
enemy.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue()*0.5);
|
||||
}
|
||||
}
|
150
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/Tower.java
Normal file
150
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/towerdefense/towers/Tower.java
Normal file
@ -0,0 +1,150 @@
|
||||
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.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;
|
||||
|
||||
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 Priority priority = Priority.FIRST;
|
||||
protected float damage;
|
||||
protected int range;
|
||||
protected TaskSchedule attackDelay;
|
||||
private final Task shootTask;
|
||||
|
||||
public Tower(@NotNull EntityType entityType, float damage, int attackDelay, int range) {
|
||||
super(entityType);
|
||||
this.damage = damage;
|
||||
this.range = range;
|
||||
this.attackDelay = TaskSchedule.millis(attackDelay);
|
||||
this.shootTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
||||
EntityCreature nextEnemy = this.getNextEnemy();
|
||||
if(nextEnemy == null) return this.attackDelay;
|
||||
this.lookAt(nextEnemy);
|
||||
this.shoot(nextEnemy);
|
||||
return this.attackDelay;
|
||||
}, 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.onEnemyHit(target);
|
||||
projectile.remove();
|
||||
})
|
||||
.addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove());
|
||||
}
|
||||
|
||||
protected void onEnemyHit(EntityCreature enemy) {
|
||||
|
||||
}
|
||||
|
||||
protected TowerdefenseRoom getRoomInstance() {
|
||||
return (TowerdefenseRoom) this.getInstance();
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void remove(boolean permanent) {
|
||||
this.shootTask.cancel();
|
||||
super.remove(permanent);
|
||||
}
|
||||
|
||||
protected abstract void shoot(EntityCreature enemy);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user