added pillars and block battle #10

Merged
jannis merged 7 commits from develop-pillars into develop 2026-02-02 19:09:44 +00:00
9 changed files with 431 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.acidRain.AcidRain
import eu.mhsl.minenet.minigames.instance.game.stateless.types.anvilRun.AnvilRunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.anvilRun.AnvilRunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.BackroomsFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.BackroomsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.BedwarsFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.BedwarsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBattle.BlockBattleFactory;
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;
@@ -14,6 +15,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.Fastbr
import eu.mhsl.minenet.minigames.instance.game.stateless.types.highGround.HighGroundFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.highGround.HighGroundFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.jumpDive.JumpDiveFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.jumpDive.JumpDiveFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun.MinerunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun.MinerunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.pillars.PillarsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.spaceSnake.SpaceSnakeFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.spaceSnake.SpaceSnakeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef.SpleefFactory; 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.stickfight.StickFightFactory;
@@ -46,7 +48,9 @@ public enum GameList {
FASTBRIDGE(new FastbridgeFactory(), GameType.OTHER), FASTBRIDGE(new FastbridgeFactory(), GameType.OTHER),
BLOCKBREAKRACE(new BlockBreakRaceFactory(), GameType.OTHER), BLOCKBREAKRACE(new BlockBreakRaceFactory(), GameType.OTHER),
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),
BLOCKBATTLE(new BlockBattleFactory(), GameType.PROTOTYPE);
private final GameFactory factory; private final GameFactory factory;
private final GameType type; private final GameType type;

View File

@@ -0,0 +1,207 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBattle;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
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.item.Material;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class BlockBattle extends StatelessGame {
private final Team teamBlue = new Team(new Pos(0,101,20).add(0.5).withView(180, 0), Team.Color.BLUE);
private final Team teamRed = new Team(new Pos(0, 101, -20).add(0.5), Team.Color.RED);
private final int itemCount;
private final Map<Player, Team> teams = new WeakHashMap<>();
public BlockBattle(int itemCount) {
super(Dimension.THE_END.key, "Block Battle", new FirstWinsScore());
this.itemCount = itemCount;
this.eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build().createNode()
);
this.eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
if(this.isBeforeBeginning) finalAttackEvent.setCancelled(true);
finalAttackEvent.setBaseDamage(0);
((Player) finalAttackEvent.getTarget()).setHealth(20);
});
}
@Override
jannis marked this conversation as resolved
Review

generateWorld wird nur hier aufgerufen, der code von generateWorld kann also direkt hier in onLoad, ohne extra Methode.

generateWorld wird nur hier aufgerufen, der code von generateWorld kann also direkt hier in onLoad, ohne extra Methode.
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
this.generatePlatform(new Pos(0, 100, 0), Block.GOLD_BLOCK, Block.YELLOW_CONCRETE_POWDER);
this.generatePlatform(new Pos(0, 101, 0), Block.AIR, Block.SANDSTONE_SLAB);
this.generatePlatform(new Pos(0, 100, 20), Block.BLUE_CONCRETE, Block.BLUE_CONCRETE_POWDER);
this.generatePlatform(new Pos(0, 100, -20), Block.RED_CONCRETE, Block.RED_CONCRETE_POWDER);
this.generatePlatform(new Pos(-5, 101, -14), Block.RED_STAINED_GLASS, Block.AIR);
this.generatePlatform(new Pos(5, 101, 14), Block.BLUE_STAINED_GLASS, Block.AIR);
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
Pos[] positionsRedGlass = {
new Pos(2, 102, -9),
new Pos(-1, 103, -9),
new Pos(-2, 104, -6),
new Pos(-5, 103, -7),
new Pos(-7, 102, -10),
new Pos(3, 102, -12),
new Pos(5, 101, -15)
};
Pos[] positionsBlueGlass = {
new Pos(-5, 101, 15),
new Pos(-3, 102, 12),
new Pos(-2, 102, 9),
new Pos(1, 103, 9),
new Pos(2, 104, 6),
new Pos(5, 103, 7),
new Pos(7, 102, 10)
};
for(Pos pos : positionsRedGlass)
batch.setBlock(pos, Block.RED_STAINED_GLASS);
for(Pos pos : positionsBlueGlass)
batch.setBlock(pos, Block.BLUE_STAINED_GLASS);
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
}
@Override
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
Pos posGoldBlock = new Pos(playerBlockPlaceEvent.getBlockPosition()).sub(0, 1, 0);
Block goldBlock = playerBlockPlaceEvent.getInstance().getBlock(posGoldBlock);
playerBlockPlaceEvent.setCancelled(goldBlock != Block.GOLD_BLOCK);
playerBlockPlaceEvent.getInstance().scheduler().scheduleNextTick(() -> {
Pos middle = new Pos(0, 101, 0);
boolean validBlue = true;
boolean validRed = true;
for(int x = middle.blockX()-1; x < middle.blockX()+2; x++) {
for(int z = middle.blockZ()-1; z < middle.blockZ()+2; z++) {
if(playerBlockPlaceEvent.getInstance().getBlock(x, 101, z) != Block.BLUE_WOOL) {
validBlue = false;
break;
}
}
if(!validBlue)
break;
}
for(int x = middle.blockX()-1; x < middle.blockX()+2; x++) {
for(int z = middle.blockZ()-1; z < middle.blockZ()+2; z++) {
if(playerBlockPlaceEvent.getInstance().getBlock(x, 101, z) != Block.RED_WOOL) {
validRed = false;
break;
}
}
if(!validRed)
break;
}
if(!validBlue && !validRed) return;
var winningTeam = validBlue ? Team.Color.BLUE : Team.Color.RED;
var winningPlayers = this.teams.entrySet().stream()
.filter(entry -> entry.getValue().color().equals(winningTeam))
jannis marked this conversation as resolved Outdated
Outdated
Review

Kann als lambda expression geschrieben werden. Schlägt IntelliJ meistens schon vor.

Kann als lambda expression geschrieben werden. Schlägt IntelliJ meistens schon vor.
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
this.getScore().insertMultiple(winningPlayers);
super.stop();
});
}
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
boolean isAllowed = Arrays.stream(Team.Color.values())
.map(Team.Color::getMaterial)
.map(Material::block)
.toList()
.contains(playerBlockBreakEvent.getBlock());
if(!isAllowed) playerBlockBreakEvent.setCancelled(true);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < 95) {
var player = playerMoveEvent.getPlayer();
jannis marked this conversation as resolved
Review

Kann direkt in onLoad rein.

Kann direkt in onLoad rein.
player.teleport(
this.isBeforeBeginning
? this.getSpawn()
: this.teams.get(player).spawnPosition()
);
this.giveItems(player);
jannis marked this conversation as resolved Outdated
Outdated
Review

Lieber ein Array pro Block mit Positionen und dann alle durchgehen. So wird "Block.RED_STAINED_GLASS" nicht wiederholt.

Lieber ein Array pro Block mit Positionen und dann alle durchgehen. So wird "Block.RED_STAINED_GLASS" nicht wiederholt.
}
}
@Override
protected void onStart() {
this.setTeams();
this.getPlayers().forEach(player -> player.teleport(this.teams.get(player).spawnPosition()).thenRun(() -> {
this.giveItems(player);
jannis marked this conversation as resolved Outdated
Outdated
Review

... hier das gleiche.

... hier das gleiche.
player.setGameMode(GameMode.SURVIVAL);
}));
}
private void generatePlatform(Pos center, Block inner, Block outer) {
for(int x = center.blockX()-2; x < center.blockX()+3; x++) {
jannis marked this conversation as resolved
Review

Anstatt immer this.setBlock aufzurufen kann man auch AbsoluteBlockBatch benutzen (siehe Tetris Playfield).
Ist aber nicht unbedingt notwendig.

Anstatt immer this.setBlock aufzurufen kann man auch AbsoluteBlockBatch benutzen (siehe Tetris Playfield). Ist aber nicht unbedingt notwendig.
for(int z = center.blockZ()-2; z < center.blockZ()+3; z++) {
this.setBlock(x, center.blockY(), z, outer);
}
}
for(int x = center.blockX()-1; x < center.blockX()+2; x++) {
for(int z = center.blockZ()-1; z < center.blockZ()+2; z++) {
this.setBlock(x, center.blockY(), z, inner);
}
}
}
private void setTeams() {
List<Player> players = new ArrayList<>(this.getPlayers());
Collections.shuffle(players);
int halfPlayers = players.size()/2;
players.subList(0, halfPlayers).forEach(player -> this.teams.put(player, this.teamBlue));
players.subList(halfPlayers, players.size()).forEach(player -> this.teams.put(player, this.teamRed));
}
@Override
public Pos getSpawn() {
return new Pos(0, 101, 0).add(0.5);
}
private void giveItems(Player player) {
player.getInventory().clear();
ItemStack item = ItemStack.of(
this.teams.get(player).color().getMaterial(),
this.itemCount
);
player.getInventory().addItemStack(item);
}
}

View File

@@ -0,0 +1,40 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBattle;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
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.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 BlockBattleFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_BlockBattle#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_BlockBattle#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("itemCount", Material.WHITE_WOOL, TranslatedComponent.byId("game_BlockBattle#itemCount"), 1, 2, 3));
}
@Override
public Material symbol() {
return Material.GREEN_CONCRETE;
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new BlockBattle(configuration.get("itemCount").getAsInt()).setParent(parent);
}
}

View File

@@ -0,0 +1,21 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBattle;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.item.Material;
public record Team(Pos spawnPosition, Color color) {
jannis marked this conversation as resolved Outdated
Outdated
Review

Könnte ein record sein. IntelliJ schlägt das vermutlich schon vor.

Könnte ein record sein. IntelliJ schlägt das vermutlich schon vor.
public enum Color {
RED(Material.RED_WOOL),
BLUE(Material.BLUE_WOOL);
private final Material block;
Color(Material block) {
this.block = block;
}
public Material getMaterial() {
return this.block;
}
}
}

View File

@@ -0,0 +1,103 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.pillars;
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.Position;
import io.github.togar2.pvp.feature.CombatFeatures;
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;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
class Pillars extends StatelessGame {
private int spawnPosx = 0;
private int spawnPosz = 0;
private final int pillarSpacing = 10;
private final int pillarRowCount = 5;
jannis marked this conversation as resolved Outdated
Outdated
Review

Leerzeile davor

Leerzeile davor
public Pillars() {
super(Dimension.THE_END.key, "Pillars", new LastWinsScore());
this.getScore().setIgnoreLastPlayers(1);
this.eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build().createNode()
);
}
@Override
protected boolean onPlayerJoin(Player p) {
Pos pos = new Pos(this.spawnPosx * this.pillarSpacing, 100, this.spawnPosz * this.pillarSpacing);
this.setBlock(pos.sub(0, 1, 0), Block.BEDROCK);
if(this.spawnPosx >= this.pillarRowCount) {
this.spawnPosx = 0;
this.spawnPosz++;
}
this.spawnPosx++;
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> p.teleport(pos.add(0.5, 0, 0.5)));
return super.onPlayerJoin(p);
}
@Override
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {}
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {}
@Override
protected void onItemDrop(@NotNull ItemDropEvent itemDropEvent) {}
@Override
public Pos getSpawn() {
return new Pos(0, 105, 0);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
jannis marked this conversation as resolved
Review

Eigene Variable für playerMoveEvent.getPlayer(), das wiederholt sich sonst.

Eigene Variable für playerMoveEvent.getPlayer(), das wiederholt sich sonst.
var player = playerMoveEvent.getPlayer();
if(this.isBeforeBeginning && Position.hasPositionChanged(player.getPosition(), playerMoveEvent.getNewPosition()))
playerMoveEvent.setCancelled(true);
if(playerMoveEvent.getNewPosition().y() < 80) {
this.getScore().insertResult(player);
jannis marked this conversation as resolved Outdated
Outdated
Review

Hier kann auch player anstatt playerMoveEvent.getPlayer() hin.

Hier kann auch player anstatt playerMoveEvent.getPlayer() hin.
player.teleport(this.getSpawn());
player.setGameMode(GameMode.SPECTATOR);
}
}
@Override
protected void onStart() {
this.getPlayers().forEach(player -> player.setGameMode(GameMode.SURVIVAL));
MinecraftServer.getSchedulerManager().submitTask(() -> {
List<Material> materials = Material.values().stream()
.filter(material -> !material.equals(Material.AIR))
jannis marked this conversation as resolved
Review

new Random() als eigene Variable in Pillars, damit nicht immer ein neues Objekt erstellt wird.
Alternativ geht ThreadLocalRandom.current()

new Random() als eigene Variable in Pillars, damit nicht immer ein neues Objekt erstellt wird. Alternativ geht ThreadLocalRandom.current()
Review

nimm ThreadLocalRandom.current()

nimm ThreadLocalRandom.current()
.toList();
this.getPlayers().forEach(player -> {
ItemStack item = ItemStack.of(materials.get(ThreadLocalRandom.current().nextInt(Material.values().toArray().length)));
player.getInventory().addItemStack(item);
});
return TaskSchedule.seconds(5);
});
}
}

View File

@@ -0,0 +1,32 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.pillars;
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 PillarsFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Pillars#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Pillars#description");
}
@Override
public Material symbol() {
return Material.BEDROCK;
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new Pillars().setParent(parent);
}
}

View File

@@ -30,6 +30,11 @@ public abstract class Score {
throw new NotImplementedException("This Score type is not able to process points"); throw new NotImplementedException("This Score type is not able to process points");
} }
public void insertMultiple(Set<Player> p) {
p.forEach(player -> this.insertResultProcessor(player, () -> {}));
this.insertResultImplementation(p);
}
public void insertResult(Player p) { public void insertResult(Player p) {
this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p))); this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p)));
} }

View File

@@ -33,9 +33,15 @@ public class Position {
public static List<Block> blocksBelowPlayer(Instance instance, Player p) { public static List<Block> blocksBelowPlayer(Instance instance, Player p) {
Point playerPos = p.getPosition(); Point playerPos = p.getPosition();
List<Block> blocks = new ArrayList<>(); List<Block> blocks = new ArrayList<>();
GeneratorUtils.foreachXZ(playerPos.sub(0.5, 1, 0.5), playerPos.add(0.5, -1, 0.5), point -> { GeneratorUtils.foreachXZ(
blocks.add(instance.getBlock(point)); playerPos.sub(0.5, 1, 0.5),
}); playerPos.add(0.5, -1, 0.5),
point -> blocks.add(instance.getBlock(point))
);
return blocks.stream().distinct().toList(); return blocks.stream().distinct().toList();
} }
public static boolean hasPositionChanged(Pos oldPos, Pos newPos) {
return !oldPos.withView(0, 0).equals(newPos.withView(0, 0));
}
} }

View File

@@ -174,3 +174,12 @@ startSpeed;Start Speed;Startgeschwindigkeit
ns:game_BoatRace#;; ns:game_BoatRace#;;
name;Boatrace;Bootrennen name;Boatrace;Bootrennen
description;; description;;
;;
ns:game_Pillars#;;
name;Pillars;Pillars
description;Build yourself up with your random blocks to reach your opponents and push them down!;Baue dich mit deinen zufälligen Blöcken zu deinen Gegnern und schupse sie runter!
;;
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!
jannis marked this conversation as resolved Outdated
Outdated
Review

Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt!

Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt!
itemCount;Block Count;Block Anzahl
1 map en_us de_de
174 ns:game_BoatRace#
175 name Boatrace Bootrennen
176 description
177
178 ns:game_Pillars#
179 name Pillars Pillars
180 description Build yourself up with your random blocks to reach your opponents and push them down! Baue dich mit deinen zufälligen Blöcken zu deinen Gegnern und schupse sie runter!
181
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