made multiplayer possible

This commit is contained in:
Lars Neuhaus 2024-10-22 15:51:00 +02:00
parent 710838645f
commit 2799a40c58
4 changed files with 156 additions and 120 deletions
src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris

@ -2,12 +2,15 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.TetrisGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.Tetromino;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.kyori.adventure.text.Component;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.*;
import net.minestom.server.item.ItemStack;
@ -16,84 +19,36 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
class Tetris extends StatelessGame {
private TetrisGame tetrisGame;
private final Map<Button, Long> lastPresses = new HashMap<>();
enum Button {
W,
A,
S,
D,
mouseLeft,
mouseRight,
space
}
private final Map<Player, TetrisGame> tetrisGames = new HashMap<>();
public Tetris() {
super(Dimension.THE_END.key, "Tetris", new FirstWinsScore());
super(Dimension.THE_END.key, "Tetris", new LastWinsScore());
this.setGenerator(new CircularPlateTerrainGenerator(30).setPlateHeight(0));
eventNode()
.addListener(PlayerUseItemEvent.class, this::onPlayerInteract)
.addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack);
.addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack)
.addListener(PlayerTickEvent.class, this::onPlayerTick);
}
@Override
protected void onStart() {
super.onStart();
this.tetrisGame.start();
}
protected void pressedButton(Button button) {
if(lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-100) return;
lastPresses.put(button, System.currentTimeMillis());
switch (button) {
case A -> {
System.out.println("A");
System.out.println(this.tetrisGame.moveLeft());
}
case S -> {
System.out.println("S");
System.out.println(this.tetrisGame.moveDown());
}
case D -> {
System.out.println("D");
System.out.println(this.tetrisGame.moveRight());
}
case W -> {
System.out.println("W");
while(this.tetrisGame.moveDown()) {
this.tetrisGame.addPoints(2);
}
}
case mouseLeft -> {
System.out.println("mouse left");
System.out.println(this.tetrisGame.rotate(false));
}
case mouseRight -> {
System.out.println("mouse right");
System.out.println(this.tetrisGame.rotate(true));
}
case space -> {
System.out.println("space");
System.out.println(this.tetrisGame.switchHold());
}
}
}
protected void releasedButton(Button button) {
lastPresses.put(button, 0L);
this.getEntities().stream()
.filter(entity -> entity.getEntityType().equals(Tetromino.getGhostEntityType()))
.forEach(Entity::remove);
this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.start());
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
protected void onStop() {
this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.sidebar.removeViewer(player));
}
@Override
protected void onPlayerLeave(Player p) {
this.tetrisGames.get(p).sidebar.removeViewer(p);
}
@Override
@ -102,9 +57,13 @@ class Tetris extends StatelessGame {
Pos previousPosition = event.getPlayer().getPosition();
Pos currentPosition = event.getNewPosition();
if(this.tetrisGame == null) return;
TetrisGame tetrisGame = this.tetrisGames.get(player);
event.setNewPosition(this.tetrisGame.getPlayerSpawnPosition().withView(event.getNewPosition()));
if(tetrisGame == null) return;
if(tetrisGame.lost) return;
if(player.getGameMode() == GameMode.SPECTATOR) return;
event.setNewPosition(tetrisGame.getPlayerSpawnPosition().withView(currentPosition));
player.setSprinting(false);
Vec movementVector = currentPosition.asVec().sub(previousPosition.asVec());
@ -122,36 +81,36 @@ class Tetris extends StatelessGame {
double leftAmount = movementVector.dot(left);
if (forwardAmount > 0.018) {
pressedButton(Button.W);
releasedButton(Button.S);
tetrisGame.pressedButton(TetrisGame.Button.W);
} else if (forwardAmount < -0.018) {
pressedButton(Button.S);
releasedButton(Button.W);
} else {
releasedButton(Button.W);
releasedButton(Button.S);
tetrisGame.pressedButton(TetrisGame.Button.S);
}
if (leftAmount > 0.018) {
pressedButton(Button.D);
releasedButton(Button.A);
tetrisGame.pressedButton(TetrisGame.Button.D);
} else if (leftAmount < -0.018) {
pressedButton(Button.A);
releasedButton(Button.D);
} else {
releasedButton(Button.A);
releasedButton(Button.D);
tetrisGame.pressedButton(TetrisGame.Button.A);
}
if(previousPosition.y() < currentPosition.y()) pressedButton(Button.space);
if(previousPosition.y() < currentPosition.y()) tetrisGame.pressedButton(TetrisGame.Button.space);
}
protected void onPlayerInteract(@NotNull PlayerUseItemEvent event) {
pressedButton(Button.mouseRight);
this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseRight);
}
protected void onPlayerAttack(@NotNull PlayerHandAnimationEvent event) {
pressedButton(Button.mouseLeft);
this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseLeft);
}
protected void onPlayerTick(PlayerTickEvent event) {
TetrisGame tetrisGame = this.tetrisGames.get(event.getPlayer());
if(tetrisGame == null) return;
if(tetrisGame.lost && event.getPlayer().getGameMode() != GameMode.SPECTATOR) {
event.getPlayer().setGameMode(GameMode.SPECTATOR);
getScore().insertResult(event.getPlayer());
tetrisGame.sidebar.removeViewer(event.getPlayer());
}
}
@Override
@ -159,16 +118,20 @@ class Tetris extends StatelessGame {
p.getInventory().setItemStack(0, ItemStack.builder(Material.BIRCH_BUTTON).customName(Component.text("Controller")).build());
p.setSprinting(false);
this.tetrisGame = new TetrisGame(this, getSpawn().sub(6, 8, 15));
this.tetrisGame.generate();
p.teleport(this.tetrisGame.getPlayerSpawnPosition());
if(this.tetrisGames.get(p) == null) {
this.tetrisGames.put(p, new TetrisGame(this, getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*23, 0, 0)));
this.tetrisGames.get(p).generate();
}
TetrisGame tetrisGame = this.tetrisGames.get(p);
p.teleport(tetrisGame.getPlayerSpawnPosition());
tetrisGame.sidebar.addViewer(p);
this.tetrisGame.sidebar.addViewer(p);
return super.onPlayerJoin(p);
}
@Override
public Pos getSpawn() {
return new Pos(0, 50, 15).withView(180, 0);
return new Pos(-50, 50, 15).withView(180, 0);
}
}

@ -3,13 +3,15 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
public class Playfield {
private final Pos lowerLeftCorner;
private final StatelessGame instance;
private final int height = 22;
private final static int height = 22;
public Playfield(Pos lowerLeftCorner, StatelessGame instance) {
this.lowerLeftCorner = lowerLeftCorner;
@ -17,7 +19,8 @@ public class Playfield {
}
public Pos getPlayerSpawnPosition() {
return this.lowerLeftCorner.add(6, 8, 20);
// return this.lowerLeftCorner.add(6.5, 9+((double) 3/16), 20.5).withView(180, 0);
return this.lowerLeftCorner.add(6.5, 9, 20.5).withView(180, 0);
}
public Pos getTetrominoSpawnPosition() {
@ -36,7 +39,7 @@ public class Playfield {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x=0; x<12; x++) {
for(int y=0; y<this.height; y++) {
for(int y = 0; y< height; y++) {
batch.setBlock(this.lowerLeftCorner.add(x, y, 0), Block.GLASS);
batch.setBlock(this.lowerLeftCorner.add(x, y, -1), Block.BLACK_CONCRETE);
@ -48,16 +51,16 @@ public class Playfield {
}
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE);
// batch.setBlock(getPlayerSpawnPosition(), Block.IRON_TRAPDOOR.withProperty("half", "bottom"));
// batch.setBlock(getPlayerSpawnPosition().add(0, 1, 0), Block.IRON_TRAPDOOR.withProperty("half", "top"));
BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
}
public int removeFullLines() {
int removedLinesCounter = 0;
for(int y=1; y<this.height; y++) {
for(int y = 1; y< height; y++) {
boolean isFullLine = true;
for(int x=1; x<11; x++) {
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false;
@ -71,19 +74,22 @@ public class Playfield {
return removedLinesCounter;
}
public void removeBlock(Block block) {
for(int x=0; x<12; x++) {
for(int y=0; y<this.height; y++) {
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)).equals(block)) {
this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), Block.AIR);
public void removeEntity(EntityType entityType) {
this.instance.getEntities().stream()
.filter(entity -> entity.getEntityType().equals(entityType))
.filter(entity -> {
Pos position = entity.getPosition();
if(position.x() > this.lowerLeftCorner.x() && position.y() > this.lowerLeftCorner.y()) {
return position.x() < this.lowerLeftCorner.x() + 11 && position.y() < this.lowerLeftCorner.y() + height;
}
}
}
return false;
})
.forEach(Entity::remove);
}
private void removeFullLine(int positionY) {
for(int y=positionY; y<this.height; y++) {
for(int y = positionY; y< height; y++) {
for(int x=1; x<11; x++) {
Block blockAbove = this.instance.getBlock(this.lowerLeftCorner.add(x, y+1, 1));
this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockAbove);

@ -8,9 +8,7 @@ import net.minestom.server.scoreboard.Sidebar;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
public class TetrisGame {
private final StatelessGame instance;
@ -18,7 +16,7 @@ public class TetrisGame {
private int level = 1;
private int lines = 0;
private int score = 0;
private boolean lost = false;
public boolean lost = false;
public boolean paused = true;
public Tetromino currentTetromino;
private Tetromino nextTetromino;
@ -29,6 +27,17 @@ public class TetrisGame {
private final Pos holdPosition;
private final Pos tetrominoSpawnPosition;
public Sidebar sidebar = new Sidebar(Component.text("Info:"));
private final Map<Button, Long> lastPresses = new HashMap<>();
public enum Button {
W,
A,
S,
D,
mouseLeft,
mouseRight,
space
}
public TetrisGame(StatelessGame instance, Pos lowerLeftCorner) {
this(instance, lowerLeftCorner, Tetromino.Shape.J);
@ -47,6 +56,24 @@ public class TetrisGame {
this.nextTetromino = this.getNextTetromino();
}
public void pressedButton(Button button) {
if(lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-100) return;
lastPresses.put(button, System.currentTimeMillis());
if(button == Button.W) lastPresses.put(button, System.currentTimeMillis()+70);
if(button == Button.S) lastPresses.put(button, System.currentTimeMillis()-70);
switch (button) {
case A -> this.moveLeft();
case S -> this.moveDown();
case D -> this.moveRight();
case W -> this.hardDrop();
case mouseLeft -> this.rotate(false);
case mouseRight -> this.rotate(true);
case space -> this.switchHold();
}
}
public Pos getPlayerSpawnPosition() {
return this.playfield.getPlayerSpawnPosition();
}
@ -76,28 +103,23 @@ public class TetrisGame {
}
}
public void addPoints(int points) {
this.score += points;
this.updateSidebar();
}
public boolean rotate(boolean clockwise) {
private boolean rotate(boolean clockwise) {
if(this.lost || this.paused) return false;
return this.currentTetromino.rotate(clockwise);
}
public boolean moveLeft() {
private boolean moveLeft() {
if(this.lost || this.paused) return false;
return this.currentTetromino.moveLeft();
}
public boolean moveRight() {
private boolean moveRight() {
if(this.lost || this.paused) return false;
return this.currentTetromino.moveRight();
}
public boolean moveDown() {
private boolean moveDown() {
if(this.lost || this.paused) return false;
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
@ -108,7 +130,23 @@ public class TetrisGame {
return true;
}
public boolean switchHold() {
private boolean hardDrop() {
if(this.lost || this.paused) return false;
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
return false;
}
this.score += 2;
this.updateSidebar();
while(this.currentTetromino.moveDown()) {
this.score += 2;
this.updateSidebar();
}
this.setActiveTetrominoDown();
return true;
}
private boolean switchHold() {
if(!holdPossible) return false;
if(this.lost || this.paused) return false;

@ -2,6 +2,9 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.instance.block.Block;
import java.util.ArrayList;
@ -14,7 +17,7 @@ public class Tetromino {
private final Playfield playfield;
private Pos position;
private int[][] shapeArray;
private final Block ghostBlock = Block.ICE;
private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK;
public enum Shape {
I,
@ -42,6 +45,10 @@ public class Tetromino {
}
}
public static EntityType getGhostEntityType() {
return ghostEntityType;
}
public void setPosition(Pos newPosition) {
this.position = newPosition;
}
@ -72,13 +79,20 @@ public class Tetromino {
public void draw(boolean withGhost) {
if(withGhost) {
this.playfield.removeBlock(this.ghostBlock);
this.playfield.removeEntity(ghostEntityType);
Pos ghostPos = this.position;
while (!checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) {
ghostPos = ghostPos.sub(0, 1, 0);
}
Pos positionChange = this.position.sub(ghostPos);
getBlockPositions().forEach(pos -> this.instance.setBlock(pos.sub(positionChange), this.ghostBlock));
getBlockPositions().forEach(pos -> {
Entity ghostBlock = new Entity(ghostEntityType);
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock());
ghostBlock.setNoGravity(true);
ghostBlock.setGlowing(true);
ghostBlock.setInvisible(true);
ghostBlock.setInstance(this.instance, pos.sub(positionChange).add(0.5, 0, 0.5));
});
}
getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock()));
@ -104,6 +118,21 @@ public class Tetromino {
return returnBlock;
}
private Block getGhostBlock() {
Block returnBlock;
switch (this.shape) {
case I -> returnBlock = Block.LIGHT_BLUE_STAINED_GLASS;
case J -> returnBlock = Block.BLUE_STAINED_GLASS;
case L -> returnBlock = Block.ORANGE_STAINED_GLASS;
case O -> returnBlock = Block.YELLOW_STAINED_GLASS;
case S -> returnBlock = Block.GREEN_STAINED_GLASS;
case T -> returnBlock = Block.PURPLE_STAINED_GLASS;
case Z -> returnBlock = Block.RED_STAINED_GLASS;
default -> returnBlock = Block.WHITE_STAINED_GLASS;
}
return returnBlock;
}
private int[][] getTurnedShapeArray(boolean clockwise) {
int iterations = 1;
if(!clockwise) iterations = 3;
@ -163,7 +192,7 @@ public class Tetromino {
for(Pos pos : newBlockPositions) {
if(isPartOfTetromino(pos)) continue;
if(this.instance.getBlock(pos) == this.ghostBlock) continue;
if(this.instance.getBlock(pos) == this.getGhostBlock()) continue;
if(this.instance.getBlock(pos) != Block.AIR) return true;
}