Compare commits
8 Commits
develop
...
develop-to
Author | SHA1 | Date | |
---|---|---|---|
3b55f16b24 | |||
5ef254b75f | |||
5e5f36153e | |||
e77879c657 | |||
e3068be160 | |||
867bee1c5a | |||
893923cc56 | |||
38c944e6c1 |
@ -28,7 +28,7 @@ class Tetris extends StatelessGame {
|
|||||||
public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) {
|
public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) {
|
||||||
super(Dimension.THE_END.key, "Tetris", new PointsWinScore());
|
super(Dimension.THE_END.key, "Tetris", new PointsWinScore());
|
||||||
|
|
||||||
this.eventNode()
|
eventNode()
|
||||||
.addListener(PlayerUseItemEvent.class, this::onPlayerInteract)
|
.addListener(PlayerUseItemEvent.class, this::onPlayerInteract)
|
||||||
.addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack)
|
.addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack)
|
||||||
.addListener(PlayerTickEvent.class, this::onPlayerTick);
|
.addListener(PlayerTickEvent.class, this::onPlayerTick);
|
||||||
@ -58,7 +58,7 @@ class Tetris extends StatelessGame {
|
|||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
this.tetrisGames.forEach((player, tetrisGame) -> {
|
this.tetrisGames.forEach((player, tetrisGame) -> {
|
||||||
tetrisGame.loose();
|
tetrisGame.loose();
|
||||||
this.getScore().insertResult(player, tetrisGame.getScore());
|
getScore().insertResult(player, tetrisGame.getScore());
|
||||||
tetrisGame.sidebar.removeViewer(player);
|
tetrisGame.sidebar.removeViewer(player);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ class Tetris extends StatelessGame {
|
|||||||
TetrisGame tetrisGame = this.tetrisGames.get(player);
|
TetrisGame tetrisGame = this.tetrisGames.get(player);
|
||||||
if(tetrisGame == null) return;
|
if(tetrisGame == null) return;
|
||||||
if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) {
|
if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) {
|
||||||
this.letPlayerLoose(player);
|
letPlayerLoose(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,15 +114,15 @@ class Tetris extends StatelessGame {
|
|||||||
TetrisGame tetrisGame = this.tetrisGames.get(player);
|
TetrisGame tetrisGame = this.tetrisGames.get(player);
|
||||||
player.setGameMode(GameMode.SPECTATOR);
|
player.setGameMode(GameMode.SPECTATOR);
|
||||||
player.setInvisible(true);
|
player.setInvisible(true);
|
||||||
this.getScore().insertResult(player, tetrisGame.getScore());
|
getScore().insertResult(player, tetrisGame.getScore());
|
||||||
|
|
||||||
boolean allGamesLost = this.tetrisGames.values().stream()
|
boolean allGamesLost = this.tetrisGames.values().stream()
|
||||||
.filter(game -> !game.lost)
|
.filter(game -> !game.lost)
|
||||||
.toList()
|
.toList()
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
if(!this.setTimeLimit && !allGamesLost) {
|
if(!setTimeLimit && !allGamesLost) {
|
||||||
this.setTimeLimit(90);
|
this.setTimeLimit(90);
|
||||||
this.setTimeLimit = true;
|
setTimeLimit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ class Tetris extends StatelessGame {
|
|||||||
if(this.tetrisGames.get(p) == null) {
|
if(this.tetrisGames.get(p) == null) {
|
||||||
this.tetrisGames.put(p, new TetrisGame(
|
this.tetrisGames.put(p, new TetrisGame(
|
||||||
this,
|
this,
|
||||||
this.getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*30, 0, 0),
|
getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*30, 0, 0),
|
||||||
Tetromino.Shape.J,
|
Tetromino.Shape.J,
|
||||||
this.nextTetrominoesCount,
|
this.nextTetrominoesCount,
|
||||||
this.isFast,
|
this.isFast,
|
||||||
|
@ -74,10 +74,10 @@ public class Playfield {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.setBlock(this.getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE);
|
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE);
|
||||||
batch.setBlock(this.getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE);
|
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE);
|
||||||
batch.setBlock(this.getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE);
|
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE);
|
||||||
batch.setBlock(this.getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE);
|
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE);
|
||||||
|
|
||||||
BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
|
BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ public class Playfield {
|
|||||||
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false;
|
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false;
|
||||||
}
|
}
|
||||||
if(isFullLine) {
|
if(isFullLine) {
|
||||||
this.removeFullLine(y);
|
removeFullLine(y);
|
||||||
removedLinesCounter += 1;
|
removedLinesCounter += 1;
|
||||||
y -= 1;
|
y -= 1;
|
||||||
}
|
}
|
||||||
@ -99,10 +99,10 @@ public class Playfield {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addLines(int lines) {
|
public void addLines(int lines) {
|
||||||
int xPosMissing = this.random.nextInt(1, 10);
|
int xPosMissing = random.nextInt(1, 10);
|
||||||
|
|
||||||
for (int i = 0; i < lines; i++) {
|
for (int i = 0; i < lines; i++) {
|
||||||
this.moveAllLinesUp();
|
moveAllLinesUp();
|
||||||
for (int x = 1; x < 11; x++) {
|
for (int x = 1; x < 11; x++) {
|
||||||
if(x != xPosMissing) {
|
if(x != xPosMissing) {
|
||||||
this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.LIGHT_GRAY_CONCRETE);
|
this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.LIGHT_GRAY_CONCRETE);
|
||||||
|
@ -121,8 +121,8 @@ public class TetrisGame {
|
|||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
if(this.lost || this.paused) return;
|
if(this.lost || this.paused) return;
|
||||||
if(!this.currentTetromino.moveDown()) {
|
if(!currentTetromino.moveDown()) {
|
||||||
this.setActiveTetrominoDown();
|
setActiveTetrominoDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ public class TetrisGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean switchHold() {
|
private boolean switchHold() {
|
||||||
if(!this.holdPossible) return false;
|
if(!holdPossible) return false;
|
||||||
|
|
||||||
Tetromino newCurrentTetromino;
|
Tetromino newCurrentTetromino;
|
||||||
if(this.holdTetromino == null) {
|
if(this.holdTetromino == null) {
|
||||||
@ -194,7 +194,7 @@ public class TetrisGame {
|
|||||||
|
|
||||||
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
|
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
|
||||||
this.currentTetromino.draw();
|
this.currentTetromino.draw();
|
||||||
if(!this.currentTetromino.moveDown()) this.loose();
|
if(!this.currentTetromino.moveDown()) loose();
|
||||||
|
|
||||||
double xChange = this.holdTetromino.getXChange();
|
double xChange = this.holdTetromino.getXChange();
|
||||||
this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
|
this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
|
||||||
@ -312,7 +312,7 @@ public class TetrisGame {
|
|||||||
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
|
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
|
||||||
this.currentTetromino.draw();
|
this.currentTetromino.draw();
|
||||||
if(!this.currentTetromino.moveDown()) {
|
if(!this.currentTetromino.moveDown()) {
|
||||||
this.loose();
|
loose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,13 @@ public class Tetromino {
|
|||||||
this.uuid = UUID.randomUUID();
|
this.uuid = UUID.randomUUID();
|
||||||
|
|
||||||
switch (this.shape) {
|
switch (this.shape) {
|
||||||
case I -> this.shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}};
|
case I -> shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}};
|
||||||
case J -> this.shapeArray = new int[][]{{1,0,0}, {1,1,1}, {0,0,0}};
|
case J -> shapeArray = new int[][]{{1,0,0}, {1,1,1}, {0,0,0}};
|
||||||
case L -> this.shapeArray = new int[][]{{0,0,1}, {1,1,1}, {0,0,0}};
|
case L -> shapeArray = new int[][]{{0,0,1}, {1,1,1}, {0,0,0}};
|
||||||
case O -> this.shapeArray = new int[][]{{1,1}, {1,1}};
|
case O -> shapeArray = new int[][]{{1,1}, {1,1}};
|
||||||
case S -> this.shapeArray = new int[][]{{0,1,1}, {1,1,0}, {0,0,0}};
|
case S -> shapeArray = new int[][]{{0,1,1}, {1,1,0}, {0,0,0}};
|
||||||
case T -> this.shapeArray = new int[][]{{0,1,0}, {1,1,1}, {0,0,0}};
|
case T -> shapeArray = new int[][]{{0,1,0}, {1,1,1}, {0,0,0}};
|
||||||
case Z -> this.shapeArray = new int[][]{{1,1,0}, {0,1,1}, {0,0,0}};
|
case Z -> shapeArray = new int[][]{{1,1,0}, {0,1,1}, {0,0,0}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,22 +58,22 @@ public class Tetromino {
|
|||||||
|
|
||||||
public boolean rotate(boolean clockwise) {
|
public boolean rotate(boolean clockwise) {
|
||||||
int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
|
int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
|
||||||
return this.checkCollisionAndMove(this.position, newShapeArray);
|
return checkCollisionAndMove(this.position, newShapeArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean moveDown() {
|
public boolean moveDown() {
|
||||||
Pos newPosition = this.position.sub(0, 1, 0);
|
Pos newPosition = this.position.sub(0, 1, 0);
|
||||||
return this.checkCollisionAndMove(newPosition, this.shapeArray);
|
return checkCollisionAndMove(newPosition, this.shapeArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean moveLeft() {
|
public boolean moveLeft() {
|
||||||
Pos newPosition = this.position.sub(1, 0, 0);
|
Pos newPosition = this.position.sub(1, 0, 0);
|
||||||
return this.checkCollisionAndMove(newPosition, this.shapeArray);
|
return checkCollisionAndMove(newPosition, this.shapeArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean moveRight() {
|
public boolean moveRight() {
|
||||||
Pos newPosition = this.position.add(1, 0, 0);
|
Pos newPosition = this.position.add(1, 0, 0);
|
||||||
return this.checkCollisionAndMove(newPosition, this.shapeArray);
|
return checkCollisionAndMove(newPosition, this.shapeArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw() {
|
public void draw() {
|
||||||
@ -83,11 +83,11 @@ public class Tetromino {
|
|||||||
public void draw(boolean withGhost) {
|
public void draw(boolean withGhost) {
|
||||||
if(withGhost) {
|
if(withGhost) {
|
||||||
Pos ghostPos = this.position;
|
Pos ghostPos = this.position;
|
||||||
while (!this.checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) {
|
while (!checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) {
|
||||||
ghostPos = ghostPos.sub(0, 1, 0);
|
ghostPos = ghostPos.sub(0, 1, 0);
|
||||||
}
|
}
|
||||||
Pos positionChange = this.position.sub(ghostPos);
|
Pos positionChange = this.position.sub(ghostPos);
|
||||||
this.getBlockPositions().forEach(pos -> {
|
getBlockPositions().forEach(pos -> {
|
||||||
Entity ghostBlock = new Entity(ghostEntityType);
|
Entity ghostBlock = new Entity(ghostEntityType);
|
||||||
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock());
|
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock());
|
||||||
ghostBlock.setNoGravity(true);
|
ghostBlock.setNoGravity(true);
|
||||||
@ -97,11 +97,11 @@ public class Tetromino {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock()));
|
getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawAsEntities() {
|
public void drawAsEntities() {
|
||||||
this.getBlockPositions().forEach(pos -> {
|
getBlockPositions().forEach(pos -> {
|
||||||
Entity ghostBlock = new Entity(ghostEntityType);
|
Entity ghostBlock = new Entity(ghostEntityType);
|
||||||
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getColoredBlock());
|
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getColoredBlock());
|
||||||
ghostBlock.setNoGravity(true);
|
ghostBlock.setNoGravity(true);
|
||||||
@ -222,10 +222,10 @@ public class Tetromino {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkCollision(Pos newPosition, int[][] newShapeArray) {
|
private boolean checkCollision(Pos newPosition, int[][] newShapeArray) {
|
||||||
List<Pos> newBlockPositions = this.getBlockPositions(newPosition, newShapeArray);
|
List<Pos> newBlockPositions = getBlockPositions(newPosition, newShapeArray);
|
||||||
|
|
||||||
for(Pos pos : newBlockPositions) {
|
for(Pos pos : newBlockPositions) {
|
||||||
if(this.isPartOfTetromino(pos)) continue;
|
if(isPartOfTetromino(pos)) continue;
|
||||||
if(this.instance.getBlock(pos) == this.getGhostBlock()) continue;
|
if(this.instance.getBlock(pos) == this.getGhostBlock()) continue;
|
||||||
if(this.instance.getBlock(pos) != Block.AIR) return true;
|
if(this.instance.getBlock(pos) != Block.AIR) return true;
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ public class Tetromino {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
|
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
|
||||||
if(!this.checkCollision(newPosition, newShapeArray)) {
|
if(!checkCollision(newPosition, newShapeArray)) {
|
||||||
this.remove();
|
this.remove();
|
||||||
this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new);
|
this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new);
|
||||||
this.setPosition(newPosition);
|
this.setPosition(newPosition);
|
||||||
|
@ -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.Dimension;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
|
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.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 eu.mhsl.minenet.minigames.score.LastWinsScore;
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.entity.EntityType;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
|
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class Towerdefense extends StatelessGame {
|
public class Towerdefense extends StatelessGame {
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
|
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
|
||||||
private final List<Pos> mazePath = new ArrayList<>();
|
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() {
|
public Towerdefense() {
|
||||||
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
|
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
|
||||||
|
|
||||||
setGenerator(new MazeGenerator());
|
this.setGenerator(new MazeGenerator());
|
||||||
this.generateMaze();
|
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() {
|
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);
|
||||||
|
position = position.add(0,0,2);
|
||||||
|
|
||||||
List<Integer> previousDirections = new ArrayList<>();
|
List<Integer> previousDirections = new ArrayList<>();
|
||||||
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
|
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
|
||||||
for (int i = 0; i < 9; i++) {
|
for (int i = 0; i < pathLength; i++) {
|
||||||
for (int j = 0; j < 3; j++) {
|
for (int j = 0; j < 9; j++) {
|
||||||
position = position.add(direction-1,0,direction%2);
|
position = position.add(direction-1,0,direction%2);
|
||||||
this.addMazePosition(position, Block.WHITE_WOOL);
|
this.addMazePosition(position, Block.WHITE_WOOL);
|
||||||
}
|
}
|
||||||
@ -48,12 +86,16 @@ public class Towerdefense extends StatelessGame {
|
|||||||
previousDirections.add(direction);
|
previousDirections.add(direction);
|
||||||
}
|
}
|
||||||
this.addMazePosition(position, Block.WHITE_WOOL);
|
this.addMazePosition(position, Block.WHITE_WOOL);
|
||||||
this.addMazePosition(position.add(0,0,1), Block.WHITE_WOOL);
|
this.addMazePosition(position.add(0,0,3), Block.WHITE_WOOL);
|
||||||
this.addMazePosition(position.add(0,0,2), Block.RED_WOOL);
|
this.addMazePosition(position.add(0,0,6), Block.RED_WOOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMazePosition(Pos position, Block pathBlock) {
|
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));
|
this.mazePath.add(position.add(0.5,1,0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,10 +109,14 @@ 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(EntityType.ENDERMAN, EntityType.BLAZE, EntityType.PLAYER, EntityType.HORSE, EntityType.ARMOR_STAND, EntityType.SKELETON));
|
|
||||||
return false;
|
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.Dimension;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
|
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.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.MinecraftServer;
|
||||||
|
import net.minestom.server.collision.Aerodynamics;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.*;
|
import net.minestom.server.entity.*;
|
||||||
import net.minestom.server.entity.attribute.Attribute;
|
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.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.ItemStack;
|
||||||
import net.minestom.server.item.Material;
|
import net.minestom.server.item.Material;
|
||||||
import net.minestom.server.timer.TaskSchedule;
|
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.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class TowerdefenseRoom extends InstanceContainer {
|
public class TowerdefenseRoom extends InstanceContainer {
|
||||||
|
private final static int reach = 30;
|
||||||
|
private long lastPlayerAttack = 0;
|
||||||
private final Player player;
|
private final Player player;
|
||||||
private final Towerdefense game;
|
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) {
|
public TowerdefenseRoom(Player player, Towerdefense game) {
|
||||||
super(UUID.randomUUID(), Dimension.OVERWORLD.key);
|
super(UUID.randomUUID(), Dimension.OVERWORLD.key);
|
||||||
@ -25,34 +50,200 @@ public class TowerdefenseRoom extends InstanceContainer {
|
|||||||
this.game = game;
|
this.game = game;
|
||||||
this.player.setGameMode(GameMode.ADVENTURE);
|
this.player.setGameMode(GameMode.ADVENTURE);
|
||||||
this.player.setAllowFlying(true);
|
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, () -> {});
|
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) {
|
private void useItem() {
|
||||||
int counter = 0;
|
Material itemInHand = this.player.getItemInMainHand().material();
|
||||||
for(EntityType entityType : entities) {
|
if(itemInHand.equals(Material.BOW)) {
|
||||||
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
this.playerAttack();
|
||||||
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) {
|
|
||||||
return;
|
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