diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/Option.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/Option.java index 26d9959..02cfea2 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/Option.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/Option.java @@ -25,7 +25,7 @@ public abstract class Option { this.name = name; this.options = options; - currentValue = options.get(0); + currentValue = options.getFirst(); } public void setRestrictionHandler(RestrictionHandler restrictionHandler) { @@ -56,6 +56,10 @@ public abstract class Option { return Integer.parseInt(getAsString()); } + public boolean getAsBoolean() { + return getAsInt() != 0; + } + public String getAsString() { return currentValue.toString(); } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/common/BoolOption.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/common/BoolOption.java index d77ba96..22017e0 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/common/BoolOption.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/config/common/BoolOption.java @@ -6,8 +6,8 @@ import net.minestom.server.item.Material; import java.util.List; -public class BoolOption extends Option { +public class BoolOption extends Option { public BoolOption(String id, Material item, TranslatedComponent name) { - super(id, item, name, List.of(true, false)); + super(id, item, name, List.of(1, 0)); } } 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 827b4c9..da6f2ec 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 @@ -21,8 +21,11 @@ import java.util.Map; class Tetris extends StatelessGame { private final Map tetrisGames = new HashMap<>(); + private final int nextTetrominoesCount; + private final boolean isFast; + private final boolean hasCombat; - public Tetris() { + public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) { super(Dimension.THE_END.key, "Tetris", new PointsWinScore()); // this.setGenerator(new CircularPlateTerrainGenerator(20)); @@ -30,6 +33,10 @@ class Tetris extends StatelessGame { .addListener(PlayerUseItemEvent.class, this::onPlayerInteract) .addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack) .addListener(PlayerTickEvent.class, this::onPlayerTick); + + this.nextTetrominoesCount = nextTetrominoesCount; + this.isFast = isFast; + this.hasCombat = hasCombat; } @Override @@ -37,6 +44,11 @@ class Tetris extends StatelessGame { this.getEntities().stream() .filter(entity -> entity.getEntityType().equals(Tetromino.getGhostEntityType())) .forEach(Entity::remove); + + if(this.hasCombat) { + this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values().stream().toList())); + } + this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.start()); } @@ -118,8 +130,16 @@ class Tetris extends StatelessGame { p.setSprinting(false); if(this.tetrisGames.get(p) == null) { - this.tetrisGames.put(p, new TetrisGame(this, getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*27, 0, 0))); + this.tetrisGames.put(p, new TetrisGame( + this, + getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*24, 0, 0), + Tetromino.Shape.J, + this.nextTetrominoesCount, + this.isFast, + this.hasCombat + )); this.tetrisGames.get(p).generate(); + this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values().stream().toList())); } TetrisGame tetrisGame = this.tetrisGames.get(p); diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/TetrisFactory.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/TetrisFactory.java index 3b5b628..56630f7 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/TetrisFactory.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/TetrisFactory.java @@ -4,6 +4,8 @@ import eu.mhsl.minenet.minigames.instance.game.Game; import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option; import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager; +import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.BoolOption; +import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption; import eu.mhsl.minenet.minigames.instance.room.Room; import eu.mhsl.minenet.minigames.message.component.TranslatedComponent; import net.minestom.server.item.Material; @@ -23,12 +25,15 @@ public class TetrisFactory implements GameFactory { @Override public ConfigManager configuration() { - return new ConfigManager(); + return new ConfigManager() + .addOption(new NumericOption("nextTetrominoesCount", Material.LADDER, TranslatedComponent.byId("game_Tetris#nextTetrominoesCount"), 3, 4, 5, 1, 2)) + .addOption(new BoolOption("isFast", Material.MINECART, TranslatedComponent.byId("game_Tetris#isFast"))) + .addOption(new BoolOption("hasCombat", Material.DIAMOND_SWORD, TranslatedComponent.byId("game_Tetris#hasCombat"))); } @Override public Game manufacture(Room parent, Map> configuration) { - return new Tetris().setParent(parent); + return new Tetris(configuration.get("nextTetrominoesCount").getAsInt(), configuration.get("isFast").getAsBoolean(), configuration.get("hasCombat").getAsBoolean()).setParent(parent); } @Override 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 8eac64c..bc11e86 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 @@ -8,14 +8,20 @@ import net.minestom.server.entity.EntityType; import net.minestom.server.instance.batch.AbsoluteBlockBatch; import net.minestom.server.instance.block.Block; +import java.util.Random; + public class Playfield { private final Pos lowerLeftCorner; private final StatelessGame instance; private final static int height = 22; + private final int nextTetrominoesCount; + private final Random random; - public Playfield(Pos lowerLeftCorner, StatelessGame instance) { + public Playfield(Pos lowerLeftCorner, StatelessGame instance, int nextTetrominoesCount) { + this.nextTetrominoesCount = nextTetrominoesCount; this.lowerLeftCorner = lowerLeftCorner; this.instance = instance; + this.random = new Random(); } public Pos getPlayerSpawnPosition() { @@ -38,6 +44,7 @@ public class Playfield { public void generate() { AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); + // actual playfield: for(int x=0; x<12; x++) { for(int y = 0; y< height; y++) { batch.setBlock(this.lowerLeftCorner.add(x, y, 0), Block.GLASS); @@ -50,16 +57,25 @@ public class Playfield { } } - for(int x = 0; x < 6; x++) { - for(int y = 0; y < 6; y++) { - if(x==0 || x==5 || y==0 || y==5) { - batch.setBlock(this.getHoldPosition().add(x-2, y-2, 0), Block.BLACK_CONCRETE); - batch.setBlock(this.getNextPosition().add(x-2, y-2, 0), Block.BLACK_CONCRETE); - batch.setBlock(this.getHoldPosition().add(x-2, y-2, -1), Block.BLACK_CONCRETE); - batch.setBlock(this.getNextPosition().add(x-2, y-2, -1), Block.BLACK_CONCRETE); - } - batch.setBlock(this.getHoldPosition().add(x-2, y-2, -1), Block.QUARTZ_BLOCK); - batch.setBlock(this.getNextPosition().add(x-2, y-2, -1), Block.QUARTZ_BLOCK); + // hold position: + for(int x = 0; x < 4; x++) { + for(int y = 0; y < 4; y++) { +// if(x==0 || x==5 || y==0 || y==5) { +// batch.setBlock(this.getHoldPosition().add(x-2, y-2, 0), Block.BLACK_CONCRETE); +// batch.setBlock(this.getHoldPosition().add(x-2, y-2, -1), Block.BLACK_CONCRETE); +// } + batch.setBlock(this.getHoldPosition().add(x-1, y-1, -1), Block.QUARTZ_BLOCK); + } + } + + // next positions: + for(int x = 0; x < 4; x++) { + for(int y = -4*this.nextTetrominoesCount+4; y < 4; y++) { +// if(x==0 || x==5 || y==-4*this.nextTetrominoesCount+4 || y==5) { +// batch.setBlock(this.getNextPosition().add(x-2, y-2, 0), Block.BLACK_CONCRETE); +// batch.setBlock(this.getNextPosition().add(x-2, y-2, -1), Block.BLACK_CONCRETE); +// } + batch.setBlock(this.getNextPosition().add(x-1, y-1, -1), Block.QUARTZ_BLOCK); } } @@ -100,6 +116,41 @@ public class Playfield { .forEach(Entity::remove); } + public void addLines(int lines) { + int xPosMissing = random.nextInt(1, 10); + + for (int i = 0; i < lines; i++) { + moveAllLinesUp(); + for (int x = 1; x < 11; x++) { + if(x != xPosMissing) { + this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.LIGHT_GRAY_CONCRETE); + } else { + this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.AIR); + } + } + } + } + + public void updateAttackingLines(int attackingLines) { + for (int y = 0; y < height + 5; y++) { + if(attackingLines > 0) { + this.instance.setBlock(this.lowerLeftCorner.add(12, y, 1), Block.REDSTONE_BLOCK); + attackingLines -= 1; + } else { + this.instance.setBlock(this.lowerLeftCorner.add(12, y, 1), Block.AIR); + } + } + } + + + private void moveAllLinesUp() { + for (int y = height + 3; y > 1; y--) { + for (int x = 1; x < 11; x++) { + Block blockBeneath = this.instance.getBlock(this.lowerLeftCorner.add(x, y - 1, 1)); + this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockBeneath); + } + } + } private void removeFullLine(int positionY) { for(int y = positionY; y< height; y++) { diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java index 6b8f921..6a86879 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java @@ -13,13 +13,16 @@ import java.util.*; public class TetrisGame { private final StatelessGame instance; private final Playfield playfield; + private final boolean isFast; private int level = 1; private int lines = 0; private int score = 0; + private int attackingLines = 0; public boolean lost = false; public boolean paused = true; + private final boolean hasCombat; public Tetromino currentTetromino; - private Tetromino nextTetromino; + private final List nextTetrominoes = new ArrayList<>(); private Tetromino holdTetromino; private final List tetrominoBag = new ArrayList<>(); private boolean holdPossible = true; @@ -28,6 +31,7 @@ public class TetrisGame { private final Pos tetrominoSpawnPosition; public Sidebar sidebar = new Sidebar(Component.text("Info:")); private final Map lastPresses = new HashMap<>(); + private final List otherTetrisGames = new ArrayList<>(); public enum Button { W, @@ -40,12 +44,14 @@ public class TetrisGame { } public TetrisGame(StatelessGame instance, Pos lowerLeftCorner) { - this(instance, lowerLeftCorner, Tetromino.Shape.J); + this(instance, lowerLeftCorner, Tetromino.Shape.J, 3, false, false); } - public TetrisGame(StatelessGame instance, Pos lowerLeftCorner, Tetromino.Shape startTetrominoShape) { + public TetrisGame(StatelessGame instance, Pos lowerLeftCorner, Tetromino.Shape startTetrominoShape, int nextTetrominoesCount, boolean isfast, boolean hasCombat) { + this.isFast = isfast; + this.hasCombat = hasCombat; this.instance = instance; - this.playfield = new Playfield(lowerLeftCorner, this.instance); + this.playfield = new Playfield(lowerLeftCorner, this.instance, nextTetrominoesCount); this.holdPosition = this.playfield.getHoldPosition(); this.nextPosition = this.playfield.getNextPosition(); @@ -53,7 +59,9 @@ public class TetrisGame { this.buildSidebar(); this.currentTetromino = new Tetromino(this.instance, startTetrominoShape, this.playfield); - this.nextTetromino = this.getNextTetromino(); + for (int i = 0; i < nextTetrominoesCount; i++) { + this.getNextTetromino(); + } } public void pressedButton(Button button) { @@ -74,10 +82,6 @@ public class TetrisGame { } } - public void releaseButton(Button button) { - this.lastPresses.put(button, 0L); - } - public Pos getPlayerSpawnPosition() { return this.playfield.getPlayerSpawnPosition(); } @@ -87,9 +91,11 @@ public class TetrisGame { Scheduler scheduler = MinecraftServer.getSchedulerManager(); scheduler.submitTask(() -> { if(this.lost) return TaskSchedule.stop(); - if(this.paused) return TaskSchedule.tick(40/this.level); + int standardTickDelay = 40; + if(this.isFast) standardTickDelay = 20; + if(this.paused) return TaskSchedule.tick(Math.round((float) standardTickDelay /this.level)); this.tick(); - return TaskSchedule.tick(40/this.level); + return TaskSchedule.tick(Math.round((float) standardTickDelay /this.level)); }); } @@ -111,6 +117,20 @@ public class TetrisGame { return this.score; } + public void updateOtherTetrisGames(List tetrisGames) { + List games = new ArrayList<>(tetrisGames); + games.remove(this); + this.otherTetrisGames.clear(); + this.otherTetrisGames.addAll(games); + } + + public void getAttacked(int lines) { + if(this.hasCombat) { + this.attackingLines += lines; + this.playfield.updateAttackingLines(this.attackingLines); + } + } + private boolean rotate(boolean clockwise) { if(this.lost || this.paused) return false; @@ -160,8 +180,9 @@ public class TetrisGame { Tetromino newCurrentTetromino; if(this.holdTetromino == null) { - newCurrentTetromino = this.nextTetromino; - this.nextTetromino = this.getNextTetromino(); + newCurrentTetromino = this.nextTetrominoes.removeFirst(); + newCurrentTetromino.remove(); + this.getNextTetromino(); } else { newCurrentTetromino = this.holdTetromino; this.holdTetromino.remove(); @@ -190,12 +211,15 @@ public class TetrisGame { Collections.shuffle(this.tetrominoBag); } - if(this.nextTetromino != null) this.nextTetromino.remove(); - Tetromino tetromino = this.tetrominoBag.removeFirst(); - tetromino.setPosition(this.nextPosition); - tetromino.draw(false); + if(!this.nextTetrominoes.isEmpty()) this.nextTetrominoes.forEach(Tetromino::remove); + Tetromino newTetromino = this.tetrominoBag.removeFirst(); + this.nextTetrominoes.add(newTetromino); + this.nextTetrominoes.forEach(tetromino -> { + tetromino.setPosition(this.nextPosition.sub(0, 4*this.nextTetrominoes.indexOf(tetromino), 0)); + tetromino.draw(false); + }); - return tetromino; + return newTetromino; } private void loose() { @@ -227,29 +251,54 @@ public class TetrisGame { } private void setActiveTetrominoDown() { - this.currentTetromino = this.nextTetromino; - this.nextTetromino = getNextTetromino(); + this.currentTetromino = this.nextTetrominoes.removeFirst(); + this.currentTetromino.remove(); + this.getNextTetromino(); int removedLines = this.playfield.removeFullLines(); + int combatLines = 0; switch (removedLines) { case 1 -> { this.lines += 1; this.score += 40 * this.level; } case 2 -> { + combatLines = 1; this.lines += 2; this.score += 100 * this.level; } case 3 -> { + combatLines = 2; this.lines += 3; this.score += 300 * this.level; } case 4 -> { + combatLines = 4; this.lines += 4; this.score += 1200 * this.level; } } + if(this.hasCombat && this.attackingLines > 0) { + if(combatLines > 0 && this.attackingLines >= combatLines) { + this.attackingLines -= combatLines; + combatLines = 0; + } else if(combatLines > 0) { + combatLines -= this.attackingLines; + this.attackingLines = 0; + } else { + this.playfield.addLines(this.attackingLines); + this.attackingLines = 0; + } + + this.playfield.updateAttackingLines(this.attackingLines); + } + + if(this.hasCombat && !this.otherTetrisGames.isEmpty()) { + Collections.shuffle(this.otherTetrisGames); + this.otherTetrisGames.getFirst().getAttacked(combatLines); + } + this.level = (int) Math.floor((double) this.lines / 10) + 1; this.holdPossible = true; 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 06f41d3..e41a57a 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 @@ -102,8 +102,7 @@ public class Tetromino { this.getBlockPositions().forEach(pos -> this.instance.setBlock(pos, Block.AIR)); } - - private Block getColoredBlock() { + public Block getColoredBlock() { Block returnBlock; switch (this.shape) { case I -> returnBlock = Block.LIGHT_BLUE_CONCRETE; @@ -118,6 +117,7 @@ public class Tetromino { return returnBlock; } + private Block getGhostBlock() { Block returnBlock; switch (this.shape) {