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;
}
}

View File

@ -0,0 +1,9 @@
map;en_us;de_de
localName;English;Deutsch
name;English;German
symbol;eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MTQ1Njg3N2Y1NGJmMWFjZTI1MWU0Y2VlNDBkYmE1OTdkMmNjNDAzNjJjYjhmNGVkNzExZTUwYjBiZTViMyJ9fX0=;eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWU3ODk5YjQ4MDY4NTg2OTdlMjgzZjA4NGQ5MTczZmU0ODc4ODY0NTM3NzQ2MjZiMjRiZDhjZmVjYzc3YjNmIn19fQ==
sample;The brown fox jumps over the white fence;Der braune Fuchs springt über den weißen Zaun
;;
ns:common#;;
select_language;Please select your prefered Language;Bitte wähle deine bevorzugte Sprache!
welcome;Welcome!;Willkommen!
1 map en_us de_de
2 localName English Deutsch
3 name English German
4 symbol eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MTQ1Njg3N2Y1NGJmMWFjZTI1MWU0Y2VlNDBkYmE1OTdkMmNjNDAzNjJjYjhmNGVkNzExZTUwYjBiZTViMyJ9fX0= eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWU3ODk5YjQ4MDY4NTg2OTdlMjgzZjA4NGQ5MTczZmU0ODc4ODY0NTM3NzQ2MjZiMjRiZDhjZmVjYzc3YjNmIn19fQ==
5 sample The brown fox jumps over the white fence Der braune Fuchs springt über den weißen Zaun
6
7 ns:common#
8 select_language Please select your prefered Language Bitte wähle deine bevorzugte Sprache!
9 welcome Welcome! Willkommen!

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More