Upload via GUI (61 Dateien)
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package net.viper.status.modules.chat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Nachrichtenformat für Chat-Spiele (NexusLobby) zwischen Sub-Server und Proxy.
|
||||
*
|
||||
* Hintergrund: Der Chat läuft komplett über diesen Proxy ({@link ChatModule}). Die Sub-Server bekommen
|
||||
* keine Chat-Ereignisse und können Antworten auf ein Chat-Spiel deshalb nicht selbst erkennen. Der
|
||||
* Sub-Server meldet dem Proxy Start und Ende eines Spiels samt gültiger Antworten. Der Proxy erkennt die
|
||||
* Antwort im Chat und meldet sie dem Sub-Server zurück.
|
||||
*
|
||||
* Richtung Proxy: Kanal "BungeeCord", Unterkanal {@link #SUBCHANNEL}
|
||||
* START: Unterkanal, "START", Spiel-ID, Sekunden, Groß/Klein beachten, Antwort verstecken, Anzahl, Antworten...
|
||||
* STOP: Unterkanal, "STOP", Spiel-ID
|
||||
* Richtung Sub-Server: Kanal {@link #ANSWER_CHANNEL}
|
||||
* ANSWER: "ANSWER", Spiel-ID, Spieler-UUID, Nachricht
|
||||
*
|
||||
* Das Format muss mit ChatGameProtocol in NexusLobby übereinstimmen.
|
||||
*/
|
||||
public final class ChatGameBridge {
|
||||
|
||||
public static final String SUBCHANNEL = "NexusChatGame";
|
||||
public static final String ANSWER_CHANNEL = "statusapi:chatgame";
|
||||
|
||||
/** So viele Sekunden länger als angegeben bleibt ein Spiel im Proxy gültig (Sicherheitspuffer). */
|
||||
private static final long GRACE_SECONDS = 5;
|
||||
private static final int MAX_ANSWERS = 20;
|
||||
|
||||
private ChatGameBridge() {}
|
||||
|
||||
/** Ein laufendes Chat-Spiel auf einem Sub-Server. */
|
||||
public static final class Game {
|
||||
public final String id;
|
||||
public final List<String> answers;
|
||||
public final boolean caseSensitive;
|
||||
public final boolean hideAnswer;
|
||||
public final long expiresAt;
|
||||
|
||||
public Game(String id, List<String> answers, boolean caseSensitive, boolean hideAnswer, long expiresAt) {
|
||||
this.id = id;
|
||||
this.answers = Collections.unmodifiableList(new ArrayList<>(answers));
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.hideAnswer = hideAnswer;
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
|
||||
public boolean expired(long now) {
|
||||
return now > expiresAt;
|
||||
}
|
||||
|
||||
/** Prüft, ob die Chat-Nachricht eine gültige Antwort ist. */
|
||||
public boolean matches(String message) {
|
||||
String text = clean(message);
|
||||
if (text.isEmpty()) return false;
|
||||
for (String answer : answers) {
|
||||
if (caseSensitive ? answer.equals(text) : answer.equalsIgnoreCase(text)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Eine Nachricht des Sub-Servers an den Proxy. */
|
||||
public static final class Message {
|
||||
public final String action; // START oder STOP
|
||||
public final String gameId;
|
||||
public final Game game; // nur bei START, sonst null
|
||||
|
||||
Message(String action, String gameId, Game game) {
|
||||
this.action = action;
|
||||
this.gameId = gameId;
|
||||
this.game = game;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest eine Plugin-Message des Sub-Servers.
|
||||
*
|
||||
* @param data Daten einer Nachricht auf dem Kanal "BungeeCord"
|
||||
* @param now aktuelle Zeit in Millisekunden
|
||||
* @return die Nachricht oder null, wenn sie nicht zum Chat-Spiel gehört oder fehlerhaft ist
|
||||
*/
|
||||
public static Message read(byte[] data, long now) {
|
||||
if (data == null) return null;
|
||||
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(data))) {
|
||||
if (!SUBCHANNEL.equals(in.readUTF())) return null;
|
||||
String action = in.readUTF();
|
||||
String id = in.readUTF();
|
||||
|
||||
if ("STOP".equals(action)) return new Message("STOP", id, null);
|
||||
if (!"START".equals(action)) return null;
|
||||
|
||||
int seconds = Math.max(1, Math.min(600, in.readInt()));
|
||||
boolean caseSensitive = in.readBoolean();
|
||||
boolean hideAnswer = in.readBoolean();
|
||||
int count = in.readInt();
|
||||
if (count < 1 || count > MAX_ANSWERS) return null;
|
||||
|
||||
List<String> answers = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) answers.add(clean(in.readUTF()));
|
||||
|
||||
long expiresAt = now + (seconds + GRACE_SECONDS) * 1000L;
|
||||
return new Message("START", id, new Game(id, answers, caseSensitive, hideAnswer, expiresAt));
|
||||
} catch (IOException | RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Baut die Antwort-Nachricht an den Sub-Server. */
|
||||
public static byte[] answer(String gameId, UUID player, String message) {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bytes);
|
||||
out.writeUTF("ANSWER");
|
||||
out.writeUTF(gameId);
|
||||
out.writeUTF(player.toString());
|
||||
out.writeUTF(message);
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e); // kann bei einem Byte-Array nicht passieren
|
||||
}
|
||||
}
|
||||
|
||||
/** Entfernt Farbcodes (Paragraphenzeichen), unsichtbare Zeichen und überflüssige Leerzeichen. */
|
||||
public static String clean(String s) {
|
||||
if (s == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == 0xA7) { // Paragraphenzeichen: das Zeichen danach gehört zum Farbcode
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if ((c >= 0x200B && c <= 0x200F) || c == 0x2060 || c == 0xFEFF) continue; // unsichtbar
|
||||
sb.append(c == 0xA0 ? ' ' : c);
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user