Merge remote-tracking branch 'origin/develop' into develop-turtleGame

# Conflicts:
#	src/main/resources/lang/locales.map.csv
This commit is contained in:
2025-10-15 22:19:39 +02:00
5 changed files with 250 additions and 1 deletions

View File

@@ -13,6 +13,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.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;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.TetrisFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.TetrisFactory;
@@ -42,7 +43,8 @@ public enum GameList {
SUMO(new SumoFactory(), GameType.PVP), SUMO(new SumoFactory(), GameType.PVP),
HIGHGROUND(new HighGroundFactory(), GameType.PVP), HIGHGROUND(new HighGroundFactory(), GameType.PVP),
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);
private final GameFactory factory; private final GameFactory factory;
private final GameType type; private final GameType type;

View File

@@ -0,0 +1,182 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.spaceSnake;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.PointsWinScore;
import eu.mhsl.minenet.minigames.util.MaterialUtil;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.events.PrepareAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.*;
import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.event.entity.EntityTickEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.WorldBorder;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SpaceSnake extends StatelessGame {
record PlayState(AtomicInteger length, Queue<Pos> blocks, Material blockType, Pos spawn) {
public void cutToLength(Consumer<Pos> removed) {
while (this.blocks.size() > this.length.get()) {
removed.accept(this.blocks.poll());
}
}
}
private final Map<Player, PlayState> playerStates = new WeakHashMap<>();
private int mapSize;
private final Supplier<Integer> posInBoundsW = () -> this.rnd.nextInt(-this.mapSize/2, this.mapSize/2);
private final Supplier<Integer> posInBoundsH = () -> this.rnd.nextInt(-60, 300);
public SpaceSnake(int mapSize, int powerUpCount) {
super(Dimension.THE_END.key, "spaceSnake", new PointsWinScore());
this.mapSize = mapSize;
this.setWorldBorder(new WorldBorder(this.mapSize, 0, 0, 0, 0));
for (int i = 0; i < powerUpCount; i++) {
this.spawnPowerUp();
}
this.eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build()
.createNode()
);
this.eventNode().addListener(PrepareAttackEvent.class, prepareAttackEvent -> {
if (this.isBeforeBeginning) prepareAttackEvent.setCancelled(true);
});
this.eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
finalAttackEvent.setBaseDamage(0);
((Player) finalAttackEvent.getTarget()).setHealth(20);
});
}
@Override
protected void onStart() {
this.getPlayers().forEach(player -> {
player.setGameMode(GameMode.SURVIVAL);
this.updateInv(player);
player.setHeldItemSlot((byte) 1);
});
}
@Override
protected void onStop() {
this.getPlayers().forEach(player -> this.getScore().insertResult(player, this.playerStates.get(player).length.get()));
}
@Override
protected boolean onPlayerJoin(Player p) {
Pos spawn = new Pos(this.posInBoundsW.get(), -60, this.posInBoundsW.get());
PlayState state = new PlayState(
new AtomicInteger(3),
new ArrayDeque<>(List.of(spawn)),
MaterialUtil.getRandomFullBlock(material -> !material.equals(Material.DIAMOND_BLOCK)),
spawn
);
this.playerStates.put(p, state);
this.setBlock(spawn, state.blockType.block());
MinecraftServer.getSchedulerManager().scheduleNextTick(
() -> p.teleport(this.getSaveSpawn(spawn))
);
return super.onPlayerJoin(p);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
PlayState state = this.playerStates.get(playerMoveEvent.getPlayer());
if(this.isBeforeBeginning) {
boolean falling = state.blocks.stream().anyMatch(pos -> pos.y() > playerMoveEvent.getNewPosition().y());
if(falling) playerMoveEvent.getPlayer().teleport(this.getSaveSpawn(state.spawn));
return;
}
if(playerMoveEvent.getNewPosition().y() < -64) {
this.getScore().insertResult(playerMoveEvent.getPlayer(), state.length.get());
playerMoveEvent.getPlayer().teleport(this.getSpawn());
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
long livingPlayers = this.getPlayers().stream()
.filter(p -> this.getScore().hasResult(p))
.count();
if(livingPlayers == 1) this.setTimeLimit(10);
if(livingPlayers == 0) this.stop();
}
}
@Override
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
if(this.isBeforeBeginning) {
playerBlockPlaceEvent.setCancelled(true);
return;
}
PlayState state = this.playerStates.get(playerBlockPlaceEvent.getPlayer());
state.blocks.add(playerBlockPlaceEvent.getBlockPosition().asVec().asPosition());
state.cutToLength(pos -> this.setBlock(pos, Block.AIR));
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> this.updateInv(playerBlockPlaceEvent.getPlayer()));
playerBlockPlaceEvent.getPlayer().setLevel(state.length.get());
}
private Pos getSaveSpawn(Pos blockPos) {
return blockPos.add(0.5).withY((y) -> y + 2);
}
private void updateInv(Player player) {
PlayerInventory inventory = player.getInventory();
inventory.clear();
inventory.addItemStack(ItemStack.of(Material.STICK, 1).with(builder -> builder.glowing(true)));
inventory.addItemStack(ItemStack.of(this.playerStates.get(player).blockType, 64));
}
private void spawnPowerUp() {
Pos spawnPos = new Pos(this.posInBoundsW.get(), this.posInBoundsH.get(), this.posInBoundsW.get());
Entity display = new Entity(EntityType.FALLING_BLOCK);
((FallingBlockMeta) display.getEntityMeta()).setBlock(Block.DIAMOND_BLOCK);
display.setGlowing(true);
display.setNoGravity(true);
display.setInstance(this, spawnPos);
display.eventNode().addListener(EntityTickEvent.class, onTick -> {
Player player = this.getPlayers().stream()
.filter(p -> !this.getScore().hasResult(p))
.filter(p -> p.getBoundingBox()
.grow(1, 1, 1)
.intersectBox(display.getPosition().sub(p.getPosition()), display.getBoundingBox())
)
.findAny()
.orElse(null);
if(player == null) return;
this.spawnPowerUp();
display.remove();
this.onPowerup(player);
});
}
private void onPowerup(Player player) {
PlayState state = this.playerStates.get(player);
state.length.incrementAndGet();
player.setLevel(player.getLevel() + 1);
player.playSound(Sound.sound(SoundEvent.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.PLAYER, 1f, 1f));
}
}

View File

@@ -0,0 +1,41 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.spaceSnake;
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 SpaceSnakeFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_SpaceSnake#name");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("width", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#width"), 20, 30, 40, 50, 60, 70, 80))
.addOption(new NumericOption("powerUpCount", Material.DIAMOND, TranslatedComponent.byId("game_SpaceSnake#powerUpCount"), 50, 100, 200, 300));
}
@Override
public Material symbol() {
return Material.GREEN_CONCRETE_POWDER;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_SpaceSnake#description");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new SpaceSnake(configuration.get("width").getAsInt(), configuration.get("powerUpCount").getAsInt()).setParent(parent);
}
}

View File

@@ -1,9 +1,28 @@
package eu.mhsl.minenet.minigames.util; package eu.mhsl.minenet.minigames.util;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
public class MaterialUtil { public class MaterialUtil {
public static Material fromString(String name, Material def) { public static Material fromString(String name, Material def) {
return Material.values().stream().filter(material -> material.name().equals(name)).findFirst().orElse(def); return Material.values().stream().filter(material -> material.name().equals(name)).findFirst().orElse(def);
} }
public static Material getRandomFullBlock(Predicate<Material> filter) {
List<Material> blocks = Material.values().stream()
.filter(filter)
.filter(Material::isBlock)
.filter(material -> material.block().isSolid())
.filter(material -> Arrays.stream(BlockFace.values())
.allMatch(face -> material.block().registry().collisionShape().isFaceFull(face))
)
.toList();
return blocks.get(ThreadLocalRandom.current().nextInt(blocks.size()));
}
} }

View File

@@ -148,6 +148,11 @@ ns:game_BlockBreakRace#;;
name;Block Break Race;Blockbruch-Rennen name;Block Break Race;Blockbruch-Rennen
description;Dig down through the tubes using the right tools. The first player to reach the bottom wins!;Grabe dich durch die Röhren nach unten und verwende dabei das richtige Werkzeug. Wer zuerst unten ankommt, gewinnt! description;Dig down through the tubes using the right tools. The first player to reach the bottom wins!;Grabe dich durch die Röhren nach unten und verwende dabei das richtige Werkzeug. Wer zuerst unten ankommt, gewinnt!
;; ;;
ns:game_SpaceSnake#;;
name;Space Snake;Weltraum-Snake
description;Collect diamonds while extending your snake bridge through space and fight other players. The player with the longest bridge wins!;Sammle Diamanten, während du deine Schlangenbrücke durchs All erweiterst und gegen andere Spieler kämpfst. Der Spieler mit den der Längsten Brücke gewinnt!
powerUpCount;Number of diamonds in the arena;Anzahl der Diamanten in der Arena
;;
ns:game_TurtleGame#;; ns:game_TurtleGame#;;
name;Turtle Game;Turtle Game name;Turtle Game;Turtle Game
description;Eat snacks and dodge bombs to get the highest score!;Esse Snacks und weiche Bomben aus, um den höchsten Score zu erreichen! description;Eat snacks and dodge bombs to get the highest score!;Esse Snacks und weiche Bomben aus, um den höchsten Score zu erreichen!
1 map en_us de_de
148 name Block Break Race Blockbruch-Rennen
149 description Dig down through the tubes using the right tools. The first player to reach the bottom wins! Grabe dich durch die Röhren nach unten und verwende dabei das richtige Werkzeug. Wer zuerst unten ankommt, gewinnt!
150
151 ns:game_SpaceSnake#
152 name Space Snake Weltraum-Snake
153 description Collect diamonds while extending your snake bridge through space and fight other players. The player with the longest bridge wins! Sammle Diamanten, während du deine Schlangenbrücke durchs All erweiterst und gegen andere Spieler kämpfst. Der Spieler mit den der Längsten Brücke gewinnt!
154 powerUpCount Number of diamonds in the arena Anzahl der Diamanten in der Arena
155
156 ns:game_TurtleGame#
157 name Turtle Game Turtle Game
158 description Eat snacks and dodge bombs to get the highest score! Esse Snacks und weiche Bomben aus, um den höchsten Score zu erreichen!