Merge branch 'refs/heads/develop-tetris' into develop
# Conflicts: # src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java # src/main/resources/lang/locales.map.csv
This commit is contained in:
commit
0a1ae69f53
@ -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(() -> {
|
||||
|
||||
|
@ -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);
|
||||
|
@ -50,7 +50,7 @@ public class StatelessGame extends Game {
|
||||
|
||||
int timeLeft = timeLimit - timePlayed;
|
||||
switch (timeLeft) {
|
||||
case 60, 30, 10, 5, 4, 3, 2, 1 ->
|
||||
case 90, 60, 30, 10, 5, 4, 3, 2, 1 ->
|
||||
new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers());
|
||||
}
|
||||
|
||||
|
@ -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<T> {
|
||||
private RestrictionHandler restrictionHandler;
|
||||
@ -25,7 +26,7 @@ public abstract class Option<T> {
|
||||
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<T> {
|
||||
}
|
||||
|
||||
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<T> {
|
||||
return Integer.parseInt(getAsString());
|
||||
}
|
||||
|
||||
public boolean getAsBoolean() {
|
||||
return Objects.equals(getAsString(), "true") || Objects.equals(getAsString(), "1");
|
||||
}
|
||||
|
||||
public String getAsString() {
|
||||
return currentValue.toString();
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import net.minestom.server.item.Material;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BoolOption extends Option<Boolean> {
|
||||
public class BoolOption extends Option<String> {
|
||||
public BoolOption(String id, Material item, TranslatedComponent name) {
|
||||
super(id, item, name, List.of(true, false));
|
||||
super(id, item, name, List.of("true", "false"));
|
||||
}
|
||||
}
|
||||
|
@ -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<Void> 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(
|
||||
|
@ -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<Player, TetrisGame> 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);
|
||||
}
|
||||
}
|
@ -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<String, Option<?>> 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Tetromino> nextTetrominoes = new ArrayList<>();
|
||||
private Tetromino holdTetromino;
|
||||
private final List<Tetromino> 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<Button, Long> lastPresses = new HashMap<>();
|
||||
private final List<TetrisGame> 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<TetrisGame> tetrisGames) {
|
||||
List<TetrisGame> 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> 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<iterations; k++) {
|
||||
for(int i=0; i<arrayLength; i++) {
|
||||
for(int j=0; j<arrayLength; j++) {
|
||||
returnArray[i][arrayLength-1-j] = startArray[j][i];
|
||||
}
|
||||
}
|
||||
startArray = Arrays.stream(returnArray).map(int[]::clone).toArray(int[][]::new);
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
private boolean isPartOfTetromino(Pos position) {
|
||||
return this.getBlockPositions().stream()
|
||||
.anyMatch(pos -> pos.equals(position));
|
||||
}
|
||||
|
||||
private List<Pos> getBlockPositions() {
|
||||
return this.getBlockPositions(this.position, this.shapeArray);
|
||||
}
|
||||
|
||||
private List<Pos> getBlockPositions(Pos position, int[][] shapeArray) {
|
||||
List<Pos> returnList = new ArrayList<>();
|
||||
if(position == null) return returnList;
|
||||
|
||||
int arrayLength = shapeArray.length;
|
||||
|
||||
for(int x=0; x<arrayLength; x++) {
|
||||
for(int y=0; y<arrayLength; y++) {
|
||||
if(shapeArray[arrayLength-1-y][x] == 1) {
|
||||
switch (this.shape) {
|
||||
case I -> 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<Pos> 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;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import java.util.Set;
|
||||
public class LastWinsScore extends Score {
|
||||
@Override
|
||||
public void insertResultImplementation(Set<Player> p) {
|
||||
getScores().add(0, p);
|
||||
getScores().addFirst(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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<Set<Player>, Integer> scoreOrder = new HashMap<>();
|
||||
|
||||
@Override
|
||||
protected void insertResultImplementation(Set<Player> 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<Player> 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");
|
||||
}
|
||||
}
|
@ -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<Player> p);
|
||||
|
||||
protected void insertResultImplementation(Set<Player> 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<Player> 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<Player> 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();
|
||||
}
|
||||
|
20
src/main/java/eu/mhsl/minenet/minigames/util/MapUtil.java
Normal file
20
src/main/java/eu/mhsl/minenet/minigames/util/MapUtil.java
Normal file
@ -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 <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
|
||||
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
|
||||
list.sort(Map.Entry.comparingByValue());
|
||||
|
||||
Map<K, V> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<K, V> entry : list) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -18,3 +18,4 @@ api:
|
||||
|
||||
admins:
|
||||
- minetec
|
||||
- 28Pupsi28
|
@ -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
|
Can't render this file because it has a wrong number of fields in line 91.
|
Loading…
x
Reference in New Issue
Block a user