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

View File

@ -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.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.TetrisGame; 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.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 eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec; 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.entity.Player;
import net.minestom.server.event.player.*; import net.minestom.server.event.player.*;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
@ -16,84 +19,36 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
class Tetris extends StatelessGame { class Tetris extends StatelessGame {
private TetrisGame tetrisGame; private final Map<Player, TetrisGame> tetrisGames = new HashMap<>();
private final Map<Button, Long> lastPresses = new HashMap<>();
enum Button {
W,
A,
S,
D,
mouseLeft,
mouseRight,
space
}
public Tetris() { 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)); this.setGenerator(new CircularPlateTerrainGenerator(30).setPlateHeight(0));
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);
} }
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); this.getEntities().stream()
.filter(entity -> entity.getEntityType().equals(Tetromino.getGhostEntityType()))
this.tetrisGame.start(); .forEach(Entity::remove);
} this.tetrisGames.forEach((player, tetrisGame) -> 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);
} }
@Override @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 @Override
@ -102,9 +57,13 @@ class Tetris extends StatelessGame {
Pos previousPosition = event.getPlayer().getPosition(); Pos previousPosition = event.getPlayer().getPosition();
Pos currentPosition = event.getNewPosition(); 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); player.setSprinting(false);
Vec movementVector = currentPosition.asVec().sub(previousPosition.asVec()); Vec movementVector = currentPosition.asVec().sub(previousPosition.asVec());
@ -122,36 +81,36 @@ class Tetris extends StatelessGame {
double leftAmount = movementVector.dot(left); double leftAmount = movementVector.dot(left);
if (forwardAmount > 0.018) { if (forwardAmount > 0.018) {
pressedButton(Button.W); tetrisGame.pressedButton(TetrisGame.Button.W);
releasedButton(Button.S);
} else if (forwardAmount < -0.018) { } else if (forwardAmount < -0.018) {
pressedButton(Button.S); tetrisGame.pressedButton(TetrisGame.Button.S);
releasedButton(Button.W);
} else {
releasedButton(Button.W);
releasedButton(Button.S);
} }
if (leftAmount > 0.018) { if (leftAmount > 0.018) {
pressedButton(Button.D); tetrisGame.pressedButton(TetrisGame.Button.D);
releasedButton(Button.A);
} else if (leftAmount < -0.018) { } else if (leftAmount < -0.018) {
pressedButton(Button.A); tetrisGame.pressedButton(TetrisGame.Button.A);
releasedButton(Button.D);
} else {
releasedButton(Button.A);
releasedButton(Button.D);
} }
if(previousPosition.y() < currentPosition.y()) pressedButton(Button.space); if(previousPosition.y() < currentPosition.y()) tetrisGame.pressedButton(TetrisGame.Button.space);
} }
protected void onPlayerInteract(@NotNull PlayerUseItemEvent event) { protected void onPlayerInteract(@NotNull PlayerUseItemEvent event) {
pressedButton(Button.mouseRight); this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseRight);
} }
protected void onPlayerAttack(@NotNull PlayerHandAnimationEvent event) { 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 @Override
@ -159,16 +118,20 @@ class Tetris extends StatelessGame {
p.getInventory().setItemStack(0, ItemStack.builder(Material.BIRCH_BUTTON).customName(Component.text("Controller")).build()); p.getInventory().setItemStack(0, ItemStack.builder(Material.BIRCH_BUTTON).customName(Component.text("Controller")).build());
p.setSprinting(false); p.setSprinting(false);
this.tetrisGame = new TetrisGame(this, getSpawn().sub(6, 8, 15)); if(this.tetrisGames.get(p) == null) {
this.tetrisGame.generate(); this.tetrisGames.put(p, new TetrisGame(this, getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*23, 0, 0)));
p.teleport(this.tetrisGame.getPlayerSpawnPosition()); 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); return super.onPlayerJoin(p);
} }
@Override @Override
public Pos getSpawn() { public Pos getSpawn() {
return new Pos(0, 50, 15).withView(180, 0); return new Pos(-50, 50, 15).withView(180, 0);
} }
} }

View File

@ -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.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.util.BatchUtil; import eu.mhsl.minenet.minigames.util.BatchUtil;
import net.minestom.server.coordinate.Pos; 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.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
public class Playfield { public class Playfield {
private final Pos lowerLeftCorner; private final Pos lowerLeftCorner;
private final StatelessGame instance; private final StatelessGame instance;
private final int height = 22; private final static int height = 22;
public Playfield(Pos lowerLeftCorner, StatelessGame instance) { public Playfield(Pos lowerLeftCorner, StatelessGame instance) {
this.lowerLeftCorner = lowerLeftCorner; this.lowerLeftCorner = lowerLeftCorner;
@ -17,7 +19,8 @@ public class Playfield {
} }
public Pos getPlayerSpawnPosition() { 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() { public Pos getTetrominoSpawnPosition() {
@ -36,7 +39,7 @@ public class Playfield {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x=0; x<12; x++) { 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, 0), Block.GLASS);
batch.setBlock(this.lowerLeftCorner.add(x, y, -1), Block.BLACK_CONCRETE); 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, 0), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE); // batch.setBlock(getPlayerSpawnPosition(), Block.IRON_TRAPDOOR.withProperty("half", "bottom"));
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE); // batch.setBlock(getPlayerSpawnPosition().add(0, 1, 0), Block.IRON_TRAPDOOR.withProperty("half", "top"));
BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {}); BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
} }
public int removeFullLines() { public int removeFullLines() {
int removedLinesCounter = 0; int removedLinesCounter = 0;
for(int y=1; y<this.height; y++) { for(int y = 1; y< height; y++) {
boolean isFullLine = true; boolean isFullLine = true;
for(int x=1; x<11; x++) { for(int x=1; x<11; x++) {
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;
@ -71,19 +74,22 @@ public class Playfield {
return removedLinesCounter; return removedLinesCounter;
} }
public void removeBlock(Block block) { public void removeEntity(EntityType entityType) {
for(int x=0; x<12; x++) { this.instance.getEntities().stream()
for(int y=0; y<this.height; y++) { .filter(entity -> entity.getEntityType().equals(entityType))
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)).equals(block)) { .filter(entity -> {
this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), Block.AIR); 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) { 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++) { for(int x=1; x<11; x++) {
Block blockAbove = this.instance.getBlock(this.lowerLeftCorner.add(x, y+1, 1)); Block blockAbove = this.instance.getBlock(this.lowerLeftCorner.add(x, y+1, 1));
this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockAbove); this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockAbove);

View File

@ -8,9 +8,7 @@ import net.minestom.server.scoreboard.Sidebar;
import net.minestom.server.timer.Scheduler; import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule; import net.minestom.server.timer.TaskSchedule;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
public class TetrisGame { public class TetrisGame {
private final StatelessGame instance; private final StatelessGame instance;
@ -18,7 +16,7 @@ public class TetrisGame {
private int level = 1; private int level = 1;
private int lines = 0; private int lines = 0;
private int score = 0; private int score = 0;
private boolean lost = false; public boolean lost = false;
public boolean paused = true; public boolean paused = true;
public Tetromino currentTetromino; public Tetromino currentTetromino;
private Tetromino nextTetromino; private Tetromino nextTetromino;
@ -29,6 +27,17 @@ public class TetrisGame {
private final Pos holdPosition; private final Pos holdPosition;
private final Pos tetrominoSpawnPosition; private final Pos tetrominoSpawnPosition;
public Sidebar sidebar = new Sidebar(Component.text("Info:")); 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) { public TetrisGame(StatelessGame instance, Pos lowerLeftCorner) {
this(instance, lowerLeftCorner, Tetromino.Shape.J); this(instance, lowerLeftCorner, Tetromino.Shape.J);
@ -47,6 +56,24 @@ public class TetrisGame {
this.nextTetromino = this.getNextTetromino(); 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() { public Pos getPlayerSpawnPosition() {
return this.playfield.getPlayerSpawnPosition(); return this.playfield.getPlayerSpawnPosition();
} }
@ -76,28 +103,23 @@ public class TetrisGame {
} }
} }
public void addPoints(int points) {
this.score += points;
this.updateSidebar();
}
private boolean rotate(boolean clockwise) {
public boolean rotate(boolean clockwise) {
if(this.lost || this.paused) return false; if(this.lost || this.paused) return false;
return this.currentTetromino.rotate(clockwise); return this.currentTetromino.rotate(clockwise);
} }
public boolean moveLeft() { private boolean moveLeft() {
if(this.lost || this.paused) return false; if(this.lost || this.paused) return false;
return this.currentTetromino.moveLeft(); return this.currentTetromino.moveLeft();
} }
public boolean moveRight() { private boolean moveRight() {
if(this.lost || this.paused) return false; if(this.lost || this.paused) return false;
return this.currentTetromino.moveRight(); return this.currentTetromino.moveRight();
} }
public boolean moveDown() { private boolean moveDown() {
if(this.lost || this.paused) return false; if(this.lost || this.paused) return false;
if(!this.currentTetromino.moveDown()) { if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown(); this.setActiveTetrominoDown();
@ -108,7 +130,23 @@ public class TetrisGame {
return true; 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(!holdPossible) return false;
if(this.lost || this.paused) return false; if(this.lost || this.paused) return false;

View File

@ -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 eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.minestom.server.coordinate.Pos; 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 net.minestom.server.instance.block.Block;
import java.util.ArrayList; import java.util.ArrayList;
@ -14,7 +17,7 @@ public class Tetromino {
private final Playfield playfield; private final Playfield playfield;
private Pos position; private Pos position;
private int[][] shapeArray; private int[][] shapeArray;
private final Block ghostBlock = Block.ICE; private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK;
public enum Shape { public enum Shape {
I, I,
@ -42,6 +45,10 @@ public class Tetromino {
} }
} }
public static EntityType getGhostEntityType() {
return ghostEntityType;
}
public void setPosition(Pos newPosition) { public void setPosition(Pos newPosition) {
this.position = newPosition; this.position = newPosition;
} }
@ -72,13 +79,20 @@ public class Tetromino {
public void draw(boolean withGhost) { public void draw(boolean withGhost) {
if(withGhost) { if(withGhost) {
this.playfield.removeBlock(this.ghostBlock); this.playfield.removeEntity(ghostEntityType);
Pos ghostPos = this.position; Pos ghostPos = this.position;
while (!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);
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())); getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock()));
@ -104,6 +118,21 @@ public class Tetromino {
return returnBlock; 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) { private int[][] getTurnedShapeArray(boolean clockwise) {
int iterations = 1; int iterations = 1;
if(!clockwise) iterations = 3; if(!clockwise) iterations = 3;
@ -163,7 +192,7 @@ public class Tetromino {
for(Pos pos : newBlockPositions) { for(Pos pos : newBlockPositions) {
if(isPartOfTetromino(pos)) continue; 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; if(this.instance.getBlock(pos) != Block.AIR) return true;
} }