develop-turtleGame #6

Merged
Pupsi merged 32 commits from develop-turtleGame into develop 2025-10-15 20:21:27 +00:00
5 changed files with 413 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.sumo.SumoFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.TrafficLightRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.TrafficLightRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.turtleGame.TurtleGameFactory;
public enum GameList { public enum GameList {
DEATHCUBE(new DeathcubeFactory(), GameType.JUMPNRUN), DEATHCUBE(new DeathcubeFactory(), GameType.JUMPNRUN),
@@ -35,6 +36,7 @@ public enum GameList {
TNTRUN(new TntRunFactory(), GameType.OTHER), TNTRUN(new TntRunFactory(), GameType.OTHER),
ANVILRUN(new AnvilRunFactory(), GameType.PVE), ANVILRUN(new AnvilRunFactory(), GameType.PVE),
ACIDRAIN(new AcidRainFactory(), GameType.PVE), ACIDRAIN(new AcidRainFactory(), GameType.PVE),
TURTLEGAME(new TurtleGameFactory(), GameType.PVE),
ELYTRARACE(new ElytraRaceFactory(), GameType.PVP), ELYTRARACE(new ElytraRaceFactory(), GameType.PVP),
SPLEEF(new SpleefFactory(), GameType.PVP), SPLEEF(new SpleefFactory(), GameType.PVP),
JUMPDIVE(new JumpDiveFactory(), GameType.JUMPNRUN), JUMPDIVE(new JumpDiveFactory(), GameType.JUMPNRUN),

View File

@@ -0,0 +1,254 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.turtleGame;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.turtleGame.gameObjects.Turtle;
import eu.mhsl.minenet.minigames.score.PointsWinScore;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
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.player.PlayerStartSneakingEvent;
import net.minestom.server.event.player.PlayerStopSneakingEvent;
import net.minestom.server.event.player.PlayerTickEvent;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
class TurtleGame extends StatelessGame {
private final int radius;
private final Map<Player, Turtle> turtlePlayerMap = new WeakHashMap<>();
private final ArrayList<Entity> snacks = new ArrayList<>();
private final ArrayList<Entity> bombs = new ArrayList<>();
private final Block snackBlock = Block.SUNFLOWER.withProperty("half", "upper");
private final double startSpeed;
public TurtleGame(int radius, int startSpeed) {
super(Dimension.OVERWORLD.key, "Turtle Game", new PointsWinScore());
this.radius = radius;
this.startSpeed = startSpeed;
this.eventNode()
.addListener(PlayerTickEvent.class, this::onPlayerTick)
.addListener(PlayerStartSneakingEvent.class, this::onPlayerStartSneak)
.addListener(PlayerStopSneakingEvent.class, this::onPlayerStopSneak);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
for(int x = -this.radius - 1; x <= this.radius + 1; x++) {
for(int z = -this.radius - 1; z <= this.radius + 1; z++) {
double distance = new Pos(x, 0, z).distance(new Pos(0, 0, 0));
if(distance <= this.radius) {
Pupsi marked this conversation as resolved
Review

eigne methode ist hier irgendwie unnötig...

eigne methode ist hier irgendwie unnötig...
boolean isEdge = this.radius - 1 < distance;
this.setBlock(x, 0, z, Block.SAND);
this.setBlock(x, 1, z, isEdge ? Block.STONE : Block.AIR);
this.setBlock(x, 2, z, isEdge ? Block.STONE_SLAB : Block.AIR);
} else {
this.setBlock(x, 0, z, Block.AIR);
}
}
}
}
private void onPlayerStartSneak(@NotNull PlayerStartSneakingEvent event) {
Player p = event.getPlayer();
this.turtlePlayerMap.get(p).boostSpeed();
}
private void onPlayerStopSneak(@NotNull PlayerStopSneakingEvent event) {
Player p = event.getPlayer();
this.turtlePlayerMap.get(p).cancelBoost();
}
protected void onPlayerTick(@NotNull PlayerTickEvent event) {
Player p = event.getPlayer();
if(p.getGameMode() == GameMode.SPECTATOR) return;
Turtle turtle = this.turtlePlayerMap.get(p);
turtle.adaptView();
if(this.isRunning()) {
turtle.move();
this.snacks.stream()
.filter(turtle::checkCollisionWithEntity)
.toList()
.forEach(snack -> {
this.eat(p, snack);
this.generateNewSnack();
if(this.turtlePlayerMap.get(p).getScore() % 5 == 0) {
this.generateNewBomb();
this.addSpeed(0.4, p);
}
});
this.bombs.stream()
.filter(turtle::checkCollisionWithEntity)
.toList()
.forEach(bomb -> {
this.explode(p, bomb);
this.generateNewBombs(2);
this.addGlobalSpeed(0.3);
});
}
}
protected void addSpeed(double amount, Player p) {
this.turtlePlayerMap.get(p).addSpeed(amount);
}
protected void addGlobalSpeed(double amount) {
this.turtlePlayerMap.values().forEach(turtle -> turtle.addSpeed(amount));
}
protected void eat(Player p, Entity snack) {
p.playSound(Sound.sound(SoundEvent.ENTITY_GENERIC_EAT, Sound.Source.MASTER, 1f, 1f), snack.getPosition());
Material snackMaterial = Objects.requireNonNull(this.snackBlock.registry().material());
p.sendPacket(new ParticlePacket(Particle.ITEM.withItem(ItemStack.of(snackMaterial)), p.getPosition(), new Pos(0.5, 0.5, 0.5), 0, 8));
this.snacks.remove(snack);
snack.remove();
this.turtlePlayerMap.get(p).increaseScore();
this.turtlePlayerMap.get(p).increaseBoostChargeLevel(0.04f);
}
protected void explode(Player p, Entity bomb) {
this.letPlayerLoose(p);
p.playSound(Sound.sound(SoundEvent.ENTITY_GENERIC_EXPLODE, Sound.Source.MASTER, 2f, 1f), bomb.getPosition());
p.playSound(Sound.sound(SoundEvent.ENTITY_GENERIC_EXPLODE, Sound.Source.MASTER, 2f, 1f), bomb.getPosition());
p.sendPacket(new ParticlePacket(Particle.EXPLOSION, p.getPosition(), new Pos(0.5, 0.5, 0.5), 0, 8));
if(this.getLeftPlayers().size() == 1) this.setTimeLimit(10);
this.bombs.remove(bomb);
bomb.remove();
}
protected void letPlayerLoose(Player p) {
p.setGameMode(GameMode.SPECTATOR);
p.setFlying(true);
this.turtlePlayerMap.get(p).destroy();
this.getScore().insertResult(p, this.turtlePlayerMap.get(p).getScore());
}
protected List<Player> getLeftPlayers() {
return this.turtlePlayerMap.keySet().stream()
.filter(player -> !this.getScore().hasResult(player))
.toList();
}
@Override
protected boolean onPlayerJoin(Player p) {
this.turtlePlayerMap.putIfAbsent(p, new Turtle(p, this.startSpeed));
p.setLevel(this.turtlePlayerMap.get(p).getScore());
Turtle turtle = this.turtlePlayerMap.get(p);
MinecraftServer.getSchedulerManager().scheduleNextTick(turtle::spawnTurtle);
return super.onPlayerJoin(p);
}
@Override
protected void onPlayerLeave(Player p) {
Turtle turtle = this.turtlePlayerMap.get(p);
turtle.remove();
}
@Override
public Pos getSpawn() {
double theta = this.rnd.nextDouble() * 2 * Math.PI;
double spawnRadius = this.radius - 4;
double x = spawnRadius * Math.cos(theta);
double z = spawnRadius * Math.sin(theta);
return new Pos(x, 1, z).withLookAt(new Pos(0, 0, 0));
}
private void generateNewSnacks(int count) {
for (int i = 0; i < count; i++) {
this.generateNewSnack();
}
}
private void generateNewSnack() {
Entity snack = new Entity(EntityType.FALLING_BLOCK);
FallingBlockMeta meta = (FallingBlockMeta) snack.getEntityMeta();
meta.setBlock(this.snackBlock);
meta.setCustomName(Component.text("Snack"));
meta.setCustomNameVisible(true);
Pos spawnPosition = this.newSpawnPosition(snack);
if(spawnPosition == null) {
snack.remove();
return;
}
snack.setInstance(this, spawnPosition);
this.snacks.add(snack);
}
private void generateNewBombs(int count) {
for (int i = 0; i < count; i++) {
this.generateNewBomb();
}
}
private void generateNewBomb() {
Entity bomb = new Entity(EntityType.FALLING_BLOCK);
FallingBlockMeta meta = (FallingBlockMeta) bomb.getEntityMeta();
meta.setBlock(Block.TNT);
meta.setCustomName(Component.text("Bomb").color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
meta.setCustomNameVisible(true);
Pos spawnPosition = this.newSpawnPosition(bomb, false);
if(spawnPosition == null) {
bomb.remove();
return;
}
bomb.setInstance(this, spawnPosition);
this.bombs.add(bomb);
}
private @Nullable Pos newSpawnPosition(Entity entity) {
return this.newSpawnPosition(entity, true);
}
private @Nullable Pos newSpawnPosition(Entity entity, boolean nearPlayers) {
Pos spawnPosition;
int counter = 0;
boolean isInRadius, collides;
Pupsi marked this conversation as resolved
Review

@Nullable gehört vor die Rückgabe parameter private @Nullable Pos newSpawnPosition(

@Nullable gehört vor die Rückgabe parameter `private @Nullable Pos newSpawnPosition(`
do {
if(counter > 200) return null;
int x = this.rnd.nextInt(-this.radius+2, this.radius-2);
int z = this.rnd.nextInt(-this.radius+2, this.radius-2);
spawnPosition = new Pos(x, 1, z).add(0.5, 0, 0.5);
Pos checkPosition = spawnPosition;
isInRadius = checkPosition.distance(0, 1, 0) < this.radius-2;
collides = this.getEntities().stream()
.filter(e -> !e.equals(entity))
.anyMatch(e -> entity.getBoundingBox().intersectBox(e.getPosition().sub(checkPosition), e.getBoundingBox()));
if(!collides && !nearPlayers) collides = this.turtlePlayerMap.values().stream()
.filter(turtle -> !turtle.equals(entity))
.anyMatch(turtle -> entity.getBoundingBox()
.growSymmetrically(turtle.getBombBorder(), 1, turtle.getBombBorder())
.intersectBox(turtle.getPosition().sub(checkPosition), turtle.getBoundingBox()));
counter++;
} while (!isInRadius || collides);
return spawnPosition;
}
@Override
protected void onStart() {
this.generateNewSnacks(this.turtlePlayerMap.size() + 1);
this.generateNewBombs((int) Math.ceil(this.snacks.size() * 0.5));
this.turtlePlayerMap.values().forEach(Turtle::startBoostRefill);
}
@Override
protected void onStop() {
this.getLeftPlayers().forEach(this::letPlayerLoose);
}
}

View File

@@ -0,0 +1,42 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.turtleGame;
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 TurtleGameFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_TurtleGame#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_TurtleGame#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 10, 20, 30, 40))
.addOption(new NumericOption("startSpeed", Material.LEATHER_BOOTS, TranslatedComponent.byId("game_TurtleGame#startSpeed"), 2, 3, 4, 6, 8));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new TurtleGame(configuration.get("radius").getAsInt(), configuration.get("startSpeed").getAsInt()).setParent(parent);
}
@Override
public Material symbol() {
return Material.TURTLE_EGG;
}
}

View File

@@ -0,0 +1,110 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.turtleGame.gameObjects;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.timer.Task;
import net.minestom.server.timer.TaskSchedule;
public class Turtle extends EntityCreature {
private final Player player;
private int score = 0;
private double speed;
private double boostSpeedMultiplier = 1;
private float boostChargeLevel = 0f;
private Task boostTask;
private Task boostRefillTask;
public Turtle(Player player, double speed) {
super(EntityType.TURTLE);
this.player = player;
this.speed = speed;
this.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.15);
this.getAttribute(Attribute.MAX_HEALTH).setBaseValue(1);
this.player.setExp(this.boostChargeLevel);
}
public void spawnTurtle() {
this.setInstance(this.player.getInstance(), this.player.getPosition());
this.addPassenger(this.player);
Pupsi marked this conversation as resolved
Review

statt teleport bei setInstance übergeben

statt teleport bei setInstance übergeben
}
public void startBoostRefill() {
this.boostRefillTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
if(this.boostChargeLevel >= 1f) return;
this.increaseBoostChargeLevel(0.02f);
}, TaskSchedule.seconds(1), TaskSchedule.seconds(1));
}
public void destroy() {
this.removePassenger(this.player);
this.remove();
if(this.boostRefillTask != null && this.boostRefillTask.isAlive()) this.boostRefillTask.cancel();
if(this.boostTask != null && this.boostTask.isAlive()) this.boostTask.cancel();
}
public void adaptView() {
if(this.getInstance() == null) return;
this.teleport(this.getPosition().withView(this.player.getPosition().withPitch(this.getPosition().pitch())));
Vec lookingVector = this.player.getPosition().direction().withY(0).mul(100);
this.lookAt(this.getPosition().add(lookingVector.asPosition()));
}
public void move() {
this.adaptView();
Vec direction = this.player.getPosition().direction();
Vec movementVector = direction.withY(0).normalize().mul(this.speed * this.boostSpeedMultiplier);
this.setVelocity(movementVector);
}
public boolean checkCollisionWithEntity(Entity other) {
return this.getBoundingBox().intersectBox(other.getPosition().sub(this.getPosition()), other.getBoundingBox());
}
public void boostSpeed() {
if(this.boostChargeLevel <= 0f) return;
this.boostSpeedMultiplier = 3.5;
this.boostTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
if(this.boostChargeLevel <= 0f) {
this.cancelBoost();
return;
}
this.boostChargeLevel = Math.max(0f, this.boostChargeLevel - 0.025f);
this.player.setExp(this.boostChargeLevel);
}, TaskSchedule.millis(30), TaskSchedule.millis(30));
Pupsi marked this conversation as resolved
Review

raus

raus
}
public void cancelBoost() {
if(this.boostTask == null || !this.boostTask.isAlive()) return;
this.boostTask.cancel();
this.boostSpeedMultiplier = 1;
}
public void increaseBoostChargeLevel(float amount) {
this.boostChargeLevel = Math.min(1f, this.boostChargeLevel + amount);
this.player.setExp(this.boostChargeLevel);
}
public void addSpeed(double amount) {
this.speed += amount;
}
public int getScore() {
return this.score;
}
public double getBombBorder() {
// 1 bei speed 2; 2 bei speed 4; 4 bei speed 8
return Math.clamp((this.speed * this.boostSpeedMultiplier) / 2, 1.5, 4);
}
public void increaseScore() {
this.score += 1;
this.player.setLevel(this.score);
}
}

View File

@@ -152,3 +152,8 @@ ns:game_SpaceSnake#;;
name;Space Snake;Weltraum-Snake 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! 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 powerUpCount;Number of diamonds in the arena;Anzahl der Diamanten in der Arena
;;
ns:game_TurtleGame#;;
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!
startSpeed;Start Speed;Startgeschwindigkeit
Can't render this file because it has a wrong number of fields in line 114.