Compare commits

...
5 Commits
Author SHA1 Message Date
jannis 3f672d1819 changed Craftattack 8 to Craftattack 9 2026-10-28 22:36:04 +01:00
jannisandClaude Opus 4.7 22c2cb3ab0 tweak consent dialog layout and clean up small issues
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-25 15:56:07 +01:00
jannis 78a01d9654 fix dialog flow and cleanup listeners 2026-10-24 23:00:51 +02:00
jannis d70b3b826e removed package minenet 2026-10-24 22:43:09 +02:00
jannis 4e6255483b added more sided dialog, added api and added PlayerHandler 2026-10-24 22:38:52 +02:00
15 changed files with 561 additions and 227 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ClaudeCodeEditorTabs">
<option name="activeSessionId" value="fe25343b-25e1-4de3-ac2a-8ba16e4c38b9" />
<option name="openSessionIds">
<list>
<option value="fe25343b-25e1-4de3-ac2a-8ba16e4c38b9" />
</list>
</option>
<option name="sessionPaths">
<map>
<entry key="fe25343b-25e1-4de3-ac2a-8ba16e4c38b9" value="/sessions/90f5ec6e-0a41-4a6f-9288-12703fc962dd" />
</map>
</option>
<option name="sessionTitles">
<map>
<entry key="fe25343b-25e1-4de3-ac2a-8ba16e4c38b9" value="/simplfy" />
</map>
</option>
</component>
</project>
+3 -2
View File
@@ -4,7 +4,7 @@ plugins {
id("com.gradleup.shadow") version "9.2.2" id("com.gradleup.shadow") version "9.2.2"
} }
group = "eu.mhsl.minenet" group = "eu.mhsl.craftattack"
version = "1.0-SNAPSHOT" version = "1.0-SNAPSHOT"
repositories { repositories {
@@ -22,12 +22,13 @@ dependencies {
implementation("org.slf4j:slf4j-simple:2.0.17") implementation("org.slf4j:slf4j-simple:2.0.17")
implementation("org.yaml:snakeyaml:2.4") implementation("org.yaml:snakeyaml:2.4")
implementation("org.spongepowered:configurate-yaml:4.2.0") implementation("org.spongepowered:configurate-yaml:4.2.0")
implementation("com.google.code.gson:gson:2.11.0")
} }
tasks.shadowJar { tasks.shadowJar {
archiveClassifier.set("") archiveClassifier.set("")
manifest { manifest {
attributes["Main-Class"] = attributes["Main-Class"] =
"eu.mhsl.minenet.loginServer.Main" "eu.mhsl.craftattack.loginServer.Main"
} }
} }
@@ -0,0 +1,100 @@
package eu.mhsl.craftattack.loginServer;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;
import java.util.UUID;
public final class Api {
private static final HttpClient client = HttpClient.newHttpClient();
private Api() {}
public static void setProfile(UUID uuid, String username, String name, String secondName, String birthday, String phoneNumber) {
JsonObject body = new JsonObject();
body.addProperty("uuid", uuid.toString());
body.addProperty("username", username);
body.addProperty("name", name);
body.addProperty("secondName", secondName);
body.addProperty("birthday", birthday);
body.addProperty("phoneNumber", phoneNumber);
send("POST", "players", "", body);
}
public static boolean isRegistered(UUID uuid) {
return find("players", "uuid", uuid.toString()).isPresent();
}
public static boolean isSecretValid(String username, String secret) {
return find("secrets", "player", username, "secret", secret).isPresent();
}
private static Optional<JsonObject> find(String collection, String... fieldValuePairs) {
StringBuilder filter = new StringBuilder();
for (int i = 0; i < fieldValuePairs.length; i += 2) {
if (i > 0) filter.append(" && ");
String escaped = fieldValuePairs[i + 1].replace("\\", "\\\\").replace("'", "\\'");
filter.append(fieldValuePairs[i]).append("='").append(escaped).append("'");
}
String query = "?perPage=1&filter=" + URLEncoder.encode(filter.toString(), StandardCharsets.UTF_8);
JsonObject response = send("GET", collection, query, null);
if (!response.has("items")) return Optional.empty();
JsonArray items = response.getAsJsonArray("items");
return items.isEmpty() ? Optional.empty() : Optional.of(items.get(0).getAsJsonObject());
}
private static JsonObject send(String method, String collection, String path, JsonObject body) {
JsonObject login = new JsonObject();
login.addProperty("identity", config("email"));
login.addProperty("password", config("password"));
String token = call("POST", "/api/collections/_superusers/auth-with-password", login, null)
.get("token").getAsString();
return call(method, "/api/collections/" + collection + "/records" + path, body, token);
}
private static JsonObject call(String method, String path, JsonObject body, String token) {
try {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(config("url") + path))
.timeout(Duration.ofSeconds(5))
.header("Content-Type", "application/json")
.method(method, body == null
? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(body.toString()));
if (token != null) builder.header("Authorization", token);
HttpResponse<String> response = client.send(builder.build(), HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) {
throw new IllegalStateException("API-Fehler " + response.statusCode() + ": " + response.body());
}
if (response.body().isBlank()) return new JsonObject();
return JsonParser.parseString(response.body()).getAsJsonObject();
} catch (IOException | InterruptedException e) {
throw new IllegalStateException("API nicht erreichbar", e);
}
}
private static String config(String key) {
return Main.globalConfig.node("api", key).getString("");
}
public static boolean removeSecret(String username, String secret) {
Optional<JsonObject> entry = find("secrets", "player", username, "secret", secret);
if (entry.isEmpty()) return false;
send("DELETE", "secrets", "/" + entry.get().get("id").getAsString(), null);
return true;
}
}
@@ -0,0 +1,250 @@
package eu.mhsl.craftattack.loginServer;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.dialog.*;
import net.minestom.server.entity.Player;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public final class Dialog {
private static final String namespace = "profile";
private static final Key nextKey = Key.key(namespace, "next");
private static final Key backKey = Key.key(namespace, "back");
private static final Key finishKey = Key.key(namespace, "finish");
private static final String privacyUrl = "https://mhsl.eu/id.html";
private static final String rulesUrl = "https://mhsl.eu/craftattack/rules";
private static final int rulesWidth = 300;
private static final String rulesPreamble = "Alle Teilnehmer müssen die Regeln lesen. Sie sorgen für einen reibungslosen, geordneten Ablauf von CraftAttack 9 und sind wörtlich zu nehmen. Sie gehen bewusst nicht tief ins Detail und umreißen manche Themen nur grob. Auf Wunsch der Spieler entscheiden die Administratoren und orientieren sich dabei an den Regeln. Für Vollständigkeit, Richtigkeit und Aktualität aller Angaben wird keine Gewähr übernommen. Ob und wie Regeln durchgesetzt werden, entscheiden die Administratoren, nachdem sie sich mit dem Geschädigten abgesprochen und eine zur Situation passende Maßnahme gewählt haben.";
private static final List<String> rules = List.of(
"1. Das Wichtigste ist ein respektvoller und toleranter Umgang miteinander. Spielspaß, Offenheit und Interaktion aller stehen im Vordergrund. Drohungen, Belästigungen und jedes andere respektlose Verhalten gegenüber Mitspielern sind streng verboten und werden hart bestraft.",
"2. Sexistische, diskriminierende, rassistische, pornographische oder illegale Inhalte sind überall untersagt, ob in Minecraft-Namen, Skins, Chat-Nachrichten oder Links. Den Chat mit Nachrichten jeder Art zuzuspammen ist ebenfalls verboten. Der bei der Anmeldung angegebene MC-Name soll bis zum Ende des Projekts nicht geändert werden. Zweitaccounts dürfen weder genutzt noch angemeldet werden.",
"3. Clientmodifikationen, die deutliche Vorteile gegenüber anderen Spielern bringen, sind nicht erlaubt. Wer nicht mit Vanilla-Minecraft spielt, muss seinen Client und seine Mods selbst darauf prüfen, ob sie entscheidende Vorteile verschaffen, und solche Mods entfernen. Einfache Minimaps (Draufsicht) und Cosmetics sind selbstverständlich erlaubt. Die Administratoren wollen, dass niemand übertreibt oder sich unfaire Vorteile verschafft, und setzen dabei auf die Selbstreflexion jedes Einzelnen. Im Zweifel sollst du dich an einen Administrator wenden, der dir genauere Auskunft geben kann.",
"4. Maschinen, Farmen und andere Bauten, die Lag erzeugen oder den Spielfluss stören könnten, dürfen weder gebaut noch betrieben werden. Im Zweifel ist eine Anfrage bei den Administratoren erwünscht. Farmen und Redstone-Schaltungen sollen nur aktiviert werden, wenn es nötig ist. Übergroße Villager-Baukomplexe dürfen nur in Absprache mit den Administratoren errichtet und betrieben werden. Farmen zu bauen gehört selbstverständlich zum Projekt und ist kein Problem, solange diese Bedingungen eingehalten werden.",
"5. Jeder Spieler darf überall Items verkaufen. Es ist aber sinnvoll und erwünscht, alle Shops gemeinsam im Shoppingdistrict anzusiedeln, um die Interaktion zu fördern. Ein Shop muss im ausgewiesenen Bereich stehen und über das dortige Wegenetz erreichbar sein. Der Shoppingdistrict ist ausschließlich für Shops gedacht. Mehrere Shops für dasselbe Item sind möglich und erwünscht. Diebstahl im Shoppingdistrict wird genauso bestraft wie Diebstahl im Allgemeinen. Private Bauten müssen einen angemessenen Abstand zum Shoppingdistrict einhalten.",
"6. Bestimmte Gebiete abzustecken ist grundsätzlich erlaubt, unangemessen große Grundstücke sind aber untersagt. Die maximale Größe wird im Einzelfall entschieden. Die Grenzen bereits abgesteckter Grundstücke dürfen nicht verändert werden. Bei Inaktivität, anderen Beschwerden oder Verstößen ist ein Administrator zu kontaktieren.",
"7. Ziel ist ein Umgang, bei dem für niemanden etwas Negatives entsteht. Andere zu bestehlen oder ohne Erlaubnis ihre Bauwerke zu verändern ist nicht gestattet. Es gibt dabei eine gewisse Toleranz und einen Spielraum, der aber nicht überschritten werden darf. Das gilt sowohl für Diebstahl als auch für Griefing, also das Zerstören fremder Bauten ohne Erlaubnis. Außerdem ist es verboten, andere ohne nachvollziehbaren Grund zu töten. Jede Situation muss einzeln bewertet werden, deshalb gibt es hier bewusst keine pauschale Regel. Klar ist aber, dass zwischen einem Töten aus Spaß mit geringen Folgen und wiederholtem oder dauerhaftem Töten mit schlimmeren Folgen unterschieden wird.",
"8. Die Administratoren bewerten einzelne Situationen, verhängen Strafen und treffen Entscheidungen. Ein internes Strikesystem sorgt dafür, dass alle Spieler gleich behandelt werden. Die Administratoren sprechen sich außerdem untereinander ab, damit alle Sichtweisen einfließen. Alle Entscheidungen müssen im Sinne des Projekts getroffen werden. Entscheidungen und Anweisungen der Administratoren ist stets Folge zu leisten, wenn sie als Administrator handeln. Im normalen Spielbetrieb sind sie ganz normale Mitspieler ohne spielentscheidende Sonderrechte. Es ist nicht ihre Aufgabe, überall nach dem Rechten zu sehen. Sie sind Ansprechpartner und übernehmen die Administratorenrolle erst, wenn ihnen ein Geschädigter ein Problem vorlegt, und handeln dann entsprechend. Grundsätzlich greifen die Administratoren nur ein, wenn die Spieler es fordern. Solange beide Parteien zufrieden sind, passiert nichts. Wenn sich zum Beispiel zwei Spieler bewusst ein PvP-Duell liefern, hat das logischerweise keine Konsequenzen.",
"9. Jeder Teilnehmer kann sich an den Support bzw. das Administratoren-Team wenden. Administratoren sind die Spieler mit Admin-Tag auf dem Server. Zwei von ihnen sind außerdem Administratoren der WhatsApp-Gruppe. Man erreicht sie direkt im Server-Chat oder auf dem TeamSpeak „mhsl.eu“. Zusätzlich kann man sie per WhatsApp anschreiben, etwa wenn gerade kein Administrator auf dem Server ist oder bei anderen Rückfragen. Bei Unzufriedenheit, Regelverstößen, Anregungen oder Fragen steht das Administratoren-Team allen Spielern jederzeit zur Verfügung.",
"10. Konflikte sollen grundsätzlich zuerst zwischen den Spielern geklärt werden, bevor ein Administrator kontaktiert wird. Jeder Regelverstoß hat unterschiedliche Folgen, von Ermahnungen über Tagesbanns bis zum permanenten Bann. Diese möglichen Konsequenzen müssen alle Teilnehmer akzeptieren."
);
private Dialog() {}
public static boolean isDialogKey(Key key) {
return key.namespace().equals(namespace);
}
public static void open(Player player, DialogState state) {
showPage(player, state, null);
}
public static boolean handleClick(Player player, DialogState state, Key key, CompoundBinaryTag payload) {
if (key.equals(backKey)) {
state.previousPage();
showPage(player, state, null);
return false;
}
saveCurrentPage(state, payload);
Component error = validateCurrentPage(player, state);
if (error != null) {
showPage(player, state, error);
return false;
}
if (key.equals(nextKey)) {
state.nextPage();
showPage(player, state, null);
return false;
}
return key.equals(finishKey);
}
private static void showPage(Player player, DialogState state, Component error) {
List<DialogBody> body = new ArrayList<>();
List<DialogInput> inputs = new ArrayList<>();
String title;
switch (state.currentPage()) {
case secret -> {
title = "Secret";
body.add(plain("Bitte gib das Secret ein, das du erhalten hast."));
inputs.add(text("secret", "Secret", state.secret, 50));
}
case data -> {
title = "Profildaten";
body.add(plain("Bitte fülle deine Profildaten aus."));
inputs.add(text("name", "Vorname", state.firstName, 50));
inputs.add(text("secondName", "Nachname", state.secondName, 50));
inputs.add(text("birthday", "Geburtstag (TT.MM.JJJJ)", state.birthday, 10));
inputs.add(text("phoneNumber", "Telefonnummer (optional)", state.phoneNumber, 30));
}
case consent -> {
title = "Einverständnis";
body.add(plain(Component.text("Serverregeln", NamedTextColor.GOLD, TextDecoration.BOLD), rulesWidth));
body.add(plain(Component.text(rulesPreamble, NamedTextColor.GRAY), rulesWidth));
for (String rule : rules) {
body.add(plain(Component.text(rule), rulesWidth));
}
body.add(plain(Component.text()
.append(link("Datenschutzerklärung", privacyUrl))
.append(Component.text(" | "))
.append(link("Vollständige Regeln", rulesUrl))
.build(), rulesWidth));
body.add(plain(Component.text(
"Mit den drei Haken erklärst du: " +
"1) Ich bin mit der Speicherung meiner in der Anmeldung angegebenen, persönlichen Daten einverstanden. " +
"2) Ich bin mit der Speicherung in Form von Logs aller meiner, beim Spielen anfallenden, " +
"persönlichen Daten durch den Server einverstanden. " +
"Die Log-Speicherung betrifft jede Interaktion im Spiel und zugehörige Daten, " +
"z.B. Chatnachrichten, welche vom Minecraft-Client an den Server übermittelt werden. " +
"3) Ich bin mit den Regeln einverstanden und achte sie.",
NamedTextColor.GRAY), rulesWidth));
inputs.add(checkbox("privacy",
"Ich stimme der Speicherung meiner\nAnmeldedaten zu. *",
state.privacyAccepted));
inputs.add(checkbox("logs",
"Ich stimme der Speicherung meiner\nSpiel-Logs zu. *",
state.logsAccepted));
inputs.add(checkbox("rules",
"Ich akzeptiere die Regeln\nund halte sie ein. *",
state.rulesAccepted));
}
default -> throw new IllegalStateException();
}
if (error != null) body.add(plain(error));
String fullTitle = "Schritt " + state.pageNumber() + "/" + state.pageCount() + " – " + title;
List<DialogActionButton> buttons = new ArrayList<>();
if (!state.isFirstPage()) buttons.add(button("← Zurück", backKey));
if (state.isLastPage()) buttons.add(button("Anmeldung abschließen", finishKey));
else buttons.add(button("Weiter →", nextKey));
player.showDialog(new net.minestom.server.dialog.Dialog.MultiAction(
new DialogMetadata(
Component.text(fullTitle),
null,
false,
false,
state.isLastPage() ? DialogAfterAction.CLOSE : DialogAfterAction.NONE,
body,
inputs
),
buttons,
null,
2
));
}
private static void saveCurrentPage(DialogState state, CompoundBinaryTag payload) {
switch (state.currentPage()) {
case secret -> state.secret = payload.getString("secret", "").trim();
case data -> {
state.firstName = payload.getString("name", "").trim();
state.secondName = payload.getString("secondName", "").trim();
state.birthday = payload.getString("birthday", "").trim();
state.phoneNumber = payload.getString("phoneNumber", "").trim();
}
case consent -> {
state.privacyAccepted = payload.getBoolean("privacy", false);
state.logsAccepted = payload.getBoolean("logs", false);
state.rulesAccepted = payload.getBoolean("rules", false);
}
}
}
private static Component validateCurrentPage(Player player, DialogState state) {
switch (state.currentPage()) {
case secret -> {
try {
if (!Api.isSecretValid(player.getUsername(), state.secret))
return error("Das Secret ist ungültig.");
} catch (IllegalStateException e) {
e.printStackTrace();
return error("Die Überprüfung ist gerade nicht möglich. Bitte versuche es später erneut.");
}
}
case data -> {
if (state.firstName.isBlank() || state.secondName.isBlank() || state.birthday.isBlank())
return error("Bitte fülle alle Pflichtfelder aus.");
if (!isValidBirthday(state.birthday))
return error("Bitte gib ein richtiges Geburtsdatum an.");
}
case consent -> {
if (!(state.privacyAccepted && state.logsAccepted && state.rulesAccepted))
return error("Du musst allen drei Punkten zustimmen, um fortzufahren.");
}
}
return null;
}
public static boolean isValidBirthday(String birthday) {
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("dd.MM.uuuu", Locale.GERMANY)
.withResolverStyle(ResolverStyle.STRICT);
try {
LocalDate date = LocalDate.parse(birthday, formatter);
return !date.isAfter(LocalDate.now());
} catch (DateTimeParseException e) {
return false;
}
}
private static DialogActionButton button(String label, Key key) {
return new DialogActionButton(
Component.text(label),
null,
150,
new DialogAction.DynamicCustom(key, CompoundBinaryTag.builder().build())
);
}
private static DialogInput text(String key, String label, String initial, int maxLength) {
return new DialogInput.Text(key, 300, Component.text(label), true, initial, maxLength, null);
}
private static DialogInput checkbox(String key, String label, boolean initial) {
return new DialogInput.Boolean(key, Component.text(label), initial, "true", "false");
}
private static DialogBody plain(String text) {
return plain(Component.text(text));
}
private static DialogBody plain(Component component) {
return plain(component, 300);
}
private static DialogBody plain(Component component, int width) {
return new DialogBody.PlainMessage(component, width);
}
private static Component error(String text) {
return Component.text(text, NamedTextColor.RED);
}
private static Component link(String text, String url) {
return Component.text(text, NamedTextColor.AQUA, TextDecoration.UNDERLINED)
.clickEvent(ClickEvent.openUrl(url));
}
}
@@ -0,0 +1,61 @@
package eu.mhsl.craftattack.loginServer;
import java.util.List;
public final class DialogState {
public enum Page { secret, data, consent }
private final List<Page> pages;
private int pageIndex = 0;
String secret = "";
String firstName = "";
String secondName = "";
String birthday = "";
String phoneNumber = "";
boolean privacyAccepted = false;
boolean logsAccepted = false;
boolean rulesAccepted = false;
private DialogState(List<Page> pages) {
this.pages = pages;
}
public static DialogState create() {
boolean isAfterStart = Main.globalConfig
.node("server", "is-after-start")
.getBoolean(false);
return new DialogState(isAfterStart
? List.of(Page.secret, Page.data, Page.consent)
: List.of(Page.data, Page.consent));
}
public Page currentPage() {
return pages.get(pageIndex);
}
public int pageNumber() {
return pageIndex + 1;
}
public int pageCount() {
return pages.size();
}
public boolean isFirstPage() {
return pageIndex == 0;
}
public boolean isLastPage() {
return pageIndex == pages.size() - 1;
}
public void nextPage() {
pageIndex = Math.min(pages.size() - 1, pageIndex + 1);
}
public void previousPage() {
pageIndex = Math.max(0, pageIndex - 1);
}
}
@@ -1,6 +1,6 @@
package eu.mhsl.minenet.loginServer; package eu.mhsl.craftattack.loginServer;
import eu.mhsl.minenet.loginServer.listeners.Listener; import eu.mhsl.craftattack.loginServer.listeners.Listener;
import net.minestom.server.Auth; import net.minestom.server.Auth;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.lan.OpenToLAN; import net.minestom.server.extras.lan.OpenToLAN;
@@ -0,0 +1,79 @@
package eu.mhsl.craftattack.loginServer;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.event.player.PlayerCustomClickEvent;
import net.minestom.server.instance.InstanceManager;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public final class PlayerHandler {
private static final Map<UUID, DialogState> states = new ConcurrentHashMap<>();
private PlayerHandler() {}
public static void join(AsyncPlayerConfigurationEvent event, InstanceManager instanceManager) {
Player player = event.getPlayer();
event.setSpawningInstance(instanceManager.createInstanceContainer());
player.setRespawnPoint(new Pos(0, 100, 0));
if (Api.isRegistered(player.getUuid())) {
player.kick("Du bist bereits auf Craftattack registriert, wir freuen uns auf dich!");
return;
}
DialogState state = states.computeIfAbsent(player.getUuid(), id -> DialogState.create());
MinecraftServer.getSchedulerManager()
.buildTask(() -> Dialog.open(player, state))
.delay(Duration.ofMillis(500))
.schedule();
}
public static void handleDialogClick(PlayerCustomClickEvent event) {
if (!Dialog.isDialogKey(event.getKey())) return;
Player player = event.getPlayer();
DialogState state = states.get(player.getUuid());
if (state == null) return;
if (!(event.getPayload() instanceof CompoundBinaryTag payload)) return;
boolean finished = Dialog.handleClick(player, state, event.getKey(), payload);
if (finished) complete(player, state);
}
private static void complete(Player player, DialogState state) {
states.remove(player.getUuid());
if (Api.isRegistered(player.getUuid())) {
player.kick("Du bist bereits auf Craftattack registriert, wir freuen uns auf dich!");
return;
}
player.sendMessage("Danke für deine Anmeldung.");
player.sendMessage("Du bist für das Projekt Craftattack 9 registriert!");
Api.setProfile(player.getUuid(), player.getUsername(), state.firstName, state.secondName, state.birthday, state.phoneNumber);
Api.removeSecret(player.getUsername(), state.secret);
MinecraftServer.getSchedulerManager()
.buildTask(() -> player.kick("Anmeldung erfolgreich, wir freuen uns auf dich!"))
.delay(Duration.ofMillis(1000))
.schedule();
System.out.println("Vorname: " + state.firstName);
System.out.println("Nachname: " + state.secondName);
System.out.println("Geburtstag: " + state.birthday);
System.out.println("Telefonnummer: " + state.phoneNumber);
System.out.println("Spielername: " + player.getUsername());
System.out.println("Spieler UUID: " + player.getUuid());
}
}
@@ -0,0 +1,13 @@
package eu.mhsl.craftattack.loginServer.listeners;
import eu.mhsl.craftattack.loginServer.PlayerHandler;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.player.PlayerCustomClickEvent;
public class DialogClickListener implements Listener {
@Override
public void register(EventNode<Event> node) {
node.addListener(PlayerCustomClickEvent.class, PlayerHandler::handleDialogClick);
}
}
@@ -1,4 +1,4 @@
package eu.mhsl.minenet.loginServer.listeners; package eu.mhsl.craftattack.loginServer.listeners;
import net.minestom.server.event.Event; import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode; import net.minestom.server.event.EventNode;
@@ -11,9 +11,9 @@ public interface Listener {
static void registerAll(EventNode<Event> node, InstanceManager instanceManager) { static void registerAll(EventNode<Event> node, InstanceManager instanceManager) {
List<Listener> listeners = List.of( List<Listener> listeners = List.of(
new onPlayerJoin(instanceManager), new PlayerJoinListener(instanceManager),
new onPlayerMove(), new PlayerMoveListener(),
new dialogOutput() new DialogClickListener()
); );
listeners.forEach(listener -> listener.register(node)); listeners.forEach(listener -> listener.register(node));
@@ -0,0 +1,19 @@
package eu.mhsl.craftattack.loginServer.listeners;
import eu.mhsl.craftattack.loginServer.PlayerHandler;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.instance.InstanceManager;
public class PlayerJoinListener implements Listener {
private final InstanceManager instanceManager;
public PlayerJoinListener(InstanceManager instanceManager) {
this.instanceManager = instanceManager;
}
@Override
public void register(EventNode<Event> node) {
node.addListener(AsyncPlayerConfigurationEvent.class, event -> PlayerHandler.join(event, instanceManager));
}
}
@@ -1,14 +1,12 @@
package eu.mhsl.minenet.loginServer.listeners; package eu.mhsl.craftattack.loginServer.listeners;
import net.minestom.server.event.Event; import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode; import net.minestom.server.event.EventNode;
import net.minestom.server.event.player.PlayerMoveEvent; import net.minestom.server.event.player.PlayerMoveEvent;
public class onPlayerMove implements Listener { public class PlayerMoveListener implements Listener {
@Override @Override
public void register(EventNode<Event> node) { public void register(EventNode<Event> node) {
node.addListener(PlayerMoveEvent.class, event -> { node.addListener(PlayerMoveEvent.class, event -> event.setCancelled(true));
event.setCancelled(true);
});
} }
} }
@@ -1,83 +0,0 @@
package eu.mhsl.minenet.loginServer;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.dialog.*;
import net.minestom.server.entity.Player;
import java.util.ArrayList;
import java.util.List;
public final class Dialog {
private static final Key submitKey = Key.key("profile", "submit");
public static void open(Player player) {
List<DialogInput> inputs = new ArrayList<>();
boolean isAfterStart = Main.globalConfig
.node("server", "is-after-start")
.getBoolean(false);
if (isAfterStart) {
inputs.add(new DialogInput.Text(
"secret", 300,
Component.text("Secret"),
true, "", 50, null
));
}
inputs.add(new DialogInput.Text(
"name", 300,
Component.text("Vorname"),
true, "", 50, null
));
inputs.add(new DialogInput.Text(
"secondName", 300,
Component.text("Nachname"),
true, "", 50, null
));
inputs.add(new DialogInput.Text(
"birthday", 300,
Component.text("Geburtstag (TT.MM.JJJJ)"),
true, "", 10, null
));
inputs.add(new DialogInput.Text(
"phoneNumber", 300,
Component.text("Telefonnummer (optional)"),
true, "", 30, null
));
net.minestom.server.dialog.Dialog dialog = new net.minestom.server.dialog.Dialog.Notice(
new DialogMetadata(
Component.text("Spielerprofil erstellen"),
null,
true,
false,
DialogAfterAction.CLOSE,
List.of(
new DialogBody.PlainMessage(
Component.text(
"Bitte fülle deine Profildaten aus."
),
300
)
),
inputs
),
new DialogActionButton(
Component.text("Speichern"),
null,
200,
new DialogAction.DynamicCustom(
submitKey,
CompoundBinaryTag.builder().build()
)
)
);
player.showDialog(dialog);
}
}
@@ -1,99 +0,0 @@
package eu.mhsl.minenet.loginServer.listeners;
import eu.mhsl.minenet.loginServer.Dialog;
import eu.mhsl.minenet.loginServer.Main;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.player.PlayerCustomClickEvent;
import net.minestom.server.timer.SchedulerManager;
import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.Locale;
import java.util.UUID;
public class dialogOutput implements Listener {
@Override
public void register(EventNode<Event> node) {
node.addListener(PlayerCustomClickEvent.class, event -> {
Player player = event.getPlayer();
SchedulerManager schedule = MinecraftServer.getSchedulerManager();
boolean isAfterStart = Main.globalConfig.node("server", "is-after-start").getBoolean(false);
if (!event.getKey().equals(
Key.key("profile", "submit"))) {
return;
}
if (!(event.getPayload() instanceof CompoundBinaryTag data)) {
return;
}
String requiredSecret = "test1234";
String secret = data.getString("secret");
String name = data.getString("name");
String secondName = data.getString("secondName");
String birthday = data.getString("birthday");
String phoneNumber = data.getString("phoneNumber");
String playerName = event.getPlayer().getUsername();
UUID playerUUID = event.getPlayer().getUuid();
if(isAfterStart && !secret.equals(requiredSecret)) {
player.sendMessage("Bitte gebe das richtige Secret an.");
schedule.buildTask(() -> {
Dialog.open(player);
}).delay(Duration.ofMillis(1500)).schedule();
return;
}
if (name.isBlank() || secondName.isBlank() || birthday.isBlank()) {
player.sendMessage("Bitte fülle alle Pflichtfelder aus.");
schedule.buildTask(() -> {
Dialog.open(player);
}).delay(Duration.ofMillis(1500)).schedule();
return;
}
if(!isValidBirthday(birthday)) {
player.sendMessage("Bitte gebe ein richtiges Geburtsdatum an.");
schedule.buildTask(() -> {
Dialog.open(player);
}).delay(Duration.ofMillis(1500)).schedule();
return;
}
player.sendMessage("Danke für deine Anmeldung.");
player.sendMessage("Du bist für das Projekt Craftattack 9 registriert!");
schedule.buildTask(() -> {
player.kick("Anmeldung erfolgreich!");
}).delay(Duration.ofMillis(1500)).schedule();
System.out.println("Vorname: " + name);
System.out.println("Nachname: " + secondName);
System.out.println("Geburtstag: " + birthday);
System.out.println("Telefonnummer: " + phoneNumber);
System.out.println("Spielername: " + playerName);
System.out.println("Spieler UUID: " + playerUUID);
});
}
public static boolean isValidBirthday(String birthday) {
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("dd.MM.uuuu", Locale.GERMANY)
.withResolverStyle(ResolverStyle.STRICT);
try {
LocalDate date = LocalDate.parse(birthday, formatter);
return !date.isAfter(LocalDate.now());
} catch (DateTimeParseException e) {
return false;
}
}
}
@@ -1,31 +0,0 @@
package eu.mhsl.minenet.loginServer.listeners;
import eu.mhsl.minenet.loginServer.Dialog;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.instance.InstanceManager;
import java.time.Duration;
public class onPlayerJoin implements Listener {
private final InstanceManager instanceManager;
public onPlayerJoin(InstanceManager instanceManager) {
this.instanceManager = instanceManager;
}
@Override
public void register(EventNode<Event> node) {
node.addListener(AsyncPlayerConfigurationEvent.class, event ->{
final Player player = event.getPlayer();
event.setSpawningInstance(instanceManager.createInstanceContainer());
player.setRespawnPoint(new Pos(0, 100, 0));
MinecraftServer.getSchedulerManager().buildTask(() -> {
Dialog.open(player);
}).delay(Duration.ofMillis(500)).schedule();
});
}
}
+6 -1
View File
@@ -11,4 +11,9 @@ bungeecord:
velocity: velocity:
enabled: false enabled: false
chat-via-proxy: false chat-via-proxy: false
secret: '' secret: ''
api:
url: ""
email: ""
password: ""