Initial commit

This commit is contained in:
2022-09-17 10:49:36 +02:00
parent 1e8420a83e
commit 59a6e1c423
368 changed files with 26176 additions and 0 deletions

View File

@ -0,0 +1,46 @@
package eu.mhsl.minenet.minigames;
import eu.mhsl.minenet.minigames.command.Commands;
import eu.mhsl.minenet.minigames.handler.Listeners;
import eu.mhsl.minenet.minigames.lang.Languages;
import eu.mhsl.minenet.minigames.server.tasks.TablistUpdateTask;
import eu.mhsl.minenet.minigames.server.provider.ByPlayerNameUuidProvider;
import io.github.bloepiloepi.pvp.PvpExtension;
import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.lan.OpenToLAN;
import net.minestom.server.timer.TaskSchedule;
public class Main {
/**
* Starts minenet minigames services
* @param args startflags
*/
public static void main(String[] args) {
System.out.println("Initialize Minecraft server...");
MinecraftServer server = MinecraftServer.init();
PvpExtension.init();
MinecraftServer.setBrandName("minenet");
MinecraftServer.setCompressionThreshold(0);
System.setProperty("minestom.chunk-view-distance", "12");
MinecraftServer.getConnectionManager().setUuidProvider(new ByPlayerNameUuidProvider());
Commands.values();
Listeners.values();
MinecraftServer.getSchedulerManager().scheduleTask(new TablistUpdateTask(), TaskSchedule.tick(20), TaskSchedule.tick(20));
//noinspection ResultOfMethodCallIgnored
Resource.values(); // This initializes and preloads the enum and extracts the resources
Languages.getInstance(); //Preload languages into the jvm
System.out.println("Starting Minecraft server ... ");
OpenToLAN.open();
//MojangAuth.init(); LET NON MIGRATORS PLAY!
server.start("0.0.0.0", 25565);
System.gc();
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> System.out.println("Minecraft server is now running!"));
}
}

View File

@ -0,0 +1,41 @@
package eu.mhsl.minenet.minigames;
import eu.mhsl.minenet.minigames.util.ResourceUtils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
/**
* Predefined resources which are extracted on Runtime
*/
public enum Resource {
HUB_MAP("maps/hub"),
LOBBY_MAP("maps/lobby"),
RBB("rbb"),
LOCALES("lang"),
SCHEMATICS("schematics");
private final Path path;
private final String name;
Resource(String name) {
this.name = name;
this.path = Path.of("resources/" + name);
try {
System.out.print("extracting resource " + name + " ... ");
ResourceUtils.extractResource(name);
System.out.println("ok");
} catch (URISyntaxException | IOException e) {
throw new RuntimeException(e);
}
}
public Path getPath() {
return path;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.command;
import eu.mhsl.minenet.minigames.command.admin.*;
import eu.mhsl.minenet.minigames.command.user.HubCommand;
import eu.mhsl.minenet.minigames.command.user.LeaveCommand;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
public enum Commands {
HUB(new HubCommand()),
LEAVE(new LeaveCommand()),
DEBUG(new DebugCommand()),
FLY(new FlyCommand()),
GAMEMODE(new GamemodeCommand()),
GC(new GcCommand()),
LANGTEST(new LangTestCommand()),
ROOM(new RoomCommand()),
UPDATE(new UpdateCommand());
Commands(Command handler) {
MinecraftServer.getCommandManager().register(handler);
}
static {
MinecraftServer.getCommandManager().setUnknownCommandCallback((sender, command) -> {
if(command.isBlank()) return;
new ChatMessage(Icon.ERROR).appendStatic("Unknown command").quote(command).send(sender);
});
}
}

View File

@ -0,0 +1,23 @@
package eu.mhsl.minenet.minigames.command.admin;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import net.kyori.adventure.text.Component;
import net.minestom.server.command.builder.Command;
public class DebugCommand extends Command {
public DebugCommand() {
super("debug");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
setDefaultExecutor((sender, args) -> {
new ChatMessage(Icon.CHAT).appendTranslated("sample").send(sender);
new ActionBarMessage().appendTranslated("sample").send(sender);
new TitleMessage().subtitle(subtitleMessage -> subtitleMessage.appendTranslated("sample")).appendTranslated("sample").send(sender);
});
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.minenet.minigames.command.admin;
import net.minestom.server.command.builder.Command;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Player;
public class FlyCommand extends Command {
public FlyCommand() {
super("fly");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
setDefaultExecutor((sender, context) -> {
Player p = (Player) sender;
p.setVelocity(new Vec(0, 5, 0));
p.setFlying(!p.isFlying());
p.setAllowFlying(!p.isAllowFlying());
});
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.minenet.minigames.command.admin;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
public class GamemodeCommand extends Command {
public GamemodeCommand() {
super("gamemode", "gm");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
addSyntax((sender, context) -> {
((Player) sender).setGameMode(context.get("target"));
}, ArgumentType.Enum("target", GameMode.class));
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.command.admin;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.util.Monitoring;
import net.minestom.server.command.builder.Command;
public class GcCommand extends Command {
private static long lastRun = System.currentTimeMillis();
public GcCommand() {
super("gc");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
setDefaultExecutor((sender, context) -> {
long nextRun = (long) (lastRun - (System.currentTimeMillis() - 30*1000)) / 1000;
if(nextRun > 0) {
new ChatMessage(Icon.ERROR).appendStatic("Please wait ").appendStatic(String.valueOf(nextRun)).appendStatic(" seconds before running GC again!").send(sender);
return;
}
lastRun = System.currentTimeMillis();
long before = Monitoring.getRamUsage();
System.gc();
long after = Monitoring.getRamUsage();
new ChatMessage(Icon.SUCCESS).appendStatic("Garbage collector ran successfully!").indent(1).newLine()
.appendStatic("before: ").appendStatic(String.valueOf(before)).appendStatic("MB").newLine()
.appendStatic("now: ").appendStatic(String.valueOf(after)).appendStatic("MB").indent(1).newLine()
.appendStatic("difference: ").appendStatic(String.valueOf(before-after)).appendStatic("MB")
.send(sender);
});
}
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.minenet.minigames.command.admin;
import eu.mhsl.minenet.minigames.lang.Languages;
import eu.mhsl.minenet.minigames.lang.Lang;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
public class LangTestCommand extends Command {
public LangTestCommand() {
super("langtest");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
setDefaultExecutor((sender, context) -> {
sendMessage(Languages.getInstance().getLanguage((Player) sender), "sample").send(sender);
});
var targetString = ArgumentType.String("mapId");
addSyntax((sender, context) -> {
sendMessage(Languages.getInstance().getLanguage((Player) sender), context.get("mapId")).send(sender);
}, targetString);
}
private TranslatableMessage sendMessage(Lang lang, String mapId) {
return new ChatMessage(Icon.SCIENCE).appendStatic(lang.getLangId()).newLine().appendTranslated(lang.getEntry(mapId));
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.command.admin;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
import java.util.stream.Collectors;
public class RoomCommand extends Command {
public RoomCommand() {
super("room");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
setDefaultExecutor((sender, context) -> {
TranslatableMessage out = new ChatMessage(Icon.SCIENCE).appendStatic("Rooms:").indent(1).newLine();
Room.getAllRooms().forEach((roomInstance) -> {
out
.newLine()
.appendStatic("Owner: ").appendStatic(roomInstance.getOwner().getUsername()).newLine()
.appendStatic("Players: ").appendStatic(String.valueOf(roomInstance.getAllMembers().size())).indent(1).newLine()
.list(roomInstance.getAllMembers().stream().map(Player::getUsername).collect(Collectors.toList())).indent(-1).newLine();
});
out.send(sender);
});
}
}

View File

@ -0,0 +1,19 @@
package eu.mhsl.minenet.minigames.command.admin;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.Icon;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
public class UpdateCommand extends Command {
public UpdateCommand() {
super("update");
setCondition((sender, commandString) -> sender.hasPermission("admin"));
setDefaultExecutor((sender, context) -> {
((Player) sender).refreshCommands();
new ChatMessage(Icon.SUCCESS).appendStatic("Updated command syntax!").send(sender);
});
}
}

View File

@ -0,0 +1,19 @@
package eu.mhsl.minenet.minigames.command.user;
import eu.mhsl.minenet.minigames.util.MoveInstance;
import eu.mhsl.minenet.minigames.instance.hub.HubInstance;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
public class HubCommand extends Command {
public HubCommand() {
super("hub");
setCondition((sender, commandString) -> ((Player) sender).getInstance() instanceof Room);
setDefaultExecutor((sender, context) -> {
MoveInstance.move((Player) sender, HubInstance.INSTANCE);
});
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.minenet.minigames.command.user;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
public class LeaveCommand extends Command {
public LeaveCommand() {
super("leave");
setCondition((sender, commandString) -> ((Player) sender).getInstance() instanceof Game);
setDefaultExecutor((sender, context) -> {
Room.setOwnRoom((Player) sender);
});
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.handler;
import eu.mhsl.minenet.minigames.handler.global.AddEntityToInstanceEventListener;
import eu.mhsl.minenet.minigames.handler.global.PlayerChatHandler;
import eu.mhsl.minenet.minigames.handler.global.PlayerLoginHandler;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.EventListener;
public enum Listeners {
SPAWN(new AddEntityToInstanceEventListener()),
CHAT(new PlayerChatHandler()),
LOGIN(new PlayerLoginHandler());
Listeners(EventListener<?> event) {
MinecraftServer.getGlobalEventHandler().addListener(event);
}
}

View File

@ -0,0 +1,37 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.instance.hub.HubInstance;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.Icon;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.timer.ExecutionType;
import org.jetbrains.annotations.NotNull;
public class AddEntityToInstanceEventListener implements EventListener<AddEntityToInstanceEvent> {
@Override
public @NotNull Class eventType() {
return AddEntityToInstanceEvent.class;
}
@Override
public @NotNull Result run(@NotNull AddEntityToInstanceEvent event) {
System.out.println("AddEntityEvent");
if(event.getEntity() instanceof Player p) {
MinecraftServer.getSchedulerManager().scheduleNextTick(p::refreshCommands, ExecutionType.ASYNC);
new ActionBarMessage().appendStatic(Component.text("Instance: ", NamedTextColor.DARK_GRAY)).appendStatic(event.getInstance().getUniqueId().toString()).send(p);
new ChatMessage(Icon.SCIENCE).appendStatic(Component.text(event.getInstance().getUniqueId().toString())).send(p);
//p.addEffect(new Potion(PotionEffect.BLINDNESS, (byte) 1, 20)); //TODO Uncomment, currently buggy causes disconnect see https://github.com/Minestom/Minestom/discussions/1302
}
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerChatEvent;
import org.jetbrains.annotations.NotNull;
public class PlayerChatHandler implements EventListener<PlayerChatEvent> {
@Override
public @NotNull Class<PlayerChatEvent> eventType() {
return PlayerChatEvent.class;
}
@Override
public @NotNull Result run(@NotNull PlayerChatEvent event) {
event.setChatFormat(
(messages) -> new ChatMessage(Icon.CHAT)
.appendStatic(event.getPlayer().getUsername())
.pipe()
.appendStatic(messages.getMessage())
.build(event.getPlayer())
);
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,42 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.skin.SkinCache;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerLoginEvent;
import eu.mhsl.minenet.minigames.instance.hub.HubInstance;
import net.minestom.server.permission.Permission;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
public class PlayerLoginHandler implements EventListener<PlayerLoginEvent> {
@Override
public @NotNull Class<PlayerLoginEvent> eventType() {
return PlayerLoginEvent.class;
}
@Override
public @NotNull Result run(@NotNull PlayerLoginEvent event) {
Player p = event.getPlayer();
p.setRespawnPoint(HubInstance.INSTANCE.getSpawn());
p.sendMessage(p.getUuid().toString());
event.setSpawningInstance(HubInstance.INSTANCE);
SkinCache.applySkin(p);
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
Room minetecsRoom = Room.getRoom(MinecraftServer.getConnectionManager().getPlayer("minetec"));
if(minetecsRoom != null) {
Room.setRoom(event.getPlayer(), minetecsRoom);
}
}, TaskSchedule.seconds(1), TaskSchedule.stop());
if(p.getUsername().equalsIgnoreCase("minetec"))
p.addPermission(new Permission("admin"));
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,41 @@
package eu.mhsl.minenet.minigames.instance;
import net.minestom.server.MinecraftServer;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.DimensionType;
/**
* Prebuilt dimensions
*/
public enum Dimension {
OVERWORLD(
DimensionType
.builder(NamespaceID.from("minenet:fullbright_overworld"))
.ambientLight(2.0f)
.build()
),
NETHER(
DimensionType
.builder(NamespaceID.from("minenet:fullbright_nether"))
.ambientLight(2.0f)
.effects("minecraft:the_nether")
.build()
),
THE_END(
DimensionType
.builder(NamespaceID.from("minenet:fullbright_end"))
.ambientLight(2.0f)
.effects("minecraft:the_end")
.build()
);
public final DimensionType DIMENSION;
Dimension(DimensionType dimType) {
this.DIMENSION = dimType;
MinecraftServer.getDimensionTypeManager().addDimension(this.DIMENSION);
}
}

View File

@ -0,0 +1,7 @@
package eu.mhsl.minenet.minigames.instance;
import net.minestom.server.coordinate.Pos;
public interface Spawnable {
Pos getSpawn();
}

View File

@ -0,0 +1,136 @@
package eu.mhsl.minenet.minigames.instance.game;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.room.Room;
import io.github.bloepiloepi.pvp.config.PvPConfig;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
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.event.trait.InstanceEvent;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public abstract class Game extends InstanceContainer implements Spawnable {
protected boolean isRunning = false;
protected boolean isBeforeBeginning = true;
protected final Random rnd = new Random(); //TODO better way than ths?
public Game(DimensionType dimensionType) {
super(UUID.randomUUID(), dimensionType);
MinecraftServer.getInstanceManager().registerInstance(this);
eventNode()
.addListener(PlayerMoveEvent.class, this::onPlayerMove)
.addListener(PlayerBlockBreakEvent.class, this::onBlockBreak)
.addListener(PlayerBlockPlaceEvent.class, this::onBlockPlace)
.addListener(AddEntityToInstanceEvent.class, this::onJoin)
.addListener(RemoveEntityFromInstanceEvent.class, this::onLeave)
.addListener(ItemDropEvent.class, this::onItemDrop);
}
public void enablePvpConfig(PvPConfig config) {
//eventNode().addChild((EventNode<? extends InstanceEvent>) config.createNode());
}
/**
* Load and start countdown
*/
public void load() {
scheduler().submitTask(() -> {
CompletableFuture<Void> callback = new CompletableFuture<>();
this.onLoad(callback);
callback.whenComplete((unused, throwable) -> this.start());
return TaskSchedule.stop();
}, ExecutionType.ASYNC);
}
protected void start() {
isRunning = true;
isBeforeBeginning = false;
this.onStart();
}
public void stop() {
isRunning = false;
this.onStop();
this.unload();
}
public void unload() {
this.onUnload();
getPlayers().forEach(Room::setOwnRoom);
scheduler().scheduleTask(() -> {
System.out.println("stopping game instance " + this.uniqueId);
getPlayers().forEach(player -> player.kick("timeout"));
MinecraftServer.getInstanceManager().unregisterInstance(this);
}, TaskSchedule.seconds(10), TaskSchedule.stop());
}
protected void onLoad(CompletableFuture<Void> callback) {}
protected void onStart() {}
protected void onStop() {}
protected void onUnload() {}
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
}
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
playerBlockBreakEvent.setCancelled(true);
}
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
playerBlockPlaceEvent.setCancelled(true);
}
protected void onJoin(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
/**
* Make sure when overriding to call checkAbandoned to insure no garbage instances
* @param removeEntityFromInstanceEvent
*/
protected void onLeave(@NotNull RemoveEntityFromInstanceEvent removeEntityFromInstanceEvent) {
this.checkAbandoned();
}
protected void onItemDrop(@NotNull ItemDropEvent itemDropEvent) {
CommonEventHandles.cancel(itemDropEvent);
}
protected void checkAbandoned() {
scheduleNextTick((instance) -> {
if(instance.getPlayers().size() == 0) this.unload();
});
}
public Pos getSpawn() {
return new Pos(0,50,0);
}
}

View File

@ -0,0 +1,108 @@
package eu.mhsl.minenet.minigames.instance.game.minigame;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.message.Countdown;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.score.Score;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
public class Minigame extends Game {
private final String name;
private Score score = new Score(this);
private int timeLimit = 0;
private int timePlayed = 0;
private boolean preventExit = false;
public Minigame(DimensionType dimensionType, String gameName) {
super(dimensionType);
this.name = gameName;
}
public Score getScore() {
return this.score;
}
public String getName() {
return name;
}
public void setTimeLimit(int limit) {
this.timeLimit = limit;
}
@Override
public void load() {
super.load();
}
/**
* Displays countdown and starts the game
* When overriding make sure to call this::start after countdown!
*/
protected CompletableFuture<Void> countdownStart() {
return new Countdown(TitleMessage.class)
.countdown(Audience.audience(getPlayers()), 5, countdownModifier -> {
countdownModifier.message = new TitleMessage(Duration.ofMillis(300), Duration.ofMillis(700))
.subtitle(subtitleMessage -> {
subtitleMessage.appendStatic(Component.text("in ", NamedTextColor.DARK_GREEN))
.appendStatic(Component.text(countdownModifier.timeLeft, NamedTextColor.GREEN))
.appendStatic(Component.text(" seconds", NamedTextColor.DARK_GREEN));
});
});
}
@Override
protected void start() {
countdownStart().thenRun(() -> {
super.start();
if(timeLimit > 0) {
scheduler().submitTask(() -> {
System.out.println("Countdown running...");
if(!isRunning || timeLimit == 0) return TaskSchedule.stop();
if(timeLimit <= timePlayed) {
stop();
return TaskSchedule.stop();
}
int timeLeft = timeLimit - timePlayed;
switch (timeLeft) {
case 60, 30, 10, 5, 4, 3, 2, 1 ->
new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers());
}
timePlayed++;
return TaskSchedule.seconds(1);
}, ExecutionType.SYNC);
}
});
}
@Override
public void stop() {
isRunning = false;
this.onStop();
countdownUnload();
}
protected void countdownUnload() {
new TitleMessage(Duration.ofSeconds(1)).appendStatic("Finish").send(getPlayers());
scheduler().scheduleTask(this::unload, TaskSchedule.seconds(5), TaskSchedule.stop());
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.minenet.minigames.instance.game.minigame;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.minerun.MinerunFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.stickfight.StickFightFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.trafficlightrace.TrafficLightRaceFactory;
public enum MinigameType {
DEATHCUBE(new DeathcubeFactory()),
STICKFIGHT(new StickFightFactory()),
MINERUN(new MinerunFactory()),
TRAFFICLIGHTRACE(new TrafficLightRaceFactory());
private GameFactory factory;
MinigameType(GameFactory factory) {
this.factory = factory;
}
public GameFactory getFactory() {
return this.factory;
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.config;
import java.util.ArrayList;
public class ConfigManager {
private final ArrayList<Option<?>> items = new ArrayList<>();
public ConfigManager addOption(Option option) {
items.add(option);
return this;
}
public ArrayList<Option<?>> getAll() {
return items;
}
}

View File

@ -0,0 +1,95 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.config;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import eu.mhsl.minenet.minigames.util.TextUtil;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.condition.InventoryConditionResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import java.util.HashMap;
import java.util.Map;
public class GameConfigurationInventory extends InteractableInventory {
private final Map<Integer, Option<?>> map = new HashMap<>();
public GameConfigurationInventory(GameFactory factory) {
super(InventoryType.CHEST_5_ROW, factory.name());
ConfigManager config = factory.configuration();
setClickableItem(
ItemStack.builder(Material.RED_WOOL).displayName(Component.text("Abbrechen", NamedTextColor.RED)).build(),
0,
itemClick -> itemClick.getPlayer().closeInventory(),
true
);
setDummyItem(Material.BLACK_STAINED_GLASS_PANE,1);
setDummyItem(
ItemStack.builder(Material.NAME_TAG).displayName(factory.name()).build(),
4
);
setDummyItem(Material.BLACK_STAINED_GLASS_PANE,7);
setClickableItem(
ItemStack.builder(Material.GREEN_WOOL).displayName(Component.text("Start", NamedTextColor.GREEN)).build(),
8,
itemClick -> {
try {
Game game = factory.manufacture(config != null ? config.getAll() : null);
Room.getRoom(itemClick.getPlayer()).moveMembersToGame(game);
game.load();
} catch (Exception e) {
e.printStackTrace();
}
},
true
);
for(int i = 9; i <= 17; i++) {
setDummyItem(Material.BLACK_STAINED_GLASS_PANE, i);
}
if(config == null) {
setDummyItem(
ItemStack.builder(Material.BARRIER).displayName(Component.text("Keine Optionen")).lore(TextUtil.autoWrap("Für dieses Spiel sind keine Einstellungen verfügbar!")).build(),
31
);
return;
}
int pos = 18;
for(Option<?> item : config.getAll()) {
map.put(pos, item);
setDummyItem(
item.getCurrent(),
pos++
);
}
}
@Override
protected void onClick(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult) {
inventoryConditionResult.setCancel(true);
if(!map.containsKey(slot)) return;
Option item = map.get(slot);
setDummyItem(
item.getNext(),
slot
);
update();
}
}

View File

@ -0,0 +1,34 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.config;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import net.kyori.adventure.text.Component;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public interface GameFactory {
Component name();
ConfigManager configuration();
default Material symbol() {
return Material.GRASS_BLOCK;
};
default Component description() {
return Component.text("- Keine Beschreibung -");
};
Minigame manufacture(Map<String, Option<?>> configuration);
default Minigame manufacture(List<Option<?>> configuration) {
if(configuration == null) return manufacture();
Map<String, Option<?>> cnf = new HashMap<>();
configuration.forEach(option -> cnf.put(option.getId(), option));
return manufacture(cnf);
}
default Minigame manufacture() {
if(this.configuration() == null) return manufacture(List.of());
return manufacture(this.configuration().getAll());
}
}

View File

@ -0,0 +1,51 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.config;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import java.util.List;
public abstract class Option<T> {
private final Material item;
private final String name;
private final String id;
protected T currentValue;
private final List<T> options;
private int pointer = 0;
public Option(String id, Material item, String name, List<T> options) {
this.id = id;
this.item = item;
this.name = name;
this.options = options;
currentValue = options.get(0);
}
public ItemStack getNext() {
if(++pointer >= options.size()) pointer = 0;
currentValue = options.get(pointer);
return getCurrent();
}
public ItemStack getCurrent() {
int amount = Integer.parseInt(options.get(pointer).toString());
return ItemStack.builder(item)
.displayName(Component.text(name).append(Component.text(" - ")).append(Component.text(amount)))
.build();
}
public int getAsInt() {
return Integer.parseInt(getAsString());
}
public String getAsString() {
return currentValue.toString();
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.config.options;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.Option;
import net.minestom.server.item.Material;
import java.util.List;
public class BoolOption extends Option<Boolean> {
public BoolOption(String id, Material item, String name) {
super(id, item, name, List.of(true, false));
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.config.options;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.Option;
import net.minestom.server.item.Material;
import java.util.List;
public class NumericOption extends Option<Integer> {
public NumericOption(String id, Material item, String name, Integer... options) {
super(id, item, name, List.of(options));
}
}

View File

@ -0,0 +1,57 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.deathcube;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularTerrainGenerator;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
class Deathcube extends Minigame {
int radius, height, percentage;
public Deathcube(int radius, int height, int percentage) {
super(Dimension.THE_END.DIMENSION, "Deathcube");
this.radius = radius;
this.height = height;
this.percentage = percentage;
this.setGenerator(new CircularTerrainGenerator(40, true));
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) continue;
for (int y = 49; y < height; y++) {
if(super.rnd.nextInt(1, 100) <= percentage) {
batch.setBlock(x, y, z, BlockPallet.WOOD.rnd());
}
}
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
super.onPlayerMove(playerMoveEvent);
if(isBeforeBeginning) if(playerMoveEvent.getNewPosition().y() > 51.5) playerMoveEvent.setCancelled(true);
if(playerMoveEvent.getNewPosition().y() > 100) getScore().addResult(playerMoveEvent.getPlayer());
}
@Override
public Pos getSpawn() {
return new Pos(0, 50, 30);
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.deathcube;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.Option;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.options.NumericOption;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.Material;
import java.util.Map;
public class DeathcubeFactory implements GameFactory {
@Override
public Component name() {
return Component.text("Deathcube");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, "Radius", 10, 30, 50, 100))
.addOption(new NumericOption("height", Material.SCAFFOLDING, "Height", 50, 100, 150, 200))
.addOption(new NumericOption("percentage", Material.COBWEB, "Percent of blocks", 5, 7, 9, 11, 13));
}
@Override
public Minigame manufacture(Map<String, Option<?>> configuration) {
return new Deathcube(configuration.get("radius").getAsInt(), configuration.get("height").getAsInt(), configuration.get("percentage").getAsInt());
}
@Override
public Material symbol() {
return Material.OAK_FENCE;
}
}

View File

@ -0,0 +1,130 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.minerun;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.Intersect;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.SquareTerrainGenerator;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
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.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
class Minerun extends Minigame {
private int minePercentage = 50;
private int width = 100;
private int length = 50;
private final int backRun = -5;
private final int preRun = 5;
private final int afterMines = 2;
private final int afterFinishLine = 10;
public Minerun(int width, int length, int minePercentage) {
super(Dimension.THE_END.DIMENSION, "Minerun");
setGenerator(new SquareTerrainGenerator(width, length + preRun + afterFinishLine, true));
this.width = width;
this.length = length;
this.minePercentage = minePercentage;
System.out.println(width + " " + length + " " + minePercentage);
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
int spawnToFinishLine = preRun + length + afterMines;
int spawnToEnd = spawnToFinishLine + afterFinishLine;
Random random = new Random();
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = 0; x <= width; x++) {
for(int z = preRun; z <= length + preRun; z++) {
if (random.nextInt(0, 100) < minePercentage) {
batch.setBlock(x, 50, z, BlockPallet.PRESSURE_PLATES.rnd());
}
}
}
Map<String, String> properties = new HashMap<>() {
{
put("west", "true");
put("east", "true");
}
};
for(int x = 0; x <= width; x++) {
batch.setBlock(x, 49, spawnToFinishLine, Block.GOLD_BLOCK);
batch.setBlock(x, 49, preRun, Block.GOLD_BLOCK);
batch.setBlock(x, 50, preRun, Block.OAK_FENCE.withProperties(properties));
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {
callback.complete(null);
});
}
@Override
protected void onStart() {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = 0; x <= width; x++) {
batch.setBlock(x, 50, preRun, Block.AIR);
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {
playSound(Sound.sound(SoundEvent.BLOCK_WOOD_BREAK, Sound.Source.BLOCK, 1f, 1f));
});
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player p = playerMoveEvent.getPlayer();
Pos middle = playerMoveEvent.getNewPosition();
if(middle.x() < 0 || middle.x() > width) { //player cannot go sidewards
playerMoveEvent.setCancelled(true);
new ActionBarMessage().appendStatic(Component.text("Please stay in line!", NamedTextColor.RED)).send(p);
}
if(!isRunning && middle.z() > preRun+0.5) { //player cannot go forward before game start
playerMoveEvent.setCancelled(true);
}
if(getScore().hasResult(p) && middle.z() < preRun + length + afterMines) { // player cannot go back
playerMoveEvent.setCancelled(true);
new ActionBarMessage().appendStatic(Component.text("You cannot go back on the Field!", NamedTextColor.RED)).send(p);
return;
}
if(Intersect.withPressurePlate(this, BlockPallet.PRESSURE_PLATES, middle)) { //Player died
p.setPose(Entity.Pose.DYING);
p.teleport(new Pos(p.getPosition().x(), getSpawn().y(), getSpawn().z()));
p.playSound(Sound.sound(SoundEvent.ENTITY_GENERIC_EXPLODE, Sound.Source.PLAYER, 1f, 1f));
}
if(middle.z() > preRun + length + afterMines) { // Player finished
getScore().addResult(p);
}
}
@Override
public Pos getSpawn() {
return new Pos(width/2, 50, 3);
}
}

View File

@ -0,0 +1,42 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.minerun;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.Option;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.options.NumericOption;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.Material;
import java.util.Map;
public class MinerunFactory implements GameFactory {
@Override
public Component name() {
return Component.text("Deathcube");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("width", Material.OAK_FENCE, "Width", 10, 30, 50, 100))
.addOption(new NumericOption("length", Material.ZOMBIE_HEAD, "Length", 50, 100, 150, 200))
.addOption(new NumericOption("percentage", Material.LIGHT_WEIGHTED_PRESSURE_PLATE, "Percent of mines", 30, 40, 50, 60, 70));
}
@Override
public Minigame manufacture(Map<String, Option<?>> configuration) {
System.out.println("Manufacture" + configuration.get("width").getAsInt());
return new Minerun(configuration.get("width").getAsInt(), configuration.get("length").getAsInt(), configuration.get("percentage").getAsInt());
}
@Override
public Material symbol() {
return Material.LIGHT_WEIGHTED_PRESSURE_PLATE;
}
@Override
public Component description() {
return Component.text("Weiche druckplatten aus");
}
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.stickfight;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.Option;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.Material;
import java.util.Map;
public class StickFightFactory implements GameFactory {
@Override
public Component name() {
return Component.text("Stickfight");
}
@Override
public ConfigManager configuration() {
return null;
}
@Override
public Minigame manufacture(Map<String, Option<?>> configuration) {
return new Stickfight();
}
@Override
public Material symbol() {
return Material.STICK;
}
}

View File

@ -0,0 +1,71 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.stickfight;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularTerrainGenerator;
import eu.mhsl.minenet.minigames.world.generator.terrain.SquareTerrainGenerator;
import io.github.bloepiloepi.pvp.PvpExtension;
import io.github.bloepiloepi.pvp.config.*;
import io.github.bloepiloepi.pvp.events.FinalAttackEvent;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.event.trait.InstanceEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class Stickfight extends Minigame {
public Stickfight() {
super(Dimension.OVERWORLD.DIMENSION, "Stickfight");
eventNode().addChild(
PvPConfig.emptyBuilder()
.damage(DamageConfig.legacyBuilder().fallDamage(false))
.attack(AttackConfig.legacyBuilder().attackCooldown(true))
.build().createNode()
);
eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
finalAttackEvent.setBaseDamage(0);
((Player) finalAttackEvent.getTarget()).setHealth(20);
});
setGenerator(new CircularTerrainGenerator(20, false));
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int z = -10; z <= 10; z++) {
batch.setBlock(0, 50, z, Block.SANDSTONE);
}
batch.setBlock(0, 50, 0, Block.GOLD_BLOCK);
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(isBeforeBeginning) playerMoveEvent.setCancelled(true);
if(playerMoveEvent.getNewPosition().y() < 40) {
playerMoveEvent.getPlayer().teleport(new Pos(0, 51, 0));
}
}
@Override
protected void onJoin(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 51, 0.5);
}
}

View File

@ -0,0 +1,29 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.trafficlightrace;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import java.util.Random;
enum LightPhase {
RED(Material.RED_WOOL, 1000, 5000),
YELLOW(Material.YELLOW_WOOL, 1000, 2000),
GREEN(Material.GREEN_WOOL, 1000, 5000);
public final ItemStack item;
private final int minDuration;
private final int maxDuration;
private static final Random rnd = new Random();
LightPhase(Material material, int minDuration, int maxDuration) {
this.item = ItemStack.of(material);
this.minDuration = minDuration;
this.maxDuration = maxDuration;
}
public TaskSchedule taskScheduleRandomDuration() {
return TaskSchedule.millis(rnd.nextLong(minDuration, maxDuration));
}
}

View File

@ -0,0 +1,69 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.trafficlightrace;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import net.minestom.server.coordinate.Vec;
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.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
class TrafficLightRace extends Minigame {
private LightPhase phase = LightPhase.RED;
private int phaseCounter = 1;
public TrafficLightRace() {
super(Dimension.THE_END.DIMENSION, "Ampelrennen");
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int x = -10; x <= 10; x++) {
for (int z = 5; z <= 100; z++) {
batch.setBlock(x, 5, z, super.rnd.nextInt(0, 100) > 90 ? Block.SOUL_SAND : Block.BLACK_CONCRETE_POWDER);
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onStart() {
scheduler().submitTask(() -> {
if(!super.isRunning) return TaskSchedule.stop();
phaseCounter++;
if(phaseCounter >= 4) phaseCounter = 0;
if(phaseCounter == 0) phase = LightPhase.RED;
if(phaseCounter == 1 || phaseCounter == 3) phase = LightPhase.YELLOW;
if(phaseCounter == 2) phase = LightPhase.GREEN;
getPlayers().forEach(player -> {
for(int i = 0; i < 9; i++) {
player.getInventory().setItemStack(i, phase.item);
}
});
return phase.taskScheduleRandomDuration();
}, ExecutionType.SYNC);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
super.onPlayerMove(playerMoveEvent);
if(playerMoveEvent.getNewPosition().z() > 110) stop();
if(phase.equals(LightPhase.RED) && playerMoveEvent.getNewPosition().z() > playerMoveEvent.getPlayer().getPosition().z()) {
playerMoveEvent.getPlayer().setVelocity(new Vec(0, 5, -10));
}
}
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.minenet.minigames.instance.game.minigame.types.trafficlightrace;
import eu.mhsl.minenet.minigames.instance.game.minigame.Minigame;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.Option;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.Material;
import java.util.Map;
public class TrafficLightRaceFactory implements GameFactory {
@Override
public Component name() {
return Component.text("TrafficLightRace");
}
@Override
public ConfigManager configuration() {
return null;
}
@Override
public Minigame manufacture(Map<String, Option<?>> configuration) {
return new TrafficLightRace();
}
@Override
public Material symbol() {
return Material.YELLOW_WOOL;
}
}

View File

@ -0,0 +1,46 @@
package eu.mhsl.minenet.minigames.instance.hub;
import eu.mhsl.minenet.minigames.Resource;
import eu.mhsl.minenet.minigames.instance.hub.entity.RoomSelector;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.Dimension;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.player.*;
import net.minestom.server.instance.AnvilLoader;
import net.minestom.server.instance.InstanceContainer;
import java.nio.file.Path;
import java.util.UUID;
public class HubInstance extends InstanceContainer implements Spawnable {
public static final HubInstance INSTANCE = new HubInstance();
static {
MinecraftServer.getInstanceManager().registerInstance(INSTANCE);
INSTANCE.setChunkLoader(new AnvilLoader(Resource.HUB_MAP.getPath()));
INSTANCE.eventNode()
.addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel)
.addListener(PlayerBlockPlaceEvent.class, CommonEventHandles::cancel)
.addListener(PlayerBlockInteractEvent.class, CommonEventHandles::cancel);
new RoomSelector().setInstance(INSTANCE, new Pos(0.5, 11, 4.5));
}
private HubInstance() {
super(UUID.randomUUID(), Dimension.THE_END.DIMENSION);
setChunkLoader(new AnvilLoader(Path.of("maps/hub")));
setTime(18000);
setTimeRate(0);
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 11, 0.5);
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.instance.hub.entity;
import eu.mhsl.minenet.minigames.instance.hub.inventory.HubInventory;
import eu.mhsl.minenet.minigames.shared.entity.InteractableEntity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.villager.AbstractVillagerMeta;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import org.jetbrains.annotations.NotNull;
public class RoomSelector extends InteractableEntity {
final AbstractVillagerMeta abstractVillagerMeta;
public RoomSelector() {
super(EntityType.VILLAGER);
abstractVillagerMeta = (AbstractVillagerMeta) this.getEntityMeta();
}
@Override
public void onSpawn(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
@Override
public void onAttack(@NotNull EntityAttackEvent entityAttackEvent) {
super.onAttack(entityAttackEvent);
abstractVillagerMeta.setHeadShakeTimer(20);
}
@Override
public void onInteract(@NotNull PlayerEntityInteractEvent playerEntityInteractEvent) {
playerEntityInteractEvent.getPlayer().openInventory(new HubInventory());
}
}

View File

@ -0,0 +1,37 @@
package eu.mhsl.minenet.minigames.instance.hub.inventory;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import net.kyori.adventure.text.Component;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemHideFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class HubInventory extends InteractableInventory {
public HubInventory() {
super(InventoryType.CHEST_3_ROW, Component.text("MineNet Servernetzwerk"));
setClickableItem(
ItemStack
.builder(Material.WRITABLE_BOOK)
.displayName(Component.text("Create own room"))
.lore(Component.text("Create new empty room"))
.meta(metaBuilder -> metaBuilder.hideFlag(ItemHideFlag.HIDE_ATTRIBUTES))
.build(),
12,
itemClick -> Room.createRoom(itemClick.getPlayer()),
true
);
setClickableItem(
ItemStack
.builder(Material.KNOWLEDGE_BOOK)
.displayName(Component.text("Browse room"))
.lore(Component.text("Browse existing rooms"))
.build(),
14,
itemClick -> itemClick.getPlayer().openInventory(new JoinInventory())
);
}
}

View File

@ -0,0 +1,70 @@
package eu.mhsl.minenet.minigames.instance.hub.inventory;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import eu.mhsl.minenet.minigames.instance.hub.HubInstance;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerPacketEvent;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.condition.InventoryConditionResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.metadata.PlayerHeadMeta;
import net.minestom.server.network.packet.client.play.ClientNameItemPacket;
import java.util.Locale;
public class JoinInventory extends InteractableInventory {
private String typedText = "";
private final String prefix = "name:";
public JoinInventory() {
super(InventoryType.ANVIL, Component.text("Enter username"));
setClickableItem(
ItemStack.builder(Material.PLAYER_HEAD)
.displayName(Component.text(prefix))
.meta(PlayerHeadMeta.class, builder -> {
})
.build(),
0,
itemClick -> {}
);
HubInstance.INSTANCE.eventNode().addListener(PlayerPacketEvent.class, event -> {
if (event.getPacket() instanceof ClientNameItemPacket packet) {
typedText = packet.itemName();
}
});
}
@Override
protected void onClick(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult) {
if(slot != 2) return;
inventoryConditionResult.setCancel(true);
player.closeInventory();
typedText = formatInput(typedText);
Room target = Room.getRoom(MinecraftServer.getConnectionManager().findPlayer(typedText));
if(target != null)
Room.setRoom(player, target);
else
new ChatMessage(Icon.ERROR).appendStatic("The room").quote(typedText).appendStatic("could not be found!").send(player);
}
private String formatInput(String raw) {
if(raw.startsWith(prefix)) {
raw = raw.split(prefix)[1];
}
return raw.toLowerCase(Locale.ROOT).trim();
}
}

View File

@ -0,0 +1,123 @@
package eu.mhsl.minenet.minigames.instance.room;
import eu.mhsl.minenet.minigames.Resource;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.util.MoveInstance;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.hub.HubInstance;
import eu.mhsl.minenet.minigames.instance.room.entity.GameSelector;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.instance.AnvilLoader;
import net.minestom.server.instance.InstanceContainer;
import java.util.*;
import java.util.stream.Collectors;
public class Room extends InstanceContainer implements Spawnable {
private static final Map<Player, Room> rooms = new WeakHashMap<>();
public static Room createRoom(Player owner) {
System.out.println("Room created by " + owner.getUsername());
setRoom(owner, new Room(owner));
return getRoom(owner);
}
public static void deleteRoom(Room room) {
System.out.println("Room deleted");
rooms.values().removeAll(Collections.singleton(room)); // remove(room) would only remove the first one
room.getAllMembers().forEach(player -> MoveInstance.move(player, HubInstance.INSTANCE));
MoveInstance.forceCloseInstance(room);
}
public static Room getRoom(Player p) {
return rooms.get(p);
}
public static void setOwnRoom(Player p) {
setRoom(p, getRoom(p));
}
public static void setRoom(Player p, Room room) {
p.clearEffects();
p.clearTitle();
p.getInventory().clear();
rooms.put(p, room);
MoveInstance.move(p, room);
}
public static void unsetRoom(Player p) {
rooms.remove(p);
}
public static Set<Room> getAllRooms() {
return new HashSet<>(rooms.values());
}
private Player owner;
private Room(Player owner) {
super(UUID.randomUUID(), Dimension.THE_END.DIMENSION);
MinecraftServer.getInstanceManager().registerInstance(this);
setChunkLoader(new AnvilLoader(Resource.LOBBY_MAP.getPath()));
eventNode().addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel);
setOwner(owner);
new GameSelector().setInstance(this, new Pos(0.5, 16, 9.5));
}
public Player getOwner() {
return owner;
}
private void setOwner(Player newOwner) {
this.owner = newOwner;
this.owner.eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> {
System.out.println("Room Leader left room");
Player p = playerDisconnectEvent.getPlayer();
if(p != this.owner) return; // return if the current handling player is really the current owner
getAllMembers().stream()
.filter(player -> player != p) // exclude the current leaving owner
.findFirst() // find first user meeting the requirement
.ifPresentOrElse(
this::setOwner, // set the new owner
() -> Room.deleteRoom(Room.getRoom(p)) // or else delete the room (no players in the room)
);
Room.unsetRoom(p); // remove the player itself from the room
new ChatMessage(Icon.ERROR).appendStatic("The room leader left!").send(getAllMembers());
new ChatMessage(Icon.SCIENCE).appendStatic(this.owner.getUsername()).appendStatic(" is the new Leader!").send(getAllMembers().stream().filter(player -> player != this.owner).collect(Collectors.toSet()));
new ChatMessage(Icon.SUCCESS).appendStatic("You are now the leader.").send(this.owner);
});
}
public void moveMembersToGame(Game game) {
this.getAllMembers().forEach(player -> {
MoveInstance.move(player, game);
});
}
public Set<Player> getAllMembers() {
return rooms.keySet().stream()
.filter(player -> getRoom(player) == this)
.collect(Collectors.toSet());
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 16, 0.5);
}
}

View File

@ -0,0 +1,49 @@
package eu.mhsl.minenet.minigames.instance.room.entity;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.instance.room.inventory.MinigameTypeSelectInventory;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.shared.entity.InteractableEntity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.villager.AbstractVillagerMeta;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import org.jetbrains.annotations.NotNull;
public class GameSelector extends InteractableEntity {
final AbstractVillagerMeta abstractVillagerMeta;
public GameSelector() {
super(EntityType.VILLAGER);
abstractVillagerMeta = (AbstractVillagerMeta) this.getEntityMeta();
}
@Override
public void onSpawn(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
@Override
public void onAttack(@NotNull EntityAttackEvent entityAttackEvent) {
super.onAttack(entityAttackEvent);
}
@Override
public void onInteract(@NotNull PlayerEntityInteractEvent playerEntityInteractEvent) {
Room room = (Room) instance;
if(playerEntityInteractEvent.getPlayer() != room.getOwner()) {
abstractVillagerMeta.setHeadShakeTimer(20);
new ChatMessage(Icon.ERROR).appendStatic("Only the room leader can start games!").indent(1).newLine()
.appendStatic("current leader: ").appendStatic(room.getOwner().getUsername())
.send(playerEntityInteractEvent.getPlayer());
return;
}
playerEntityInteractEvent.getPlayer().openInventory(new MinigameTypeSelectInventory());
}
}

View File

@ -0,0 +1,11 @@
package eu.mhsl.minenet.minigames.instance.room.inventory;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import net.kyori.adventure.text.Component;
import net.minestom.server.inventory.InventoryType;
public class BoardInventory extends InteractableInventory {
public BoardInventory() {
super(InventoryType.CHEST_3_ROW, Component.text("Brettspiele"));
}
}

View File

@ -0,0 +1,64 @@
package eu.mhsl.minenet.minigames.instance.room.inventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.MinigameType;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameConfigurationInventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameFactory;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import net.kyori.adventure.text.Component;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemHideFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class MinigameTypeSelectInventory extends InteractableInventory {
public MinigameTypeSelectInventory() {
super(InventoryType.CHEST_3_ROW, Component.text("MineNet Servernetzwerk"));
int i = 0;
for (MinigameType minigameType : MinigameType.values()) {
GameFactory gameFactory = minigameType.getFactory();
setClickableItem(
ItemStack.builder(gameFactory.symbol())
.displayName(gameFactory.name())
.lore(gameFactory.description())
.meta(metaBuilder -> metaBuilder.hideFlag(ItemHideFlag.HIDE_ATTRIBUTES))
.build(),
i,
itemClick -> itemClick.getPlayer().openInventory(new GameConfigurationInventory(gameFactory))
);
i++;
}
// setClickableItem(
// ItemStack
// .builder(Material.IRON_SWORD)
// .displayName(Component.text("PVP Spiele"))
// .lore(Component.text("Player versus Player"))
// .meta(metaBuilder -> metaBuilder.hideFlag(ItemHideFlag.HIDE_ATTRIBUTES))
// .build(),
// 11,
// itemClick -> itemClick.getPlayer().openInventory(new PvpInventory())
// );
//
// setClickableItem(
// ItemStack
// .builder(Material.ZOMBIE_HEAD)
// .displayName(Component.text("PVE Arenen"))
// .lore(Component.text("Player versus Entity"))
// .build(),
// 13,
// itemClick -> itemClick.getPlayer().openInventory(new PveInventory())
// );
//
// setClickableItem(
// ItemStack
// .builder(Material.BRICK_SLAB)
// .displayName(Component.text("Brettspiele"))
// .lore(Component.text("Bekannte Brettspieler aller Art"))
// .build(),
// 15,
// itemClick -> itemClick.getPlayer().openInventory(new BoardInventory())
// );
}
}

View File

@ -0,0 +1,41 @@
package eu.mhsl.minenet.minigames.instance.room.inventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameConfigurationInventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.minerun.MinerunFactory;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.trafficlightrace.TrafficLightRaceFactory;
import net.kyori.adventure.text.Component;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class PveInventory extends InteractableInventory {
public PveInventory() {
super(InventoryType.CHEST_3_ROW, Component.text("PVE"));
setClickableItem(
ItemStack.builder(Material.LIGHT_WEIGHTED_PRESSURE_PLATE).displayName(Component.text("Minerun")).lore(Component.text("Jump between ground mines to the finish")).build(),
0,
itemClick -> {
itemClick.getPlayer().openInventory(new GameConfigurationInventory(new MinerunFactory()));
}
);
setClickableItem(
ItemStack.builder(Material.YELLOW_WOOL).displayName(Component.text("Ampelrennen")).build(),
1,
itemClick -> {
itemClick.getPlayer().openInventory(new GameConfigurationInventory(new TrafficLightRaceFactory()));
}
);
setClickableItem(
ItemStack.builder(Material.RED_WOOL).displayName(Component.text("Deathcube")).build(),
2,
itemClick -> {
itemClick.getPlayer().openInventory(new GameConfigurationInventory(new DeathcubeFactory()));
}
);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.minenet.minigames.instance.room.inventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.config.GameConfigurationInventory;
import eu.mhsl.minenet.minigames.instance.game.minigame.types.stickfight.StickFightFactory;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import net.kyori.adventure.text.Component;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.condition.InventoryConditionResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class PvpInventory extends InteractableInventory {
public PvpInventory() {
super(InventoryType.CHEST_6_ROW, Component.text("PVP"));
setClickableItem(ItemStack.of(Material.GOLD_INGOT), 4, itemClick -> {
itemClick.getPlayer().openInventory(new GameConfigurationInventory(new StickFightFactory()));
});
}
@Override
protected void onClick(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult) {
super.onClick(player, slot, clickType, inventoryConditionResult);
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.minenet.minigames.lang;
public class DummyLang extends Lang {
public DummyLang() {
super("dummy");
}
@Override
public String getEntry(String key) {
return "translation:" + key;
}
}

View File

@ -0,0 +1,30 @@
package eu.mhsl.minenet.minigames.lang;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
public class Lang {
private String langId;
private Map<String, String> entries = new HashMap<>();
public Lang(String langId) {
this.langId = langId;
}
public void addEntry(String key, String value) {
entries.put(key, value);
}
public String getEntry(String key) {
return entries.computeIfAbsent(key, s -> {
Logger.getLogger("localisation").warning(s + " is not known by translation files!");
return new DummyLang().getEntry(s);
});
}
public String getLangId() {
return langId;
}
}

View File

@ -0,0 +1,105 @@
package eu.mhsl.minenet.minigames.lang;
import eu.mhsl.minenet.minigames.Resource;
import net.minestom.server.entity.Player;
import java.io.File;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
public class Languages {
private static Languages instance;
private boolean blockAccess = false;
private Map<String, Lang> languages = new HashMap<>();
public static Languages getInstance() {
if(instance == null) instance = new Languages();
return instance;
}
private Languages() {
readAll();
}
public Lang getLanguage(Player p) {
return getLanguage(p.getSettings().getLocale());
}
public Lang getLanguage(String mapId) {
return languages.computeIfAbsent(mapId, unused -> languages.computeIfAbsent("en_us", (key) -> new DummyLang()));
}
private void readAll() {
File locales = new File(Resource.LOCALES.getPath().toString());
File[] files = locales.listFiles(File::canRead);
if(files.length == 0) {
System.err.println("Failed to find any Language-files!");
return;
}
for(File locale : files) {
try {
System.out.print("reading translation " + locale.getName() + " ... ");
Map<Integer, Lang> langColumn = new HashMap<>();
String namespace = "";
boolean computedFileHeader = false;
for(String line : Files.readAllLines(locale.toPath())) {
line = line.replaceAll("[^\\p{L}\\s,#_+.:;]+", "");
String[] columns = line.split(";");
if(columns.length < 1) continue;
if(columns[0].equalsIgnoreCase("map")) {
// file header
computedFileHeader = true;
int index = -1;
for(String langId : columns) {
index++;
if(langId.endsWith("map")) continue;
languages.computeIfAbsent(langId, Lang::new);
Lang lang = languages.get(langId);
langColumn.put(index, lang);
languages.put(langId, lang);
}
} else if(columns[0].startsWith("ns:")) {
if(!computedFileHeader) throw new IllegalStateException("Cannot compute namespace data before file-header was red!");
namespace = columns[0].split(":")[1];
} else {
// file contents
if(!computedFileHeader) throw new IllegalStateException("Cannot compute translation data before file-header was red!");
int index = 0;
String mapId = "";
for(String translation : columns) {
if(index == 0) {
// store map name
mapId = translation;
} else {
// add map name and value
langColumn.get(index).addEntry(namespace + mapId, translation);
}
index++;
}
}
}
System.out.println("ok");
} catch (Exception e) {
System.out.println("fail: " + e.getMessage());
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,45 @@
package eu.mhsl.minenet.minigames.message;
import net.kyori.adventure.audience.Audience;
import net.minestom.server.MinecraftServer;
import net.minestom.server.timer.TaskSchedule;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class Countdown {
public Countdown(Class<? extends TranslatableMessage> messageType) {
}
public CompletableFuture<Void> countdown(Audience targets, int from, Consumer<CountdownModifier> modifier) {
CompletableFuture<Void> future = new CompletableFuture<>();
AtomicInteger count = new AtomicInteger(from);
MinecraftServer.getSchedulerManager().submitTask(() -> {
if(count.get() <= 0) {
future.complete(null);
return TaskSchedule.stop();
}
try {
CountdownModifier countdownModifier = new CountdownModifier(count.getAndDecrement());
modifier.accept(countdownModifier);
countdownModifier.message.send(targets);
System.out.println("SEND TITLE" + System.currentTimeMillis());
} catch (Exception e) {
throw new RuntimeException(e);
}
return TaskSchedule.seconds(1);
});
return future;
}
public static class CountdownModifier {
public TranslatableMessage message;
public final int timeLeft;
public CountdownModifier(int timeLeft) {
this.timeLeft = timeLeft;
}
}
}

View File

@ -0,0 +1,31 @@
package eu.mhsl.minenet.minigames.message;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
public enum Icon {
SCIENCE("\uD83E\uDDEA", NamedTextColor.LIGHT_PURPLE),
STAR("\u2606", NamedTextColor.GOLD),
CHAT("\u276F\u276F", NamedTextColor.WHITE),
SUCCESS("\u2714", NamedTextColor.GREEN),
ERROR("\u274C", NamedTextColor.RED);
private final String symbol;
private final NamedTextColor color;
Icon(String symbol, NamedTextColor color) {
this.symbol = symbol;
this.color = color;
}
public String getSymbol() {
return symbol;
}
public NamedTextColor getColor() {
return color;
}
public Component getComponent() {
return Component.text(getSymbol(), getColor());
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.minenet.minigames.message;
import net.kyori.adventure.audience.Audience;
import net.minestom.server.entity.Player;
import java.util.List;
import java.util.Set;
//TODO maybe async large batches
public interface Sendable {
void send(Player p);
default void send(Audience players) {
players.forEachAudience(audience -> {
this.send((Player) audience);
});
}
default void send(List<Player> players) {
players.forEach(this::send);
}
default void send(Set<Player> players) {
players.forEach(this::send);
}
}

View File

@ -0,0 +1,76 @@
package eu.mhsl.minenet.minigames.message;
import eu.mhsl.minenet.minigames.message.component.Translatable;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.Player;
import java.util.ArrayList;
import java.util.List;
public abstract class TranslatableMessage implements Sendable {
int indention = 0;
List<ComponentLike> chain = new ArrayList<>();
public TranslatableMessage() {
}
public TranslatableMessage appendStatic(Component component) {
chain.add(component);
return this;
}
public TranslatableMessage appendStatic(String text) {
chain.add(Component.text(text));
return this;
}
public TranslatableMessage appendTranslated(String mapId) {
chain.add(new TranslatedComponent(mapId));
return this;
}
public TranslatableMessage list(List<String> list) {
list.forEach(s -> {
chain.add(Component.text(s));
newLine();
});
return this;
}
public TranslatableMessage pipe() {
chain.add(Component.text(" | ", NamedTextColor.DARK_GRAY));
return this;
}
public TranslatableMessage quote(String text) {
chain.add(Component.text("'" + text + "'"));
return this;
}
public TranslatableMessage indent(int amount) {
this.indention += amount;
this.newLine();
return this;
}
public TranslatableMessage newLine() {
chain.add(Component.text(" ".repeat(indention) + "\n"));
return this;
}
public Component build(Player p) {
ComponentBuilder out = Component.text();
chain.forEach(componentLike -> {
if(componentLike instanceof Translatable t) t.compute(p);
out.append(componentLike);
});
return out.build();
}
}

View File

@ -0,0 +1,7 @@
package eu.mhsl.minenet.minigames.message.component;
import net.minestom.server.entity.Player;
public interface Translatable {
void compute(Player p);
}

View File

@ -0,0 +1,25 @@
package eu.mhsl.minenet.minigames.message.component;
import eu.mhsl.minenet.minigames.lang.Languages;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
public class TranslatedComponent implements ComponentLike, Translatable {
private String mapId;
private String result;
public TranslatedComponent(String mapId) {
this.mapId = mapId;
}
public void compute(Player p) {
result = Languages.getInstance().getLanguage(p).getEntry(mapId);
}
@Override
public @NotNull Component asComponent() {
return Component.text(result);
}
}

View File

@ -0,0 +1,11 @@
package eu.mhsl.minenet.minigames.message.type;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import net.minestom.server.entity.Player;
public class ActionBarMessage extends TranslatableMessage {
@Override
public void send(Player p) {
p.sendActionBar(build(p));
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.message.type;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import net.kyori.adventure.text.Component;
import net.minestom.server.entity.Player;
public class ChatMessage extends TranslatableMessage {
public ChatMessage(Icon icon) {
appendStatic(icon.getComponent());
pipe();
}
public void send(Player p) {
p.sendMessage(build(p));
}
}

View File

@ -0,0 +1,13 @@
package eu.mhsl.minenet.minigames.message.type;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import net.minestom.server.entity.Player;
import javax.management.RuntimeErrorException;
public class SubtitleMessage extends TranslatableMessage {
@Override
public void send(Player p) {
throw new RuntimeErrorException(new Error("Cannot send subtitle-only to player"));
}
}

View File

@ -0,0 +1,64 @@
package eu.mhsl.minenet.minigames.message.type;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.title.TitlePart;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.time.Duration;
import java.util.function.Consumer;
public class TitleMessage extends TranslatableMessage {
private Title.Times times;
private SubtitleMessage subtitle = new SubtitleMessage();
public TitleMessage() {
times = Title.Times.times(Duration.ZERO, Duration.ofSeconds(1), Duration.ZERO);
}
public TitleMessage(Duration stay) {
times = Title.Times.times(Duration.ZERO, stay, Duration.ZERO);
}
public TitleMessage(Duration stay, Duration fade) {
times = Title.Times.times(fade, stay, fade);
}
public void setTimes(Title.Times times) {
this.times = times;
}
public TranslatableMessage subtitle(Consumer<SubtitleMessage> callback) {
this.subtitle = new SubtitleMessage();
callback.accept(subtitle);
return this;
}
@Override
public void send(Player p) {
Audience.audience(p).showTitle(new Title() {
@Override
public @NotNull Component title() {
return build(p);
}
@Override
public @NotNull Component subtitle() {
return subtitle.build(p);
}
@Override
public @Nullable Times times() {
return times;
}
@Override
public <T> @UnknownNullability T part(@NotNull TitlePart<T> part) {
return null;
}
});
}
}

View File

@ -0,0 +1,90 @@
package eu.mhsl.minenet.minigames.score;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.instance.game.Game;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.Player;
import net.minestom.server.event.Event;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import java.time.Duration;
import java.util.*;
public class Score {
private boolean closed = false;
private final HashMap<Player, Integer> results = new HashMap<>();
protected final Game instance;
private Runnable callback;
public Score(Game instance) {
this.instance = instance;
this.callback = instance::stop;
instance.eventNode()
.addListener(AddEntityToInstanceEvent.class, this::checkGameEnd)
.addListener(RemoveEntityFromInstanceEvent.class, this::checkGameEnd)
.addListener(PlayerDisconnectEvent.class, this::checkGameEnd);
}
public void onClose(Runnable callback) {
this.callback = callback;
}
private void checkGameEnd(Event e) {
if(closed) return;
if(instance.getPlayers().size() < 1) return;
if(countResults() >= instance.getPlayers().size()) {
callback.run();
new ChatMessage(Icon.STAR)
.appendStatic("Ergebnisse:").indent(1)
.list(getMapFormatted())
.indent(-1).newLine()
.appendStatic("Vielen Dank für's Spielen!")
.send(instance.getPlayers());
closed = true;
}
}
public void addResult(Player p) {
if(closed) return;
if(results.containsKey(p)) return;
results.put(p, countResults()+1);
new TitleMessage(Duration.ofMillis(500), Duration.ofSeconds(1)).appendStatic(Component.text("Fertig", NamedTextColor.GREEN)).send(p);
checkGameEnd(null);
}
public boolean hasResult(Player p) {
return results.containsKey(p);
}
public HashMap<Player, Integer> getMap() {
return results;
}
public List<String> getMapFormatted() {
List<String> out = new ArrayList<>();
int counter = 0;
for (Map.Entry<Player, Integer> entry : getMap().entrySet()) {
out.add(entry.getValue() + ": " + entry.getKey().getUsername());
}
return out;
}
public int countResults() {
return results.size();
}
}

View File

@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.server.provider;
import eu.mhsl.minenet.minigames.util.UuidUtil;
import net.minestom.server.MinecraftServer;
import net.minestom.server.network.UuidProvider;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.mojang.MojangUtils;
import java.util.UUID;
import java.util.logging.Logger;
public class ByPlayerNameUuidProvider implements UuidProvider {
@Override
public UUID provide(PlayerConnection playerConnection, String username) {
try {
if(MinecraftServer.getConnectionManager().getPlayer(username) != null) throw new IllegalStateException();
String client = MojangUtils.fromUsername(username).get("id").getAsString();
return UuidUtil.unTrimm(client);
} catch (NullPointerException e) {
Logger.getGlobal().info("Player " + username + " is an known by Mojang! (Using random UUID)");
} catch (IllegalStateException e) {
Logger.getGlobal().info("Player with the username " + username + " is already online. (Using random UUID)");
playerConnection.disconnect();
}
return UUID.randomUUID();
}
}

View File

@ -0,0 +1,34 @@
package eu.mhsl.minenet.minigames.server.tasks;
import eu.mhsl.minenet.minigames.util.Monitoring;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.entity.Player;
import java.util.Collection;
public class TablistUpdateTask implements Runnable {
@Override
public void run() {
Collection<Player> players = MinecraftServer.getConnectionManager().getOnlinePlayers();
if (players.isEmpty()) return;
final Component header =
Component.newline()
.append(Component.text("MineNet Network", NamedTextColor.GOLD))
.append(Component.newline()).append(Component.text("Players: " + players.size()))
.append(Component.newline())
.append(Component.newline()).append(Component.text("RAM: " + Monitoring.getRamUsage() + " MB", NamedTextColor.GRAY))
.append(Component.newline()).append(Component.text("TICK: " + Monitoring.getTickMonitor().getTickTime() + "ms", NamedTextColor.GRAY))
.append(Component.newline());
final Component footer =
Component.newline()
.append(Component.text("mhsl.eu"))
.append(Component.newline());
Audiences.players().sendPlayerListHeaderAndFooter(header, footer);
}
}

View File

@ -0,0 +1,93 @@
package eu.mhsl.minenet.minigames.shared.entity;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.ai.target.ClosestEntityTarget;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
public class InteractableEntity extends EntityCreature {
/**
* Declares an Entity with direct callbacks on interaction
* @param entityType type of entity
*/
public InteractableEntity(@NotNull EntityType entityType) {
super(entityType);
eventNode()
.addListener(AddEntityToInstanceEvent.class, this::setInstanceEvent)
.addListener(AddEntityToInstanceEvent.class, this::onSpawn)
.addListener(RemoveEntityFromInstanceEvent.class, this::onDespawn)
.addListener(PlayerEntityInteractEvent.class, this::onInteract)
.addListener(EntityAttackEvent.class, this::onAttack);
}
private void setInstanceEvent(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
if(addEntityToInstanceEvent.getInstance() instanceof Spawnable instance) {
scheduleNextTick((unused) -> {
this.lookAt(instance.getSpawn()); //TODO only works someitmes?
});
}
addEntityToInstanceEvent.getInstance().eventNode()
.addListener(PlayerEntityInteractEvent.class, playerEntityInteractEvent -> {
if(playerEntityInteractEvent.getTarget() == this) onInteract(playerEntityInteractEvent);
})
.addListener(EntityAttackEvent.class, entityAttackEvent -> {
if(entityAttackEvent.getTarget() == this) onAttack(entityAttackEvent);
})
.addListener(PlayerMoveEvent.class, playerMoveEvent -> {
//TODO this is heavy in production
//maybe store the player and update to the closest Entity only periodic
scheduler().submitTask(() -> {
setTarget(new ClosestEntityTarget(this, 5, entity -> entity instanceof Player).findTarget());
if(getTarget() != null) lookAt(getTarget());
return TaskSchedule.stop();
}, ExecutionType.ASYNC);
});
}
/**
* Called when instance of entity is set
* @param addEntityToInstanceEvent
*/
protected void onSpawn(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
/**
* Called when instance of entity is unset
* @param removeEntityFromInstanceEvent
*/
protected void onDespawn(@NotNull RemoveEntityFromInstanceEvent removeEntityFromInstanceEvent) {
}
/**
* Called when a Player interacts with the entity
* @param playerEntityInteractEvent
*/
protected void onInteract(@NotNull PlayerEntityInteractEvent playerEntityInteractEvent) {
}
/**
* Called when a Player attacks the entity
* @param entityAttackEvent
*/
protected void onAttack(@NotNull EntityAttackEvent entityAttackEvent) {
}
}

View File

@ -0,0 +1,81 @@
package eu.mhsl.minenet.minigames.shared.inventory;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.condition.InventoryConditionResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
public class InteractableInventory extends Inventory {
/**
* Defines an Inventory with direct callbacks for ItemSlots
* @param inventoryType
* @param title
*/
protected InteractableInventory(@NotNull InventoryType inventoryType, @NotNull Component title) {
super(inventoryType, title);
addInventoryCondition(this::onClick);
}
/**
* Set Item with Callback
* @param item
* @param slot
* @param callback
*/
protected void setClickableItem(ItemStack item, int slot, Consumer<ItemClick> callback, boolean closeAfter) {
setItemStack(slot, item);
addInventoryCondition((player, clickedSlot, clickType, inventoryConditionResult) -> {
if(clickedSlot == slot) {
if(closeAfter) player.closeInventory();
callback.accept(new ItemClick(player, this, clickedSlot, item, clickType));
}
inventoryConditionResult.setCancel(true);
});
}
protected void setClickableItem(ItemStack item, int slot, Consumer<ItemClick> callback) {
this.setClickableItem(item, slot, callback, false);
}
/**
* Set Item without handler
* @param item
* @param slot
*/
protected void setDummyItem(ItemStack item, int slot) {
this.setClickableItem(
item,
slot,
itemClick -> {}
);
}
protected void setDummyItem(Material material, int slot) {
this.setDummyItem(
ItemStack.builder(material).displayName(Component.text("")).build(),
slot
);
}
/**
* You may want to Override this method to get more generic click events
* @param player
* @param slot
* @param clickType
* @param inventoryConditionResult
*/
protected void onClick(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult) {
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.minenet.minigames.shared.inventory;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.item.ItemStack;
public class ItemClick {
private final Player player;
private final InteractableInventory inventory;
private final int clickedSlot;
private final ItemStack item;
private final ClickType clickType;
/**
* Describes a click on an Item from an IntractableInventory
* @param player
* @param inventory
* @param clickedSlot
* @param item
* @param clickType
*/
public ItemClick(Player player, InteractableInventory inventory, int clickedSlot, ItemStack item, ClickType clickType) {
this.player = player;
this.inventory = inventory;
this.clickedSlot = clickedSlot;
this.item = item;
this.clickType = clickType;
}
public Player getPlayer() {
return player;
}
public Inventory getInventory() {
return inventory;
}
public int getClickedSlot() {
return clickedSlot;
}
public ItemStack getItem() {
return item;
}
public ClickType getClickType() {
return clickType;
}
}

View File

@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.skin;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.thread.Acquirable;
import net.minestom.server.thread.Acquired;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import java.util.HashMap;
import java.util.Map;
public class SkinCache {
private static final Map<String, PlayerSkin> skins = new HashMap<>();
public static PlayerSkin getSkin(Player p) {
return SkinCache.getSkin(p.getUsername());
}
public static PlayerSkin getSkin(String p) {
if(!skins.containsKey(p)) skins.put(p, PlayerSkin.fromUsername(p));
return skins.get(p);
}
public static void applySkin(Player p) {
MinecraftServer.getSchedulerManager().submitTask(() -> {
p.setSkin(SkinCache.getSkin(p.getUsername()));
return TaskSchedule.stop();
}, ExecutionType.ASYNC);
}
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.minenet.minigames.util;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.batch.ChunkBatch;
import net.minestom.server.utils.chunk.ChunkUtils;
import java.lang.reflect.Field;
public class BatchUtil {
public static long[] getAffectedChunks(AbsoluteBlockBatch batch) {
try {
Field field = batch.getClass().getDeclaredField("chunkBatchesMap");
field.setAccessible(true);
@SuppressWarnings("unchecked")
Long2ObjectMap<ChunkBatch> chunkBatchesMap = (Long2ObjectMap<ChunkBatch>) field.get(batch);
return chunkBatchesMap.keySet().toLongArray();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void loadAndApplyBatch(AbsoluteBlockBatch batch, InstanceContainer instance, Runnable onFinish) {
batch.awaitReady();
long[] affectedChunks = BatchUtil.getAffectedChunks(batch);
ChunkUtils.optionalLoadAll(instance, affectedChunks, null).thenRun(() -> batch.apply(instance, onFinish));
}
}

View File

@ -0,0 +1,15 @@
package eu.mhsl.minenet.minigames.util;
import net.kyori.adventure.text.format.NamedTextColor;
public class ColorUtil {
public static NamedTextColor scoreColor(int score) {
switch (score) {
case 1: return NamedTextColor.GOLD;
case 2: return NamedTextColor.GREEN;
case 3: return NamedTextColor.DARK_GREEN;
}
return NamedTextColor.GRAY;
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.util;
import net.minestom.server.event.trait.CancellableEvent;
public class CommonEventHandles {
/**
* Cancels the given Event
* @param event
*/
public static void cancel(CancellableEvent event) {
event.setCancelled(true);
}
public static void cancel(CancellableEvent event, boolean condition) {
event.setCancelled(condition);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.minenet.minigames.util;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
public class Intersect {
public static boolean withPressurePlate(Instance instance, BlockPallet target, Pos playerPosition) {
Pos[] corners = {
playerPosition.add(0.3-Position.PIXEL, 0, 0.3-Position.PIXEL),
playerPosition.add(0.3-Position.PIXEL, 0, -0.3+Position.PIXEL),
playerPosition.add(-0.3+Position.PIXEL, 0, 0.3-Position.PIXEL),
playerPosition.add(-0.3+Position.PIXEL, 0, -0.3+Position.PIXEL)
};
for(Pos coroner : corners) {
Block pressed = instance.getBlock(coroner);
if(target.contains(pressed) && playerPosition.y() < playerPosition.blockY() + Position.PIXEL)
return true;
}
return false;
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.minenet.minigames.util;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.server.ServerTickMonitorEvent;
import net.minestom.server.monitoring.TickMonitor;
import java.util.concurrent.atomic.AtomicReference;
public class Monitoring {
private static final Runtime runtime = Runtime.getRuntime();
private static final AtomicReference<TickMonitor> lastTick = new AtomicReference<>();
static {
MinecraftServer.getGlobalEventHandler().addListener(ServerTickMonitorEvent .class, event -> lastTick.set(event.getTickMonitor()));
}
public static long getRamUsage() {
return (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
}
public static TickMonitor getTickMonitor() {
return lastTick.get() != null ? lastTick.get() : new TickMonitor(0, 0);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.minenet.minigames.util;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.timer.TaskSchedule;
import java.util.Set;
public class MoveInstance {
public static void move(Set<Player> playerList, Spawnable destination) {
playerList.forEach(player -> move(player, destination));
}
public static void move(Entity p, Spawnable destination) {
p.setInstance((Instance) destination, destination.getSpawn());
}
public static void forceCloseInstance(InstanceContainer instance) {
instance.scheduler().scheduleTask(() -> {
instance.getPlayers().forEach(player -> player.kick("you exceeded the switch timeout while an instance got closed"));
MinecraftServer.getInstanceManager().unregisterInstance(instance);
}, TaskSchedule.seconds(10), TaskSchedule.stop());
}
}

View File

@ -0,0 +1,5 @@
package eu.mhsl.minenet.minigames.util;
public class Position {
public static final double PIXEL = 0.0625;
}

View File

@ -0,0 +1,11 @@
package eu.mhsl.minenet.minigames.util;
public class RangeMap {
public static double map(double oldValue, double oldMin, double oldMax, double newMin, double newMax) {
double out = (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin;
if(out > newMax) out = newMax;
if(out < newMin) out = newMin;
return out;
}
}

View File

@ -0,0 +1,61 @@
package eu.mhsl.minenet.minigames.util;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/**
* Class from the Minestom Arena example
*/
public final class ResourceUtils {
public static void extractResource(String source) throws URISyntaxException, IOException {
final URI uri = Objects.requireNonNull(ResourceUtils.class.getResource("/" + source)).toURI();
FileSystem fileSystem = null;
// Only create a new filesystem if it's a jar file
// (People can run this from their IDE too)
if (uri.toString().startsWith("jar:"))
fileSystem = FileSystems.newFileSystem(uri, Map.of("create", "true"));
try {
final Path jarPath = Paths.get(uri);
final Path target = Path.of("resources/" + source);
if (Files.exists(target)) {
try (Stream<Path> pathStream = Files.walk(target)) {
pathStream.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
Files.walkFileTree(jarPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
Path currentTarget = target.resolve(jarPath.relativize(dir).toString());
Files.createDirectories(currentTarget);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
final Path to = target.resolve(jarPath.relativize(file).toString());
Files.copy(file, to, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
} finally {
if (fileSystem != null)
fileSystem.close();
}
}
}

View File

@ -0,0 +1,5 @@
package eu.mhsl.minenet.minigames.util;
public abstract class Static {
public abstract void load(); //TODO REMOVE
}

View File

@ -0,0 +1,134 @@
package eu.mhsl.minenet.minigames.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextUtil {
public static Component autoWrap(String input, NamedTextColor color) {
System.out.println(wrap(input, 30, "\n", true, "-", " ")); //TODO not working
return Component.text(wrap(input, 30, "\n", true, "-", " "), color);
}
public static Component autoWrap(String input) {
return autoWrap(input, NamedTextColor.WHITE);
}
/**
* Wraps a source String into a series of lines having a maximum specified length. The source is
* wrapped at: spaces, horizontal tabs, system newLine characters, or a specified newLine character
* sequence. Existing newLine character sequences in the source string, whether they be the system
* newLine or the specified newLine, are honored. Existing whitespace (spaces and horizontal tabs)
* is preserved.
* <p>
* When <tt>wrapLongWords</tt> is true, words having a length greater than the specified
* <tt>lineLength</tt> will be broken, the specified <tt>longWordBreak</tt> terminator appended,
* and a new line initiated with the text of the specified <tt>longWordLinePrefix</tt> string. The
* position of the break will be unceremoniously chosen such that <tt>ineLength</tt> is honored.
* One use of <tt>longWordLinePrefix</tt> is to effect "hanging indents" by specifying a series of
* spaces for this parameter. This parameter can contain the lineFeed character(s). Although
* <tt>longWordLinePrefix</tt> can contain the horizontal tab character, the results are not
* guaranteed because no attempt is made to determine the quantity of character positions occupied by a
* horizontal tab.</p>
* <p>
* Example usage:
* <pre>
* wrap( " A very long word is Abracadabra in my book", 11, "\n", true, "-", " ");</pre>
* returns (note the effect of the single-character lineFeed):
* <pre>
* A very
* long word
* is Abraca-
* dabra in
* my book</pre>
* Whereas, the following:
* <pre>
* wrap( " A very long word is Abracadabra in my book", 11, null, true, null, " ");</pre>
* returns (due to the 2-character system linefeed):
* <pre>
* A very
* long
* word is A
* bracada
* bra in
* my book</pre></p>
*
* @param src the String to be word wrapped, may be null
* @param lineLength the maximum line length, including the length of <tt>newLineStr</tt> and, when
* applicable, <tt>longWordLinePrefix</tt>. If the value is insufficient to accommodate
* these two parameters + 1 character, it will be increased accordingly.
* @param newLineStr the string to insert for a new line, or <code>null</code> to use the value
* reported as the system line separator by the JVM
* @param wrapLongWords when <tt>false</tt>, words longer than <tt>wrapLength</t> will not be broken
* @param longWordBreak string with which to precede <tt>newLineStr</tt> on each line of a broken word,
* excepting the last line, or <tt>null</tt> if this feature is not to be used
* @param longWordLinePrefix string with which to prefix each line of a broken word, subsequent
* to the first line, or <tt>null</tt> if no prefix is to be used
* @return a line with newlines inserted, or <code>null</code> if <tt>src</tt> is null
*/
private static String wrap(String src, int lineLength, String newLineStr, boolean wrapLongWords, String longWordBreak, String longWordLinePrefix) {
// Trivial case
if ( src == null ) return null;
if ( newLineStr == null )
newLineStr = System.getProperty( "line.separator" );
if ( longWordBreak == null )
longWordBreak = "";
if ( longWordLinePrefix == null )
longWordLinePrefix = "";
// Adjust maximum line length to accommodate the newLine string
lineLength -= newLineStr.length();
if ( lineLength < 1 )
lineLength = 1;
// Guard for long word break or prefix that would create an infinite loop
if ( wrapLongWords && lineLength - longWordBreak.length() - longWordLinePrefix.length() < 1 )
lineLength += longWordBreak.length() + longWordLinePrefix.length();
int
remaining = lineLength,
breakLength = longWordBreak.length();
Matcher m = Pattern.compile( ".+?[ \\t]|.+?mis" + newLineStr + "singValue|.+?$" ).matcher( src );
StringBuilder cache = new StringBuilder();
while ( m.find() ) {
String word = m.group();
// Breakup long word
while ( wrapLongWords && word.length() > lineLength ) {
cache
.append(word, 0, remaining - breakLength)
.append( longWordBreak )
.append( newLineStr );
word = longWordLinePrefix + word.substring( remaining - breakLength );
remaining = lineLength;
} // if
// Linefeed if word exceeds remaining space
if ( word.length() > remaining ) {
cache
.append( newLineStr )
.append( word );
remaining = lineLength;
} // if
// Word fits in remaining space
else
cache.append( word );
remaining -= word.length();
} // while
return cache.toString();
} // wrap()
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.minenet.minigames.util;
import java.util.UUID;
public class UuidUtil {
public static UUID unTrimm(String trimmedUuid) {
StringBuilder builder = new StringBuilder(trimmedUuid.trim());
try {
builder.insert(20, "-");
builder.insert(16, "-");
builder.insert(12, "-");
builder.insert(8, "-");
} catch (StringIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
return UUID.fromString(builder.toString());
}
}

View File

@ -0,0 +1,31 @@
package eu.mhsl.minenet.minigames.world.generator;
import net.minestom.server.instance.block.Block;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public enum BlockPallet {
GROUND(new Block[] {Block.GRAVEL, Block.STONE, Block.DIRT, Block.GRASS_BLOCK, Block.COARSE_DIRT, Block.ROOTED_DIRT}),
WOOD(new Block[] {Block.ACACIA_WOOD, Block.BIRCH_WOOD, Block.JUNGLE_WOOD, Block.OAK_WOOD, Block.DARK_OAK_WOOD, Block.SPRUCE_WOOD}),
STONE(new Block[] {Block.CHISELED_STONE_BRICKS, Block.STONE_BRICKS, Block.POLISHED_ANDESITE, Block.POLISHED_BLACKSTONE, Block.POLISHED_DIORITE}),
PRESSURE_PLATES(new Block[] {Block.ACACIA_PRESSURE_PLATE, Block.BIRCH_PRESSURE_PLATE, Block.CRIMSON_PRESSURE_PLATE, Block.JUNGLE_PRESSURE_PLATE, Block.OAK_PRESSURE_PLATE, Block.DARK_OAK_PRESSURE_PLATE, Block.HEAVY_WEIGHTED_PRESSURE_PLATE, Block.HEAVY_WEIGHTED_PRESSURE_PLATE, Block.POLISHED_BLACKSTONE_PRESSURE_PLATE, Block.SPRUCE_PRESSURE_PLATE, Block.STONE_PRESSURE_PLATE, Block.WARPED_PRESSURE_PLATE});
final List<Block> list;
final Random rnd = new Random();
BlockPallet(Block[] blocks) {
this.list = new ArrayList<>(List.of(blocks));
}
public Block rnd() {
return list.get(rnd.nextInt(list.size()));
}
public boolean contains(Block b) {
return list.contains(b);
}
}

View File

@ -0,0 +1,15 @@
package eu.mhsl.minenet.minigames.world.generator;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import org.jetbrains.annotations.NotNull;
public class PlaneGenerator implements Generator {
@Override
public void generate(@NotNull GenerationUnit unit) {
unit.modifier()
.fillHeight(0, 5, Block.STONE);
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.minenet.minigames.world.generator.structures;
import net.minestom.server.instance.block.Block;
import java.util.Random;
public abstract class Structure {
protected final Random rnd = new Random();
public abstract void generateGame(Block.Setter setter);
}

View File

@ -0,0 +1,29 @@
package eu.mhsl.minenet.minigames.world.generator.structures.generatable;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.structures.Structure;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.block.Block;
public class PeakRock extends Structure {
private final Point position;
public PeakRock(Point position) {
this.position = position;
}
@Override
public void generateGame(Block.Setter setter) {
for (int stoneX = -4; stoneX < 4; stoneX++) {
for (int stoneZ = -4; stoneZ < 4; stoneZ++) {
double distanceToCenter = position.add(stoneX, 0, stoneZ).distance(position);
if(distanceToCenter > 3) continue;
for (int stoneY = 0; stoneY < 10-(int) distanceToCenter * rnd.nextDouble(2, 5); stoneY++) {
Point blockPos = position.add(stoneX, stoneY, stoneZ);
setter.setBlock(blockPos, BlockPallet.STONE.rnd());
}
}
}
}
}

View File

@ -0,0 +1,102 @@
package eu.mhsl.minenet.minigames.world.generator.terrain;
import de.articdive.jnoise.JNoise;
import eu.mhsl.minenet.minigames.util.RangeMap;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.structures.generatable.PeakRock;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
public class CircularTerrainGenerator implements Generator {
protected final Random rnd = new Random();
private final int size;
protected final Pos mapCenter = new Pos(0, 50, 0);
private boolean generatePlate;
public CircularTerrainGenerator(int size, boolean generatePlate) {
this.size = size;
this.generatePlate = generatePlate;
}
private final JNoise base = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.01)
.build();
private final JNoise batches = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.05)
.build();
private final JNoise peaks = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.1)
.build();
@Override
public void generate(@NotNull GenerationUnit unit) {
Point start = unit.absoluteStart().withY(0);
if(unit.absoluteStart().distance(new Pos(0, 0, 0)) > 500 + this.size) return;
for (int x = 0; x < unit.size().x(); x++) {
for (int z = 0; z < unit.size().z(); z++) {
Point bottom = start.add(x, 0, z);
double distance = bottom.distance(new Pos(0, 0, 0));
if(distance <= this.size && generatePlate) {
unit.modifier().fill(bottom, bottom.add(1, 50, 1), BlockPallet.GROUND.rnd());
continue;
}
unit.modifier().setBlock(bottom, Block.GRASS_BLOCK);
synchronized (base) {
double baseNoise = base.getNoise(bottom.x(), bottom.z());
double currentHeight = minTwo(RangeMap.map(distance, 0, 400, -(this.size / 5), 200)) + baseNoise * 8;
synchronized (batches) {
double elementNoise = batches.getNoise(bottom.x(), bottom.z());
unit.modifier().fill(
bottom,
bottom.add(1, 1, 1)
.withY(currentHeight),
elementNoise < 0.9 ? elementNoise > 0 ? Block.GRASS_BLOCK : Block.SOUL_SAND : Block.STONE
);
}
synchronized (peaks) {
double peakNoise = peaks.getNoise(bottom.x(), bottom.z());
if(peakNoise > 0.97 && bottom.distance(new Pos(0, 0, 0)) > (this.size + 20)) {
Point center = bottom.add(1, currentHeight-3, 1);
unit.fork(setter -> new PeakRock(center).generateGame(setter));
}
}
}
}
}
}
private static double minTwo(double input) {
if(input < 2) return 2;
return input;
}
}

View File

@ -0,0 +1,110 @@
package eu.mhsl.minenet.minigames.world.generator.terrain;
import de.articdive.jnoise.JNoise;
import eu.mhsl.minenet.minigames.util.RangeMap;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.structures.generatable.PeakRock;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
public class SquareTerrainGenerator implements Generator {
protected final Random rnd = new Random();
private int width;
private int length;
private boolean generatePlate;
protected final Pos mapStart = new Pos(0, 50, 0);
public SquareTerrainGenerator(int width, int length, boolean generatePlate) {
this.width = width;
this.length = length;
this.generatePlate = generatePlate;
}
private final JNoise base = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.01)
.build();
private final JNoise batches = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.05)
.build();
private final JNoise peaks = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.1)
.build();
@Override
public void generate(@NotNull GenerationUnit unit) {
Point start = unit.absoluteStart().withY(0);
// don't generate more than 500 blocks outwards
Point chunkStart = unit.absoluteStart();
if(chunkStart.z() > length + 500 || chunkStart.z() < -500) return;
if(chunkStart.x() < -500 || chunkStart.x() > width + 500) return;
for (int x = 0; x < unit.size().x(); x++) {
for (int z = 0; z < unit.size().z(); z++) {
Point bottom = start.add(x, 0, z);
double distance = bottom.distance(new Pos(0, 0, 0));
if(generatePlate) {
if(bottom.x() <= width && bottom.x() >= 0 && bottom.z() <= length && bottom.z() >= 0) {
unit.modifier().fill(bottom, bottom.add(1, 50, 1), BlockPallet.GROUND.rnd());
continue;
}
}
unit.modifier().setBlock(bottom, Block.GRASS_BLOCK);
synchronized (base) {
double baseNoise = base.getNoise(bottom.x(), bottom.z());
double currentHeight = minTwo(RangeMap.map(distance, 0, 400, -(this.width / 5), 200)) + baseNoise * 8;
synchronized (batches) {
double elementNoise = batches.getNoise(bottom.x(), bottom.z());
unit.modifier().fill(
bottom,
bottom.add(1, 1, 1)
.withY(currentHeight),
elementNoise < 0.9 ? elementNoise > 0 ? Block.GRASS_BLOCK : Block.SOUL_SAND : Block.STONE
);
}
synchronized (peaks) {
double peakNoise = peaks.getNoise(bottom.x(), bottom.z());
if(peakNoise > 0.97 && bottom.distance(new Pos(0, 0, 0)) > (this.width + 20)) {
Point center = bottom.add(1, currentHeight-3, 1);
unit.fork(setter -> new PeakRock(center).generateGame(setter));
}
}
}
}
}
}
private static double minTwo(double input) {
if(input < 2) return 2;
return input;
}
}