13 Commits

8 changed files with 326 additions and 62 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.blockBreakRace.BlockBreakRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.boatRace.BoatRaceFactory; 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.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.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.FastbridgeFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.FastbridgeFactory;
@@ -50,7 +51,8 @@ public enum GameList {
SPACESNAKE(new SpaceSnakeFactory(), GameType.PVP), SPACESNAKE(new SpaceSnakeFactory(), GameType.PVP),
BOATRACE(new BoatRaceFactory(), GameType.OTHER), BOATRACE(new BoatRaceFactory(), GameType.OTHER),
PILLARS(new PillarsFactory(), GameType.PROTOTYPE), 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 GameFactory factory;
private final GameType type; 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; UPSIDE_DOWN;
public Orientation rotated(boolean clockwise) { public Orientation rotated(boolean clockwise) {
switch(this) { return switch(this) {
case NONE -> { case NONE -> clockwise ? Orientation.RIGHT : Orientation.LEFT;
return clockwise ? Orientation.RIGHT : Orientation.LEFT; case RIGHT -> clockwise ? Orientation.UPSIDE_DOWN : Orientation.NONE;
} case UPSIDE_DOWN -> clockwise ? Orientation.LEFT : Orientation.RIGHT;
case RIGHT -> { case LEFT -> clockwise ? Orientation.NONE : Orientation.UPSIDE_DOWN;
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;
}
}
} }
} }

View File

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

View File

@@ -44,7 +44,7 @@ public class Tetromino {
this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z()); this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z());
} }
public void rotate(boolean clockwise) { public boolean rotate(boolean clockwise) {
int[][] newShapeArray = this.getTurnedShapeArray(clockwise); int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
Orientation newOrientation = this.orientation.rotated(clockwise); Orientation newOrientation = this.orientation.rotated(clockwise);
@@ -53,10 +53,11 @@ public class Tetromino {
Pos candidate = new Pos(this.position.x() + k[0], this.position.y() + k[1], this.position.z()); Pos candidate = new Pos(this.position.x() + k[0], this.position.y() + k[1], this.position.z());
if(this.checkCollisionAndMove(candidate, newShapeArray)) { if(this.checkCollisionAndMove(candidate, newShapeArray)) {
this.orientation = newOrientation; this.orientation = newOrientation;
return; return true;
} }
} }
return false;
} }
public boolean moveDown() { public boolean moveDown() {
@@ -64,14 +65,14 @@ public class Tetromino {
return this.checkCollisionAndMove(newPosition, this.shapeArray); return this.checkCollisionAndMove(newPosition, this.shapeArray);
} }
public void moveLeft() { public boolean moveLeft() {
Pos newPosition = this.position.sub(1, 0, 0); Pos newPosition = this.position.sub(1, 0, 0);
this.checkCollisionAndMove(newPosition, this.shapeArray); return this.checkCollisionAndMove(newPosition, this.shapeArray);
} }
public void moveRight() { public boolean moveRight() {
Pos newPosition = this.position.add(1, 0, 0); Pos newPosition = this.position.add(1, 0, 0);
this.checkCollisionAndMove(newPosition, this.shapeArray); return this.checkCollisionAndMove(newPosition, this.shapeArray);
} }
public void draw() { public void draw() {
@@ -230,6 +231,10 @@ public class Tetromino {
return false; return false;
} }
public boolean isGrounded() {
return this.hasCollision(this.position.sub(0, 1, 0), this.shapeArray);
}
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) { private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
if(this.hasCollision(newPosition, newShapeArray)) return false; if(this.hasCollision(newPosition, newShapeArray)) return false;
this.remove(); this.remove();
@@ -239,6 +244,10 @@ public class Tetromino {
return true; return true;
} }
public int getYPosition() {
return Math.toIntExact(Math.round(this.position.y()));
}
public enum Shape { public enum Shape {
I, I,
J, J,

View File

@@ -182,4 +182,8 @@ description;Build yourself up with your random blocks to reach your opponents an
ns:game_BlockBattle#;; ns:game_BlockBattle#;;
name;Block Battle;Block Kampf 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! 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 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
182 ns:game_BlockBattle#
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!