From 2799a40c589a6a40cd77419b3b3fb08501cb8e8b Mon Sep 17 00:00:00 2001 From: lars Date: Tue, 22 Oct 2024 15:51:00 +0200 Subject: [PATCH] made multiplayer possible --- .../game/stateless/types/tetris/Tetris.java | 137 +++++++----------- .../types/tetris/game/Playfield.java | 36 +++-- .../types/tetris/game/TetrisGame.java | 66 +++++++-- .../types/tetris/game/Tetromino.java | 37 ++++- 4 files changed, 156 insertions(+), 120 deletions(-) diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java index ed29ff8..5ccef02 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java @@ -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 lastPresses = new HashMap<>(); - - enum Button { - W, - A, - S, - D, - mouseLeft, - mouseRight, - space - } + private final Map 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 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); } } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java index 882c221..819f013 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java @@ -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 {}); } public int removeFullLines() { int removedLinesCounter = 0; - for(int y=1; y 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 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; diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index bd5b56d..06f41d3 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -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; }