diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/Game.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/Game.java index dfae886..7cd870b 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/Game.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/Game.java @@ -11,6 +11,7 @@ import eu.mhsl.minenet.minigames.instance.Spawnable; import eu.mhsl.minenet.minigames.instance.room.Room; import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; import net.minestom.server.event.item.ItemDropEvent; import net.minestom.server.event.player.PlayerBlockBreakEvent; @@ -98,7 +99,11 @@ public abstract class Game extends MineNetInstance implements Spawnable { public void unload() { this.onUnload(); - getPlayers().forEach(Room::setOwnRoom); + getPlayers().forEach(player -> { + Room.setOwnRoom(player); + player.setGameMode(GameMode.SURVIVAL); + player.setInvisible(false); + }); scheduler().scheduleTask(() -> { diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java index f8bbc35..fc0b8ac 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java @@ -11,6 +11,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.Deathcu import eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun.MinerunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef.SpleefFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight.StickFightFactory; +import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.TetrisFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.TrafficLightRaceFactory; @@ -23,9 +24,10 @@ public enum GameList { TOWERDEFENSE(new TowerdefenseFactory(), GameType.PROTOTYPE), BEDWARS(new BedwarsFactory(), GameType.PROTOTYPE), BACKROOMS(new BackroomsFactory(), GameType.PROTOTYPE), + ANVILRUN(new AnvilRunFactory(), GameType.PROTOTYPE), + TETRIS(new TetrisFactory(), GameType.OTHER), TNTRUN(new TntRunFactory(), GameType.OTHER), ACIDRAIN(new AcidRainFactory(), GameType.PVE), - ANVILRUN(new AnvilRunFactory(), GameType.PVE), ELYTRARACE(new ElytraRaceFactory(), GameType.PVP), SPLEEF(new SpleefFactory(), GameType.PVP), BOWSPLEEF(new BowSpleefFactory(), GameType.PVP); diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/StatelessGame.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/StatelessGame.java index c9efbb0..465e306 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/StatelessGame.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/StatelessGame.java @@ -50,8 +50,8 @@ public class StatelessGame extends Game { int timeLeft = timeLimit - timePlayed; switch (timeLeft) { - case 60, 30, 10, 5, 4, 3, 2, 1 -> - new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers()); + case 90, 60, 30, 10, 5, 4, 3, 2, 1 -> + new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers()); } timePlayed++; 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..1c6fc8f 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 @@ -9,6 +9,7 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import java.util.List; +import java.util.Objects; public abstract class Option { private RestrictionHandler restrictionHandler; @@ -25,7 +26,7 @@ public abstract class Option { this.name = name; this.options = options; - currentValue = options.get(0); + currentValue = options.getFirst(); } public void setRestrictionHandler(RestrictionHandler restrictionHandler) { @@ -44,11 +45,11 @@ public abstract class Option { } public ItemStack getCurrent(Player p) { - int amount = Integer.parseInt(options.get(pointer).toString()); + String value = options.get(pointer).toString(); return ItemStack.builder(item) .customName(name.getAssembled(p)) .lore(TranslatedComponent.byId("optionCommon#value").setColor(NamedTextColor.GOLD).getAssembled(p) - .append(Component.text(": ")).append(Component.text(amount))) + .append(Component.text(": ")).append(Component.text(value))) .build(); } @@ -56,6 +57,10 @@ public abstract class Option { return Integer.parseInt(getAsString()); } + public boolean getAsBoolean() { + return Objects.equals(getAsString(), "true") || Objects.equals(getAsString(), "1"); + } + 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..ae9548f 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("true", "false")); } } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/elytraRace/ElytraRace.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/elytraRace/ElytraRace.java index 17209c8..258f8a9 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/elytraRace/ElytraRace.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/elytraRace/ElytraRace.java @@ -40,6 +40,7 @@ public class ElytraRace extends StatelessGame { private final ValeGenerator vale = new ValeGenerator(); private final int gameHeight = 0; + private final int seaLevel = -55; private final int ringSpacing = 100; private final int ringCount; @@ -62,7 +63,7 @@ public class ElytraRace extends StatelessGame { this.ringCount = ringCount; setGenerator(vale); - vale.setCalculateSeaLevel(point -> -55); + vale.setCalculateSeaLevel(point -> seaLevel); vale.setXShiftMultiplier(integer -> NumberUtil.map(integer, 50, 500, 0, 1)); vale.addMixIn(new PlaneTerrainGenerator(gameHeight, Block.BARRIER)); @@ -106,9 +107,7 @@ public class ElytraRace extends StatelessGame { @Override protected void onLoad(@NotNull CompletableFuture callback) { Point spawnpoint = new Pos(vale.getXShiftAtZ(0), -46, 0); - GeneratorUtils.iterateArea(spawnpoint.sub(5, 0, 5), spawnpoint.add(5, 0, 5), point -> { - setBlock(point, BlockPallet.STREET.rnd()); - }); + GeneratorUtils.iterateArea(spawnpoint.sub(5, 0, 5), spawnpoint.add(5, 0, 5), point -> setBlock(point, BlockPallet.STREET.rnd())); generateRing(ringSpacing); generateRing(ringSpacing * 2); @@ -195,8 +194,8 @@ public class ElytraRace extends StatelessGame { Point ringPos = getRingPositionAtZ(zPos); GeneratorUtils.iterateArea( - ringPos.sub(100, 0, 0).withY(0), // TODO 0 was before update getDimensionType().getMinY, might not work correctly - ringPos.add(100, 0, 0).withY(gameHeight), + ringPos.sub(100, 0, 0).withY(0), + ringPos.add(100, 0, 0).withY(seaLevel), point -> batch.setBlock(point, Block.BARRIER) ); GeneratorUtils.iterateArea( 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 new file mode 100644 index 0000000..84be942 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java @@ -0,0 +1,185 @@ +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.instance.Dimension; +import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.Tetromino; +import eu.mhsl.minenet.minigames.score.PointsWinScore; +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; +import net.minestom.server.item.Material; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +class Tetris extends StatelessGame { + private final Map tetrisGames = new WeakHashMap<>(); + private final int nextTetrominoesCount; + private final boolean isFast; + private final boolean hasCombat; + private boolean setTimeLimit = false; + private final long randomSeed; + + public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) { + super(Dimension.THE_END.key, "Tetris", new PointsWinScore()); + + eventNode() + .addListener(PlayerUseItemEvent.class, this::onPlayerInteract) + .addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack) + .addListener(PlayerTickEvent.class, this::onPlayerTick); + + this.nextTetrominoesCount = nextTetrominoesCount; + this.isFast = isFast; + this.hasCombat = hasCombat; + + Random random = new Random(); + this.randomSeed = random.nextLong(); + } + + @Override + protected void onStart() { + this.getEntities().stream() + .filter(entity -> entity.getEntityType().equals(Tetromino.getGhostEntityType())) + .forEach(Entity::remove); + + if(this.hasCombat) { + this.tetrisGames.values().forEach(tetrisGame -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values())); + } + + this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.start()); + } + + @Override + protected void onStop() { + this.tetrisGames.forEach((player, tetrisGame) -> { + tetrisGame.loose(); + getScore().insertResult(player, tetrisGame.getScore()); + tetrisGame.sidebar.removeViewer(player); + }); + } + + @Override + protected void onPlayerLeave(Player p) { + this.tetrisGames.get(p).sidebar.removeViewer(p); + this.letPlayerLoose(p); + } + + @Override + protected void onPlayerMove(@NotNull PlayerMoveEvent event) { + Player player = event.getPlayer(); + Pos previousPosition = event.getPlayer().getPosition(); + Pos currentPosition = event.getNewPosition(); + + TetrisGame tetrisGame = this.tetrisGames.get(player); + + if(tetrisGame == null) { + event.setCancelled(true); + 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()); + + float yaw = player.getPosition().yaw(); + + double yawRadians = Math.toRadians(yaw); + double forwardX = -Math.sin(yawRadians); + double forwardZ = Math.cos(yawRadians); + + Vec forward = new Vec(forwardX, 0, forwardZ).normalize(); + Vec left = forward.cross(new Vec(0, 1, 0)).normalize(); + + double forwardAmount = movementVector.dot(forward); + double leftAmount = movementVector.dot(left); + + double buttonPressAmount = 0.018; + if (forwardAmount > buttonPressAmount) { + tetrisGame.pressedButton(TetrisGame.Button.W); + } else if (forwardAmount < -buttonPressAmount) { + tetrisGame.pressedButton(TetrisGame.Button.S); + } + + if (leftAmount > buttonPressAmount) { + tetrisGame.pressedButton(TetrisGame.Button.D); + } else if (leftAmount < -buttonPressAmount) { + tetrisGame.pressedButton(TetrisGame.Button.A); + } + + if(previousPosition.y() < currentPosition.y()) tetrisGame.pressedButton(TetrisGame.Button.space); + } + + protected void onPlayerInteract(@NotNull PlayerUseItemEvent event) { + this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseRight); + } + + protected void onPlayerAttack(@NotNull PlayerHandAnimationEvent event) { + this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseLeft); + } + + protected void onPlayerTick(PlayerTickEvent event) { + Player player = event.getPlayer(); + TetrisGame tetrisGame = this.tetrisGames.get(player); + if(tetrisGame == null) return; + if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) { + letPlayerLoose(player); + } + } + + private void letPlayerLoose(Player player) { + TetrisGame tetrisGame = this.tetrisGames.get(player); + player.setGameMode(GameMode.SPECTATOR); + player.setInvisible(true); + getScore().insertResult(player, tetrisGame.getScore()); + + boolean allGamesLost = this.tetrisGames.values().stream() + .filter(game -> !game.lost) + .toList() + .isEmpty(); + if(!setTimeLimit && !allGamesLost) { + this.setTimeLimit(90); + setTimeLimit = true; + } + } + + @Override + protected boolean onPlayerJoin(Player p) { + p.getInventory().setItemStack(0, ItemStack.builder(Material.BIRCH_BUTTON).customName(Component.text("Controller")).build()); + 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()*30, 0, 0), + Tetromino.Shape.J, + this.nextTetrominoesCount, + this.isFast, + this.hasCombat, + this.randomSeed + )); + this.tetrisGames.get(p).generate(); + this.tetrisGames.values().forEach(tetrisGame -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values())); + } + + TetrisGame tetrisGame = this.tetrisGames.get(p); + + p.teleport(tetrisGame.getPlayerSpawnPosition()); + tetrisGame.sidebar.addViewer(p); + + return super.onPlayerJoin(p); + } + + @Override + public Pos getSpawn() { + return new Pos(0, 30, 15).withView(180, 0); + } +} 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 new file mode 100644 index 0000000..56630f7 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/TetrisFactory.java @@ -0,0 +1,43 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris; + +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; + +import java.util.Map; + +public class TetrisFactory implements GameFactory { + @Override + public TranslatedComponent name() { + return TranslatedComponent.byId("game_Tetris#name"); + } + + @Override + public TranslatedComponent description() { + return TranslatedComponent.byId("game_Tetris#description"); + } + + @Override + public ConfigManager configuration() { + 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(configuration.get("nextTetrominoesCount").getAsInt(), configuration.get("isFast").getAsBoolean(), configuration.get("hasCombat").getAsBoolean()).setParent(parent); + } + + @Override + public Material symbol() { + return Material.PURPLE_WOOL; + } +} 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 new file mode 100644 index 0000000..edf4ddb --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java @@ -0,0 +1,191 @@ +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.instance.batch.AbsoluteBlockBatch; +import net.minestom.server.instance.block.Block; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Random; + +public class Playfield { + private final Pos lowerLeftCorner; + private final StatelessGame instance; + private final static int height = 22; + private final static Block scoreBlock = Block.STONE; + private final int nextTetrominoesCount; + private final Random random; + + public Playfield(Pos lowerLeftCorner, StatelessGame instance, int nextTetrominoesCount) { + this.nextTetrominoesCount = nextTetrominoesCount; + this.lowerLeftCorner = lowerLeftCorner; + this.instance = instance; + this.random = new Random(); + } + + public Pos getPlayerSpawnPosition() { + return this.lowerLeftCorner.add(6, 9, 20).withView(180, 0); + } + + public Pos getTetrominoSpawnPosition() { + return this.lowerLeftCorner.add(5, 21, 1); + } + + public Pos getHoldPosition() { + return this.lowerLeftCorner.add(-4, 18, 1); + } + + public Pos getNextPosition() { + return this.lowerLeftCorner.add(15, 18, 1); + } + + public Pos getScorePosition() { + return this.lowerLeftCorner.add(-5, height+3, 0); + } + + 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); + batch.setBlock(this.lowerLeftCorner.add(x, y, -1), Block.BLACK_CONCRETE); + + if(x==0 || x==11 || y==0) { + batch.setBlock(this.lowerLeftCorner.add(x, y, 1), Block.GRAY_CONCRETE); + batch.setBlock(this.lowerLeftCorner.add(x, y, 0), Block.GRAY_CONCRETE); + } + } + } + + // hold position: + for(int x = 0; x < 4; x++) { + for(int y = 0; y < 4; y++) { + 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++) { + batch.setBlock(this.getNextPosition().add(x-1, y-1, -1), Block.QUARTZ_BLOCK); + } + } + + batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE); + batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE); + batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE); + batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE); + + BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {}); + } + + public int removeFullLines() { + int removedLinesCounter = 0; + 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; + } + if(isFullLine) { + removeFullLine(y); + removedLinesCounter += 1; + y -= 1; + } + } + return removedLinesCounter; + } + + 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); + } + } + } + + public void updateScore(int score) { + this.removeDigits(); + String scoreString = String.valueOf(score); + char[] characters = scoreString.toCharArray(); + ArrayUtils.reverse(characters); + for(int i = 6; i > 0; i--) { + char digit; + if(i <= characters.length) { + digit = characters[i-1]; + } else { + digit = '0'; + } + this.displayDigit(digit, 6-i); + } + } + + + private void displayDigit(char digit, int positionFromLeft) { + int[][] digitArray; + switch (digit) { + case '1' -> digitArray = new int[][]{{0,0,1},{0,1,1},{0,0,1},{0,0,1},{0,0,1}}; + case '2' -> digitArray = new int[][]{{1,1,1},{0,0,1},{1,1,1},{1,0,0},{1,1,1}}; + case '3' -> digitArray = new int[][]{{1,1,1},{0,0,1},{0,1,1},{0,0,1},{1,1,1}}; + case '4' -> digitArray = new int[][]{{1,0,1},{1,0,1},{1,1,1},{0,0,1},{0,0,1}}; + case '5' -> digitArray = new int[][]{{1,1,1},{1,0,0},{1,1,1},{0,0,1},{1,1,1}}; + case '6' -> digitArray = new int[][]{{1,1,1},{1,0,0},{1,1,1},{1,0,1},{1,1,1}}; + case '7' -> digitArray = new int[][]{{1,1,1},{0,0,1},{0,1,0},{0,1,0},{0,1,0}}; + case '8' -> digitArray = new int[][]{{1,1,1},{1,0,1},{1,1,1},{1,0,1},{1,1,1}}; + case '9' -> digitArray = new int[][]{{1,1,1},{1,0,1},{1,1,1},{0,0,1},{1,1,1}}; + default -> digitArray = new int[][]{{1,1,1},{1,0,1},{1,0,1},{1,0,1},{1,1,1}}; + } + + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 5; y++) { + if(digitArray[4-y][x] == 1) this.instance.setBlock(this.getScorePosition().add(positionFromLeft*4+x, y, 0), scoreBlock); + } + } + } + + private void removeDigits() { + for (int x = 0; x < 4 * 6; x++) { + for (int y = 0; y < 5; y++) { + this.instance.setBlock(this.getScorePosition().add(x, y, 0), 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++) { + 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); + } + } + } +} 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 new file mode 100644 index 0000000..20d2902 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java @@ -0,0 +1,318 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game; + +import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame; +import net.kyori.adventure.text.Component; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.scoreboard.Sidebar; +import net.minestom.server.timer.Scheduler; +import net.minestom.server.timer.TaskSchedule; + +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 combo = 0; + private int attackingLines = 0; + public boolean lost = false; + public boolean paused = true; + private final boolean hasCombat; + public Tetromino currentTetromino; + private final List nextTetrominoes = new ArrayList<>(); + private Tetromino holdTetromino; + private final List tetrominoBag = new ArrayList<>(); + private boolean holdPossible = true; + private final Pos nextPosition; + private final Pos holdPosition; + private final Pos tetrominoSpawnPosition; + public Sidebar sidebar = new Sidebar(Component.text("Info:")); + private final Map lastPresses = new HashMap<>(); + private final List otherTetrisGames = new ArrayList<>(); + private final Random random; + + public enum Button { + W, + A, + S, + D, + mouseLeft, + mouseRight, + space + } + + public TetrisGame(StatelessGame instance, Pos lowerLeftCorner, Tetromino.Shape startTetrominoShape, int nextTetrominoesCount, boolean isfast, boolean hasCombat, long randomSeed) { + this.isFast = isfast; + this.hasCombat = hasCombat; + this.instance = instance; + this.playfield = new Playfield(lowerLeftCorner, this.instance, nextTetrominoesCount); + this.random = new Random(randomSeed); + + this.holdPosition = this.playfield.getHoldPosition(); + this.nextPosition = this.playfield.getNextPosition(); + this.tetrominoSpawnPosition = this.playfield.getTetrominoSpawnPosition(); + this.buildSidebar(); + + this.currentTetromino = new Tetromino(this.instance, startTetrominoShape); + for (int i = 0; i < nextTetrominoesCount; i++) { + this.updateNextTetrominoes(); + } + } + + public void pressedButton(Button button) { + final int standardButtonDelay = 100; + final int buttonDebounce = 70; + + if(this.lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-standardButtonDelay) return; + + this.lastPresses.put(button, System.currentTimeMillis()); + if(button == Button.W) this.lastPresses.put(button, System.currentTimeMillis()+buttonDebounce); + if(button == Button.S) this.lastPresses.put(button, System.currentTimeMillis()-buttonDebounce); + + if(this.lost || this.paused) return; + + switch (button) { + case A -> this.currentTetromino.moveLeft(); + case S -> this.moveDown(); + case D -> this.currentTetromino.moveRight(); + case W -> this.hardDrop(); + case mouseLeft -> this.currentTetromino.rotate(false); + case mouseRight -> this.currentTetromino.rotate(true); + case space -> this.switchHold(); + } + } + + public Pos getPlayerSpawnPosition() { + return this.playfield.getPlayerSpawnPosition(); + } + + public void start() { + this.paused = false; + Scheduler scheduler = MinecraftServer.getSchedulerManager(); + scheduler.submitTask(() -> { + if(this.lost) return TaskSchedule.stop(); + int standardTickDelay = 40; + if(this.isFast) standardTickDelay = 20; + + TaskSchedule nextTick = TaskSchedule.tick(Math.round((float) standardTickDelay /this.level)); + if(this.paused) return nextTick; + this.tick(); + return nextTick; + }); + + this.updateInfo(); + this.nextTetrominoes.forEach(tetromino -> { + double xChange = -tetromino.getXChange(); + tetromino.setPosition(this.nextPosition.sub(xChange, 4*this.nextTetrominoes.indexOf(tetromino), 0)); + tetromino.drawAsEntities(); + }); + } + + public void generate() { + this.playfield.generate(); + + this.currentTetromino.setPosition(this.tetrominoSpawnPosition); + this.currentTetromino.draw(false); + } + + public void tick() { + if(this.lost || this.paused) return; + if(!currentTetromino.moveDown()) { + setActiveTetrominoDown(); + } + } + + public int getScore() { + return this.score; + } + + public void updateOtherTetrisGames(Collection tetrisGames) { + List games = new ArrayList<>(tetrisGames); + games.remove(this); + games.removeIf(tetrisGame -> tetrisGame.lost); + this.otherTetrisGames.clear(); + this.otherTetrisGames.addAll(games); + } + + public void getAttacked(int lines) { + if(this.hasCombat && !this.lost) { + this.attackingLines += lines; + this.playfield.updateAttackingLines(this.attackingLines); + } + } + + public void loose() { + this.lost = true; + } + + + private boolean moveDown() { + if(!this.currentTetromino.moveDown()) { + this.setActiveTetrominoDown(); + return false; + } + this.score += 1; + this.updateInfo(); + return true; + } + + private boolean hardDrop() { + if(!this.currentTetromino.moveDown()) { + this.setActiveTetrominoDown(); + return false; + } + this.score += 2; + this.updateInfo(); + while(this.currentTetromino.moveDown()) { + this.score += 2; + this.updateInfo(); + } + this.setActiveTetrominoDown(); + return true; + } + + private boolean switchHold() { + if(!holdPossible) return false; + + Tetromino newCurrentTetromino; + if(this.holdTetromino == null) { + newCurrentTetromino = this.nextTetrominoes.removeFirst(); + newCurrentTetromino.remove(); + this.updateNextTetrominoes(); + } else { + newCurrentTetromino = this.holdTetromino; + this.holdTetromino.remove(); + } + + this.currentTetromino.remove(); + this.holdTetromino = new Tetromino(this.instance, this.currentTetromino.getShape()); + this.currentTetromino = newCurrentTetromino; + + this.currentTetromino.setPosition(this.tetrominoSpawnPosition); + this.currentTetromino.draw(); + if(!this.currentTetromino.moveDown()) loose(); + + double xChange = this.holdTetromino.getXChange(); + this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0)); + this.holdTetromino.drawAsEntities(); + this.holdPossible = false; + return true; + } + + + private void updateNextTetrominoes() { + if(this.tetrominoBag.isEmpty()) { + for(Tetromino.Shape shape : Tetromino.Shape.values()) { + this.tetrominoBag.add(new Tetromino(this.instance, shape)); + } + Collections.shuffle(this.tetrominoBag, this.random); + } + + if(!this.nextTetrominoes.isEmpty()) this.nextTetrominoes.forEach(Tetromino::remove); + Tetromino newTetromino = this.tetrominoBag.removeFirst(); + this.nextTetrominoes.add(newTetromino); + this.nextTetrominoes.forEach(tetromino -> { + double xChange = -tetromino.getXChange(); + tetromino.setPosition(this.nextPosition.sub(xChange, 4*this.nextTetrominoes.indexOf(tetromino), 0)); + tetromino.drawAsEntities(); + }); + } + + private void buildSidebar() { + this.sidebar.createLine(new Sidebar.ScoreboardLine( + "0", + Component.text("Score: "), + 0 + )); + this.sidebar.createLine(new Sidebar.ScoreboardLine( + "1", + Component.text("Lines: "), + 0 + )); + this.sidebar.createLine(new Sidebar.ScoreboardLine( + "2", + Component.text("Level: "), + 1 + )); + } + + private void updateInfo() { + this.playfield.updateScore(this.score); + this.sidebar.updateLineScore("0", this.score); + this.sidebar.updateLineScore("1", this.lines); + this.sidebar.updateLineScore("2", this.level); + } + + private void setActiveTetrominoDown() { + this.currentTetromino.removeOwnEntities(); + this.currentTetromino = this.nextTetrominoes.removeFirst(); + this.currentTetromino.remove(); + this.updateNextTetrominoes(); + + int removedLines = this.playfield.removeFullLines(); + int combatLines = 0; + this.combo += 1; + switch (removedLines) { + case 0 -> this.combo = 0; + 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; + } + } + + this.score += 50 * this.combo * this.level; + if(this.combo >= 2) { + combatLines += (int) Math.floor((double) this.combo /2); + } + + 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; + + this.updateInfo(); + + this.currentTetromino.setPosition(this.tetrominoSpawnPosition); + this.currentTetromino.draw(); + if(!this.currentTetromino.moveDown()) { + loose(); + } + } +} 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 new file mode 100644 index 0000000..f673961 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -0,0 +1,246 @@ +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 net.minestom.server.tag.Tag; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class Tetromino { + private final Shape shape; + private final StatelessGame instance; + private Pos position; + private int[][] shapeArray; + private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK; + private final UUID uuid; + private final static Tag uuidTag = Tag.String("uuid"); + + public enum Shape { + I, + J, + L, + O, + S, + T, + Z + } + + public Tetromino(StatelessGame instance, Shape shape) { + this.instance = instance; + this.shape = shape; + this.uuid = UUID.randomUUID(); + + switch (this.shape) { + case I -> shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}; + case J -> shapeArray = new int[][]{{1,0,0}, {1,1,1}, {0,0,0}}; + case L -> shapeArray = new int[][]{{0,0,1}, {1,1,1}, {0,0,0}}; + case O -> shapeArray = new int[][]{{1,1}, {1,1}}; + case S -> shapeArray = new int[][]{{0,1,1}, {1,1,0}, {0,0,0}}; + case T -> shapeArray = new int[][]{{0,1,0}, {1,1,1}, {0,0,0}}; + case Z -> shapeArray = new int[][]{{1,1,0}, {0,1,1}, {0,0,0}}; + } + } + + public static EntityType getGhostEntityType() { + return ghostEntityType; + } + + public void setPosition(Pos newPosition) { + this.position = newPosition; + } + + public boolean rotate(boolean clockwise) { + int[][] newShapeArray = this.getTurnedShapeArray(clockwise); + return checkCollisionAndMove(this.position, newShapeArray); + } + + public boolean moveDown() { + Pos newPosition = this.position.sub(0, 1, 0); + return checkCollisionAndMove(newPosition, this.shapeArray); + } + + public boolean moveLeft() { + Pos newPosition = this.position.sub(1, 0, 0); + return checkCollisionAndMove(newPosition, this.shapeArray); + } + + public boolean moveRight() { + Pos newPosition = this.position.add(1, 0, 0); + return checkCollisionAndMove(newPosition, this.shapeArray); + } + + public void draw() { + this.draw(true); + } + + public void draw(boolean withGhost) { + if(withGhost) { + 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 -> { + Entity ghostBlock = new Entity(ghostEntityType); + ((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock()); + ghostBlock.setNoGravity(true); + ghostBlock.setGlowing(true); + ghostBlock.setTag(uuidTag, this.uuid.toString()); + ghostBlock.setInstance(this.instance, pos.sub(positionChange).add(0.5, 0, 0.5)); + }); + } + + getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock())); + } + + public void drawAsEntities() { + getBlockPositions().forEach(pos -> { + Entity ghostBlock = new Entity(ghostEntityType); + ((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getColoredBlock()); + ghostBlock.setNoGravity(true); + ghostBlock.setTag(uuidTag, this.uuid.toString()); + ghostBlock.setInstance(this.instance, pos.add(0.5, 0, 0.5)); + }); + } + + public void remove() { + this.removeOwnEntities(); + this.getBlockPositions().forEach(pos -> this.instance.setBlock(pos, Block.AIR)); + } + + public Block getColoredBlock() { + Block returnBlock; + switch (this.shape) { + case I -> returnBlock = Block.LIGHT_BLUE_CONCRETE; + case J -> returnBlock = Block.BLUE_CONCRETE; + case L -> returnBlock = Block.ORANGE_CONCRETE; + case O -> returnBlock = Block.YELLOW_CONCRETE; + case S -> returnBlock = Block.GREEN_CONCRETE; + case T -> returnBlock = Block.PURPLE_CONCRETE; + case Z -> returnBlock = Block.RED_CONCRETE; + default -> returnBlock = Block.WHITE_CONCRETE; + } + return returnBlock; + } + + public void removeOwnEntities() { + this.instance.getEntities().stream() + .filter(entity -> { + String tagValue = entity.getTag(uuidTag); + if(tagValue == null) return false; + return entity.getTag(uuidTag).equals(this.uuid.toString()); + }) + .forEach(Entity::remove); + } + + public double getXChange() { + switch (this.shape) { + case O, I -> { + return 0; + } + case null, default -> { + return 0.5; + } + } + } + + + public Shape getShape() { + return this.shape; + } + + 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; + + int arrayLength = this.shapeArray.length; + int[][] startArray = Arrays.stream(this.shapeArray).map(int[]::clone).toArray(int[][]::new); + int[][] returnArray = new int[arrayLength][arrayLength]; + + for(int k=0; k pos.equals(position)); + } + + private List getBlockPositions() { + return this.getBlockPositions(this.position, this.shapeArray); + } + + private List getBlockPositions(Pos position, int[][] shapeArray) { + List returnList = new ArrayList<>(); + if(position == null) return returnList; + + int arrayLength = shapeArray.length; + + for(int x=0; x returnList.add(position.add(x-1, y-2, 0)); + case O -> returnList.add(position.add(x, y, 0)); + default -> returnList.add(position.add(x-1, y-1, 0)); + } + } + } + } + + return returnList; + } + + private boolean checkCollision(Pos newPosition, int[][] newShapeArray) { + List newBlockPositions = getBlockPositions(newPosition, newShapeArray); + + for(Pos pos : newBlockPositions) { + if(isPartOfTetromino(pos)) continue; + if(this.instance.getBlock(pos) == this.getGhostBlock()) continue; + if(this.instance.getBlock(pos) != Block.AIR) return true; + } + + return false; + } + + private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) { + if(!checkCollision(newPosition, newShapeArray)) { + this.remove(); + this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new); + this.setPosition(newPosition); + this.draw(); + return true; + } + return false; + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/score/LastWinsScore.java b/src/main/java/eu/mhsl/minenet/minigames/score/LastWinsScore.java index c2bae16..c37cb96 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/score/LastWinsScore.java +++ b/src/main/java/eu/mhsl/minenet/minigames/score/LastWinsScore.java @@ -10,7 +10,7 @@ import java.util.Set; public class LastWinsScore extends Score { @Override public void insertResultImplementation(Set p) { - getScores().add(0, p); + getScores().addFirst(p); } @Override diff --git a/src/main/java/eu/mhsl/minenet/minigames/score/PointsWinScore.java b/src/main/java/eu/mhsl/minenet/minigames/score/PointsWinScore.java new file mode 100644 index 0000000..d9190f7 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/score/PointsWinScore.java @@ -0,0 +1,65 @@ +package eu.mhsl.minenet.minigames.score; + +import eu.mhsl.minenet.minigames.message.Icon; +import eu.mhsl.minenet.minigames.message.TranslatableMessage; +import eu.mhsl.minenet.minigames.message.type.ChatMessage; +import eu.mhsl.minenet.minigames.message.type.TitleMessage; +import eu.mhsl.minenet.minigames.util.MapUtil; +import net.minestom.server.entity.Player; + +import java.time.Duration; +import java.util.*; +import java.util.stream.Collectors; + +public class PointsWinScore extends Score { + private Map, Integer> scoreOrder = new HashMap<>(); + + @Override + protected void insertResultImplementation(Set p, int currentPoints) { + this.scoreOrder.put(p, currentPoints); + this.scoreOrder = MapUtil.sortByValue(this.scoreOrder); + getScores().clear(); + this.scoreOrder.forEach((player, integer) -> getScores().addFirst(player)); + } + + @Override + protected void insertResultImplementation(Set p) { + this.insertResultImplementation(p, 0); + } + + @Override + public void setDone() { + if(isClosed()) return; + close(); + new ChatMessage(Icon.STAR, true) + .appendTranslated("score#result") + .newLine() + .indent() + .numberedList( + getScores() + .stream() + .filter(players -> !players.stream() + .filter(player -> !player.getUsername().isBlank()) + .toList() + .isEmpty()) + .map(players -> players + .stream() + .filter(player -> scoreOrder.get(Set.of(player)) != null) + .map(player -> player.getUsername()+" : "+scoreOrder.get(Set.of(player)).toString()) + .collect(Collectors.joining(", ")) + ) + .toList() + ) + .undent() + .newLine() + .appendTranslated("score#thanks") + .send(instance.getPlayers()); + + instance.stop(); + } + + @Override + protected TranslatableMessage scoreMessage() { + return new TitleMessage(Duration.ofMillis(1000), Duration.ofSeconds(1)).appendTranslated("score#death"); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/score/Score.java b/src/main/java/eu/mhsl/minenet/minigames/score/Score.java index 92d7385..dce3e92 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/score/Score.java +++ b/src/main/java/eu/mhsl/minenet/minigames/score/Score.java @@ -8,6 +8,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.event.instance.AddEntityToInstanceEvent; import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent; import net.minestom.server.event.player.PlayerDisconnectEvent; +import org.apache.commons.lang3.NotImplementedException; import java.util.ArrayList; import java.util.List; @@ -22,10 +23,19 @@ public abstract class Score { public Score() {} - public Score(int ignoreLastPlayers) { - this.ignoreLastPlayers = ignoreLastPlayers; + protected abstract void insertResultImplementation(Set p); + + protected void insertResultImplementation(Set p, int points) { + throw new NotImplementedException("This Score type is not able to process points"); } + public void insertResult(Player p) { + this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p))); + } + + public void insertResult(Player p, int points) { + this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p), points)); + } public void attachListeners() { this.instance.eventNode() @@ -45,15 +55,8 @@ public abstract class Score { setDone(); } } - protected abstract void insertResultImplementation(Set p); - protected abstract TranslatableMessage scoreMessage(); - public void insertResult(Player p) { - if(hasResult(p)) return; - this.scoreMessage().send(p); - this.insertResultImplementation(Set.of(p)); - this.checkGameEnd(); - } + protected abstract TranslatableMessage scoreMessage(); public void insertRemainingPlayers(Set players) { this.insertResultImplementation(players.stream().filter(p -> !hasResult(p)).collect(Collectors.toSet())); @@ -93,10 +96,19 @@ public abstract class Score { instance.stop(); } + private void insertResultProcessor(Player p, Runnable callback) { + if(hasResult(p)) return; + this.scoreMessage().send(p); + callback.run(); + this.checkGameEnd(); + } + public boolean isClosed() { return isClosed; } + public void close() { isClosed = true; } + protected void onGameEnd() { this.instance.stop(); } diff --git a/src/main/java/eu/mhsl/minenet/minigames/util/MapUtil.java b/src/main/java/eu/mhsl/minenet/minigames/util/MapUtil.java new file mode 100644 index 0000000..ac5506b --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/util/MapUtil.java @@ -0,0 +1,20 @@ +package eu.mhsl.minenet.minigames.util; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class MapUtil { + public static > Map sortByValue(Map map) { + List> list = new ArrayList<>(map.entrySet()); + list.sort(Map.Entry.comparingByValue()); + + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9f18e11..39956b0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -17,4 +17,5 @@ api: port: 8080 admins: - - minetec \ No newline at end of file + - minetec + - 28Pupsi28 \ No newline at end of file diff --git a/src/main/resources/lang/locales.map.csv b/src/main/resources/lang/locales.map.csv index fa4deca..249f47e 100644 --- a/src/main/resources/lang/locales.map.csv +++ b/src/main/resources/lang/locales.map.csv @@ -92,6 +92,13 @@ name;Spleef;Spleef; description;Spleef other players and be the last survivor;Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld shovelName;Snow thrower;Schneeflug ;; +ns:game_Tetris#;; +name;Tetris;Tetris +description;Sort falling blocks and clear lines;Sortiere fallende Blöcke und kläre Linien +nextTetrominoesCount;Number of upcoming Tetrominos displayed;Anzahl der angezeigten kommenden Tetrominos +isFast;Fast mode;Schneller Modus +hasCombat;Competitive mode;Kompetitiver Modus +;; ns:game_AnvilRun#;; name;Anvil Run;Anvil Run description;Run away from falling anvils;Renne von fallenden Ambossen davon \ No newline at end of file