added SpaceSnake #5

Merged
MineTec merged 8 commits from develop-spaceSnake into develop 2025-10-15 20:11:37 +00:00
5 changed files with 250 additions and 2 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.jumpDive.JumpDiveFactory;
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.stickfight.StickFightFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.TetrisFactory;
@@ -40,7 +41,8 @@ public enum GameList {
SUMO(new SumoFactory(), GameType.PVP),
HIGHGROUND(new HighGroundFactory(), GameType.PVP),
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 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));
}
MineTec marked this conversation as resolved
Review

Diamant Blöcke ausschließen wäre gut.

Diamant Blöcke ausschließen wäre gut.
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);
MineTec marked this conversation as resolved Outdated
Outdated
Review

Seiht man auf Bedrock wahrscheinlich nicht. Vielleicht geht falling block.

Seiht man auf Bedrock wahrscheinlich nicht. Vielleicht geht falling block.
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())
MineTec marked this conversation as resolved Outdated
Outdated
Review

Vielleicht Kollision mit bounding box überprüfen? Wäre dann gelichmäßiger (vor allem auf y-Achse).

Vielleicht Kollision mit bounding box überprüfen? Wäre dann gelichmäßiger (vor allem auf y-Achse).
)
.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;
import net.minestom.server.instance.block.BlockFace;
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 static Material fromString(String name, Material 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

@@ -147,4 +147,8 @@ description;Speedbridge to the other platform. The first one there wins!;Baue di
ns:game_BlockBreakRace#;;
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!
;;
;;
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
1 map en_us de_de
147 ns:game_BlockBreakRace#
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