added move to login or craftattack
This commit is contained in:
1 parent
ad532c8b99
commit
869efecee6
6 files changed
+350
-2
No files matched your search
@@ -9,6 +9,7 @@ Listenern, Befehlen und eigenem Config-Abschnitt. Braucht LuckPerms auf Velocity
|
|||||||
| `mute` | `/mute`, `/unmute`, LuckPerms-Permission `chatbridge.muted` | – |
|
| `mute` | `/mute`, `/unmute`, LuckPerms-Permission `chatbridge.muted` | – |
|
||||||
| `chat` | öffentlicher Chat, `/msg`, `/r`, `/adminchat`, Join/Leave, Chat-Einstellungen | `mute` |
|
| `chat` | öffentlicher Chat, `/msg`, `/r`, `/adminchat`, Join/Leave, Chat-Einstellungen | `mute` |
|
||||||
| `tablist` | Spieler anderer Server in der Tabliste | `chat` |
|
| `tablist` | Spieler anderer Server in der Tabliste | `chat` |
|
||||||
|
| `signup` | erster Server beim Join: Login-Server oder CraftAttack, je nach Anmeldung auf der Website | – |
|
||||||
|
|
||||||
## Aufbau
|
## Aufbau
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package eu.mhsl.craftattack.proxy.appliances.signup;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||||
|
import eu.mhsl.craftattack.proxy.appliances.signup.SignupApi.Status;
|
||||||
|
import eu.mhsl.craftattack.proxy.core.Config;
|
||||||
|
import eu.mhsl.craftattack.proxy.core.appliance.Appliance;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public final class Signup extends Appliance {
|
||||||
|
enum Phase { BEFORE, LOGIN, OPEN, LATE }
|
||||||
|
|
||||||
|
enum Target { LOGIN, CRAFTATTACK }
|
||||||
|
|
||||||
|
private RegisteredServer login;
|
||||||
|
private RegisteredServer craftattack;
|
||||||
|
private RegisteredServer fallback;
|
||||||
|
private Duration pingTimeout;
|
||||||
|
private ZoneId zone;
|
||||||
|
private Instant registrationRequiredFrom;
|
||||||
|
private Instant openForAllFrom;
|
||||||
|
private Instant lateSignupFrom;
|
||||||
|
private SignupApi api;
|
||||||
|
private @Nullable ScheduledTask recheckTask;
|
||||||
|
private final Set<UUID> checking = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
private static final DateTimeFormatter START_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy 'um' HH:mm");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnable() {
|
||||||
|
Config config = this.config();
|
||||||
|
this.login = this.server(config.string("login-server", "login"));
|
||||||
|
this.craftattack = this.server(config.string("craftattack-server", "craftattack"));
|
||||||
|
this.fallback = this.server(config.string("fallback-server", "limbo"));
|
||||||
|
this.pingTimeout = Duration.ofSeconds(config.integer("ping-timeout-seconds", 3));
|
||||||
|
|
||||||
|
ZoneId zone = this.zone = ZoneId.of(config.string("timezone", "Europe/Berlin"));
|
||||||
|
this.registrationRequiredFrom = LocalDateTime.parse(config.string("registration-required-from", "2026-12-01T00:00")).atZone(zone).toInstant();
|
||||||
|
this.openForAllFrom = LocalDateTime.parse(config.string("open-for-all-from", "2026-12-26T14:00")).atZone(zone).toInstant();
|
||||||
|
this.lateSignupFrom = LocalDateTime.parse(config.string("late-signup-from", "2026-12-29T14:00")).atZone(zone).toInstant();
|
||||||
|
if (!this.registrationRequiredFrom.isBefore(this.openForAllFrom) || !this.openForAllFrom.isBefore(this.lateSignupFrom))
|
||||||
|
throw new IllegalArgumentException("signup: registration-required-from < open-for-all-from < late-signup-from erwartet");
|
||||||
|
|
||||||
|
this.api = new SignupApi(
|
||||||
|
config.string("api-url", ""),
|
||||||
|
config.string("late-signup-path", "signup"),
|
||||||
|
config.string("api-secret", ""),
|
||||||
|
Duration.ofSeconds(config.integer("timeout-seconds", 5)),
|
||||||
|
this.logger());
|
||||||
|
|
||||||
|
int recheck = config.integer("recheck-seconds", 10);
|
||||||
|
if (recheck > 0) this.recheckTask = this.task(this::recheck).repeat(Duration.ofSeconds(recheck)).schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RegisteredServer server(String name) {
|
||||||
|
return this.proxy().getServer(name).orElseThrow(() -> new IllegalArgumentException("Server " + name + " ist in Velocity nicht eingetragen"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Phase phase(Instant now) {
|
||||||
|
return phase(now, this.registrationRequiredFrom, this.openForAllFrom, this.lateSignupFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Phase phase(Instant now, Instant registrationRequiredFrom, Instant openForAllFrom, Instant lateSignupFrom) {
|
||||||
|
if (now.isBefore(registrationRequiredFrom)) return Phase.BEFORE;
|
||||||
|
if (now.isBefore(openForAllFrom)) return Phase.LOGIN;
|
||||||
|
if (now.isBefore(lateSignupFrom)) return Phase.OPEN;
|
||||||
|
return Phase.LATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Target target(Phase phase, Status registered, Status signedUp) {
|
||||||
|
if (registered == Status.FOUND || registered == Status.BLOCKED) return Target.CRAFTATTACK;
|
||||||
|
return switch (phase) {
|
||||||
|
case BEFORE, OPEN -> Target.CRAFTATTACK;
|
||||||
|
case LOGIN -> Target.LOGIN;
|
||||||
|
case LATE -> signedUp == Status.NOT_FOUND ? Target.CRAFTATTACK : Target.LOGIN;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Decision> decide(Player player) {
|
||||||
|
Phase phase = this.phase(Instant.now());
|
||||||
|
// Vor dem 1.12. und im offenen Zeitraum braucht es die Website nicht
|
||||||
|
if (phase == Phase.BEFORE || phase == Phase.OPEN)
|
||||||
|
return CompletableFuture.completedFuture(new Decision(phase, Status.UNKNOWN, Status.UNKNOWN));
|
||||||
|
return this.api.registered(player).thenCompose(registered -> {
|
||||||
|
// Angemeldete und Gesperrte kommen immer auf CraftAttack, die nachträgliche Anmeldung ist dann egal
|
||||||
|
if (phase != Phase.LATE || registered == Status.FOUND || registered == Status.BLOCKED)
|
||||||
|
return CompletableFuture.completedFuture(new Decision(phase, registered, Status.UNKNOWN));
|
||||||
|
return this.api.signedUp(player).thenApply(signedUp -> new Decision(phase, registered, signedUp));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
record Decision(Phase phase, Status registered, Status signedUp) {
|
||||||
|
Target target() {
|
||||||
|
return Signup.target(this.phase, this.registered, this.signedUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.phase + ", angemeldet " + this.registered + ", nachträglich " + this.signedUp + " -> " + this.target();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<RegisteredServer> server(Target target) {
|
||||||
|
RegisteredServer server = target == Target.LOGIN ? this.login : this.craftattack;
|
||||||
|
return server.ping()
|
||||||
|
.orTimeout(this.pingTimeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.thenApply(ping -> server)
|
||||||
|
.exceptionally(error -> {
|
||||||
|
this.logger().warn("[signup] {} nicht erreichbar, stattdessen {}", server.getServerInfo().getName(), this.fallback.getServerInfo().getName());
|
||||||
|
return this.fallback;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recheck() {
|
||||||
|
for (Player player : this.login.getPlayersConnected()) {
|
||||||
|
if (!this.checking.add(player.getUniqueId())) continue;
|
||||||
|
this.decide(player).thenCompose(decision -> {
|
||||||
|
if (decision.target() != Target.CRAFTATTACK || !player.isActive()) return CompletableFuture.completedFuture(null);
|
||||||
|
if (decision.phase() != Phase.LATE && decision.registered() == Status.FOUND) {
|
||||||
|
this.logger().info("[signup] {} angemeldet, gekickt: {}", player.getUsername(), decision);
|
||||||
|
player.disconnect(this.thanks());
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
return this.server(Target.CRAFTATTACK).thenAccept(server -> {
|
||||||
|
this.logger().info("[signup] {} vom Login-Server auf {}: {}", player.getUsername(), server.getServerInfo().getName(), decision);
|
||||||
|
if (server == this.craftattack)
|
||||||
|
player.sendMessage(Component.text("Du bist angemeldet und wirst auf CraftAttack verbunden.", NamedTextColor.GREEN));
|
||||||
|
player.createConnectionRequest(server).fireAndForget();
|
||||||
|
});
|
||||||
|
}).whenComplete((ignored, error) -> {
|
||||||
|
this.checking.remove(player.getUniqueId());
|
||||||
|
if (error != null) this.logger().error("[signup] Prüfung für {} fehlgeschlagen", player.getUsername(), error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component thanks() {
|
||||||
|
if (Instant.now().isBefore(this.openForAllFrom))
|
||||||
|
return Component.text("Danke für deine Anmeldung! CraftAttack startet am "
|
||||||
|
+ START_FORMAT.format(this.openForAllFrom.atZone(this.zone)) + " Uhr.", NamedTextColor.GREEN);
|
||||||
|
return Component.text("Danke für deine Anmeldung! Verbinde dich neu, um auf CraftAttack zu spielen.", NamedTextColor.GREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isLogin(RegisteredServer server) {
|
||||||
|
return server.equals(this.login);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisable() {
|
||||||
|
if (this.recheckTask != null) this.recheckTask.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Logger logger() {
|
||||||
|
return super.logger();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Object> listeners() {
|
||||||
|
return List.of(new SignupListener(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package eu.mhsl.craftattack.proxy.appliances.signup;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
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.Base64;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
final class SignupApi {
|
||||||
|
enum Status { FOUND, NOT_FOUND, BLOCKED, UNKNOWN }
|
||||||
|
|
||||||
|
private final HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
|
||||||
|
private final URI api;
|
||||||
|
private final String signupPath;
|
||||||
|
private final @Nullable String authorization;
|
||||||
|
private final Duration timeout;
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
SignupApi(String url, String signupPath, String secret, Duration timeout, Logger logger) {
|
||||||
|
if (url.isEmpty()) throw new IllegalArgumentException("signup.api-url fehlt");
|
||||||
|
this.api = root(url);
|
||||||
|
this.signupPath = signupPath.startsWith("/") ? signupPath.substring(1) : signupPath;
|
||||||
|
// Website: Basic-Auth mit Benutzer „api“ und API_SECRET; ohne Secret prüft sie nichts
|
||||||
|
this.authorization = secret.isEmpty() ? null
|
||||||
|
: "Basic " + Base64.getEncoder().encodeToString(("api:" + secret).getBytes(StandardCharsets.UTF_8));
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
static URI root(String url) {
|
||||||
|
String base = url.replaceAll("/+$", "");
|
||||||
|
if (base.endsWith("/api")) base = base.substring(0, base.length() - "/api".length());
|
||||||
|
return URI.create(base + "/api/");
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Status> registered(Player player) {
|
||||||
|
String path = "users/" + player.getUniqueId();
|
||||||
|
return this.get(path)
|
||||||
|
.thenApply(response -> switch (response.statusCode()) {
|
||||||
|
case 200 -> Status.FOUND;
|
||||||
|
case 403 -> Status.BLOCKED;
|
||||||
|
case 404 -> Status.NOT_FOUND;
|
||||||
|
default -> this.unexpected(path, player, response);
|
||||||
|
})
|
||||||
|
.exceptionally(error -> this.unreachable(path, player, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Status> signedUp(Player player) {
|
||||||
|
return this.get(this.signupPath)
|
||||||
|
.thenApply(response -> {
|
||||||
|
if (response.statusCode() != 200) return this.unexpected(this.signupPath, player, response);
|
||||||
|
return uuids(response.body()).contains(player.getUniqueId()) ? Status.FOUND : Status.NOT_FOUND;
|
||||||
|
})
|
||||||
|
.exceptionally(error -> this.unreachable(this.signupPath, player, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Set<UUID> uuids(String json) {
|
||||||
|
Set<UUID> uuids = new HashSet<>();
|
||||||
|
for (JsonElement entry : JsonParser.parseString(json).getAsJsonArray()) {
|
||||||
|
String uuid = entry.getAsString().trim().toLowerCase(Locale.ROOT);
|
||||||
|
if (uuid.length() == 32) uuid = uuid.replaceFirst("(.{8})(.{4})(.{4})(.{4})(.{12})", "$1-$2-$3-$4-$5");
|
||||||
|
uuids.add(UUID.fromString(uuid));
|
||||||
|
}
|
||||||
|
return uuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<HttpResponse<String>> get(String path) {
|
||||||
|
HttpRequest.Builder request = HttpRequest.newBuilder(this.api.resolve(path))
|
||||||
|
.timeout(this.timeout)
|
||||||
|
.GET();
|
||||||
|
if (this.authorization != null) request.header("Authorization", this.authorization);
|
||||||
|
return this.http.sendAsync(request.build(), HttpResponse.BodyHandlers.ofString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Status unexpected(String path, Player player, HttpResponse<?> response) {
|
||||||
|
this.logger.warn("[signup] Website antwortet {} auf {} für {}", response.statusCode(), path, player.getUsername());
|
||||||
|
return Status.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Status unreachable(String path, Player player, Throwable error) {
|
||||||
|
this.logger.warn("[signup] Website nicht erreichbar oder Antwort ungültig ({}) für {}: {}", path, player.getUsername(), error.toString());
|
||||||
|
return Status.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package eu.mhsl.craftattack.proxy.appliances.signup;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.EventTask;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||||
|
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
|
final class SignupListener {
|
||||||
|
private final Signup signup;
|
||||||
|
|
||||||
|
SignupListener(Signup signup) {
|
||||||
|
this.signup = signup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public EventTask onChooseInitialServer(PlayerChooseInitialServerEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
return EventTask.resumeWhenComplete(this.signup.decide(player).thenCompose(decision -> {
|
||||||
|
this.signup.logger().info("[signup] {} ({}): {}", player.getUsername(), player.getUniqueId(), decision);
|
||||||
|
return this.signup.server(decision.target());
|
||||||
|
}).thenAccept(event::setInitialServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onKicked(KickedFromServerEvent event) {
|
||||||
|
if (!this.signup.isLogin(event.getServer())) return;
|
||||||
|
Component reason = event.getServerKickReason().orElse(Component.text("Verbindung zum Login-Server getrennt."));
|
||||||
|
event.setResult(KickedFromServerEvent.DisconnectPlayer.create(reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
|||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import eu.mhsl.craftattack.proxy.appliances.chat.Chat;
|
import eu.mhsl.craftattack.proxy.appliances.chat.Chat;
|
||||||
import eu.mhsl.craftattack.proxy.appliances.mute.Mute;
|
import eu.mhsl.craftattack.proxy.appliances.mute.Mute;
|
||||||
|
import eu.mhsl.craftattack.proxy.appliances.signup.Signup;
|
||||||
import eu.mhsl.craftattack.proxy.appliances.tablist.NetworkTabList;
|
import eu.mhsl.craftattack.proxy.appliances.tablist.NetworkTabList;
|
||||||
import eu.mhsl.craftattack.proxy.core.appliance.Appliance;
|
import eu.mhsl.craftattack.proxy.core.appliance.Appliance;
|
||||||
import net.luckperms.api.LuckPerms;
|
import net.luckperms.api.LuckPerms;
|
||||||
@@ -42,7 +43,8 @@ public final class Main {
|
|||||||
private static final List<Supplier<Appliance>> APPLIANCES = List.of(
|
private static final List<Supplier<Appliance>> APPLIANCES = List.of(
|
||||||
Mute::new,
|
Mute::new,
|
||||||
Chat::new,
|
Chat::new,
|
||||||
NetworkTabList::new
|
NetworkTabList::new,
|
||||||
|
Signup::new
|
||||||
);
|
);
|
||||||
|
|
||||||
private final ProxyServer proxy;
|
private final ProxyServer proxy;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Listen werden mit | getrennt. Änderungen brauchen einen Neustart von Velocity.
|
# Listen werden mit | getrennt. Änderungen brauchen einen Neustart von Velocity.
|
||||||
# Schlüssel einer Appliance beginnen mit ihrem Namen (chat., tablist. …).
|
# Schlüssel einer Appliance beginnen mit ihrem Namen (chat., tablist. …).
|
||||||
|
|
||||||
# Appliances, die nicht geladen werden (mute, chat, tablist). chat braucht mute, tablist braucht chat.
|
# Appliances, die nicht geladen werden (mute, chat, tablist, signup). chat braucht mute, tablist braucht chat.
|
||||||
disabled-appliances=
|
disabled-appliances=
|
||||||
|
|
||||||
# Adminchat, /mute und /unmute (LuckPerms auf Velocity)
|
# Adminchat, /mute und /unmute (LuckPerms auf Velocity)
|
||||||
@@ -34,3 +34,41 @@ chat.marker-permission=adminmarker
|
|||||||
|
|
||||||
# Abgleich der Tablisten in Sekunden; nach Join, Wechsel und Quit zusätzlich kurz danach
|
# Abgleich der Tablisten in Sekunden; nach Join, Wechsel und Quit zusätzlich kurz danach
|
||||||
tablist.refresh-seconds=2
|
tablist.refresh-seconds=2
|
||||||
|
|
||||||
|
|
||||||
|
# --- signup -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Erster Server beim Join. Wer angemeldet oder gesperrt ist, kommt immer auf craftattack-server.
|
||||||
|
signup.login-server=login
|
||||||
|
signup.craftattack-server=craftattack
|
||||||
|
|
||||||
|
# Antwortet login-server bzw. craftattack-server nicht innerhalb von ping-timeout-seconds auf einen
|
||||||
|
# Ping, kommt der Spieler stattdessen hierhin.
|
||||||
|
signup.fallback-server=limbo
|
||||||
|
signup.ping-timeout-seconds=3
|
||||||
|
|
||||||
|
# Was mit nicht Angemeldeten passiert, je Zeitpunkt (JJJJ-MM-TTTHH:MM in timezone):
|
||||||
|
# davor -> craftattack-server
|
||||||
|
# ab registration-required-from -> login-server
|
||||||
|
# ab open-for-all-from -> craftattack-server
|
||||||
|
# ab late-signup-from -> login-server, wenn sie in der nachträglichen Anmeldung
|
||||||
|
# stehen (late-signup-path), sonst craftattack-server
|
||||||
|
signup.registration-required-from=2026-12-01T00:00
|
||||||
|
signup.open-for-all-from=2026-12-26T14:00
|
||||||
|
signup.late-signup-from=2026-12-29T14:00
|
||||||
|
signup.timezone=Europe/Berlin
|
||||||
|
|
||||||
|
# Website wie api.url und api.secret im Login-Server (mit oder ohne /api am Ende), Secret wie
|
||||||
|
# API_SECRET der Website. Angemeldet: GET <website>/api/users/<uuid>.
|
||||||
|
# Nachträgliche Anmeldung: GET <website>/api/<late-signup-path> gibt eine JSON-Liste aller UUIDs zurück,
|
||||||
|
# die sich nachträglich anmelden dürfen, z. B. ["069a79f4-44e9-4726-a5be-fca90e38aaf5"].
|
||||||
|
# Antwortet die Website nicht, kommt der Spieler auf login-server, wenn er dort hin könnte.
|
||||||
|
signup.api-url=
|
||||||
|
signup.api-secret=
|
||||||
|
signup.late-signup-path=signup
|
||||||
|
signup.timeout-seconds=5
|
||||||
|
|
||||||
|
# Spieler auf login-server werden so oft (Sekunden) neu geprüft. Wer sich angemeldet hat, wird vor
|
||||||
|
# late-signup-from gekickt, danach auf craftattack-server geschickt. 0 = aus.
|
||||||
|
# Wer vom login-server gekickt wird, fliegt auch vom Proxy.
|
||||||
|
signup.recheck-seconds=10
|
||||||
Reference in new issue
Block a user