15 Commits

8 changed files with 327 additions and 85 deletions

View File

@@ -9,6 +9,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBattle.Block
import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace.BlockBreakRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.boatRace.BoatRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef.BowSpleefFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.colorJump.ColorJumpFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.FastbridgeFactory;
@@ -50,7 +51,8 @@ public enum GameList {
SPACESNAKE(new SpaceSnakeFactory(), GameType.PVP),
BOATRACE(new BoatRaceFactory(), GameType.OTHER),
PILLARS(new PillarsFactory(), GameType.PROTOTYPE),
BLOCKBATTLE(new BlockBattleFactory(), GameType.PROTOTYPE);
BLOCKBATTLE(new BlockBattleFactory(), GameType.PVP),
COLORJUMP(new ColorJumpFactory(), GameType.PROTOTYPE);
private final GameFactory factory;
private final GameType type;

View File

@@ -0,0 +1,170 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.colorJump;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
public class ColorJump extends StatelessGame {
private final List<Block> blocks = List.of(
Block.RED_CONCRETE,
Block.ORANGE_CONCRETE,
Block.YELLOW_CONCRETE,
Block.LIME_CONCRETE,
Block.LIGHT_BLUE_CONCRETE,
Block.BLUE_CONCRETE,
Block.PURPLE_CONCRETE,
Block.PINK_CONCRETE
);
private Block currentBlock;
private Block blockLastRound;
private double roundTime = 5;
private final double multiplierNextRoundTime = 0.9;
public ColorJump() {
super(Dimension.THE_END.key, "ColorJump", new LastWinsScore());
this.getScore().setIgnoreLastPlayers(1);
this.setGenerator(new CircularPlateTerrainGenerator(100));
this.eventNode().addListener(
InventoryPreClickEvent.class,
inventoryPreClickEvent -> inventoryPreClickEvent.setCancelled(true)
);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
this.generate();
}
@Override
protected void onStart() {
this.nextRound();
}
private void nextRound() {
this.generate();
do {
this.currentBlock = this.blocks.get(ThreadLocalRandom.current().nextInt(this.blocks.size()));
} while(this.currentBlock == this.blockLastRound);
this.blockLastRound = this.currentBlock;
ItemStack item = ItemStack.of(Objects.requireNonNull(this.currentBlock.registry().material()));
this.getPlayers().forEach(player -> {
player.getInventory().clear();
for(int i = 0; i < 9; i++) {
player.getInventory().setItemStack(i, item);
}
});
var scheduler = MinecraftServer.getSchedulerManager();
TaskSchedule stop = TaskSchedule.stop();
scheduler.scheduleTask(() -> {
if(!this.isRunning) return stop;
this.destroyAllExcept(this.currentBlock);
scheduler.scheduleTask(() -> {
if(this.isRunning) this.nextRound();
return stop;
}, TaskSchedule.seconds(3));
return stop;
}, TaskSchedule.seconds((long) this.roundTime));
this.roundTime = this.roundTime * this.multiplierNextRoundTime;
if(this.roundTime <= 1) this.roundTime = 1;
long totalTicks = (long) (this.roundTime * 20);
final long[] remainingTicks = { totalTicks };
scheduler.scheduleTask(() -> {
if (!this.isRunning)
return stop;
if (remainingTicks[0] <= 0) {
this.getPlayers().forEach(player -> {
player.setExp(0f);
player.setLevel(0);
});
return stop;
}
float progress = (float) remainingTicks[0] / totalTicks;
this.getPlayers().forEach(player -> {
player.setExp(progress);
player.setLevel((int) Math.ceil(remainingTicks[0] / 20.0));
});
remainingTicks[0]--;
return TaskSchedule.tick(1);
}, TaskSchedule.tick(1));
}
private void generate() {
int y = 30;
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int x = -20; x <= 21; x += 2) {
for (int z = -11; z <= 10; z += 2) {
Block randomBlock = this.blocks.get(ThreadLocalRandom.current().nextInt(this.blocks.size()));
for (int dx = 0; dx < 2; dx++) {
for (int dz = 0; dz < 2; dz++) {
batch.setBlock(x + dx, y, z + dz, randomBlock);
}
}
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
}
private void destroyAllExcept(Block block) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
int y = 30;
for (int x = -20; x <= 21; x++) {
for(int z = -11; z <= 10; z++) {
Pos blockPos = new Pos(x, y, z);
if(this.getBlock(blockPos) != block)
batch.setBlock(blockPos, Block.AIR);
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
}
@Override
public Pos getSpawn() {
return new Pos(0, 31, 0);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
var player = playerMoveEvent.getPlayer();
if(playerMoveEvent.getNewPosition().y() >= 29)
return;
player.teleport(this.getSpawn());
if(this.isRunning) {
this.getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
}
}
}

View File

@@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.colorJump;
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.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class ColorJumpFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_ColorJump#name");
}
@Override
public Material symbol() {
return Material.PINK_CONCRETE;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_ColorJump#description");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new ColorJump().setParent(parent);
}
}

View File

@@ -7,22 +7,11 @@ public enum Orientation {
UPSIDE_DOWN;
public Orientation rotated(boolean clockwise) {
switch(this) {
case NONE -> {
return clockwise ? Orientation.RIGHT : Orientation.LEFT;
}
case RIGHT -> {
return clockwise ? Orientation.UPSIDE_DOWN : Orientation.NONE;
}
case UPSIDE_DOWN -> {
return clockwise ? Orientation.LEFT : Orientation.RIGHT;
}
case LEFT -> {
return clockwise ? Orientation.NONE : Orientation.UPSIDE_DOWN;
}
default -> {
return Orientation.NONE;
}
}
return switch(this) {
case NONE -> clockwise ? Orientation.RIGHT : Orientation.LEFT;
case RIGHT -> clockwise ? Orientation.UPSIDE_DOWN : Orientation.NONE;
case UPSIDE_DOWN -> clockwise ? Orientation.LEFT : Orientation.RIGHT;
case LEFT -> clockwise ? Orientation.NONE : Orientation.UPSIDE_DOWN;
};
}
}

View File

@@ -3,7 +3,8 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import java.util.Map;
import java.util.stream.IntStream;
public final class RotationChecker {private static final Map<Orientation, int[][]> STANDARD_WALL_KICKS = Map.of(
public final class RotationChecker {
private static final Map<Orientation, int[][]> STANDARD_WALL_KICKS = Map.of(
Orientation.NONE, new int[][] {{0,0}, {0,0}, {0,0}, {0,0}, {0,0}},
Orientation.RIGHT, new int[][] {{0,0}, {1,0}, {1,-1}, {0,2}, {1,2}},
Orientation.UPSIDE_DOWN, new int[][] {{0,0}, {0,0}, {0,0}, {0,0}, {0,0}},

View File

@@ -2,10 +2,9 @@ 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.Task;
import net.minestom.server.timer.TaskSchedule;
import java.util.*;
@@ -23,6 +22,9 @@ public class TetrisGame {
private final Map<Button, Long> lastPresses = new HashMap<>();
private final List<TetrisGame> otherTetrisGames = new ArrayList<>();
private final Random random;
private Task tetrominoLockTask;
private int lockDelayResets;
private int currentTetrominoLowestY = Integer.MAX_VALUE;
public boolean lost = false;
public boolean paused = true;
public Tetromino currentTetromino;
@@ -54,24 +56,27 @@ public class TetrisGame {
}
public void pressedButton(Button button) {
final int standardButtonDelay = 100;
final int buttonDebounce = 70;
final int standardButtonDelay = 95;
final int wsButtonDebounce = 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);
switch(button) {
case W -> this.lastPresses.put(button, System.currentTimeMillis() + wsButtonDebounce);
case S -> this.lastPresses.put(button, System.currentTimeMillis() - wsButtonDebounce);
case mouseLeft, mouseRight -> this.lastPresses.put(button, 0L);
default -> this.lastPresses.put(button, System.currentTimeMillis());
}
if(this.lost || this.paused) return;
switch(button) {
case A -> this.currentTetromino.moveLeft();
case A -> this.moveLeft();
case S -> this.moveDown();
case D -> this.currentTetromino.moveRight();
case D -> this.moveRight();
case W -> this.hardDrop();
case mouseLeft -> this.currentTetromino.rotate(false);
case mouseRight -> this.currentTetromino.rotate(true);
case mouseLeft -> this.rotate(false);
case mouseRight -> this.rotate(true);
case space -> this.switchHold();
}
}
@@ -82,8 +87,7 @@ public class TetrisGame {
public void start() {
this.paused = false;
Scheduler scheduler = MinecraftServer.getSchedulerManager();
scheduler.submitTask(() -> {
this.instance.scheduler().submitTask(() -> {
if(this.lost) return TaskSchedule.stop();
int standardTickDelay = 40;
if(this.isFast) standardTickDelay = 20;
@@ -111,9 +115,12 @@ public class TetrisGame {
public void tick() {
if(this.lost || this.paused) return;
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
if(this.currentTetromino.moveDown()) {
this.stopTetrominoLockTask(this.currentTetromino.getYPosition() < this.currentTetrominoLowestY, false);
} else {
this.scheduleTetrominoLock();
}
this.currentTetrominoLowestY = Math.min(this.currentTetrominoLowestY, this.currentTetromino.getYPosition());
}
public int getScore() {
@@ -139,33 +146,43 @@ public class TetrisGame {
this.lost = true;
}
private boolean moveDown() {
private void moveDown() {
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
return false;
this.scheduleTetrominoLock();
return;
}
this.stopTetrominoLockTask(this.currentTetromino.getYPosition() < this.currentTetrominoLowestY, false);
this.score += 1;
this.updateInfo();
return true;
}
private boolean hardDrop() {
private void moveLeft() {
if(this.currentTetromino.moveLeft()) this.onSuccessfulMoveOrRotate();
}
private void moveRight() {
if(this.currentTetromino.moveRight()) this.onSuccessfulMoveOrRotate();
}
private void rotate(boolean clockwise) {
if(this.currentTetromino.rotate(clockwise)) this.onSuccessfulMoveOrRotate();
}
private void hardDrop() {
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
return false;
this.lockActiveTetromino();
return;
}
this.score += 2;
this.updateInfo();
while(this.currentTetromino.moveDown()) {
do {
this.score += 2;
this.updateInfo();
}
this.setActiveTetrominoDown();
return true;
} while(this.currentTetromino.moveDown());
this.updateInfo();
this.lockActiveTetromino();
}
private boolean switchHold() {
if(!this.holdPossible) return false;
private void switchHold() {
if(!this.holdPossible) return;
this.stopTetrominoLockTask(true);
Tetromino newCurrentTetromino;
if(this.holdTetromino == null) {
@@ -181,6 +198,7 @@ public class TetrisGame {
this.holdTetromino = new Tetromino(this.instance, this.currentTetromino.getShape());
this.currentTetromino = newCurrentTetromino;
this.currentTetrominoLowestY = Integer.MAX_VALUE;
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) this.loose();
@@ -189,7 +207,12 @@ public class TetrisGame {
this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
this.holdTetromino.drawAsEntities();
this.holdPossible = false;
return true;
}
private void onSuccessfulMoveOrRotate() {
if(!this.currentTetromino.isGrounded()) return;
this.stopTetrominoLockTask(false);
this.scheduleTetrominoLock();
}
private void updateNextTetrominoes() {
@@ -235,11 +258,39 @@ public class TetrisGame {
this.sidebar.updateLineScore("2", this.level);
}
private void setActiveTetrominoDown() {
private void scheduleTetrominoLock() {
if(this.tetrominoLockTask == null || !this.tetrominoLockTask.isAlive())
this.tetrominoLockTask = this.instance.scheduler().scheduleTask(() -> {
if(this.currentTetromino.isGrounded()) {
this.lockActiveTetromino();
} else {
this.stopTetrominoLockTask(false, false);
}
return TaskSchedule.stop();
}, TaskSchedule.millis(500));
}
private void stopTetrominoLockTask(boolean resetHard) {
this.stopTetrominoLockTask(resetHard, true);
}
private void stopTetrominoLockTask(boolean resetHard, boolean addToResets) {
if(resetHard) this.lockDelayResets = 0;
if(this.lockDelayResets >= 15 && addToResets) {
this.lockActiveTetromino();
return;
}
if(this.tetrominoLockTask != null) {
this.tetrominoLockTask.cancel();
this.tetrominoLockTask = null;
if(!resetHard && addToResets) this.lockDelayResets++;
}
}
private void lockActiveTetromino() {
this.stopTetrominoLockTask(true);
this.currentTetromino.removeOwnEntities();
this.currentTetromino = this.nextTetrominoes.removeFirst();
this.currentTetromino.remove();
this.updateNextTetrominoes();
int removedLines = this.playfield.removeFullLines();
int combatLines = 0;
@@ -297,6 +348,11 @@ public class TetrisGame {
this.updateInfo();
this.currentTetromino = this.nextTetrominoes.removeFirst();
this.currentTetromino.remove();
this.updateNextTetrominoes();
this.currentTetrominoLowestY = Integer.MAX_VALUE;
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) {

View File

@@ -8,10 +8,7 @@ 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;
import java.util.*;
public class Tetromino {
private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK;
@@ -44,21 +41,17 @@ public class Tetromino {
}
public void setPosition(Pos newPosition) {
this.position = newPosition;
this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z());
}
public boolean rotate(boolean clockwise) {
int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
Orientation newOrientation = this.orientation.rotated(clockwise);
// TODO: doesn't work for I-Tetromino
int[][] kicksArray = RotationChecker.getKicksArray(this.orientation, newOrientation, this.shape);
System.out.println(this.shape.toString() + ": " + Arrays.deepToString(kicksArray));
for(int[] k : kicksArray) {
Pos candidate = new Pos(this.position.x() + k[0], this.position.y() + k[1], this.position.z());
boolean moved = this.checkCollisionAndMove(candidate, newShapeArray);
System.out.println("Candidate: " + Arrays.toString(k) + " " + moved);
if(moved) {
if(this.checkCollisionAndMove(candidate, newShapeArray)) {
this.orientation = newOrientation;
return true;
}
@@ -198,7 +191,7 @@ public class Tetromino {
private boolean isPartOfTetromino(Pos position) {
return this.getBlockPositions().stream()
.anyMatch(pos -> pos.equals(position));
.anyMatch(pos -> pos.sameBlock(position));
}
private List<Pos> getBlockPositions() {
@@ -214,9 +207,10 @@ public class Tetromino {
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(new Pos(position).add(x - 2, y - 2, 0));
default -> returnList.add(new Pos(position).add(x - 1, y - 1, 0));
if(Objects.requireNonNull(this.shape) == Shape.I) {
returnList.add(position.add(x - 2, y - 2, 0));
} else {
returnList.add(position.add(x - 1, y - 1, 0));
}
}
}
@@ -225,21 +219,6 @@ public class Tetromino {
return returnList;
}
// private List<Pos> getBlockPositions(Pos position, int[][] shapeArray) {
// List<Pos> returnList = new ArrayList<>();
// if(position == null) return returnList;
//
// for(int x = 0; x < shapeArray.length; x++) {
// for(int y = 0; y < shapeArray.length; y++) {
// if(shapeArray[x][y] == 1) {
// returnList.add(new Pos(position).sub(1, 1, 0).add(x, y, 0));
// }
// }
// }
//
// return returnList;
// }
private boolean hasCollision(Pos newPosition, int[][] newShapeArray) {
List<Pos> newBlockPositions = this.getBlockPositions(newPosition, newShapeArray);
@@ -252,6 +231,10 @@ public class Tetromino {
return false;
}
public boolean isGrounded() {
return this.hasCollision(this.position.sub(0, 1, 0), this.shapeArray);
}
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
if(this.hasCollision(newPosition, newShapeArray)) return false;
this.remove();
@@ -261,6 +244,10 @@ public class Tetromino {
return true;
}
public int getYPosition() {
return Math.toIntExact(Math.round(this.position.y()));
}
public enum Shape {
I,
J,

View File

@@ -183,3 +183,7 @@ ns:game_BlockBattle#;;
name;Block Battle;Block Kampf
description;The team that fills the center with their color first wins!;Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt!
itemCount;Block Count;Block Anzahl
;;
ns:game_ColorJump#;;
name;Color Jump;Farbsprung
description;Jump on the right color!;Springe auf die richtige Farbe!
1 map en_us de_de
183 name Block Battle Block Kampf
184 description The team that fills the center with their color first wins! Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt!
185 itemCount Block Count Block Anzahl
186
187 ns:game_ColorJump#
188 name Color Jump Farbsprung
189 description Jump on the right color! Springe auf die richtige Farbe!