diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java index 29bad6a..0d846b6 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java @@ -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.backrooms.BackroomsFactory; 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.boatRace.BoatRaceFactory; 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.jumpDive.JumpDiveFactory; 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.spleef.SpleefFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight.StickFightFactory; @@ -46,7 +48,9 @@ public enum GameList { FASTBRIDGE(new FastbridgeFactory(), GameType.OTHER), BLOCKBREAKRACE(new BlockBreakRaceFactory(), GameType.OTHER), 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 GameType type; diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/BlockBattle.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/BlockBattle.java new file mode 100644 index 0000000..11018d6 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/BlockBattle.java @@ -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 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 + protected void onLoad(@NotNull CompletableFuture 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)) + .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(); + + player.teleport( + this.isBeforeBeginning + ? this.getSpawn() + : this.teams.get(player).spawnPosition() + ); + + this.giveItems(player); + } + } + + @Override + protected void onStart() { + this.setTeams(); + this.getPlayers().forEach(player -> player.teleport(this.teams.get(player).spawnPosition()).thenRun(() -> { + this.giveItems(player); + player.setGameMode(GameMode.SURVIVAL); + })); + } + + private void generatePlatform(Pos center, Block inner, Block outer) { + for(int x = center.blockX()-2; x < center.blockX()+3; x++) { + 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 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); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/BlockBattleFactory.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/BlockBattleFactory.java new file mode 100644 index 0000000..8884337 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/BlockBattleFactory.java @@ -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> configuration) throws Exception { + return new BlockBattle(configuration.get("itemCount").getAsInt()).setParent(parent); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/Team.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/Team.java new file mode 100644 index 0000000..fda428a --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/blockBattle/Team.java @@ -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) { + 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; + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/pillars/Pillars.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/pillars/Pillars.java new file mode 100644 index 0000000..7c3e68d --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/pillars/Pillars.java @@ -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; + + 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) { + 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); + player.teleport(this.getSpawn()); + player.setGameMode(GameMode.SPECTATOR); + } + } + + @Override + protected void onStart() { + this.getPlayers().forEach(player -> player.setGameMode(GameMode.SURVIVAL)); + + MinecraftServer.getSchedulerManager().submitTask(() -> { + List materials = Material.values().stream() + .filter(material -> !material.equals(Material.AIR)) + .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); + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/pillars/PillarsFactory.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/pillars/PillarsFactory.java new file mode 100644 index 0000000..22e03a2 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/pillars/PillarsFactory.java @@ -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> configuration) throws Exception { + return new Pillars().setParent(parent); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/score/Score.java b/src/main/java/eu/mhsl/minenet/minigames/score/Score.java index cd7982d..967cc0d 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/score/Score.java +++ b/src/main/java/eu/mhsl/minenet/minigames/score/Score.java @@ -30,6 +30,11 @@ public abstract class Score { throw new NotImplementedException("This Score type is not able to process points"); } + public void insertMultiple(Set p) { + p.forEach(player -> this.insertResultProcessor(player, () -> {})); + this.insertResultImplementation(p); + } + public void insertResult(Player p) { this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p))); } diff --git a/src/main/java/eu/mhsl/minenet/minigames/util/Position.java b/src/main/java/eu/mhsl/minenet/minigames/util/Position.java index 4dd189f..bb5dc2e 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/util/Position.java +++ b/src/main/java/eu/mhsl/minenet/minigames/util/Position.java @@ -33,9 +33,15 @@ public class Position { public static List blocksBelowPlayer(Instance instance, Player p) { Point playerPos = p.getPosition(); List blocks = new ArrayList<>(); - GeneratorUtils.foreachXZ(playerPos.sub(0.5, 1, 0.5), playerPos.add(0.5, -1, 0.5), point -> { - blocks.add(instance.getBlock(point)); - }); + GeneratorUtils.foreachXZ( + 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(); } + + public static boolean hasPositionChanged(Pos oldPos, Pos newPos) { + return !oldPos.withView(0, 0).equals(newPos.withView(0, 0)); + } } diff --git a/src/main/resources/lang/locales.map.csv b/src/main/resources/lang/locales.map.csv index 4d22dc4..9418927 100644 --- a/src/main/resources/lang/locales.map.csv +++ b/src/main/resources/lang/locales.map.csv @@ -174,3 +174,12 @@ startSpeed;Start Speed;Startgeschwindigkeit ns:game_BoatRace#;; name;Boatrace;Bootrennen 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! +itemCount;Block Count;Block Anzahl \ No newline at end of file